Simple RSS Reader inspired by Gxxgle Reader
Revisión | 2aed231f6b845c329fc0fc3a3d6219c72f380321 (tree) |
---|---|
Tiempo | 2013-07-11 01:03:14 |
Autor | hylom <hylom@hylo...> |
Commiter | hylom |
to use hogan.js template engine in client-side
@@ -1,7 +1,7 @@ | ||
1 | 1 | Grrreader |
2 | -======================================== | |
2 | +========= | |
3 | 3 | |
4 | -Grrreader - gxxgle-Reader-like Rss READER | |
4 | +Grrreader - Gxxgle-Reader-inspired Rss READER | |
5 | 5 | (a.k.a. Gxxgle Reader Clone) |
6 | 6 | |
7 | 7 |
@@ -20,8 +20,8 @@ Requires | ||
20 | 20 | -------- |
21 | 21 | * Python 2.7.x |
22 | 22 | * Node.js 0.10.x |
23 | - * Some python modules: feedparser, dateutil.parser, mysql.connector | |
24 | - * Some node.js modules: defined in client/package.json and forever | |
23 | + * Some python modules: "feedparser", "dateutil.parser", "mysql.connector" | |
24 | + * Some node.js modules: defined in client/package.json and "forever" | |
25 | 25 | * MySQL |
26 | 26 | |
27 | 27 |
@@ -29,7 +29,7 @@ How to install | ||
29 | 29 | -------------- |
30 | 30 | |
31 | 31 | 1. install Python (>2.7.x), Node.js (>0.10.x), MySQL |
32 | -2. create MySQL database and user for use | |
32 | +2. create MySQL database and user, tables for use | |
33 | 33 | 3. run `npm install` in client directory |
34 | 34 | 4. copy client/config.json.sample to client/config.json |
35 | 35 | 5. edit client/config.json |
@@ -40,6 +40,26 @@ How to install | ||
40 | 40 | 10. add backend/feedfetcher.py to crontab |
41 | 41 | 11. start rrreader service like: `# service grreader start` |
42 | 42 | |
43 | +Create Tables | |
44 | +--------------- | |
45 | +To create Tables, do below commands. | |
46 | + | |
47 | + $ cd backend | |
48 | + $ python | |
49 | + Python 2.7.2 (default, Oct 11 2012, 20:14:37) | |
50 | + [GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin | |
51 | + Type "help", "copyright", "credits" or "license" for more information. | |
52 | + >>> import db | |
53 | + >>> db.MySQLDatabase().create_tables() | |
54 | + >>> ^D | |
55 | + $ | |
56 | + | |
57 | + | |
58 | +Import Google Reader's registered feeds | |
59 | +--------------------------------------- | |
60 | + $ cd backend | |
61 | + $ python greaderimport.py < ../subscriptions.xml | |
62 | + | |
43 | 63 | |
44 | 64 | Sample crontab |
45 | 65 | -------------- |
@@ -9,6 +9,7 @@ | ||
9 | 9 | "express": "3.3.1", |
10 | 10 | "jade": "*", |
11 | 11 | "mysql": "*", |
12 | - "winston": "*" | |
12 | + "winston": "*", | |
13 | + "hogan.js": "*" | |
13 | 14 | } |
14 | 15 | } |
@@ -1,33 +1,44 @@ | ||
1 | 1 | // grdc.js |
2 | +// require hogan.js template engine. | |
2 | 3 | |
3 | 4 | (function () { |
5 | + // private functions | |
6 | + | |
4 | 7 | function updateContentsPane(data) { |
5 | 8 | var pane = $('#contentsPane'); |
6 | 9 | pane.empty(); |
10 | + var tmpl = '<tr class="contentHeader" id="chead{{content_id}}">' | |
11 | + + '<td class="contentTitle"><h4>' | |
12 | + + ' <a href="#" class="contentTitleString" cid="{{content_id}}">{{title}}</a>' | |
13 | + + '</h4></td>' | |
14 | + + '<td class="contentTimestamp">{{formatedTimestamp}}</td>' | |
15 | + + '<td class="contentLink">' | |
16 | + + ' <a target="_blank" href="{{url}}">' | |
17 | + + ' <i class="icon-chevron-right"></i></a>' | |
18 | + + '</td></tr>' | |
19 | + + '<tr class="contentBody" id="cbody{{content_id}}">' | |
20 | + + ' <td colspan="3">{{{body}}}</td>' | |
21 | + + '</tr>' | |
22 | + ; | |
23 | + var tableRows = Hogan.compile(tmpl); | |
24 | + | |
7 | 25 | for (var i = 0; i < data.length; i++) { |
8 | 26 | var item = data[i]; |
9 | - var elem = '<tr class="contentHeader" id="chead' + item.content_id + '">' | |
10 | - + '<td class="contentTitle"><h4><a href="#" class="contentTitleString" cid="' + item.content_id + '">' + item.title + '</a></h4></td>' | |
11 | - + '<td class="contentTimestamp">' + item.formatedTimestamp + '</td>' | |
12 | - + '<td class="contentLink"><a target="_blank" href="' + item.url + '">' | |
13 | - + '<i class="icon-chevron-right"></i></a></td>' | |
14 | - + '</tr>' | |
15 | - + '<tr class="contentBody" id="cbody' + item.content_id + '">' | |
16 | - + '<td colspan="3">' | |
17 | - + item.body | |
18 | - + '</td>' | |
19 | - + '</tr>' | |
20 | - ; | |
27 | + var elem = tableRows.render(item); | |
21 | 28 | pane.append(elem); |
22 | 29 | } |
23 | 30 | } |
24 | 31 | |
25 | 32 | function loadFeed(event) { |
26 | 33 | var feedId = $(event.target).attr('feed-id'); |
34 | + var feedTitle = $(event.target).text(); | |
27 | 35 | if (feedId !== undefined) { |
28 | 36 | var url = '/api/feed/' + feedId + '/contents'; |
29 | 37 | $.getJSON(url, updateContentsPane); |
30 | 38 | } |
39 | + $('#feedTitle').text(feedTitle); | |
40 | + var pane = $('#contentsPane'); | |
41 | + pane.empty(); | |
31 | 42 | return false; |
32 | 43 | } |
33 | 44 |
@@ -42,6 +53,12 @@ | ||
42 | 53 | |
43 | 54 | $(document).on('click', ".feedItem", loadFeed); |
44 | 55 | $(document).on('click', ".contentTitleString", toggleContentBody); |
56 | + | |
57 | + $(document).ready(function () { | |
58 | + var feedId = 0; | |
59 | + var url = '/api/feed/' + feedId + '/contents'; | |
60 | + $.getJSON(url, updateContentsPane); | |
61 | + }); | |
45 | 62 | }).apply(); |
46 | 63 | |
47 | 64 |
@@ -0,0 +1,5 @@ | ||
1 | +/** | |
2 | +* @preserve Copyright 2012 Twitter, Inc. | |
3 | +* @license http://www.apache.org/licenses/LICENSE-2.0.txt | |
4 | +*/ | |
5 | +var Hogan={};(function(a,b){function i(a){return String(a===null||a===undefined?"":a)}function j(a){return a=i(a),h.test(a)?a.replace(c,"&").replace(d,"<").replace(e,">").replace(f,"'").replace(g,"""):a}a.Template=function(a,c,d,e){this.r=a||this.r,this.c=d,this.options=e,this.text=c||"",this.buf=b?[]:""},a.Template.prototype={r:function(a,b,c){return""},v:j,t:i,render:function(b,c,d){return this.ri([b],c||{},d)},ri:function(a,b,c){return this.r(a,b,c)},rp:function(a,b,c,d){var e=c[a];return e?(this.c&&typeof e=="string"&&(e=this.c.compile(e,this.options)),e.ri(b,c,d)):""},rs:function(a,b,c){var d=a[a.length-1];if(!k(d)){c(a,b,this);return}for(var e=0;e<d.length;e++)a.push(d[e]),c(a,b,this),a.pop()},s:function(a,b,c,d,e,f,g){var h;return k(a)&&a.length===0?!1:(typeof a=="function"&&(a=this.ls(a,b,c,d,e,f,g)),h=a===""||!!a,!d&&h&&b&&b.push(typeof a=="object"?a:b[b.length-1]),h)},d:function(a,b,c,d){var e=a.split("."),f=this.f(e[0],b,c,d),g=null;if(a==="."&&k(b[b.length-2]))return b[b.length-1];for(var h=1;h<e.length;h++)f&&typeof f=="object"&&e[h]in f?(g=f,f=f[e[h]]):f="";return d&&!f?!1:(!d&&typeof f=="function"&&(b.push(g),f=this.lv(f,b,c),b.pop()),f)},f:function(a,b,c,d){var e=!1,f=null,g=!1;for(var h=b.length-1;h>=0;h--){f=b[h];if(f&&typeof f=="object"&&a in f){e=f[a],g=!0;break}}return g?(!d&&typeof e=="function"&&(e=this.lv(e,b,c)),e):d?!1:""},ho:function(a,b,c,d,e){var f=this.c,g=this.options;g.delimiters=e;var d=a.call(b,d);return d=d==null?String(d):d.toString(),this.b(f.compile(d,g).render(b,c)),!1},b:b?function(a){this.buf.push(a)}:function(a){this.buf+=a},fl:b?function(){var a=this.buf.join("");return this.buf=[],a}:function(){var a=this.buf;return this.buf="",a},ls:function(a,b,c,d,e,f,g){var h=b[b.length-1],i=null;if(!d&&this.c&&a.length>0)return this.ho(a,h,c,this.text.substring(e,f),g);i=a.call(h);if(typeof i=="function"){if(d)return!0;if(this.c)return this.ho(i,h,c,this.text.substring(e,f),g)}return i},lv:function(a,b,c){var d=b[b.length-1],e=a.call(d);if(typeof e=="function"){e=i(e.call(d));if(this.c&&~e.indexOf("{{"))return this.c.compile(e,this.options).render(d,c)}return i(e)}};var c=/&/g,d=/</g,e=/>/g,f=/\'/g,g=/\"/g,h=/[&<>\"\']/,k=Array.isArray||function(a){return Object.prototype.toString.call(a)==="[object Array]"}})(typeof exports!="undefined"?exports:Hogan),function(a){function h(a){a.n.substr(a.n.length-1)==="}"&&(a.n=a.n.substring(0,a.n.length-1))}function i(a){return a.trim?a.trim():a.replace(/^\s*|\s*$/g,"")}function j(a,b,c){if(b.charAt(c)!=a.charAt(0))return!1;for(var d=1,e=a.length;d<e;d++)if(b.charAt(c+d)!=a.charAt(d))return!1;return!0}function k(a,b,c,d){var e=[],f=null,g=null;while(a.length>0){g=a.shift();if(g.tag=="#"||g.tag=="^"||l(g,d))c.push(g),g.nodes=k(a,g.tag,c,d),e.push(g);else{if(g.tag=="/"){if(c.length===0)throw new Error("Closing tag without opener: /"+g.n);f=c.pop();if(g.n!=f.n&&!m(g.n,f.n,d))throw new Error("Nesting error: "+f.n+" vs. "+g.n);return f.end=g.i,e}e.push(g)}}if(c.length>0)throw new Error("missing closing tag: "+c.pop().n);return e}function l(a,b){for(var c=0,d=b.length;c<d;c++)if(b[c].o==a.n)return a.tag="#",!0}function m(a,b,c){for(var d=0,e=c.length;d<e;d++)if(c[d].c==a&&c[d].o==b)return!0}function n(a){return a.replace(f,"\\\\").replace(c,'\\"').replace(d,"\\n").replace(e,"\\r")}function o(a){return~a.indexOf(".")?"d":"f"}function p(a){var b="";for(var c=0,d=a.length;c<d;c++){var e=a[c].tag;e=="#"?b+=q(a[c].nodes,a[c].n,o(a[c].n),a[c].i,a[c].end,a[c].otag+" "+a[c].ctag):e=="^"?b+=r(a[c].nodes,a[c].n,o(a[c].n)):e=="<"||e==">"?b+=s(a[c]):e=="{"||e=="&"?b+=t(a[c].n,o(a[c].n)):e=="\n"?b+=v('"\\n"'+(a.length-1==c?"":" + i")):e=="_v"?b+=u(a[c].n,o(a[c].n)):e===undefined&&(b+=v('"'+n(a[c])+'"'))}return b}function q(a,b,c,d,e,f){return"if(_.s(_."+c+'("'+n(b)+'",c,p,1),'+"c,p,0,"+d+","+e+',"'+f+'")){'+"_.rs(c,p,"+"function(c,p,_){"+p(a)+"});c.pop();}"}function r(a,b,c){return"if(!_.s(_."+c+'("'+n(b)+'",c,p,1),c,p,1,0,0,"")){'+p(a)+"};"}function s(a){return'_.b(_.rp("'+n(a.n)+'",c,p,"'+(a.indent||"")+'"));'}function t(a,b){return"_.b(_.t(_."+b+'("'+n(a)+'",c,p,0)));'}function u(a,b){return"_.b(_.v(_."+b+'("'+n(a)+'",c,p,0)));'}function v(a){return"_.b("+a+");"}var b=/\S/,c=/\"/g,d=/\n/g,e=/\r/g,f=/\\/g,g={"#":1,"^":2,"/":3,"!":4,">":5,"<":6,"=":7,_v:8,"{":9,"&":10};a.scan=function(c,d){function w(){p.length>0&&(q.push(new String(p)),p="")}function x(){var a=!0;for(var c=t;c<q.length;c++){a=q[c].tag&&g[q[c].tag]<g._v||!q[c].tag&&q[c].match(b)===null;if(!a)return!1}return a}function y(a,b){w();if(a&&x())for(var c=t,d;c<q.length;c++)q[c].tag||((d=q[c+1])&&d.tag==">"&&(d.indent=q[c].toString()),q.splice(c,1));else b||q.push({tag:"\n"});r=!1,t=q.length}function z(a,b){var c="="+v,d=a.indexOf(c,b),e=i(a.substring(a.indexOf("=",b)+1,d)).split(" ");return u=e[0],v=e[1],d+c.length-1}var e=c.length,f=0,k=1,l=2,m=f,n=null,o=null,p="",q=[],r=!1,s=0,t=0,u="{{",v="}}";d&&(d=d.split(" "),u=d[0],v=d[1]);for(s=0;s<e;s++)m==f?j(u,c,s)?(--s,w(),m=k):c.charAt(s)=="\n"?y(r):p+=c.charAt(s):m==k?(s+=u.length-1,o=g[c.charAt(s+1)],n=o?c.charAt(s+1):"_v",n=="="?(s=z(c,s),m=f):(o&&s++,m=l),r=s):j(v,c,s)?(q.push({tag:n,n:i(p),otag:u,ctag:v,i:n=="/"?r-v.length:s+u.length}),p="",s+=v.length-1,m=f,n=="{"&&(v=="}}"?s++:h(q[q.length-1]))):p+=c.charAt(s);return y(r,!0),q},a.generate=function(b,c,d){var e='var _=this;_.b(i=i||"");'+p(b)+"return _.fl();";return d.asString?"function(c,p,i){"+e+";}":new a.Template(new Function("c","p","i",e),c,a,d)},a.parse=function(a,b,c){return c=c||{},k(a,"",[],c.sectionTags||[])},a.cache={},a.compile=function(a,b){b=b||{};var c=a+"||"+!!b.asString,d=this.cache[c];return d?d:(d=this.generate(this.parse(this.scan(a,b.delimiters),a,b),a,b),this.cache[c]=d)}}(typeof exports!="undefined"?exports:Hogan) | |
\ No newline at end of file |
@@ -89,14 +89,30 @@ exports.feedContent = function (req, res) { | ||
89 | 89 | exports.feedContents = function (req, res) { |
90 | 90 | var feedId = req.params.fid; |
91 | 91 | var connection = mysql.createConnection(config.mysql); |
92 | + var skip = req.query.skip || 0; | |
93 | + var count = req.query.count || 20; | |
94 | + | |
95 | + if (feedId === '0') { | |
96 | + // show all feeds | |
97 | + // TODO: if use multiple users, need checking subscribing feeds | |
98 | + var sql = 'SELECT content_id, feed_id, title, url, body, timestamp' | |
99 | + + ' FROM feed_contents' | |
100 | + + ' ORDER BY timestamp DESC' | |
101 | + + ' LIMIT ?, ?' | |
102 | + + ';'; | |
103 | + var params = [skip, count]; | |
104 | + } else { | |
105 | + var sql = 'SELECT content_id, feed_id, title, url, body, timestamp' | |
106 | + + ' FROM feed_contents' | |
107 | + + ' WHERE feed_id = ?' | |
108 | + + ' ORDER BY timestamp DESC' | |
109 | + + ' LIMIT ?, ?' | |
110 | + + ';'; | |
111 | + var params = [feedId, skip, count]; | |
112 | + } | |
92 | 113 | |
93 | 114 | connection.connect(); |
94 | - var sql = 'SELECT content_id, feed_id, title, url, body, timestamp' | |
95 | - + ' FROM feed_contents' | |
96 | - + ' WHERE feed_id = ?' | |
97 | - + ' ORDER BY timestamp DESC' | |
98 | - + ';'; | |
99 | - connection.query(sql, feedId, function (err, rows, fields) { | |
115 | + connection.query(sql, params, function (err, rows, fields) { | |
100 | 116 | connection.end(); |
101 | 117 | if (err) { |
102 | 118 | logger.debug("query error at index.feedContents: " + util.inspect(err)); |
@@ -4,8 +4,10 @@ block content | ||
4 | 4 | #contents-body |
5 | 5 | .row |
6 | 6 | .span3 |
7 | + a.feedItem(href='#', feed-id='0') All Feeds | |
7 | 8 | ul.unstyled |
8 | 9 | each feed in feeds |
9 | 10 | li: a.feedItem(href='#', feed-id=feed.feed_id) #{feed.title} |
10 | 11 | .span9 |
12 | + h4#feedTitle All Feeds: | |
11 | 13 | table.table#contentsPane |
@@ -8,6 +8,7 @@ html | ||
8 | 8 | body |
9 | 9 | script(src="http://code.jquery.com/jquery.js") |
10 | 10 | script(src="/js/bootstrap.min.js") |
11 | + script(src="/js/hogan-2.0.0.min.js") | |
11 | 12 | script(src="/js/grdc.js") |
12 | 13 | #header |
13 | 14 | p grdc - RSS reader |