From 16df68a5118801472909f63fc48caf16e548060c Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Sat, 16 Jun 2018 09:55:36 -0700 Subject: add back in search support, requires sqlite --- models/item/item.go | 17 ++++++++++--- sqlite.init.sql | 71 +++++++++++++++++++++++++++++++++-------------------- static/ui.html | 3 +++ web/rice-box.go | 12 ++++----- web/web.go | 8 +++++- 5 files changed, 73 insertions(+), 38 deletions(-) diff --git a/models/item/item.go b/models/item/item.go index dd8b52c..badd566 100644 --- a/models/item/item.go +++ b/models/item/item.go @@ -82,7 +82,7 @@ func filterPolicy() *bluemonday.Policy { } func ItemById(id int64) *Item { - items, _ := Filter(0, 0, "", false, false, id) + items, _ := Filter(0, 0, "", false, false, id, "") return items[0] } @@ -122,17 +122,21 @@ func (i *Item) GetFullContent() { } } -func Filter(max_id int64, feed_id int64, category string, unread_only bool, starred_only bool, item_id int64) ([]*Item, error) { +func Filter(max_id int64, feed_id int64, category string, unread_only bool, starred_only bool, item_id int64, search_query string) ([]*Item, error) { var args []interface{} + tables := " feed,item" + if search_query != "" { + tables = tables + ",fts_item" + } query := `SELECT item.id, item.feed_id, item.title, item.url, item.description, item.read_state, item.starred, item.publish_date, item.full_content, item.header_image, feed.url, feed.title, feed.category - FROM feed,item - WHERE item.feed_id=feed.id AND item.id!=0 ` + FROM ` + query = query + tables + ` WHERE item.feed_id=feed.id AND item.id!=0 ` if max_id != 0 { query = query + "AND item.id < ? " @@ -158,6 +162,11 @@ func Filter(max_id int64, feed_id int64, category string, unread_only bool, star args = append(args, item_id) } + if search_query != "" { + query = query + " AND fts_item match ? AND fts_item.rowid=item.id " + args = append(args, search_query) + } + // this is kind of dumb, but to keep the logic the same // we kludge it this way for a "by id" select if starred_only { diff --git a/sqlite.init.sql b/sqlite.init.sql index d4d08c8..411d152 100644 --- a/sqlite.init.sql +++ b/sqlite.init.sql @@ -1,30 +1,47 @@ -CREATE TABLE IF NOT EXISTS "feed" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "url" varchar(100) NOT NULL UNIQUE, - "web_url" varchar(255) NOT NULL DEFAULT '', - "title" varchar(255) NOT NULL DEFAULT '', - "last_updated" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "category" varchar(255) NOT NULL DEFAULT 'uncategorized' +CREATE TABLE feed ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + url varchar(100) NOT NULL UNIQUE, + web_url varchar(255) NOT NULL DEFAULT '', + title varchar(255) NOT NULL DEFAULT '', + last_updated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + category varchar(255) NOT NULL DEFAULT 'uncategorized' ); -CREATE INDEX "feed_url" ON "feed" ("url"); -CREATE INDEX "feed_category" ON "feed" ("category"); -CREATE INDEX "feed_id" ON "feed" ("id"); +CREATE INDEX feed_url ON feed (url); +CREATE INDEX feed_category ON feed (category); -CREATE TABLE IF NOT EXISTS "item" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "feed_id" int(11) NOT NULL, - "title" text NOT NULL DEFAULT '', - "url" varchar(255) NOT NULL UNIQUE, - "description" text NOT NULL DEFAULT '', - "publish_date" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - "read_state" tinyint(1) NOT NULL DEFAULT '0', - "starred" tinyint(1) NOT NULL DEFAULT '0', - "full_content" text NOT NULL DEFAULT '', - "header_image" text NOT NULL DEFAULT '', - CONSTRAINT "item_ibfk_1" FOREIGN KEY ("feed_id") REFERENCES "feed" ("id") ON DELETE CASCADE +CREATE TABLE item ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + feed_id int(11) NOT NULL, + title text NOT NULL DEFAULT '', + url varchar(255) NOT NULL UNIQUE, + description text NOT NULL DEFAULT '', + publish_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + read_state tinyint(1) NOT NULL DEFAULT '0', + starred tinyint(1) NOT NULL DEFAULT '0', + full_content text NOT NULL DEFAULT '', + header_image text NOT NULL DEFAULT '', + CONSTRAINT item_ibfk_1 FOREIGN KEY (feed_id) REFERENCES feed(id) ON DELETE CASCADE ); -CREATE INDEX "item_url" ON "item" ("url"); -CREATE INDEX "item_publish_date" ON "item" ("publish_date"); -CREATE INDEX "item_feed_id" ON "item" ("feed_id"); -CREATE INDEX "item_rev_id" ON "item" ("id"); -CREATE INDEX "item_read_state" ON "item" ("read_state"); +CREATE INDEX item_url ON item (url); +CREATE INDEX item_publish_date ON item (publish_date); +CREATE INDEX item_feed_id ON item (feed_id); +CREATE INDEX item_read_state ON item (read_state); + +CREATE VIRTUAL TABLE fts_item using fts4(content="item", title, url, description); + +INSERT INTO fts_item(fts_item) VALUES('rebuild'); + + +CREATE TRIGGER item_bu BEFORE UPDATE ON item BEGIN + DELETE FROM fts_item WHERE docid=old.rowid; +END; +CREATE TRIGGER t2_bd BEFORE DELETE ON t2 BEGIN + DELETE FROM t3 WHERE docid=old.rowid; +END; + +CREATE TRIGGER t2_au AFTER UPDATE ON t2 BEGIN + INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c); +END; +CREATE TRIGGER t2_ai AFTER INSERT ON t2 BEGIN + INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c); +END; diff --git a/static/ui.html b/static/ui.html index 4f10cf0..5fa7005 100644 --- a/static/ui.html +++ b/static/ui.html @@ -96,6 +96,9 @@
  • +
  • +
  • +
  • diff --git a/web/rice-box.go b/web/rice-box.go index 6a652a5..58199c7 100644 --- a/web/rice-box.go +++ b/web/rice-box.go @@ -60,13 +60,13 @@ func init() { } filec := &embedded.EmbeddedFile{ Filename: "ui.html", - FileModTime: time.Unix(1528842640, 0), - Content: string("\n\n \n neko rss mode\n \n \n \n \n \n \n \n \n \n \n \n

    🐱

    \n\n
    \n\n
    \n

    Tags

    \n \n \n

    Feeds

    \n \n
    \n\n \n\n
    \n
    \n
    \n
    \n \n \n\n \n\n \n\n \n \n\n \n"), + FileModTime: time.Unix(1528941212, 0), + Content: string("\n\n \n neko rss mode\n \n \n \n \n \n \n \n \n \n \n \n

    🐱

    \n\n
    \n\n
    \n

    Tags

    \n \n \n

    Feeds

    \n \n
    \n\n \n\n
    \n
    \n
    \n
    \n \n \n\n \n\n \n\n \n \n\n \n"), } filed := &embedded.EmbeddedFile{ Filename: "ui.js", - FileModTime: time.Unix(1528842461, 0), - Content: string("var templates = {};\n\n$(document).ready(function() {\n if ( $(window).width() < 1024 ) {\n $('#filters').addClass('hidden');\n }\n boot();\n});\n\nvar AppModel = Backbone.Model.extend({\n defaults: {\n 'selectedIndex': 0,\n 'starredFilter': false,\n 'allFilter': false,\n 'unreadFilter': true,\n 'feedFilter': undefined,\n 'searchFilter': undefined\n },\n\n initialize: function() {\n this.bind('change:selectedIndex', this.scroll_to_selected);\n// this.bind('change:selectedIndex', this.scroll_to_selected)\n },\n\n boot: function() {\n this.items.boot();\n this.tags.boot();\n this.feeds.fetch({set: true, remove: false})\n window.setInterval(function() { App.update_read_status() }, 5000);\n },\n\n filterToFeed: function(feed) {\n if (feed.get('selected')) {\n feed.set('selected', false);\n this.set('feedFilter', undefined);\n }\n else {\n App.tags.models.forEach ( function (t) {\n t.set('selected', false); \n });\n App.tag = null;\n App.feeds.models.forEach ( function (f) {\n f.set('selected', false); \n });\n\n this.set('feedFilter', feed);\n feed.set('selected', true);\n }\n this.items.reboot();\n },\n\n filterToTag: function(tag) {\n App.tag = null;\n if (tag.get('selected')) {\n tag.set('selected', false);\n }\n else {\n App.tags.models.forEach ( function (t) {\n t.set('selected', false); \n });\n App.feeds.models.forEach ( function (f) {\n f.set('selected', false); \n });\n this.set('feedFilter', undefined);\n tag.set('selected', true);\n App.tag = tag.get('title');\n }\n App.items.reboot();\n },\n\n filterToStarred: function() {\n this.set('starredFilter', true);\n this.set('allFilter', false);\n this.set('unreadFilter', false);\n this.items.reboot();\n },\n\n filterToAll: function() {\n this.set('starredFilter', false);\n this.set('allFilter', true);\n this.set('unreadFilter', false);\n this.items.reboot();\n },\n\n filterToUnread: function() {\n this.set('starredFilter', false);\n this.set('allFilter', false);\n this.set('unreadFilter', true);\n this.items.reboot();\n },\n\n filterToSearch: function() {\n this.set('searchFilter', $('#search').val());\n // this.set('starredFilter', false);\n // this.set('allFilter', true);\n // this.set('unreadFilter', false);\n this.items.reboot();\n },\n\n update_read_status: function() {\n var screen_top = $(window).scrollTop();\n var screen_bottom = $(window).scrollTop() + $(window).height();\n\n // // mark all visible items as read\n $.each($('.item'), function(i,v) {\n\t var item_top = $(v).offset().top;\n // console.log(\"i \", i, \"item_top \", item_top, \"screen_top \", screen_top, \"screen_bottom \", screen_bottom);\n\n if( (item_top < screen_top)) {\n App.items.at(i).markRead();\n // console.log('marking as read: ', i);\n\t }\n });\n// window.setTimeout(App.update_read_status, 5000);\n },\n\n scroll_to_selected: function() {\n\t var item = $('.item').eq(this.get('selectedIndex'));\n if(item.offset()) {\n\t var item_top = item.offset().top;\n\t $('.item').removeClass('selected');\n\t item.addClass('selected');\n\t $(window).scrollTop(item_top);\n }\n App.items.at(this.get('selectedIndex')).markRead();\n if(App.items.models.length>1) {\n if(this.get('selected')>=App.items.models.length-1) {\n App.items.boot();\n }\n }\n },\n \n next: function() {\n\t if(this.get('selectedIndex') < this.items.models.length-1) {\n\t this.set('selectedIndex', this.get('selectedIndex')+1);\n\t }\n if(this.get('selectedIndex') == this.items.models.length-1) {\n App.items.boot();\n }\n },\n\n previous: function() {\n\t if(this.get('selectedIndex') > 0) {\n\t this.set('selectedIndex', this.get('selectedIndex')-1);\n\t }\n },\n\n star: function() {\n\t if(this.get('selectedIndex') >= 0) {\n App.items.at(this.get('selectedIndex')).toggleStar();\n }\n },\n\n full: function() {\n\t if(this.get('selectedIndex') >= 0) {\n App.items.at(this.get('selectedIndex')).full();\n }\n }\n\n});\nvar App = new AppModel();\n\nvar ControlsView = Backbone.View.extend({\n className: 'controls',\n\n events: {\n 'click .starred_filter': 'filterToStarred',\n 'click .all_filter': 'filterToAll',\n 'click .unread_filter': 'filterToUnread',\n 'click .new_feed': 'newFeed',\n 'click .search_go': 'filterToSearch',\n },\n\n initialize: function() {\n\t _.bindAll(this, 'render');\n\t this.model.bind('change', this.render);\n },\n\n filterToStarred: function() {\n App.filterToStarred();\n },\n\n filterToAll: function() {\n App.filterToAll();\n },\n\n filterToUnread: function() {\n App.filterToUnread();\n },\n\n filterToSearch: function() {\n App.filterToSearch();\n },\n \n newFeed: function() {\n var feed_url = prompt('New url to subscribe to');\n var feed = new Feed({'url': feed_url});\n App.feeds.add(feed);\n feed.save();\n },\n\n\trender: function() {\n var h = $.tmpl(templates.controls_template, { 'app': this.model.toJSON() });\n\t $(this.el).html(h);\n\t return this;\n\t},\n\n});\n\n\n\nvar Item = Backbone.Model.extend({\n idAttribute: \"_id\",\n url: '/item/',\n\n initialize: function() {\n var p_url = this.get('url');\n p_url = p_url.replace('https://', '');\n p_url = p_url.replace('http://', '');\n this.set('p_url', p_url);\n this.bind('change', this.maybeSave);\n },\n\n maybeSave: function() {\n if(this.hasChanged()) {\n this.save();\n }\n },\n\n markRead: function() {\n // recover if not tag\n if(this.get('read')) {\n\t return;\n }\n\n // var t = this.get('feed').tag;\n // var tag = App.tags.find(function(x){ return x.get('name') == t });\n this.set('read', true);\n // if(tag) {\n // tag.set('unread', tag.get('unread')-1);\n // }\n },\n\n toggleStar: function() {\n this.set({'starred': !(this.get('starred'))} );\n },\n\n star: function() {\n this.set({'starred': true});\n },\n\n unstar: function() {\n this.set({'starred': false});\n },\n \n full: function() {\n this.set({'full': !(this.get('full'))} );\n // this should just use this.fetch() but\n // it kept GETing from /item instead of /item/id\n // so just hacking this in for now\n\n if(this.get('full_content') == \"\") { \n $.getJSON('/item/' + this.get('_id'), function(data) {\n var i = App.items.get(data['_id'])\n i.set('full_content', data['full_content']);\n });\n }\n }\n\n});\n\n\nvar ItemCollection = Backbone.Collection.extend({\n model: Item,\n\n\tinitialize: function() {\n\t _.bindAll(this, 'boot', 'reboot');\n },\n\n boot: function() {\n if(App.loading) {\n return;\n }\n if(App.noMore) {\n return;\n }\n\n App.loading = true;\n url = '/stream/';\n url=url+'?foo=bar'\n if(App.get('searchFilter')) {\n url = url + '&q=' + App.get('searchFilter');\n }\n if(App.get('feedFilter')) {\n url = url + '&feed_url=' + App.get('feedFilter').get('url');\n }\n if(App.get('starredFilter')) {\n url = url + '&starred=1';\n }\n if(App.tag != undefined) {\n url = url + '&tag=' + App.tag;\n }\n if(App.items.last()) {\n url = url + '&max_id=' + App.items.last().get('_id');\n }\n \n if(App.get('allFilter') || App.get('starredFilter')) {\n\t url = url + '&read_filter=all';\n }\n\n console.log('fetching from ', url);\n var t = this;\n $.getJSON(url, function(data) {\n var items = [];\n\t $.each(data, function(i,v) {\n var item = new Item(v);\n t.add(item);\n items.push(item);\n if(t.models.length==1){\n App.set('selectedIndex', 0);\n }\n\t });\n // console.log(\"items \", items)\n if(items.length == 0) {\n // console.log(\"no more items\");\n App.noMore = true;\n // App.loading = true;\n }\n else {\n App.loading = false; \n }\n // we wait and add them all at once for performance on mobile\n App.itemListView.addAll(items);\n\n }); \n },\n\n reboot: function() {\n App.noMore = false;\n App.loading = false;\n this.reset();\n this.boot();\n },\n\n\n});\nApp.items = new ItemCollection();\n\n\nvar ItemView = Backbone.View.extend({\n\ttagName: \"div\",\t\n\tclassName: \"item\",\n\ttemplate: templates.item_template,\n\tevents: {\n \"click .star\": \"star\",\n \"click .unstar\": \"unstar\",\n \"click .full\": \"full\",\n },\n\t\n\tinitialize: function() {\n\t _.bindAll(this, 'render', 'star');\n\t this.model.bind('change', this.render);\n\t},\n\n star: function() {\n this.model.star();\n this.render();\n },\n\n unstar: function() {\n this.model.unstar();\n this.render();\n },\n\n full: function() {\n this.model.full();\n this.render();\n },\n\n\trender: function() {\n var h = $.tmpl(templates.item_template, { 'item': this.model.toJSON() });\n\t $(this.el).html(h);\n\t return this;\n\t},\n});\n\nvar ItemListView = Backbone.View.extend( {\n\tinitialize: function() {\n\t _.bindAll(this, 'addOne', 'addAll', 'change', 'render', 'reset');\n\t // App.items.bind('add', this.addOne);\t \n\t App.items.bind('reset', this.reset);\n\t},\n\taddOne: function(item) {\n\t var view = new ItemView({'model': item});\n this.$el.append(view.render().el);\n\t},\n\taddAll: function(items) {\n\t // Posts.each(this.addOne);\n for(i in items) {\n item = items[i];\n var view = new ItemView({'model': item});\n this.$el.append(view.render().el);\n };\n\t},\n change: function() {\n },\n render: function() {\n },\n reset: function() {\n this.$el.children().remove();\n }\n});\n \n\nvar Tag = Backbone.Model.extend({\n});\n\nvar TagCollection = Backbone.Collection.extend({\n model: Tag,\n\tinitialize: function() {\n\t _.bindAll(this, 'boot');\n },\n\n boot: function() {\n var t = this;\n $.getJSON('/tag/', function(data) {\n\t $.each(data, function(i,v) {\n var tag = new Tag(v);\n t.add(tag);\n\t });\n });\n }\n});\nApp.tags = new TagCollection();\n\n\nvar TagView = Backbone.View.extend({\n\ttagName: \"li\",\t\n\tclassName: \"tag\",\n\tevents: {\n \"click\": \"filterTo\",\n },\n\tinitialize: function() {\n\t _.bindAll(this, 'render', 'filterTo');\n\t this.model.bind('change', this.render);\n\t},\n\trender: function() {\n var h = $.tmpl(templates.tag_template, { 'tag': this.model.toJSON() });\n\t $(this.el).html(h);\n\t return this;\n\t},\n filterTo: function() {\n App.filterToTag(this.model);\n }\n});\n\n\nvar TagListView = Backbone.View.extend( {\n\n\tinitialize: function() {\n\t _.bindAll(this, 'addOne', 'addAll', 'change', 'render');\n\t App.tags.bind('add', this.addOne);\n\t App.tags.bind('refresh', this.addAll);\n\t App.tags.bind('change', this.render);\n\t},\n\taddOne: function(tag) {\n\t var view = new TagView({'model': tag});\n this.$el.append(view.render().el);\n\t},\n\taddAll: function() {\n\t App.tags.each(this.addOne);\n\t},\n change: function() {\n },\n render: function() {\n },\n});\n\nApp.tag = undefined;\nApp.page = 0;\nApp.read_filter = 'unread';\n\n\nvar Feed = Backbone.Model.extend({\n idAttribute: \"_id\",\n});\n\nvar FeedCollection = Backbone.Collection.extend({\n model: Feed,\n url: '/feed/',\n\n\tinitialize: function() {\n\t /// _.bindAll(this, 'boot');\n //console.log('initialized');\n },\n});\nApp.feeds = new FeedCollection();\n\nvar FeedView = Backbone.View.extend({\n\ttagName: \"li\",\t\n\tclassName: \"feed\",\n\tevents: {\n \"click .txt\": \"filterTo\",\n \"click .delete\": \"del\",\n \"click .edit\": \"edit\",\n },\n\tinitialize: function() {\n\t _.bindAll(this, 'render', 'filterTo', \"del\");\n\t this.model.bind('change', this.render);\n\t},\n\trender: function() {\n var h = $.tmpl(templates.feed_template, { 'feed': this.model.toJSON() });\n\t $(this.el).html(h);\n\t return this;\n\t},\n filterTo: function() {\n // console.log('filtering to feed ', this.model);\n App.filterToFeed(this.model);\n },\n del: function() {\n if( window.confirm(\"Unsubscribe from \" + this.model.get(\"url\") + \"?\" ) ) {\n this.model.destroy();\n this.$el.remove();\n }\n },\n edit: function() {\n var cat = window.prompt(\"Category for this feed?\", this.model.get(\"category\"));\n if (cat != null) {\n this.model.set(\"category\", cat);\n this.model.save();\n }\n },\n});\n\n\nvar FeedListView = Backbone.View.extend( {\n\tinitialize: function() {\n\t _.bindAll(this, 'addOne', 'addAll', 'change', 'render');\n\t App.feeds.bind('add', this.addOne);\n\t App.feeds.bind('refresh', this.addAll);\n\t App.feeds.bind('change', this.render);\n\t},\n\taddOne: function(feed) {\n // console.log('adding a feed...', feed);\n\t var view = new FeedView({'model': feed});\n this.$el.append(view.render().el);\n\t},\n\taddAll: function() {\n // console.log('feed add all...');\n\t App.feeds.each(this.addOne);\n\t},\n change: function() {\n // console.log('feeds changed add all...');\n },\n render: function() {\n },\n});\n\n\n// var page = 0;\n// var read_filter = 'unread';\n\nvar selected_item = 0;\n\nfunction boot() {\n templates['item_template'] = $('#item_template').html();\n templates['tag_template'] = $('#tag_template').html();\n templates['feed_template'] = $('#feed_template').html();\n templates['controls_template'] = $('#controls_template').html();\n\n App.itemListView = new ItemListView();\n App.itemListView.setElement($('#items'));\n App.tagListView = new TagListView();\n App.tagListView.setElement($('#tags'));\n App.feedListView = new FeedListView();\n App.feedListView.setElement($('#feeds'));\n App.controlsView = new ControlsView({model: App});\n App.controlsView.setElement($('#controls'));\n App.controlsView.render();\n\n infini_scroll();\n\n $('#unread_filter').on('click', function() {\n App.read_filter = 'unread';\n App.items.reboot();\n });\n\n $('#all_filter').on('click', function() {\n App.read_filter = 'all';\n App.items.reboot();\n });\n\n// $('.logo').on('click', function() {\n // App.set('feedFilter', undefined);\n // App.items.reboot();\n \n// });\n\n // keyboard shortcuts\n $('body').keydown(function(event) {\n\t if (event.which == 74) {\n\t event.preventDefault();\n App.next();\n\t }\n\t if (event.which == 75) {\n\t event.preventDefault();\n App.previous();\n\t }\n if (event.which == 83) {\n\t event.preventDefault();\n App.star(); \n }\n if (event.which == 70) {\n\t event.preventDefault();\n App.full(); \n }\n });\n\n App.boot();\n}\n\n\n// // this is legacy code\n\nfunction infini_scroll() {\n if(App.loading) { \n }\n else {\n var dh = $('#items').height() - $(window).height();\n var st = $(window).scrollTop();\n if ( (dh-st) < 100 ){\n App.items.boot();\n }\n }\n window.setTimeout(infini_scroll, 1000);\n}\n\n\nvar ItemSelector = { \n selected_index: 0,\n}\n"), + FileModTime: time.Unix(1529168083, 0), + Content: string("var templates = {};\n\n$(document).ready(function() {\n if ( $(window).width() < 1024 ) {\n $('#filters').addClass('hidden');\n }\n boot();\n});\n\nvar AppModel = Backbone.Model.extend({\n defaults: {\n 'selectedIndex': 0,\n 'starredFilter': false,\n 'allFilter': false,\n 'unreadFilter': true,\n 'feedFilter': undefined,\n 'searchFilter': undefined\n },\n\n initialize: function() {\n this.bind('change:selectedIndex', this.scroll_to_selected);\n// this.bind('change:selectedIndex', this.scroll_to_selected)\n },\n\n boot: function() {\n this.items.boot();\n this.tags.boot();\n this.feeds.fetch({set: true, remove: false})\n window.setInterval(function() { App.update_read_status() }, 5000);\n },\n\n filterToFeed: function(feed) {\n if (feed.get('selected')) {\n feed.set('selected', false);\n this.set('feedFilter', undefined);\n }\n else {\n App.tags.models.forEach ( function (t) {\n t.set('selected', false);\n });\n App.tag = null;\n App.feeds.models.forEach ( function (f) {\n f.set('selected', false);\n });\n\n this.set('feedFilter', feed);\n feed.set('selected', true);\n }\n this.items.reboot();\n },\n\n filterToTag: function(tag) {\n App.tag = null;\n if (tag.get('selected')) {\n tag.set('selected', false);\n }\n else {\n App.tags.models.forEach ( function (t) {\n t.set('selected', false);\n });\n App.feeds.models.forEach ( function (f) {\n f.set('selected', false);\n });\n this.set('feedFilter', undefined);\n tag.set('selected', true);\n App.tag = tag.get('title');\n }\n App.items.reboot();\n },\n\n filterToStarred: function() {\n this.set('starredFilter', true);\n this.set('allFilter', false);\n this.set('unreadFilter', false);\n this.items.reboot();\n },\n\n filterToAll: function() {\n this.set('starredFilter', false);\n this.set('allFilter', true);\n this.set('unreadFilter', false);\n this.items.reboot();\n },\n\n filterToUnread: function() {\n this.set('starredFilter', false);\n this.set('allFilter', false);\n this.set('unreadFilter', true);\n this.items.reboot();\n },\n\n filterToSearch: function() {\n this.set('searchFilter', $('#search').val());\n this.set('starredFilter', false);\n this.set('allFilter', true);\n this.set('unreadFilter', false);\n this.items.reboot();\n },\n\n update_read_status: function() {\n var screen_top = $(window).scrollTop();\n var screen_bottom = $(window).scrollTop() + $(window).height();\n\n // // mark all visible items as read\n $.each($('.item'), function(i,v) {\n var item_top = $(v).offset().top;\n // console.log(\"i \", i, \"item_top \", item_top, \"screen_top \", screen_top, \"screen_bottom \", screen_bottom);\n\n if( (item_top < screen_top)) {\n App.items.at(i).markRead();\n // console.log('marking as read: ', i);\n }\n });\n// window.setTimeout(App.update_read_status, 5000);\n },\n\n scroll_to_selected: function() {\n var item = $('.item').eq(this.get('selectedIndex'));\n if(item.offset()) {\n var item_top = item.offset().top;\n $('.item').removeClass('selected');\n item.addClass('selected');\n $(window).scrollTop(item_top);\n }\n App.items.at(this.get('selectedIndex')).markRead();\n if(App.items.models.length>1) {\n if(this.get('selected')>=App.items.models.length-1) {\n App.items.boot();\n }\n }\n },\n\n next: function() {\n if(this.get('selectedIndex') < this.items.models.length-1) {\n this.set('selectedIndex', this.get('selectedIndex')+1);\n }\n if(this.get('selectedIndex') == this.items.models.length-1) {\n App.items.boot();\n }\n },\n\n previous: function() {\n if(this.get('selectedIndex') > 0) {\n this.set('selectedIndex', this.get('selectedIndex')-1);\n }\n },\n\n star: function() {\n if(this.get('selectedIndex') >= 0) {\n App.items.at(this.get('selectedIndex')).toggleStar();\n }\n },\n\n full: function() {\n if(this.get('selectedIndex') >= 0) {\n App.items.at(this.get('selectedIndex')).full();\n }\n }\n\n});\nvar App = new AppModel();\n\nvar ControlsView = Backbone.View.extend({\n className: 'controls',\n\n events: {\n 'click .starred_filter': 'filterToStarred',\n 'click .all_filter': 'filterToAll',\n 'click .unread_filter': 'filterToUnread',\n 'click .new_feed': 'newFeed',\n 'click .search_go': 'filterToSearch',\n },\n\n initialize: function() {\n _.bindAll(this, 'render');\n this.model.bind('change', this.render);\n },\n\n filterToStarred: function() {\n App.filterToStarred();\n },\n\n filterToAll: function() {\n App.filterToAll();\n },\n\n filterToUnread: function() {\n App.filterToUnread();\n },\n\n filterToSearch: function() {\n App.filterToSearch();\n },\n\n newFeed: function() {\n var feed_url = prompt('New url to subscribe to');\n var feed = new Feed({'url': feed_url});\n App.feeds.add(feed);\n feed.save();\n },\n\n render: function() {\n var h = $.tmpl(templates.controls_template, { 'app': this.model.toJSON() });\n $(this.el).html(h);\n return this;\n },\n\n});\n\n\n\nvar Item = Backbone.Model.extend({\n idAttribute: \"_id\",\n url: '/item/',\n\n initialize: function() {\n var p_url = this.get('url');\n p_url = p_url.replace('https://', '');\n p_url = p_url.replace('http://', '');\n this.set('p_url', p_url);\n this.bind('change', this.maybeSave);\n },\n\n maybeSave: function() {\n if(this.hasChanged()) {\n this.save();\n }\n },\n\n markRead: function() {\n // recover if not tag\n if(this.get('read')) {\n return;\n }\n\n // var t = this.get('feed').tag;\n // var tag = App.tags.find(function(x){ return x.get('name') == t });\n this.set('read', true);\n // if(tag) {\n // tag.set('unread', tag.get('unread')-1);\n // }\n },\n\n toggleStar: function() {\n this.set({'starred': !(this.get('starred'))} );\n },\n\n star: function() {\n this.set({'starred': true});\n },\n\n unstar: function() {\n this.set({'starred': false});\n },\n\n full: function() {\n this.set({'full': !(this.get('full'))} );\n // this should just use this.fetch() but\n // it kept GETing from /item instead of /item/id\n // so just hacking this in for now\n\n if(this.get('full_content') == \"\") {\n $.getJSON('/item/' + this.get('_id'), function(data) {\n var i = App.items.get(data['_id'])\n i.set('full_content', data['full_content']);\n });\n }\n }\n\n});\n\n\nvar ItemCollection = Backbone.Collection.extend({\n model: Item,\n\n initialize: function() {\n _.bindAll(this, 'boot', 'reboot');\n },\n\n boot: function() {\n if(App.loading) {\n return;\n }\n if(App.noMore) {\n return;\n }\n\n App.loading = true;\n url = '/stream/';\n url=url+'?foo=bar'\n if(App.get('searchFilter')) {\n url = url + '&q=' + App.get('searchFilter');\n }\n if(App.get('feedFilter')) {\n url = url + '&feed_url=' + App.get('feedFilter').get('url');\n }\n if(App.get('starredFilter')) {\n url = url + '&starred=1';\n }\n if(App.tag != undefined) {\n url = url + '&tag=' + App.tag;\n }\n if(App.items.last()) {\n url = url + '&max_id=' + App.items.last().get('_id');\n }\n\n if(App.get('allFilter') || App.get('starredFilter')) {\n url = url + '&read_filter=all';\n }\n\n console.log('fetching from ', url);\n var t = this;\n $.getJSON(url, function(data) {\n var items = [];\n $.each(data, function(i,v) {\n var item = new Item(v);\n t.add(item);\n items.push(item);\n if(t.models.length==1){\n App.set('selectedIndex', 0);\n }\n });\n // console.log(\"items \", items)\n if(items.length == 0) {\n // console.log(\"no more items\");\n App.noMore = true;\n // App.loading = true;\n }\n else {\n App.loading = false;\n }\n // we wait and add them all at once for performance on mobile\n App.itemListView.addAll(items);\n\n });\n },\n\n reboot: function() {\n App.noMore = false;\n App.loading = false;\n this.reset();\n this.boot();\n },\n\n\n});\nApp.items = new ItemCollection();\n\n\nvar ItemView = Backbone.View.extend({\n tagName: \"div\",\n className: \"item\",\n template: templates.item_template,\n events: {\n \"click .star\": \"star\",\n \"click .unstar\": \"unstar\",\n \"click .full\": \"full\",\n },\n\n initialize: function() {\n _.bindAll(this, 'render', 'star');\n this.model.bind('change', this.render);\n },\n\n star: function() {\n this.model.star();\n this.render();\n },\n\n unstar: function() {\n this.model.unstar();\n this.render();\n },\n\n full: function() {\n this.model.full();\n this.render();\n },\n\n render: function() {\n var h = $.tmpl(templates.item_template, { 'item': this.model.toJSON() });\n $(this.el).html(h);\n return this;\n },\n});\n\nvar ItemListView = Backbone.View.extend( {\n initialize: function() {\n _.bindAll(this, 'addOne', 'addAll', 'change', 'render', 'reset');\n // App.items.bind('add', this.addOne);\n App.items.bind('reset', this.reset);\n },\n addOne: function(item) {\n var view = new ItemView({'model': item});\n this.$el.append(view.render().el);\n },\n addAll: function(items) {\n // Posts.each(this.addOne);\n for(i in items) {\n item = items[i];\n var view = new ItemView({'model': item});\n this.$el.append(view.render().el);\n };\n },\n change: function() {\n },\n render: function() {\n },\n reset: function() {\n this.$el.children().remove();\n }\n});\n\nvar Tag = Backbone.Model.extend({\n});\n\nvar TagCollection = Backbone.Collection.extend({\n model: Tag,\n initialize: function() {\n _.bindAll(this, 'boot');\n },\n\n boot: function() {\n var t = this;\n $.getJSON('/tag/', function(data) {\n $.each(data, function(i,v) {\n var tag = new Tag(v);\n t.add(tag);\n });\n });\n }\n});\nApp.tags = new TagCollection();\n\n\nvar TagView = Backbone.View.extend({\n tagName: \"li\",\t\n className: \"tag\",\n events: {\n \"click\": \"filterTo\",\n },\n initialize: function() {\n _.bindAll(this, 'render', 'filterTo');\n this.model.bind('change', this.render);\n },\n render: function() {\n var h = $.tmpl(templates.tag_template, { 'tag': this.model.toJSON() });\n $(this.el).html(h);\n return this;\n },\n filterTo: function() {\n App.filterToTag(this.model);\n }\n});\n\n\nvar TagListView = Backbone.View.extend( {\n\n initialize: function() {\n _.bindAll(this, 'addOne', 'addAll', 'change', 'render');\n App.tags.bind('add', this.addOne);\n App.tags.bind('refresh', this.addAll);\n App.tags.bind('change', this.render);\n },\n addOne: function(tag) {\n var view = new TagView({'model': tag});\n this.$el.append(view.render().el);\n },\n addAll: function() {\n App.tags.each(this.addOne);\n },\n change: function() {\n },\n render: function() {\n },\n});\n\nApp.tag = undefined;\nApp.page = 0;\nApp.read_filter = 'unread';\n\n\nvar Feed = Backbone.Model.extend({\n idAttribute: \"_id\",\n});\n\nvar FeedCollection = Backbone.Collection.extend({\n model: Feed,\n url: '/feed/',\n\n initialize: function() {\n /// _.bindAll(this, 'boot');\n //console.log('initialized');\n },\n});\nApp.feeds = new FeedCollection();\n\nvar FeedView = Backbone.View.extend({\n tagName: \"li\",\n className: \"feed\",\n events: {\n \"click .txt\": \"filterTo\",\n \"click .delete\": \"del\",\n \"click .edit\": \"edit\",\n },\n initialize: function() {\n _.bindAll(this, 'render', 'filterTo', \"del\");\n this.model.bind('change', this.render);\n },\n render: function() {\n var h = $.tmpl(templates.feed_template, { 'feed': this.model.toJSON() });\n $(this.el).html(h);\n return this;\n },\n filterTo: function() {\n // console.log('filtering to feed ', this.model);\n App.filterToFeed(this.model);\n },\n del: function() {\n if( window.confirm(\"Unsubscribe from \" + this.model.get(\"url\") + \"?\" ) ) {\n this.model.destroy();\n this.$el.remove();\n }\n },\n edit: function() {\n var cat = window.prompt(\"Category for this feed?\", this.model.get(\"category\"));\n if (cat != null) {\n this.model.set(\"category\", cat);\n this.model.save();\n }\n },\n});\n\n\nvar FeedListView = Backbone.View.extend( {\n initialize: function() {\n _.bindAll(this, 'addOne', 'addAll', 'change', 'render');\n App.feeds.bind('add', this.addOne);\n App.feeds.bind('refresh', this.addAll);\n App.feeds.bind('change', this.render);\n },\n addOne: function(feed) {\n // console.log('adding a feed...', feed);\n var view = new FeedView({'model': feed});\n this.$el.append(view.render().el);\n },\n addAll: function() {\n // console.log('feed add all...');\n App.feeds.each(this.addOne);\n },\n change: function() {\n // console.log('feeds changed add all...');\n },\n render: function() {\n },\n});\n\n\n// var page = 0;\n// var read_filter = 'unread';\n\nvar selected_item = 0;\n\nfunction boot() {\n templates['item_template'] = $('#item_template').html();\n templates['tag_template'] = $('#tag_template').html();\n templates['feed_template'] = $('#feed_template').html();\n templates['controls_template'] = $('#controls_template').html();\n\n App.itemListView = new ItemListView();\n App.itemListView.setElement($('#items'));\n App.tagListView = new TagListView();\n App.tagListView.setElement($('#tags'));\n App.feedListView = new FeedListView();\n App.feedListView.setElement($('#feeds'));\n App.controlsView = new ControlsView({model: App});\n App.controlsView.setElement($('#controls'));\n App.controlsView.render();\n\n infini_scroll();\n\n $('#unread_filter').on('click', function() {\n App.read_filter = 'unread';\n App.items.reboot();\n });\n\n $('#all_filter').on('click', function() {\n App.read_filter = 'all';\n App.items.reboot();\n });\n\n// $('.logo').on('click', function() {\n // App.set('feedFilter', undefined);\n // App.items.reboot();\n\n// });\n\n // keyboard shortcuts\n $('body').keydown(function(event) {\n if(document.activeElement.id == \"search\") {\n return;\n }\n if (event.which == 74) {\n event.preventDefault();\n App.next();\n }\n if (event.which == 75) {\n event.preventDefault();\n App.previous();\n }\n if (event.which == 83) {\n event.preventDefault();\n App.star();\n }\n if (event.which == 70) {\n event.preventDefault();\n App.full();\n }\n });\n\n App.boot();\n}\n\n\n// // this is legacy code\n\nfunction infini_scroll() {\n if(App.loading) {\n }\n else {\n var dh = $('#items').height() - $(window).height();\n var st = $(window).scrollTop();\n if ( (dh-st) < 100 ){\n App.items.boot();\n }\n }\n window.setTimeout(infini_scroll, 1000);\n}\n\n\nvar ItemSelector = {\n selected_index: 0,\n}\n"), } filee := &embedded.EmbeddedFile{ Filename: "underscore-1.8.3.min.js", @@ -82,7 +82,7 @@ func init() { // define dirs dir1 := &embedded.EmbeddedDir{ Filename: "", - DirModTime: time.Unix(1528842651, 0), + DirModTime: time.Unix(1529168083, 0), ChildFiles: []*embedded.EmbeddedFile{ file2, // ".DS_Store" file3, // "backbone-1.3.3.min.js" @@ -108,7 +108,7 @@ func init() { // register embeddedBox embedded.RegisterEmbeddedBox(`../static`, &embedded.EmbeddedBox{ Name: `../static`, - Time: time.Unix(1528842651, 0), + Time: time.Unix(1529168083, 0), Dirs: map[string]*embedded.EmbeddedDir{ "": dir1, }, diff --git a/web/web.go b/web/web.go index 742ce54..231dace 100644 --- a/web/web.go +++ b/web/web.go @@ -51,8 +51,14 @@ func streamHandler(w http.ResponseWriter, r *http.Request) { starred_only = true } + search_query := "" + if r.FormValue("q") != "" { + search_query = r.FormValue("q") + unread_only = false + } + var items []*item.Item - items, err := item.Filter(int64(max_id), feed_id, category, unread_only, starred_only, 0) + items, err := item.Filter(int64(max_id), feed_id, category, unread_only, starred_only, 0, search_query) if err != nil { log.Println(err) } -- cgit v1.2.3