From 50c34e4f336a826ca3b78047e221ad29143d0e93 Mon Sep 17 00:00:00 2001 From: Baris Soner Usakli Date: Sat, 10 Aug 2013 16:14:50 -0400 Subject: [PATCH] added infinite scrolling to unread page, issue #141 --- public/src/forum/recent.js | 14 ----- public/src/forum/unread.js | 103 ++++++++++++++++++++++++++++++++++++ public/templates/unread.tpl | 5 +- src/posts.js | 49 ++++++----------- src/routes/api.js | 2 +- src/topics.js | 63 ++++++++-------------- src/webserver.js | 1 + src/websockets.js | 88 +++++++++++++++++++++++++++++- 8 files changed, 231 insertions(+), 94 deletions(-) create mode 100644 public/src/forum/unread.js diff --git a/public/src/forum/recent.js b/public/src/forum/recent.js index 84d22ea8b8..44a078a278 100644 --- a/public/src/forum/recent.js +++ b/public/src/forum/recent.js @@ -47,20 +47,6 @@ ++newPostCount; updateAlertText(); }); - - $('#mark-allread-btn').on('click', function() { - var btn = $(this); - socket.emit('api:topics.markAllRead', {} , function(success) { - if(success) { - btn.remove(); - $('#topics-container').empty(); - $('#category-no-topics').removeClass('hidden'); - app.alertSuccess('All topics marked as read!'); - } else { - app.alertError('There was an error marking topics read!'); - } - }); - }); function onTopicsLoaded(topics) { diff --git a/public/src/forum/unread.js b/public/src/forum/unread.js new file mode 100644 index 0000000000..8b2342df36 --- /dev/null +++ b/public/src/forum/unread.js @@ -0,0 +1,103 @@ +(function() { + var loadingMoreTopics = false; + + app.enter_room('recent_posts'); + + ajaxify.register_events([ + 'event:new_topic', + 'event:new_post' + ]); + + var newTopicCount = 0, newPostCount = 0; + + $('#new-topics-alert').on('click', function() { + $(this).hide(); + }); + + socket.on('event:new_topic', function(data) { + + ++newTopicCount; + updateAlertText(); + + }); + + function updateAlertText() { + var text = ''; + + if(newTopicCount > 1) + text = 'There are ' + newTopicCount + ' new topics'; + else if(newTopicCount === 1) + text = 'There is 1 new topic'; + else + text = 'There are no new topics'; + + if(newPostCount > 1) + text += ' and ' + newPostCount + ' new posts.'; + else if(newPostCount === 1) + text += ' and 1 new post.'; + else + text += ' and no new posts.'; + + text += ' Click here to reload.'; + + $('#new-topics-alert').html(text).fadeIn('slow'); + } + + socket.on('event:new_post', function(data) { + ++newPostCount; + updateAlertText(); + }); + + $('#mark-allread-btn').on('click', function() { + var btn = $(this); + socket.emit('api:topics.markAllRead', {} , function(success) { + if(success) { + btn.remove(); + $('#topics-container').empty(); + $('#category-no-topics').removeClass('hidden'); + app.alertSuccess('All topics marked as read!'); + } else { + app.alertError('There was an error marking topics read!'); + } + }); + }); + + function onTopicsLoaded(topics) { + + var html = templates.prepare(templates['unread'].blocks['topics']).parse({ topics: topics }), + container = $('#topics-container'); + + $('#category-no-topics').remove(); + + container.append(html); + } + + function loadMoreTopics() { + loadingMoreTopics = true; + socket.emit('api:topics.loadMoreUnreadTopics', {after:parseInt($('#topics-container').attr('data-next-start'), 10)}, function(data) { + if(data.topics && data.topics.length) { + onTopicsLoaded(data.topics); + $('#topics-container').attr('data-next-start', data.nextStart); + } + loadingMoreTopics = false; + }); + } + + $(window).off('scroll').on('scroll', function() { + var windowHeight = document.body.offsetHeight - $(window).height(), + half = windowHeight / 2; + + if (document.body.scrollTop > half && !loadingMoreTopics) { + loadMoreTopics(); + } + }); + + + if($("body").height() <= $(window).height() && $('#topics-container').children().length) + $('#load-more-btn').show(); + + $('#load-more-btn').on('click', function() { + loadMoreTopics(); + }); + +})(); \ No newline at end of file diff --git a/public/templates/unread.tpl b/public/templates/unread.tpl index a44d30aa1b..7e4481aeac 100644 --- a/public/templates/unread.tpl +++ b/public/templates/unread.tpl @@ -21,7 +21,7 @@
-
- \ No newline at end of file + \ No newline at end of file diff --git a/src/posts.js b/src/posts.js index 0a2bcd1cab..9a0dda28e4 100644 --- a/src/posts.js +++ b/src/posts.js @@ -178,34 +178,30 @@ var RDB = require('./redis.js'), alert_id: 'post_error' }); } + + Posts.emitTooManyPostsAlert = function(socket) { + socket.emit('event:alert', { + title: 'Too many posts!', + message: 'You can only post every '+ (config.post_delay / 1000) + ' seconds.', + type: 'error', + timeout: 2000 + }); + } - Posts.reply = function(socket, tid, uid, content, images) { + Posts.reply = function(tid, uid, content, images, callback) { if(content) { content = content.trim(); } - if (uid < 1) { - socket.emit('event:alert', { - title: 'Reply Unsuccessful', - message: 'You don't seem to be logged in, so you cannot reply.', - type: 'error', - timeout: 2000 - }); - return; - } else if (!content || content.length < Posts.minimumPostLength) { - Posts.emitContentTooShortAlert(socket); + if (!content || content.length < Posts.minimumPostLength) { + callback(new Error('content-too-short'), null); return; } user.getUserField(uid, 'lastposttime', function(lastposttime) { if(Date.now() - lastposttime < config.post_delay) { - socket.emit('event:alert', { - title: 'Too many posts!', - message: 'You can only post every '+ (config.post_delay / 1000) + ' seconds.', - type: 'error', - timeout: 2000 - }); + callback(new Error('too-many-posts'), null); return; } @@ -221,18 +217,8 @@ var RDB = require('./redis.js'), }); }); - Posts.getTopicPostStats(socket); - - // Send notifications to users who are following this topic threadTools.notify_followers(tid, uid); - socket.emit('event:alert', { - title: 'Reply Successful', - message: 'You have successfully replied. Click here to view your reply.', - type: 'notify', - timeout: 2000 - }); - postData.content = postTools.markdownToHTML(postData.content); postData.post_rep = 0; postData.relativeTime = utils.relativeTime(postData.timestamp) @@ -251,14 +237,9 @@ var RDB = require('./redis.js'), io.sockets.in('recent_posts').emit('event:new_post', socketData); }); - + callback(null, 'Reply successful'); } else { - socket.emit('event:alert', { - title: 'Reply Unsuccessful', - message: 'Your reply could not be posted at this time. Please try again later.', - type: 'notify', - timeout: 2000 - }); + callback(new Error('reply-error'), null); } }); }); diff --git a/src/routes/api.js b/src/routes/api.js index a47a7a35ca..0414b1570f 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -115,7 +115,7 @@ var user = require('./../user.js'), app.get('/api/unread', function(req, res) { var uid = (req.user) ? req.user.uid : 0; - topics.getUnreadTopics(uid, 0, -1, function(data) { + topics.getUnreadTopics(uid, 0, 19, function(data) { res.json(data); }); }); diff --git a/src/topics.js b/src/topics.js index 3d3ba5bf86..6730ee00ac 100644 --- a/src/topics.js +++ b/src/topics.js @@ -148,6 +148,7 @@ marked.setOptions({ function sendUnreadTopics(topicIds) { Topics.getTopicsByTids(topicIds, uid, function(topicData) { unreadTopics.topics = topicData; + unreadTopics.nextStart = start + tids.length; callback(unreadTopics); }); } @@ -532,7 +533,7 @@ marked.setOptions({ }); } - Topics.post = function(socket, uid, title, content, category_id, images) { + Topics.post = function(uid, title, content, category_id, images, callback) { if (!category_id) throw new Error('Attempted to post without a category_id'); @@ -542,33 +543,20 @@ marked.setOptions({ title = title.trim(); if (uid === 0) { - socket.emit('event:alert', { - title: 'Thank you for posting', - message: 'Since you are unregistered, your post is awaiting approval. Click here to register now.', - type: 'warning', - timeout: 7500, - clickfn: function() { - ajaxify.go('register'); - } - }); - return; // for now, until anon code is written. + callback(new Error('not-logged-in'), null); + return; } else if(!title || title.length < Topics.minimumTitleLength) { - Topics.emitTitleTooShortAlert(socket); + callback(new Error('title-too-short'), null); return; } else if (!content || content.length < posts.miminumPostLength) { - posts.emitContentTooShortAlert(socket); + callback(new Error('content-too-short'), null); return; } user.getUserField(uid, 'lastposttime', function(lastposttime) { if(Date.now() - lastposttime < config.post_delay) { - socket.emit('event:alert', { - title: 'Too many posts!', - message: 'You can only post every '+ (config.post_delay / 1000) + ' seconds.', - type: 'error', - timeout: 2000 - }); + callback(new Error('too-many-posts'), null); return; } @@ -604,23 +592,6 @@ marked.setOptions({ topicSearch.index(title, tid); RDB.set('topicslug:' + slug + ':tid', tid); - posts.create(uid, tid, content, images, function(postData) { - if (postData) { - RDB.lpush(schema.topics(tid).posts, postData.pid); - - // Auto-subscribe the post creator to the newly created topic - threadTools.toggleFollow(tid, uid); - - // Notify any users looking at the category that a new topic has arrived - Topics.getTopicForCategoryView(tid, uid, function(topicData) { - io.sockets.in('category_' + category_id).emit('event:new_topic', topicData); - io.sockets.in('recent_posts').emit('event:new_topic', topicData); - }); - - posts.getTopicPostStats(socket); - } - }); - user.addTopicIdToUser(uid, tid); // let everyone know that there is an unread topic in this category @@ -636,11 +607,21 @@ marked.setOptions({ feed.updateCategory(category_id); - socket.emit('event:alert', { - title: 'Thank you for posting', - message: 'You have successfully posted. Click here to view your post.', - type: 'notify', - timeout: 2000 + posts.create(uid, tid, content, images, function(postData) { + if (postData) { + RDB.lpush(schema.topics(tid).posts, postData.pid); + + // Auto-subscribe the post creator to the newly created topic + threadTools.toggleFollow(tid, uid); + + // Notify any users looking at the category that a new topic has arrived + Topics.getTopicForCategoryView(tid, uid, function(topicData) { + io.sockets.in('category_' + category_id).emit('event:new_topic', topicData); + io.sockets.in('recent_posts').emit('event:new_topic', topicData); + }); + + callback(null, postData); + } }); }); }); diff --git a/src/webserver.js b/src/webserver.js index aa2be7a669..379bec362f 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -400,6 +400,7 @@ var express = require('express'), } }); }); + }); }(WebServer)); diff --git a/src/websockets.js b/src/websockets.js index f7dc80c28b..57b6e1973f 100644 --- a/src/websockets.js +++ b/src/websockets.js @@ -304,7 +304,41 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }), }); socket.on('api:topics.post', function(data) { - topics.post(socket, uid, data.title, data.content, data.category_id, data.images); + + topics.post(uid, data.title, data.content, data.category_id, data.images, function(err, result) { + if(err) { + if(err.message === 'not-logged-in') { + socket.emit('event:alert', { + title: 'Thank you for posting', + message: 'Since you are unregistered, your post is awaiting approval. Click here to register now.', + type: 'warning', + timeout: 7500, + clickfn: function() { + ajaxify.go('register'); + } + }); + } else if(err.message === 'title-too-short') { + topics.emitTitleTooShortAlert(socket); + } else if(err.message === 'content-too-short') { + posts.emitContentTooShortAlert(socket); + } else if (err.message === 'too-many-posts') { + posts.emitTooManyPostsAlert(socket); + } + return; + } + + if(result) { + posts.getTopicPostStats(socket); + + socket.emit('event:alert', { + title: 'Thank you for posting', + message: 'You have successfully posted. Click here to view your post.', + type: 'notify', + timeout: 2000 + }); + } + }); + }); socket.on('api:topics.markAllRead', function(data, callback) { @@ -318,7 +352,47 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }), }); socket.on('api:posts.reply', function(data) { - posts.reply(socket, data.topic_id, uid, data.content, data.images); + if(uid < 1) { + socket.emit('event:alert', { + title: 'Reply Unsuccessful', + message: 'You don't seem to be logged in, so you cannot reply.', + type: 'error', + timeout: 2000 + }); + return; + } + + posts.reply(data.topic_id, uid, data.content, data.images, function(err, result) { + if(err) { + if(err.message === 'content-too-short') { + posts.emitContentTooShortAlert(socket); + } else if(err.messages === 'too-many-posts') { + posts.emitTooManyPostsAlert(socket); + } else if(err.message === 'reply-error') { + socket.emit('event:alert', { + title: 'Reply Unsuccessful', + message: 'Your reply could not be posted at this time. Please try again later.', + type: 'notify', + timeout: 2000 + }); + } + return; + } + + if(result) { + + posts.getTopicPostStats(socket); + + socket.emit('event:alert', { + title: 'Reply Successful', + message: 'You have successfully replied. Click here to view your reply.', + type: 'notify', + timeout: 2000 + }); + + } + + }); }); socket.on('api:user.active.get', function() { @@ -578,6 +652,16 @@ var SocketIO = require('socket.io').listen(global.server, { log:false }), callback(latestTopics); }); }); + + socket.on('api:topics.loadMoreUnreadTopics', function(data, callback) { + var start = data.after, + end = start + 9; + + console.log(start, end); + topics.getUnreadTopics(uid, start, end, function(unreadTopics) { + callback(unreadTopics); + }); + }); socket.on('api:admin.topics.getMore', function(data) { topics.getAllTopics(data.limit, data.after, function(topics) {