aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--models/item/item.go17
-rw-r--r--sqlite.init.sql71
-rw-r--r--static/ui.html3
-rw-r--r--web/rice-box.go12
-rw-r--r--web/web.go8
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
@@ -97,6 +97,9 @@
<li>
<button class="new_feed"> + </button>
</li>
+ <li>
+ <input id="search" type="search" /><button class="search_go">search</button>
+ </li>
<li>
</li>
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("<!DOCTYPE html>\n<html>\n <head>\n <title>neko rss mode</title>\n <link rel=\"stylesheet\" href=\"/static/style.css\" />\n <script src=\"/static/jquery-3.3.1.min.js\"></script>\n <script src=\"/static/jquery.tmpl.min.js\"></script>\n <script src=\"/static/underscore-1.8.3.min.js\"></script>\n <script src=\"/static/backbone-1.3.3.min.js\"></script>\n <script>\n PUBLIC_VERSION = false;\n </script>\n <script src=\"/static/ui.js\"></script>\n <meta name=\"viewport\" content=\"width=device-width,height=device-height, initial-scale=1, maximum-scale=1\" />\n <base target=\"_blank\">\n </head>\n <body>\n <h1 class=\"logo\" onclick=\"$('#filters').toggleClass('hidden');\">🐱</h1>\n\n <div id=\"filters\">\n\n <div id=\"controls\"></div>\n <h4 onclick=\"$('#tags').toggle();\">Tags</h4> \n <ul id=\"tags\" style=\"display: none;\">\n </ul>\n \n <h4 onclick=\"$('#feeds').toggle();\">Feeds</h4> \n <ul id=\"feeds\" style=\"display: none;\">\n </ul>\n </div>\n\n </div>\n\n <div id=\"c\">\n <div id=\"items\">\n </div> \n </div>\n \n <script id=\"item_template\" type=\"text/jqtmp\">\n <h2><a class=\"i\" id=\"i_${item_id}\" href=\"${item.url}\">${item.title }</a> \n <span class={{if item.starred}}\"unstar\"{{else}}\"star\"{{/if}}>★</span>\n </h2>\n <p class=\"dateline\" style=\"clear: both;\">\n <a href=\"${item.feed_url}\">${item.feed_title}</a> | <a href=\"${item.url}\">${item.p_url}</a>\n | ${item.feed_category} |\n <span class=\"full\">{{if item.full}}hide{{else}}scrape{{/if}} full text</span>\n \n </p>\n {{if item.header_image}}\n <div class=\"img\"><img src=\"${item.header_image}\" /></div>\n {{/if}}\n <div class=\"description\">\n {{if item.full}}\n {{html item.full_content}}\n {{else}}\n {{html item.description}}\n {{/if}}\n </div>\n </script>\n\n <script id=\"tag_template\" type=\"text/jqtmp\">\n {{if tag.selected}}<b>{{/if}}\n ${tag.title}\n {{if tag.selected}}</b>{{/if}}\n </script>\n\n <script id=\"feed_template\" type=\"text/jqtmp\">\n {{if feed.selected}}<b>{{/if}}\n <span class=\"txt\">\n {{if feed.title}}\n ${feed.title}\n {{else}}\n ${feed.url}\n {{/if}}\n\n </span>\n <span class=\"edit\">[e]</span>\n <span class=\"delete\">[x]</span>\n {{if feed.selected}}</b>{{/if}}\n </script>\n\n <script id=\"controls_template\" type=\"text/jqtmp\">\n <ul>\n <li>\n <a {{if app.unreadFilter}}style=\"font-weight: bold;\"{{/if}} \n class=\"unread_filter\">unread</a>\n </li>\n <li>\n <a \n {{if app.allFilter}}style=\"font-weight: bold;\"{{/if}} \n class=\"all_filter\">all</a> \n </li>\n <li>\n <a {{if app.starredFilter}}style=\"font-weight: bold;\"{{/if}}\n class=\"starred_filter\">★ starred</a>\n </li>\n <li>\n <button class=\"new_feed\"> + </button>\n </li>\n\t\t<li>\n\t\t</li>\n\n\t </ul>\n </script> \n \n</body>\n</html> \n"),
+ FileModTime: time.Unix(1528941212, 0),
+ Content: string("<!DOCTYPE html>\n<html>\n <head>\n <title>neko rss mode</title>\n <link rel=\"stylesheet\" href=\"/static/style.css\" />\n <script src=\"/static/jquery-3.3.1.min.js\"></script>\n <script src=\"/static/jquery.tmpl.min.js\"></script>\n <script src=\"/static/underscore-1.8.3.min.js\"></script>\n <script src=\"/static/backbone-1.3.3.min.js\"></script>\n <script>\n PUBLIC_VERSION = false;\n </script>\n <script src=\"/static/ui.js\"></script>\n <meta name=\"viewport\" content=\"width=device-width,height=device-height, initial-scale=1, maximum-scale=1\" />\n <base target=\"_blank\">\n </head>\n <body>\n <h1 class=\"logo\" onclick=\"$('#filters').toggleClass('hidden');\">🐱</h1>\n\n <div id=\"filters\">\n\n <div id=\"controls\"></div>\n <h4 onclick=\"$('#tags').toggle();\">Tags</h4> \n <ul id=\"tags\" style=\"display: none;\">\n </ul>\n \n <h4 onclick=\"$('#feeds').toggle();\">Feeds</h4> \n <ul id=\"feeds\" style=\"display: none;\">\n </ul>\n </div>\n\n </div>\n\n <div id=\"c\">\n <div id=\"items\">\n </div> \n </div>\n \n <script id=\"item_template\" type=\"text/jqtmp\">\n <h2><a class=\"i\" id=\"i_${item_id}\" href=\"${item.url}\">${item.title }</a> \n <span class={{if item.starred}}\"unstar\"{{else}}\"star\"{{/if}}>★</span>\n </h2>\n <p class=\"dateline\" style=\"clear: both;\">\n <a href=\"${item.feed_url}\">${item.feed_title}</a> | <a href=\"${item.url}\">${item.p_url}</a>\n | ${item.feed_category} |\n <span class=\"full\">{{if item.full}}hide{{else}}scrape{{/if}} full text</span>\n \n </p>\n {{if item.header_image}}\n <div class=\"img\"><img src=\"${item.header_image}\" /></div>\n {{/if}}\n <div class=\"description\">\n {{if item.full}}\n {{html item.full_content}}\n {{else}}\n {{html item.description}}\n {{/if}}\n </div>\n </script>\n\n <script id=\"tag_template\" type=\"text/jqtmp\">\n {{if tag.selected}}<b>{{/if}}\n ${tag.title}\n {{if tag.selected}}</b>{{/if}}\n </script>\n\n <script id=\"feed_template\" type=\"text/jqtmp\">\n {{if feed.selected}}<b>{{/if}}\n <span class=\"txt\">\n {{if feed.title}}\n ${feed.title}\n {{else}}\n ${feed.url}\n {{/if}}\n\n </span>\n <span class=\"edit\">[e]</span>\n <span class=\"delete\">[x]</span>\n {{if feed.selected}}</b>{{/if}}\n </script>\n\n <script id=\"controls_template\" type=\"text/jqtmp\">\n <ul>\n <li>\n <a {{if app.unreadFilter}}style=\"font-weight: bold;\"{{/if}} \n class=\"unread_filter\">unread</a>\n </li>\n <li>\n <a \n {{if app.allFilter}}style=\"font-weight: bold;\"{{/if}} \n class=\"all_filter\">all</a> \n </li>\n <li>\n <a {{if app.starredFilter}}style=\"font-weight: bold;\"{{/if}}\n class=\"starred_filter\">★ starred</a>\n </li>\n <li>\n <button class=\"new_feed\"> + </button>\n </li>\n <li>\n <input id=\"search\" type=\"search\" /><button class=\"search_go\">search</button>\n </li>\n\t\t<li>\n\t\t</li>\n\n\t </ul>\n </script> \n \n</body>\n</html> \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)
}