diff options
| -rw-r--r-- | models/item/item.go | 17 | ||||
| -rw-r--r-- | sqlite.init.sql | 71 | ||||
| -rw-r--r-- | static/ui.html | 3 | ||||
| -rw-r--r-- | web/rice-box.go | 12 | ||||
| -rw-r--r-- | 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 @@ -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,  		}, @@ -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)  	}  | 
