Permalink
Browse files

Initial (Rough, quick implementation)

  • Loading branch information...
0 parents commit 37091fe1351ce12bfd75d0713e857e5b99b40792 @medikoo committed May 7, 2012
@@ -0,0 +1,4 @@
+.DS_Store
+/config.json
+/node_modules
+/npm-debug.log
@@ -0,0 +1,20 @@
+# Github News Reader
+
+_Rough quick implementation for own purposes, tested with Node v0.6 and Google Chrome browser_
+
+Groups articles into relative subjects and provides Google Reader like interface.
+Individual sections can be unsubscribed.
+
+## Installation
+
+ $ git clone https://github.com/medikoo/github-news-reader.git
+ $ npm install
+ $ npm run-script setup
+
+Create `config.json` in project path with following settings:
+
+* `port` - Port on which application server should listen
+* `username` - Your github username
+* `token` - Copy it from your news feed url
+
+ $ npm start
@@ -0,0 +1,67 @@
+'use strict';
+
+var isArray = Array.isArray
+ , pluck = require('es5-ext/lib/Function/pluck')
+ , not = require('es5-ext/lib/Function/prototype/not')
+ , count = require('es5-ext/lib/Object/count')
+ , forEach = require('es5-ext/lib/Object/for-each')
+ , map = require('es5-ext/lib/Object/map')
+ , ee = require('event-emitter')
+
+ , markRead;
+
+markRead = function () {
+ if (!this.read) {
+ this.read = true;
+ this.emit('update', { type: 'read' });
+ }
+};
+
+var data = JSON.parse("%RSS%");
+
+console.log("DATA", data);
+
+module.exports = exports = map(data, function self(data, name) {
+ if (isArray(data)) {
+ data = ee(data);
+ data.forEach(function (article) {
+ ee(article);
+ article.on('update', function () {
+ data.emit('update');
+ });
+ article.markRead = markRead
+ });
+ } else {
+ data = ee(map(data, self));
+ forEach(data, function (obj, name) {
+ if (isArray(obj)) {
+ obj.on('update', function () {
+ if (!this.filter(not.call(pluck('read'))).length) {
+ delete data[name];
+ data.emit('update');
+ }
+ });
+ obj.on('ignore', function () {
+ delete data[name];
+ data.emit('update');
+ });
+ } else {
+ obj.on('update', function () {
+ if (!count(this)) {
+ delete data[name];
+ data.emit('update');
+ }
+ });
+ }
+ });
+ }
+ return data;
+});
+
+forEach(exports, function (obj, name) {
+ obj.on('update', function () {
+ if (!count(this)) {
+ delete exports[name];
+ }
+ });
+});
@@ -0,0 +1,5 @@
+'use strict';
+
+require('../view');
+
+require('../server');
@@ -0,0 +1,30 @@
+'use strict';
+
+var isArray = Array.isArray
+ , copy = require('es5-ext/lib/Array/prototype/copy')
+ , forEach = require('es5-ext/lib/Object/for-each')
+ , data = require('./data')
+ , socket = io.connect(location.protocol + '//' + location.host)
+
+ , path = [];
+
+forEach(data, function self(value, name, context) {
+ path.push(name);
+ if (isArray(value)) {
+ value.$path = copy.call(path);
+ value.forEach(function (article) {
+ article.on('update', function (e) {
+ if (e && (e.type === 'read')) {
+ socket.emit('read', value.$path.concat(article.guid));
+ }
+ });
+ });
+
+ value.on('ignore', function () {
+ socket.emit('ignore', value.$path);
+ });
+ } else {
+ forEach(value, self);
+ }
+ path.pop();
+});
@@ -0,0 +1,138 @@
+'use strict';
+
+var isArray = Array.isArray
+ , call = Function.prototype.call
+ , max = Math.max
+ , keys = Object.keys
+ , last = require('es5-ext/lib/Array/prototype/last')
+ , format = require('es5-ext/lib/Date/get-format')('%Y-%m-%d %H:%S:%S')
+ , memoize = require('es5-ext/lib/Function/memoize')
+ , pluck = require('es5-ext/lib/Function/pluck')
+ , not = require('es5-ext/lib/Function/prototype/not')
+ , count = require('es5-ext/lib/Object/count')
+ , map = require('es5-ext/lib/Object/map')
+ , mapToArray = require('es5-ext/lib/Object/map-to-array')
+ , lcSort = call.bind(
+ require('es5-ext/lib/String/prototype/case-insensitive-compare'))
+ , domjs = require('domjs/lib/html5')(document)
+ , data = require('./data')
+
+ , articleDOM;
+
+articleDOM = memoize(function (article) {
+ var el, body
+ el = this.li({ 'class': 'article' },
+ !article.skipTitle &&
+ this.h2(this.a({ href: article.link, target: '_blank' }, article.title)),
+ !article.skipAuthor && this.div({ 'class': 'author' }, this.b(article.author), ' at ' +
+ format.call(new Date(Date.parse(article.date)))),
+ body = this.div({ 'class': 'body' })())();
+ body.innerHTML = article.description;
+ return el;
+}.bind(domjs.map));
+
+document.body.appendChild(domjs.build(function () {
+ var nest = 0, container, content, load, selected, fixPadding
+ , offsets = [], current, scope, articles, ignore, reset;
+
+ reset = function () {
+ scope = data;
+ while (!isArray(scope)) {
+ scope = scope[keys(scope).sort(lcSort)[0]];
+ }
+ load.call(scope);
+ };
+
+ load = function () {
+ var els;
+ current = this;
+ els = this.map(articleDOM);
+ articles().innerHTML = '';
+ articles(els);
+ this.emit('select');
+ this.some(function (el, i) {
+ if (!el.read) {
+ container.scrollTop = max(els[i].offsetTop - ((i === 0) ? 50 : 20), 0);
+ return true;
+ }
+ });
+ offsets = els.map(function (el) {
+ return el.offsetTop + 60;
+ });
+ };
+
+ ignore = function () {
+ current.emit('ignore');
+ };
+
+ fixPadding = function () {
+ content.style.paddingBottom = max(document.body.scrollHeight,
+ document.body.offsetHeight,
+ document.documentElement.clientHeight,
+ document.documentElement.scrollHeight,
+ document.documentElement.offsetHeight) + 'px';
+ };
+
+ section({ 'class': 'aside' },
+ ul({ 'class': 'nest-' + nest }, mapToArray(data, function self(value, key, context) {
+ var el, len;
+ if (isArray(value)) {
+ el = li({ 'class': 'feed' }, a({ onclick: load.bind(value) },
+ (last.call(value).headTitle || key) + "\u00a0(",
+ (len = _text(value.filter(not.call(pluck('read'))).length)), ")"))();
+ value.on('select', function () {
+ if (selected) {
+ selected.classList.remove('selected');
+ }
+ el.classList.add('selected');
+ selected = el;
+ });
+ value.on('update', function () {
+ var rlen = this.filter(not.call(pluck('read'))).length;
+ len.data = rlen;
+ if (!rlen) {
+ el.parentNode.removeChild(el);
+ }
+ });
+ value.on('ignore', function () {
+ el.parentNode.removeChild(el);
+ reset();
+ });
+ } else {
+ el = li(h3(key), ul({ 'class': 'nest-' + (++nest) },
+ mapToArray(value, self, null, lcSort)))();
+ --nest;
+ value.on('update', function () {
+ if (!count(this)) {
+ el.parentNode.removeChild(el);
+ }
+ });
+ }
+ return el;
+ }, null, lcSort)));
+ container = section({ 'class': 'content' },
+ content = div(
+ p({ 'class': 'controls' },
+ input({ type: 'button', value: 'Unsubscribe', onclick: ignore })),
+ articles = ul(),
+ p({ 'class': 'controls' },
+ input({ type: 'button', value: 'Unsubscribe', onclick: ignore }))
+ )())();
+
+ fixPadding();
+ window.onresize = fixPadding;
+
+ container.onscroll = function () {
+ var index = -1, pos = container.scrollTop;
+ offsets.every(function (offset, i) {
+ if (pos > offset) {
+ current[i].markRead();
+ return true;
+ }
+ });
+ };
+
+ // Show first
+ reset();
+
+}));
@@ -0,0 +1,39 @@
+'use strict';
+
+var find = require('es5-ext/lib/Array/prototype/find')
+ , forEach = require('es5-ext/lib/Object/for-each')
+ , data = require('./data')
+ , socket = require('socket.io').listen(require('./server')).sockets
+
+ , actions;
+
+actions = {
+ read: function (path) {
+ var scope = data
+ , guid = path.pop();
+
+ console.log("READ", path.join('|'), guid);
+ path.forEach(function (name) {
+ scope = scope[name];
+ });
+ find.call(scope, function (art) {
+ return art.guid === guid;
+ }).read = true;
+ },
+ ignore: function (path) {
+ var scope = data
+ , name = path.pop();
+
+ console.log("IGNORE", path.join('|'), name);
+ path.forEach(function (name) {
+ scope = scope[name];
+ });
+ scope[name] = false;
+ }
+};
+
+socket.on('connection', function (socket) {
+ forEach(actions, function (listener, name) {
+ socket.on(name, listener);
+ });
+});
Oops, something went wrong.

0 comments on commit 37091fe

Please sign in to comment.