");
11 | $alert.attr("class", "bootstrap-growl alert");
12 | if (options.type) {
13 | $alert.addClass("alert-" + options.type);
14 | }
15 | if (options.allow_dismiss) {
16 | $alert.append("
× ");
17 | }
18 | $alert.append(message);
19 | if (options.top_offset) {
20 | options.offset = {
21 | from: "top",
22 | amount: options.top_offset
23 | };
24 | }
25 | offsetAmount = options.offset.amount;
26 | $(".bootstrap-growl").each(function() {
27 | return offsetAmount = Math.max(offsetAmount, parseInt($(this).css(options.offset.from)) + $(this).outerHeight() + options.stackup_spacing);
28 | });
29 | css = {
30 | "position": (options.ele === "body" ? "fixed" : "absolute"),
31 | "margin": 0,
32 | "z-index": "9999",
33 | "display": "none"
34 | };
35 | css[options.offset.from] = offsetAmount + "px";
36 | $alert.css(css);
37 | if (options.width !== "auto") {
38 | $alert.css("width", options.width + "px");
39 | }
40 | $(options.ele).append($alert);
41 | switch (options.align) {
42 | case "center":
43 | $alert.css({
44 | "left": "50%",
45 | "margin-left": "-" + ($alert.outerWidth() / 2) + "px"
46 | });
47 | break;
48 | case "left":
49 | $alert.css("left", "20px");
50 | break;
51 | default:
52 | $alert.css("right", "20px");
53 | }
54 | $alert.fadeIn();
55 | if (options.delay > 0) {
56 | $alert.delay(options.delay).fadeOut(function() {
57 | return $(this).alert("close");
58 | });
59 | }
60 | return $alert;
61 | };
62 |
63 | $.bootstrapGrowl.default_options = {
64 | ele: "body",
65 | type: "info",
66 | offset: {
67 | from: "top",
68 | amount: 20
69 | },
70 | align: "right",
71 | width: 250,
72 | delay: 4000,
73 | allow_dismiss: true,
74 | stackup_spacing: 10
75 | };
76 |
77 | }).call(this);
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | process.chdir(__dirname)
2 |
3 | var express = require('express');
4 | var mongodb = require('mongodb');
5 | var passport = require('passport');
6 | var SocketManager = require('./lib/socketManager');
7 |
8 | var http = require('http');
9 | var sockjs = require('sockjs');
10 |
11 | var allowCrossDomain = function(req, res, next) {
12 | res.header('Access-Control-Allow-Origin', '*');
13 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
14 | res.header('Access-Control-Allow-Headers', 'Content-Type');
15 | next();
16 | }
17 |
18 | var app = express();
19 | app.use(express.static('public'));
20 | app.set('view engine', 'ejs');
21 | app.use(express.cookieParser());
22 | app.use(express.bodyParser());
23 | app.use(express.session({ secret: 'keyboard cat' }));
24 | app.use(passport.initialize());
25 | app.use(passport.session());
26 | app.use(allowCrossDomain);
27 | app.use('/static', express.static('static'));
28 | app.use(function(req, res, next) {
29 | if(process.env.NKO) {
30 | req.user = {username: 'demo'}
31 | } else {
32 | //if not NKO redirect to the admin
33 | if(req.url == '/') {
34 | req.url = '/admin';
35 | }
36 | }
37 | next();
38 | });
39 |
40 | var isProduction = (process.env.NODE_ENV === 'production');
41 | var http = require('http');
42 | var port = process.env.PORT || (isProduction ? 80 : 8000);
43 |
44 | var sockjs_opts = {sockjs_url: "http://cdn.sockjs.org/sockjs-0.3.min.js"};
45 | var sockets = sockjs.createServer();
46 |
47 | //listen to the app
48 | var server = http.createServer(app);
49 | sockets.installHandlers(server, {prefix:'/rt'});
50 |
51 | console.info('open-comment-box starting on port:', port);
52 | server.listen(port);
53 |
54 | //Env
55 | var env = {};
56 | env.socketManager = new SocketManager();
57 | env.sockets=sockets;
58 |
59 | //connect to mongo
60 | var MONGO_URL= process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || process.env.MONGO_URL || 'mongodb://localhost/ocb';
61 | mongodb.MongoClient.connect(MONGO_URL, afterConnected);
62 |
63 | function afterConnected(err, db) {
64 | if (err) {
65 | throw err;
66 | } else {
67 | //load models
68 | env.models = require('./lib/models')(db);
69 | env.models.config.init();
70 |
71 | //load Routes
72 | require('./lib/sockets')(app, db, env);
73 | require('./lib/routes')(app, db, env);
74 | require('./lib/passport')(app, db, env);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/static/client.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | var base_url = document.getElementById('ocb_script_loader').dataset.base_url;
5 | var api_key = document.getElementById('ocb_script_loader').dataset.api_key;
6 |
7 | var messageRoutes = {
8 | 'OCB::resizeParentFrame' : function (e) {
9 | // console.log('OCB::resizeParentFrame');
10 | var data = JSON.parse(e.data);
11 | var iframe = document.getElementById('ocb_iframe');
12 | var height = parseInt(data.height) || 250;
13 | var height_buffer = 200;
14 | iframe.style.height = height + height_buffer + 'px';
15 | }
16 | ,'OCB::scrollToComment' : function (e) {
17 | console.log('OCB::scrollToComment', e);
18 | var data = JSON.parse(e.data);
19 | var offset = data.offset + ocb_iframe.offsetTop;
20 | setTimeout(function(){scrollTo(0,offset);}, 1000);
21 | }
22 | ,'OCB::getBaseDetails' : function (e) {
23 | console.log('OCB::getBaseDetails');
24 | var data = JSON.parse(e.data);
25 | var commentId = location.hash.substring(1,location.hash.length);
26 | e.source.postMessage({ base_url: base_url, api_key: api_key, commentId: commentId, action: 'OCB::baseDetails' }, '*');
27 | }
28 | ,'OCB::updateHash' : function (e) {
29 | console.log('OCB::updateHash');
30 | var data = JSON.parse(e.data);
31 | location.hash = data.hash;
32 | }
33 | };
34 |
35 | window.addEventListener('message', function handleMessage (e) {
36 | if (e.origin !== base_url) return;
37 | var data = JSON.parse(e.data) || {};
38 | var action = messageRoutes[data.action];
39 | action && action(e);
40 | }, false);
41 |
42 | var ocb_script_loader = document.getElementById('ocb_script_loader');
43 | var ocb_iframe = document.getElementById('ocb_iframe');
44 | ocb_iframe.src = base_url + '/static/comments.html';
45 | ocb_iframe.style.cssText = 'border-style: none; width: 100%; height: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0;';
46 | ocb_script_loader.parentNode.insertBefore(ocb_iframe, ocb_script_loader);
47 |
48 | })();
49 |
--------------------------------------------------------------------------------
/static/comments.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Open Comment Box
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Loading comments
18 |
19 |
20 |
21 |
22 |
23 |
Log Out
24 |
25 |
Add Comment
26 |
27 | Login with
28 | Google
29 | , Facebook
30 | or Twitter
31 | to comment
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/static/scripts/modules/Channel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Channel = {};
4 |
5 | // JSONP
6 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7 |
8 | Channel.jsonp = function (url) {
9 | console.log('[ ] Channel.jsonp()', url);
10 | var script = document.createElement('script');
11 | script.src = url;
12 | document.head.appendChild(script);
13 | };
14 |
15 | Channel.onData = function (data) {
16 | console.log('[ ] Channel.onData()', data);
17 | Comment.comments = data.comments;
18 | User.users = data.users || {};
19 | Comment.render();
20 | Interface.onReady();
21 | };
22 |
23 | document.addEventListener('DOMContentLoaded', function (e) {
24 | parent.postMessage(JSON.stringify({ action: 'OCB::getBaseDetails' }), '*');
25 | window.addEventListener('message', function handleMessage (e) {
26 | var data = e.data || {};
27 | if (data.action === 'OCB::baseDetails') {
28 | Channel.BASE_URL = data.base_url;
29 | Channel.API_TOKEN = data.api_key;
30 | Channel.SOCK_URL = Channel.BASE_URL+'/rt';
31 | Channel.REF_COMMENT = data.commentId;
32 | Channel.sock = new SockJS(Channel.SOCK_URL);
33 | Channel.initSock();
34 | Channel.getInitData();
35 | }
36 | }, false);
37 | });
38 |
39 | // SOCKJS
40 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41 |
42 | Channel.BASE_URL = null;
43 | Channel.DOC_URL = (window.location != window.parent.location) ? document.referrer: document.location;
44 | Channel.DOC_URL = Channel.DOC_URL.split('#')[0];
45 | Channel.SOCK_URL = null;
46 | Channel.API_TOKEN = null;
47 | Channel.REF_COMMENT = null;
48 | Channel.sock = null;
49 |
50 | Channel.sockRoutes = {
51 | 'commentsAdded' : function (data) {
52 | for (var idx=data.length; idx-->0;){
53 | data[idx].isNewComment = true;
54 | Comment.comments.push(data[idx]);
55 | User.users[data[idx].user._id] = data[idx].user;
56 | Comment.unreadCount++;
57 | Comment.showUnread();
58 | }
59 | Comment.render();
60 | }
61 | ,'commentsDeleted' : function (data) {
62 | for (var idx=data.length; idx-->0;)
63 | for (var idx2=Comment.comments.length; idx2-->0;)
64 | if (Comment.comments[idx2] && Comment.comments[idx2]._id === data[idx])
65 | delete Comment.comments[idx2];
66 | Comment.render();
67 | }
68 | };
69 |
70 | Channel.getInitData = function () {
71 | console.log('[ ] Channel.getInitData()');
72 | var url =
73 | Channel.BASE_URL
74 | + '/comments/init'
75 | + '?callback=Channel.onData'
76 | + '&apiToken=' + Channel.API_TOKEN
77 | + '&url=' + encodeURIComponent(Channel.DOC_URL);
78 | Channel.jsonp(url);
79 | }
80 |
81 | Channel.initSock = function () {
82 |
83 | Channel.sock.onopen = function (e) {
84 | console.log('[ ] Channel.sock.onopen()');
85 | Channel.sock.send(JSON.stringify({
86 | command: 'init'
87 | ,apiToken: Channel.API_TOKEN
88 | ,url: Channel.DOC_URL.split('#')[0]
89 | }));
90 | };
91 |
92 | Channel.sock.onclose = function (e) {
93 | console.log('[ ] Channel.sock.onclose()');
94 | };
95 |
96 | Channel.sock.onmessage = function (e) {
97 | console.log('[ ] Channel.sock.onmessage()', e);
98 | var data = JSON.parse(e.data);
99 | Channel.sockRoutes[data.event] && Channel.sockRoutes[data.event](data.data);
100 | };
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/static/scripts/modules/Comment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Comment = {};
4 |
5 | Comment.elCommentsWrapper = null;
6 | Comment.elUnreadComments = null;
7 | Comment.elNewCommentForm = null;
8 | Comment.elNewCommentText = null;
9 | Comment.elNewCommentSubmit = null;
10 | Comment.elNewCommentReply = null;
11 |
12 | Comment.comments = [];
13 | Comment.replyId = null;
14 | Comment.unreadCount = 0;
15 |
16 | Comment.getCommentById = function (commentId) {
17 | console.log('[ ] Comment.getCommentById()', commentId);
18 | for (var idx = Comment.comments.length; idx-->0;)
19 | if (Comment.comments[idx] && Comment.comments[idx]._id === commentId)
20 | return Comment.comments[idx];
21 | };
22 |
23 | Comment.validate = function () {
24 | console.log('[ ] Comment.validate()');
25 | var message = Comment.elNewCommentText.value.trim();
26 | return message.length > 0;
27 | }
28 |
29 | Comment.create = function () {
30 | console.log('[ ] Comment.create()');
31 | if (!Comment.validate()) return;
32 | var message = Comment.elNewCommentText.value.trim();
33 | Comment.elNewCommentText.value = '';
34 | var url =
35 | Channel.BASE_URL
36 | + '/comments/create?callback=console.log'
37 | + '&apiToken=' + encodeURIComponent(Channel.API_TOKEN)
38 | + '&userId=' + encodeURIComponent(User.getId())
39 | + '&userToken=' + encodeURIComponent(User.getToken())
40 | + '&message=' + encodeURIComponent(message)
41 | + '&url=' + encodeURIComponent(Channel.DOC_URL);
42 | if (Comment.replyId)
43 | url += '&parentId=' + Comment.replyId;
44 | Comment.replyId = null;
45 | Comment.elNewCommentReply.innerHTML = '';
46 | Comment.elNewCommentReply.classList.remove('visible');
47 | Comment.elNewCommentText.style.height = '46px';
48 | Channel.jsonp(url);
49 | };
50 |
51 | Comment.handleNewComment = function (element, idx) {
52 | element.classList.add('new-comment');
53 | setTimeout(function(element){element.classList.remove('new-comment');}, 2000, element);
54 | Comment.comments[idx].isNewComment = false;
55 | if (User.user._id == Comment.comments[idx].userId) {
56 | var offset = element.offsetTop;
57 | var msg = JSON.stringify({ action: 'OCB::scrollToComment', offset: offset });
58 | parent.postMessage(msg, '*');
59 | }
60 | }
61 |
62 | Comment.scrollToHashComment = function () {
63 | console.log('[ ] Comment.scrollToComment()');
64 | var element = document.getElementById(Channel.REF_COMMENT);
65 | if(!element) return;
66 | var offset = element.offsetTop;
67 | var msg = JSON.stringify({ action: 'OCB::scrollToComment', offset: offset });
68 | parent.postMessage(msg, '*');
69 | Channel.REF_COMMENT = null;
70 | }
71 |
72 | Comment.render = function () {
73 | console.log('[ ] Comment.render()');
74 | var comments = JSON.parse(JSON.stringify(Comment.comments));
75 | var elements = document.getElementsByClassName('comment');
76 | for (var idx=elements.length; idx-->0;)
77 | elements[idx].parentNode.removeChild(elements[idx]);
78 | var stop_infinite_loop_counter = 3;
79 | while(Object.keys(comments).length > 0 && stop_infinite_loop_counter--> 0) for(var idx in comments) {
80 | var comment = comments[idx];
81 | if(!comment) continue;
82 | var element = document.createElement('div');
83 | element.classList.add('comment');
84 | element.id = 'comment_' + comment._id;
85 | element.dataset.commentId = comment._id;
86 | var user = User.users[comment.userId] || {
87 | name: 'Anonymous'
88 | , link: '#'
89 | , avatar: 'http://meteorhacks.2013.nodeknockout.com/images/user.png'
90 | };
91 | var timeago = moment(comment.createdAt).fromNow();
92 | var html =
93 | '
'+ user.name +' - '+ timeago +' '
94 | + '
'
95 | + ''
96 | + '
'
97 | + 'Reply '
98 | + 'Share '
99 | if ( User.user && User.user._id == comment.userId )
100 | html +=
101 | 'Delete '
102 | html +=
103 | '
'
104 | + '';
105 | element.innerHTML = html
106 | if (document.getElementById('comment_'+comment._id)) return;
107 | if (comment.parentId == null) {
108 | Comment.elCommentsWrapper.appendChild(element);
109 | comment.isNewComment && Comment.handleNewComment(element, idx);
110 | delete comments[idx];
111 | } else if (document.getElementById('comment_'+comment.parentId)) {
112 | document.getElementById('comment_'+comment.parentId).getElementsByClassName('comment_replies')[0].appendChild(element);
113 | comment.isNewComment && Comment.handleNewComment(element, idx);
114 | delete comments[idx];
115 | }
116 | }
117 | Comment.scrollToHashComment();
118 | };
119 |
120 | Comment.reply = function (commentId) {
121 | console.log('[ ] Comment.reply()', commentId);
122 | Comment.replyId = commentId;
123 | var comment = Comment.getCommentById(commentId);
124 | var user = User.users[comment.userId];
125 | var username = user ? user.name : 'Anonymous';
126 | Comment.elNewCommentReply.innerHTML = '@' + username + ' "' + comment.message.slice(0,15) + '..."';
127 | Comment.elNewCommentReply.classList.add('visible');
128 | Comment.elNewCommentText.focus();
129 | };
130 |
131 | Comment.share = function (commentId) {
132 | console.log('[ ] Comment.share()', commentId);
133 | var element = document.getElementById('comment_' + commentId);
134 | if(!element) return;
135 | var offset = element.offsetTop;
136 | var msg = JSON.stringify({ action: 'OCB::scrollToComment', offset: offset });
137 | parent.postMessage(msg, '*');
138 | msg = JSON.stringify({ action: 'OCB::updateHash', hash: 'comment_' + commentId });
139 | parent.postMessage(msg, '*');
140 | }
141 |
142 | Comment.delete = function (commentId) {
143 | console.log('[ ] Comment.delete()', commentId);
144 | if(!confirm('Delete Comment?')) return;
145 | var url =
146 | Channel.BASE_URL
147 | + '/comments/delete'
148 | + '?callback=console.log'
149 | + '&apiToken=' + Channel.API_TOKEN
150 | + '&id=' + commentId
151 | + '&url=' + encodeURIComponent(Channel.DOC_URL);
152 | Channel.jsonp(url);
153 | for (var idx = Comment.comments.length; idx-->0;) if (Comment.comments[idx] && Comment.comments[idx]._id === commentId)
154 | delete Comment.comments[idx];
155 | Comment.render();
156 | }
157 |
158 | Comment.onNewComment = function (data) {
159 | console.log('[ ] Comment.onNewComment()', data);
160 | for(var idx=data.length; idx-->0;)
161 | Comment.comments.push(data[idx]);
162 | Comment.render();
163 | };
164 |
165 | Comment.showUnread = function () {
166 | Comment.elUnreadComments.classList.add('visible');
167 | Comment.elUnreadComments.innerHTML = Comment.unreadCount + ' New Comments';
168 | }
169 |
170 | Comment.hideUnread = function () {
171 | Comment.unreadCount = 0;
172 | Comment.elUnreadComments.classList.remove('visible');
173 | }
174 |
175 | document.addEventListener('DOMContentLoaded', function (e) {
176 | Comment.elCommentsWrapper = document.getElementById('commentsWrap');
177 | Comment.elUnreadComments = document.getElementById('unreadComments');
178 | Comment.elNewCommentForm = document.getElementById('messageWrap');
179 | Comment.elNewCommentText = document.getElementById('message');
180 | Comment.elNewCommentSubmit = document.getElementById('messageSubmit');
181 | Comment.elNewCommentReply = document.getElementById('replyTo');
182 | Comment.elNewCommentSubmit.onclick = Comment.create;
183 | Comment.elUnreadComments.onclick = Comment.hideUnread;
184 | Comment.elUnreadComments.onmouseover = Comment.hideUnread;
185 | });
186 |
--------------------------------------------------------------------------------
/static/scripts/modules/Interface.js:
--------------------------------------------------------------------------------
1 |
2 | var Interface = new Object();
3 |
4 | Interface.elLoadingAnimation = null;
5 |
6 | Interface.autoGrowTextarea = function (textarea) {
7 | console.log('[ ] Interface.autoGrowTextarea()');
8 | // Based on http://stackoverflow.com/a/19408351
9 | textarea.addEventListener('keydown', function(e){
10 | setTimeout(function (el) {
11 | var height = parseInt(el.scrollHeight);
12 | el.style.cssText = 'height:0; padding:0';
13 | el.style.cssText = 'height:' + height + 'px';
14 | }, 0, this);
15 | });
16 | }
17 |
18 | Interface.resizeParentFrame = function (content) {
19 | return function () {
20 | var msg = JSON.stringify({ action: 'OCB::resizeParentFrame', height: content.scrollHeight });
21 | parent.postMessage(msg, '*');
22 | }
23 | }
24 |
25 | Interface.onReady = function () {
26 | console.log('[ ] Interface.onReady()');
27 | Interface.elLoadingAnimation.classList.remove('visible');
28 | setTimeout(function(){Interface.elLoadingAnimation.style.display = 'none';},1000);
29 | }
30 |
31 | document.addEventListener('DOMContentLoaded', function (e) {
32 | Interface.elLoadingAnimation = document.getElementById('loadingAnimation');
33 | Interface.autoGrowTextarea(document.getElementById('message'));
34 | setInterval(Interface.resizeParentFrame(document.getElementById('content')), 1000);
35 | setInterval(function(){Comment.render()}, 30 * 1000);
36 | });
37 |
--------------------------------------------------------------------------------
/static/scripts/modules/User.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var User = new Object();
4 |
5 | User.elLoginWrapper = null;
6 | User.elUserAvatar = null;
7 | User.elLogoutButton = null;
8 |
9 | User.users = {};
10 | User.user = null;
11 | $
12 | User.validateToken = function () {
13 | // @FIXME Do come real validation
14 | console.log('[ ] User.validateToken()');
15 | return true;
16 | };
17 |
18 | User.isLoggedIn = function () {
19 | console.log('[ ] User.isLoggedIn()');
20 | User.user = JSON.parse(localStorage.getItem('user'));
21 | if (User.user){
22 | console.log()
23 | return User.validateToken();
24 | }
25 | };
26 |
27 | User.getId = function () {
28 | console.log('[ ] User.getId()');
29 | if (!User.isLoggedIn()) return;
30 | return User.user._id;
31 | };
32 |
33 | User.getToken = function () {
34 | console.log('[ ] User.getToken()');
35 | if (!User.isLoggedIn()) return;
36 | return User.user.userToken;
37 | };
38 |
39 | User.requestLogin = function (handler) {
40 | console.log('[ ] User.requestLogin()', handler);
41 | var url = Channel.BASE_URL + '/auth/' + handler.toLowerCase();
42 | var popup = window.open(url, 'Open Comment Box Login', 'width=500,height=400');
43 | window.focus && popup.focus();
44 | window.addEventListener('message', function(e) {
45 | var data = JSON.parse(e.data);
46 | if( data.action === 'OCB:authResult' )
47 | User.login(data.data);
48 | }, false);
49 | };
50 |
51 | User.login = function (user) {
52 | console.log('[ ] User.login()', user);
53 | User.user = user;
54 | localStorage.setItem('user', JSON.stringify(User.user));
55 | User.elLoginWrapper.classList.remove('visible');
56 | User.elUserAvatar.src = User.user.avatar;
57 | User.elUserAvatar.classList.add('visible');
58 | User.elLogoutButton.classList.add('visible');
59 | Comment.elNewCommentSubmit.classList.add('visible');
60 | Comment.render();
61 | };
62 |
63 | User.logout = function () {
64 | console.log('[ ] User.logout()');
65 | User.user = null;
66 | localStorage.removeItem('user');
67 | Comment.elNewCommentSubmit.classList.remove('visible');
68 | User.elUserAvatar.classList.remove('visible');
69 | User.elLogoutButton.classList.remove('visible');
70 | User.elLoginWrapper.classList.add('visible');
71 | Comment.elNewCommentText.value = '';
72 | Comment.replyId = null;
73 | Comment.elNewCommentReply.innerHTML = '';
74 | Comment.elNewCommentReply.classList.remove('visible');
75 | Comment.render();
76 | };
77 |
78 | document.addEventListener('DOMContentLoaded', function (e) {
79 | User.elLoginWrapper = document.getElementById('loginWrapper');
80 | User.elUserAvatar = document.getElementById('avatar');
81 | User.elLogoutButton = document.getElementById('logoutButton');
82 | if (User.isLoggedIn()) {
83 | User.elLoginWrapper.classList.remove('visible');
84 | User.elUserAvatar.src = User.user.avatar;
85 | User.elUserAvatar.classList.add('visible');
86 | User.elLogoutButton.classList.add('visible');
87 | Comment.elNewCommentSubmit.classList.add('visible');
88 | } else {
89 | User.elUserAvatar.classList.remove('visible');
90 | User.elLogoutButton.classList.remove('visible');
91 | Comment.elNewCommentSubmit.classList.remove('visible');
92 | User.elLoginWrapper.classList.add('visible');
93 | }
94 | });
95 |
--------------------------------------------------------------------------------
/static/scripts/scripts.js:
--------------------------------------------------------------------------------
1 |
2 | 'use strict';
3 |
4 | var Channel = {};
5 |
6 | // JSONP
7 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8 |
9 | Channel.jsonp = function (url) {
10 | console.log('[ ] Channel.jsonp()', url);
11 | var script = document.createElement('script');
12 | script.src = url;
13 | document.head.appendChild(script);
14 | };
15 |
16 | Channel.onData = function (data) {
17 | console.log('[ ] Channel.onData()', data);
18 | Comment.comments = data.comments;
19 | User.users = data.users || {};
20 | // @FIXME Remove Dummy User
21 | User.users['527ea4071e8bbc5bb6476835'] = { _id: '527ea4071e8bbc5bb6476835', name: 'Thanish' };
22 | Comment.render();
23 | };
24 |
25 | document.addEventListener('DOMContentLoaded', function (e) {
26 | Channel.jsonp('http://localhost:8000/comments/init?callback=Channel.onData&apiToken=' + Channel.API_TOKEN);
27 | });
28 |
29 | // SOCKJS
30 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
31 |
32 | Channel.SOCK_URL = 'http://localhost:8000/rt';
33 | Channel.API_TOKEN = '123456';
34 | Channel.sock = new SockJS(Channel.SOCK_URL);
35 |
36 | Channel.sockRoutes = {
37 | 'commentsAdded' : function (data) {
38 | for (var idx=data.length; idx-->0;)
39 | Comment.comments.push(data[idx]);
40 | Comment.render();
41 | }
42 | };
43 |
44 | Channel.sock.onopen = function (e) {
45 | console.log('[ ] Channel.sock.onopen()');
46 | Channel.sock.send(JSON.stringify({
47 | command: 'init'
48 | ,apiToken: Channel.API_TOKEN
49 | ,url: document.URL
50 | }));
51 | };
52 |
53 | Channel.sock.onclose = function (e) {
54 | console.log('[ ] Channel.sock.onclose()');
55 | };
56 |
57 | Channel.sock.onmessage = function (e) {
58 | console.log('[ ] Channel.sock.onmessage()');
59 | var data = JSON.parse(e.data);
60 | Channel.sockRoutes[data.event] && Channel.sockRoutes[data.event](data.data);
61 | };
62 | 'use strict';
63 |
64 | var Comment = {};
65 |
66 | Comment.elCommentsWrapper = null;
67 | Comment.elNewCommentForm = null;
68 | Comment.elNewCommentText = null;
69 | Comment.elNewCommentSubmit = null;
70 | Comment.elNewCommentReply = null;
71 |
72 | Comment.comments = [];
73 | Comment.replyId = null;
74 |
75 | Comment.getCommentById = function (commentId) {
76 | console.log('[ ] Comment.getCommentById()', commentId);
77 | for (var idx = Comment.comments.length; idx-->0;)
78 | if (Comment.comments[idx]._id === commentId)
79 | return Comment.comments[idx];
80 | };
81 |
82 | Comment.validate = function () {
83 | console.log('[ ] Comment.validate()');
84 | var message = Comment.elNewCommentText.value.trim();
85 | return message.length > 0;
86 | }
87 |
88 | Comment.create = function () {
89 | console.log('[ ] Comment.create()');
90 | if (!Comment.validate()) return;
91 | var message = Comment.elNewCommentText.value.trim();
92 | Comment.elNewCommentText.value = '';
93 | var url =
94 | 'http://localhost:8000/comments/create?callback=console.log'
95 | + '&apiToken=' + encodeURIComponent(Channel.API_TOKEN)
96 | + '&userId=' + encodeURIComponent(User.getId())
97 | + '&userToken=' + encodeURIComponent(User.getToken())
98 | + '&message=' + encodeURIComponent(message);
99 | if (Comment.replyId)
100 | url += '&parentId=' + Comment.replyId;
101 | Comment.replyId = null;
102 | Comment.elNewCommentReply.innerHTML = '';
103 | Comment.elNewCommentReply.classList.remove('visible');
104 | Channel.jsonp(url);
105 | };
106 |
107 | Comment.render = function () {
108 | console.log('[ ] Comment.render()');
109 | var comments = JSON.parse(JSON.stringify(Comment.comments));
110 | var elements = document.getElementsByClassName('comment');
111 | for (var idx=elements.length; idx-->0;)
112 | elements[idx].parentNode.removeChild(elements[idx]);
113 | while(Object.keys(comments).length > 0) for(var idx in comments) {
114 | var comment = comments[idx];
115 | var element = document.createElement('div');
116 | element.classList.add('comment');
117 | element.id = 'comment_' + comment._id;
118 | element.dataset.commentId = comment._id;
119 | var user = User.getDetailsById(comment.userId);
120 | var username = user ? user.name : 'Anonymous';
121 | var timeago = moment(comment.createdAt).fromNow()
122 | element.innerHTML =
123 | '
'+ username +' - '+ timeago +' '
124 | + ''
125 | + '
'
126 | + 'Reply '
127 | + 'Share '
128 | + '
'
129 | + '';
130 | if (document.getElementById('comment_'+comment._id)) return;
131 | if (comment.parentId == null) {
132 | Comment.elCommentsWrapper.appendChild(element);
133 | delete comments[idx];
134 | } else if (document.getElementById('comment_'+comment.parentId)) {
135 | document.getElementById('comment_'+comment.parentId).getElementsByClassName('comment_replies')[0].appendChild(element);
136 | delete comments[idx];
137 | }
138 | }
139 | };
140 |
141 | Comment.reply = function (commentId) {
142 | console.log('[ ] Comment.reply()', commentId);
143 | if (!User.isLoggedIn()) return;
144 | Comment.replyId = commentId;
145 | var comment = Comment.getCommentById(commentId);
146 | var user = User.getDetailsById(comment.userId);
147 | var username = user ? user.name : 'Anonymous';
148 | Comment.elNewCommentReply.innerHTML = '@' + username + ' "' + comment.message.slice(0,15) + '..."';
149 | Comment.elNewCommentReply.classList.add('visible');
150 | Comment.elNewCommentText.focus();
151 | };
152 |
153 | Comment.share = function (commentId) {
154 | console.log('[ ] Comment.share()', commentId);
155 | };
156 |
157 | Comment.onNewComment = function (data) {
158 | console.log('[ ] Comment.onNewComment()', data);
159 | for(var idx=data.length; idx-->0;)
160 | Comment.comments.push(data[idx]);
161 | Comment.render();
162 | };
163 |
164 | document.addEventListener('DOMContentLoaded', function (e) {
165 | Comment.elCommentsWrapper = document.getElementById('commentsWrap');
166 | Comment.elNewCommentForm = document.getElementById('messageWrap');
167 | Comment.elNewCommentText = document.getElementById('message');
168 | Comment.elNewCommentSubmit = document.getElementById('messageSubmit');
169 | Comment.elNewCommentReply = document.getElementById('replyTo');
170 | Comment.elNewCommentSubmit.onclick = Comment.create;
171 | });
172 |
173 | var Interface = new Object();
174 |
175 | Interface.autoGrowTextarea = function (textarea) {
176 | // Based on http://stackoverflow.com/a/19408351
177 | textarea.addEventListener('keydown', function(e){
178 | setTimeout(function (el) {
179 | var height = parseInt(el.scrollHeight);
180 | el.style.cssText = 'height:0; padding:0';
181 | el.style.cssText = 'height:' + height + 'px';
182 | }, 0, this);
183 | });
184 | }
185 |
186 | Interface.resizeParentFrame = function (content) {
187 | return function () {
188 | var msg = JSON.stringify({ action: 'OCB::resizeParentFrame', height: content.scrollHeight });
189 | parent.postMessage( msg, '*');
190 | }
191 | }
192 |
193 | document.addEventListener('DOMContentLoaded', function (e) {
194 | Interface.autoGrowTextarea(document.getElementById('message'));
195 | setInterval(Interface.resizeParentFrame(document.getElementById('content')), 1000);
196 | });
197 | 'use strict';
198 |
199 | var User = new Object();
200 |
201 | User.elLoginWrapper = null;
202 | User.elLogoutButton = null;
203 |
204 | User.users = {};
205 | User.user = null;
206 |
207 | User.getDetailsById = function (userId) {
208 | console.log('[ ] User.getDetailsById()', userId);
209 | return User.users[userId];
210 | };
211 |
212 | User.validateToken = function () {
213 | // @FIXME Do come real validation
214 | console.log('[ ] User.validateToken()');
215 | return true;
216 | };
217 |
218 | User.isLoggedIn = function () {
219 | console.log('[ ] User.isLoggedIn()');
220 | User.user = JSON.parse(localStorage.getItem('user'));
221 | if (User.user){
222 | console.log()
223 | return User.validateToken();
224 | }
225 | };
226 |
227 | User.getId = function () {
228 | console.log('[ ] User.getId()');
229 | if (!User.isLoggedIn()) return;
230 | return User.user._id;
231 | };
232 |
233 | User.getToken = function () {
234 | console.log('[ ] User.getToken()');
235 | if (!User.isLoggedIn()) return;
236 | return User.user.token;
237 | };
238 |
239 | User.requestLogin = function (handler) {
240 | console.log('[ ] User.requestLogin()', handler);
241 | // @FIXME Replace Mock Data with Data From Real Handlers
242 | User.login({
243 | _id: '527ea4071e8bbc5bb6476835'
244 | ,token: 'user_token'
245 | ,name: 'Thanish'
246 | })
247 | };
248 |
249 | User.login = function (user) {
250 | console.log('[ ] User.login()', user);
251 | User.user = user;
252 | localStorage.setItem('user', JSON.stringify(User.user));
253 | User.elLoginWrapper.classList.remove('visible');
254 | User.elLogoutButton.classList.add('visible');
255 | Comment.elNewCommentSubmit.classList.add('visible');
256 | };
257 |
258 | User.logout = function () {
259 | console.log('[ ] User.logout()');
260 | User.user = null;
261 | localStorage.removeItem('user');
262 | Comment.elNewCommentSubmit.classList.remove('visible');
263 | User.elLogoutButton.classList.remove('visible');
264 | User.elLoginWrapper.classList.add('visible');
265 | Comment.elNewCommentText.value = '';
266 | Comment.replyId = null;
267 | Comment.elNewCommentReply.innerHTML = '';
268 | Comment.elNewCommentReply.classList.remove('visible');
269 | };
270 |
271 | document.addEventListener('DOMContentLoaded', function (e) {
272 | User.elLoginWrapper = document.getElementById('loginWrapper');
273 | User.elLogoutButton = document.getElementById('logoutButton');
274 | if (User.isLoggedIn()) {
275 | User.elLoginWrapper.classList.remove('visible');
276 | User.elLogoutButton.classList.add('visible');
277 | Comment.elNewCommentSubmit.classList.add('visible');
278 | } else {
279 | User.elLogoutButton.classList.remove('visible');
280 | Comment.elNewCommentSubmit.classList.remove('visible');
281 | User.elLoginWrapper.classList.add('visible');
282 | }
283 | });
284 |
--------------------------------------------------------------------------------
/static/styles/loader.gif:
--------------------------------------------------------------------------------
1 | GIF89a<