psychobunny 11 years ago
commit 6ab08e91a2

@ -106,11 +106,9 @@
upgrade.check(function(schema_ok) { upgrade.check(function(schema_ok) {
if (schema_ok || nconf.get('check-schema') === false) { if (schema_ok || nconf.get('check-schema') === false) {
sockets.init(); sockets.init(webserver.server);
plugins.init(); plugins.init();
global.templates = {};
global.translator = translator;
translator.loadServer(); translator.loadServer();

@ -33,5 +33,8 @@
"motd.get": "Get NodeBB", "motd.get": "Get NodeBB",
"motd.fork": "Fork", "motd.fork": "Fork",
"motd.like": "Like", "motd.like": "Like",
"motd.follow": "Follow" "motd.follow": "Follow",
"previouspage": "Previous Page",
"nextpage": "Next Page"
} }

@ -396,6 +396,16 @@ var socket,
}); });
}; };
app.enableInfiniteLoading = function(callback) {
$(window).off('scroll').on('scroll', function() {
var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom) {
callback();
}
});
}
var titleObj = { var titleObj = {
active: false, active: false,
interval: undefined, interval: undefined,
@ -528,6 +538,9 @@ var socket,
}); });
templates.setGlobal('relative_path', RELATIVE_PATH); templates.setGlobal('relative_path', RELATIVE_PATH);
templates.setGlobal('usePagination', config.usePagination);
templates.setGlobal('topicsPerPage', config.topicsPerPage);
templates.setGlobal('postsPerPage', config.postsPerPage);
}); });
showWelcomeMessage = location.href.indexOf('loggedin') !== -1; showWelcomeMessage = location.href.indexOf('loggedin') !== -1;

@ -1,13 +1,10 @@
define(['composer'], function(composer) { define(['composer', 'forum/pagination'], function(composer, pagination) {
var Category = {}, var Category = {},
loadingMoreTopics = false; loadingMoreTopics = false;
Category.init = function() { Category.init = function() {
var cid = templates.get('category_id'), var cid = templates.get('category_id'),
categoryName = templates.get('category_name'), categoryName = templates.get('category_name'),
twitterEl = jQuery('#twitter-intent'),
facebookEl = jQuery('#facebook-share'),
googleEl = jQuery('#google-share'),
categoryUrl = encodeURIComponent(window.location.href), categoryUrl = encodeURIComponent(window.location.href),
twitterUrl = "https://twitter.com/intent/tweet?url=" + categoryUrl + "&text=" + encodeURIComponent(categoryName), twitterUrl = "https://twitter.com/intent/tweet?url=" + categoryUrl + "&text=" + encodeURIComponent(categoryName),
facebookUrl = "https://www.facebook.com/sharer/sharer.php?u=" + categoryUrl, facebookUrl = "https://www.facebook.com/sharer/sharer.php?u=" + categoryUrl,
@ -15,17 +12,17 @@ define(['composer'], function(composer) {
app.enterRoom('category_' + cid); app.enterRoom('category_' + cid);
$('#twitter-intent').on('click', function () {
twitterEl.on('click', function () {
window.open(twitterUrl, '_blank', 'width=550,height=420,scrollbars=no,status=no'); window.open(twitterUrl, '_blank', 'width=550,height=420,scrollbars=no,status=no');
return false; return false;
}); });
facebookEl.on('click', function () {
$('#facebook-share').on('click', function () {
window.open(facebookUrl, '_blank', 'width=626,height=436,scrollbars=no,status=no'); window.open(facebookUrl, '_blank', 'width=626,height=436,scrollbars=no,status=no');
return false; return false;
}); });
googleEl.on('click', function () {
$('#google-share').on('click', function () {
window.open(googleUrl, '_blank', 'width=500,height=570,scrollbars=no,status=no'); window.open(googleUrl, '_blank', 'width=500,height=570,scrollbars=no,status=no');
return false; return false;
}); });
@ -42,15 +39,21 @@ define(['composer'], function(composer) {
socket.emit('categories.getRecentReplies', cid, renderRecentReplies); socket.emit('categories.getRecentReplies', cid, renderRecentReplies);
$(window).off('scroll').on('scroll', function (ev) { enableInfiniteLoading();
var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
Category.loadMoreTopics(cid);
}
});
}; };
function enableInfiniteLoading() {
if(!config.usePagination) {
app.enableInfiniteLoading(function() {
if(!loadingMoreTopics) {
Category.loadMoreTopics(templates.get('category_id'));
}
});
} else {
pagination.init(templates.get('currentPage'), templates.get('pageCount'));
}
}
Category.onNewTopic = function(data) { Category.onNewTopic = function(data) {
var html = templates.prepare(templates['category'].blocks['topics']).parse({ var html = templates.prepare(templates['category'].blocks['topics']).parse({
topics: [data] topics: [data]
@ -85,7 +88,12 @@ define(['composer'], function(composer) {
addActiveUser(data); addActiveUser(data);
socket.emit('categories.getPageCount', templates.get('category_id'), function(err, newPageCount) {
pagination.recreatePaginationLinks(newPageCount);
});
$('#topics-container span.timeago').timeago(); $('#topics-container span.timeago').timeago();
app.createUserTooltips();
}); });
} }
@ -116,9 +124,15 @@ define(['composer'], function(composer) {
jQuery('#category-no-topics').remove(); jQuery('#category-no-topics').remove();
html = $(translatedHTML); html = $(translatedHTML);
container.append(html);
if(config.usePagination) {
container.empty().append(html);
} else {
container.append(html);
}
$('#topics-container span.timeago').timeago(); $('#topics-container span.timeago').timeago();
app.createUserTooltips();
app.makeNumbersHumanReadable(html.find('.human-readable-number')); app.makeNumbersHumanReadable(html.find('.human-readable-number'));
}); });
} }

@ -52,12 +52,6 @@ define(function() {
}); });
document.querySelector('#content input').focus(); document.querySelector('#content input').focus();
if(!config.emailSetup)
$('#reset-link').addClass('hide');
else
$('#reset-link').removeClass('hide');
}; };
return Login; return Login;

@ -0,0 +1,110 @@
define(function() {
var pagination = {};
pagination.currentPage = 0;
pagination.pageCount = 0;
pagination.init = function(currentPage, pageCount) {
pagination.currentPage = parseInt(currentPage, 10);
pagination.pageCount = parseInt(pageCount, 10);
pagination.recreatePaginationLinks(pageCount);
$('.pagination')
.on('click', '.previous', function() {
return pagination.loadPage(pagination.currentPage - 1);
}).on('click', '.next', function() {
return pagination.loadPage(pagination.currentPage + 1);
}).on('click', '.page', function() {
return pagination.loadPage($(this).attr('data-page'));
}).on('click', '.select_page', function(e) {
e.preventDefault();
bootbox.prompt('Enter page number:', function(pageNum) {
pagination.loadPage(pageNum);
});
});
}
pagination.recreatePaginationLinks = function(newPageCount) {
pagination.pageCount = parseInt(newPageCount, 10);
var pagesToShow = [1];
if(pagination.pageCount !== 1) {
pagesToShow.push(pagination.pageCount);
}
var previous = pagination.currentPage - 1;
var next = pagination.currentPage + 1;
if(previous > 1 && pagesToShow.indexOf(previous) === -1) {
pagesToShow.push(previous);
}
if(next < pagination.pageCount && pagesToShow.indexOf(next) === -1) {
pagesToShow.push(next);
}
if(pagesToShow.indexOf(pagination.currentPage) === -1) {
pagesToShow.push(pagination.currentPage);
}
pagesToShow.sort(function(a, b) {
return parseInt(a, 10) - parseInt(b, 10);
});
var html = '';
for(var i=0; i<pagesToShow.length; ++i) {
if(i > 0) {
if (pagesToShow[i] - 1 !== pagesToShow[i-1]) {
html += '<li><a class="select_page" href="#">|</a></li>';
}
}
html += '<li class="page" data-page="' + pagesToShow[i] + '"><a href="#">' + pagesToShow[i] + '</a></li>';
}
$('.pagination li.page').remove();
$(html).insertAfter($('.pagination li.previous'));
updatePageLinks();
}
pagination.loadPage = function(page, callback) {
page = parseInt(page, 10);
if(!utils.isNumber(page) || page < 1 || page > pagination.pageCount) {
return false;
}
ajaxify.go(window.location.pathname.slice(1) + '?page=' + page);
return true;
}
function updatePageLinks() {
if(pagination.pageCount === 0) {
$('.pagination').addClass('hide');
} else {
$('.pagination').removeClass('hide');
}
$('.pagination .next').removeClass('disabled');
$('.pagination .previous').removeClass('disabled');
if(pagination.currentPage === 1) {
$('.pagination .previous').addClass('disabled');
}
if(pagination.currentPage === pagination.pageCount) {
$('.pagination .next').addClass('disabled');
}
$('.pagination .page').removeClass('active');
$('.pagination .page[data-page="' + pagination.currentPage + '"]').addClass('active');
$('.pagination .page').each(function(index, element) {
var li = $(this);
var page = li.attr('data-page');
li.find('a').attr('href', window.location.pathname + '?page=' + page);
});
}
return pagination;
});

@ -1,20 +1,16 @@
define(function() { define(function() {
var Recent = {}; var Recent = {};
Recent.newTopicCount = 0; var newTopicCount = 0,
Recent.newPostCount = 0; newPostCount = 0,
Recent.loadingMoreTopics = false; loadingMoreTopics = false;
var active = ''; var active = '';
Recent.init = function() { Recent.init = function() {
app.enterRoom('recent_posts'); app.enterRoom('recent_posts');
ajaxify.register_events([ Recent.watchForNewPosts();
'event:new_topic',
'event:new_post'
]);
function getActiveSection() { function getActiveSection() {
var url = window.location.href, var url = window.location.href,
@ -37,26 +33,34 @@ define(function() {
$(this).addClass('hide'); $(this).addClass('hide');
}); });
socket.on('event:new_topic', function(data) {
++Recent.newTopicCount;
Recent.updateAlertText();
app.enableInfiniteLoading(function() {
if(!loadingMoreTopics) {
Recent.loadMoreTopics();
}
}); });
};
socket.on('event:new_post', function(data) { Recent.watchForNewPosts = function () {
++Recent.newPostCount;
newPostCount = 0;
newTopicCount = 0;
ajaxify.register_events([
'event:new_topic',
'event:new_post'
]);
socket.on('event:new_topic', function(data) {
++newTopicCount;
Recent.updateAlertText(); Recent.updateAlertText();
}); });
$(window).off('scroll').on('scroll', function() { socket.on('event:new_post', function(data) {
var bottom = ($(document).height() - $(window).height()) * 0.9; ++newPostCount;
Recent.updateAlertText();
if ($(window).scrollTop() > bottom && !Recent.loadingMoreTopics) {
Recent.loadMoreTopics();
}
}); });
}; }
Recent.updateAlertText = function() { Recent.updateAlertText = function() {
var text = 'There'; var text = 'There';
@ -76,41 +80,43 @@ define(function() {
text += '. Click here to reload.'; text += '. Click here to reload.';
$('#new-topics-alert').html(text).removeClass('hide').fadeIn('slow'); $('#new-topics-alert').html(text).removeClass('hide').fadeIn('slow');
$('#category-no-topics').addClass('hide');
} }
Recent.onTopicsLoaded = function(topics) { Recent.loadMoreTopics = function() {
var html = templates.prepare(templates['recent'].blocks['topics']).parse({ loadingMoreTopics = true;
socket.emit('topics.loadMoreRecentTopics', {
after: $('#topics-container').children('li').length,
term: active
}, function(err, data) {
if(err) {
return app.alertError(err.message);
}
if (data.topics && data.topics.length) {
Recent.onTopicsLoaded('recent', data.topics);
}
loadingMoreTopics = false;
});
}
Recent.onTopicsLoaded = function(template, topics) {
var html = templates.prepare(templates[template].blocks['topics']).parse({
topics: topics topics: topics
}); });
translator.translate(html, function(translatedHTML) { translator.translate(html, function(translatedHTML) {
var container = $('#topics-container');
$('#category-no-topics').remove(); $('#category-no-topics').remove();
html = $(translatedHTML); html = $(translatedHTML);
container.append(html); $('#topics-container').append(html);
$('span.timeago').timeago(); $('span.timeago').timeago();
app.createUserTooltips(); app.createUserTooltips();
app.makeNumbersHumanReadable(html.find('.human-readable-number')); app.makeNumbersHumanReadable(html.find('.human-readable-number'));
}); });
} }
Recent.loadMoreTopics = function() {
Recent.loadingMoreTopics = true;
socket.emit('topics.loadMoreRecentTopics', {
after: $('#topics-container').children('li').length,
term: active
}, function(err, data) {
if(err) {
return app.alertError(err.message);
}
if (data.topics && data.topics.length) {
Recent.onTopicsLoaded(data.topics);
}
Recent.loadingMoreTopics = false;
});
}
return Recent; return Recent;
}); });

@ -1,11 +1,11 @@
define(['composer'], function(composer) { define(['composer', 'forum/pagination'], function(composer, pagination) {
var Topic = {}, var Topic = {},
infiniteLoaderActive = false, infiniteLoaderActive = false,
pagination; pagination;
function showBottomPostBar() { function showBottomPostBar() {
if($('#post-container .post-row').length > 1) { if($('#post-container .post-row').length > 1 || !$('#post-container li[data-index="0"]').length) {
$('.topic-main-buttons').removeClass('hide').parent().removeClass('hide'); $('.bottom-post-bar').removeClass('hide');
} }
} }
@ -17,7 +17,11 @@ define(['composer'], function(composer) {
deleted: templates.get('deleted'), deleted: templates.get('deleted'),
pinned: templates.get('pinned') pinned: templates.get('pinned')
}, },
topic_name = templates.get('topic_name'); topic_name = templates.get('topic_name'),
currentPage = parseInt(templates.get('currentPage'), 10),
pageCount = parseInt(templates.get('pageCount'), 10);
Topic.postCount = templates.get('postcount');
function fixDeleteStateForPosts() { function fixDeleteStateForPosts() {
@ -38,7 +42,8 @@ define(['composer'], function(composer) {
showBottomPostBar(); showBottomPostBar();
// Resetting thread state updateHeader();
if (thread_state.locked === '1') set_locked_state(true); if (thread_state.locked === '1') set_locked_state(true);
if (thread_state.deleted === '1') set_delete_state(true); if (thread_state.deleted === '1') set_delete_state(true);
if (thread_state.pinned === '1') set_pinned_state(true); if (thread_state.pinned === '1') set_pinned_state(true);
@ -327,21 +332,6 @@ define(['composer'], function(composer) {
Topic.scrollToPost(parseInt(bookmark, 10)); Topic.scrollToPost(parseInt(bookmark, 10));
} }
$('#post-container').on('click', '.deleted', function(ev) {
$(this).toggleClass('deleted-expanded');
});
// Show the paginator block, now that the DOM has finished loading
(function delayedHeaderUpdate() {
if (!Topic.postCount) {
setTimeout(function() {
delayedHeaderUpdate();
}, 25);
} else {
updateHeader();
}
})();
$('#post-container').on('mouseenter', '.favourite-tooltip', function(e) { $('#post-container').on('mouseenter', '.favourite-tooltip', function(e) {
if (!$(this).data('users-loaded')) { if (!$(this).data('users-loaded')) {
$(this).data('users-loaded', "true"); $(this).data('users-loaded', "true");
@ -355,15 +345,19 @@ define(['composer'], function(composer) {
}); });
function enableInfiniteLoading() { function enableInfiniteLoading() {
$(window).off('scroll').on('scroll', function() { if(!config.usePagination) {
var bottom = ($(document).height() - $(window).height()) * 0.9; app.enableInfiniteLoading(function() {
if (!infiniteLoaderActive && $('#post-container').children().length) {
loadMorePosts(tid, function(posts) {
fixDeleteStateForPosts();
});
}
});
} else {
$('.pagination-block').addClass('hide');
if ($(window).scrollTop() > bottom && !infiniteLoaderActive && $('#post-container').children().length) { pagination.init(currentPage, pageCount);
loadMorePosts(tid, function(posts) { }
fixDeleteStateForPosts();
});
}
});
} }
$('.topic').on('click', '.post_reply', function() { $('.topic').on('click', '.post_reply', function() {
@ -380,7 +374,7 @@ define(['composer'], function(composer) {
var username = '', var username = '',
post = $(this).parents('li[data-pid]'); post = $(this).parents('li[data-pid]');
if (post.length) { if (post.length) {
username = '@' + post.attr('data-username') + ' '; username = '@' + post.attr('data-username').replace(/\s/g, '-') + ' ';
} }
if (thread_state.locked !== '1') { if (thread_state.locked !== '1') {
@ -395,12 +389,12 @@ define(['composer'], function(composer) {
pid = $(this).parents('.post-row').attr('data-pid'); pid = $(this).parents('.post-row').attr('data-pid');
if (post.length) { if (post.length) {
username = '@' + post.attr('data-username'); username = '@' + post.attr('data-username').replace(/\s/g, '-');
} }
socket.emit('posts.getRawPost', pid, function(err, post) { socket.emit('posts.getRawPost', pid, function(err, post) {
if(err) { if(err) {
return app.alert(err.message); return app.alertError(err.message);
} }
var quoted = ''; var quoted = '';
if(post) { if(post) {
@ -433,7 +427,7 @@ define(['composer'], function(composer) {
var pid = $(this).parents('.post-row').attr('data-pid'); var pid = $(this).parents('.post-row').attr('data-pid');
$('#post_' + pid + '_link').val(window.location.href + "#" + pid); $('#post_' + pid + '_link').val(window.location.href + "#" + pid);
// without the setTimeout can't select the text in the input // without the setTimeout can't select the text in the input
setTimeout(function(){ setTimeout(function() {
$('#post_' + pid + '_link').select(); $('#post_' + pid + '_link').select();
}, 50); }, 50);
}); });
@ -641,6 +635,11 @@ define(['composer'], function(composer) {
}); });
socket.on('event:new_post', function(data) { socket.on('event:new_post', function(data) {
if(config.usePagination) {
onNewPostPagination(data);
return;
}
var posts = data.posts; var posts = data.posts;
for (var p in posts) { for (var p in posts) {
if (posts.hasOwnProperty(p)) { if (posts.hasOwnProperty(p)) {
@ -894,55 +893,37 @@ define(['composer'], function(composer) {
} }
function toggle_post_delete_state(pid) { function toggle_post_delete_state(pid) {
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')); var postEl = $('#post-container li[data-pid="' + pid + '"]');
if (postEl[0]) {
quoteEl = postEl.find('.quote'),
favEl = postEl.find('.favourite'),
replyEl = postEl.find('.post_reply');
socket.emit('posts.getPrivileges', pid, function(err, privileges) { if (postEl.length) {
if(err) { postEl.toggleClass('deleted');
return app.alert(err.message);
}
if (privileges.editable) { toggle_post_tools(pid, postEl.hasClass('deleted'));
if (!postEl.hasClass('deleted')) {
toggle_post_tools(pid, false);
} else {
toggle_post_tools(pid, true);
}
}
if (privileges.view_deleted) { updatePostCount();
postEl.toggleClass('deleted');
} else {
postEl.toggleClass('none');
}
updatePostCount();
});
} }
} }
function toggle_post_tools(pid, state) { function toggle_post_tools(pid, isDeleted) {
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')), var postEl = $('#post-container li[data-pid="' + pid + '"]'),
quoteEl = $(postEl[0].querySelector('.quote')), quoteEl = $(postEl[0].querySelector('.quote')),
favEl = $(postEl[0].querySelector('.favourite')), favEl = $(postEl[0].querySelector('.favourite')),
replyEl = $(postEl[0].querySelector('.post_reply')); replyEl = $(postEl[0].querySelector('.post_reply')),
chatEl = $(postEl[0].querySelector('.chat'));
if (state) { if (isDeleted) {
quoteEl.removeClass('none');
favEl.removeClass('none');
replyEl.removeClass('none');
} else {
quoteEl.addClass('none'); quoteEl.addClass('none');
favEl.addClass('none'); favEl.addClass('none');
replyEl.addClass('none'); replyEl.addClass('none');
chatEl.addClass('none');
} else {
quoteEl.removeClass('none');
favEl.removeClass('none');
replyEl.removeClass('none');
chatEl.removeClass('none');
} }
} }
Topic.postCount = templates.get('postcount');
window.onscroll = updateHeader; window.onscroll = updateHeader;
}; };
@ -969,7 +950,8 @@ define(['composer'], function(composer) {
var progressBarContainer = $('.progress-container'); var progressBarContainer = $('.progress-container');
var tid = templates.get('topic_id'); var tid = templates.get('topic_id');
pagination.parentNode.style.display = 'block'; if(pagination.parentNode)
pagination.parentNode.style.display = 'block';
progressBarContainer.css('display', ''); progressBarContainer.css('display', '');
if (scrollTop < jQuery('.posts > .post-row:first-child').height() && Topic.postCount > 1) { if (scrollTop < jQuery('.posts > .post-row:first-child').height() && Topic.postCount > 1) {
@ -1019,36 +1001,67 @@ define(['composer'], function(composer) {
return; return;
} }
var container = $(window), if(config.usePagination) {
socket.emit('posts.getPidPage', pid, function(err, page) {
if(err) {
return;
}
if(parseInt(page, 10) !== pagination.currentPage) {
pagination.loadPage(page);
} else {
scrollToPid(pid);
}
});
} else {
scrollToPid(pid);
}
function scrollToPid(pid) {
var container = $(window),
scrollTo = $('#post_anchor_' + pid), scrollTo = $('#post_anchor_' + pid),
tid = $('#post-container').attr('data-tid'); tid = $('#post-container').attr('data-tid');
function animateScroll() { function animateScroll() {
$('window,html').animate({ $('window,html').animate({
scrollTop: scrollTo.offset().top + container.scrollTop() - $('#header-menu').height() scrollTop: scrollTo.offset().top + container.scrollTop() - $('#header-menu').height()
}, 400); }, 400);
} }
if (!scrollTo.length && tid) { if (!scrollTo.length && tid) {
var intervalID = setInterval(function () { var intervalID = setInterval(function () {
loadMorePosts(tid, function (posts) { loadMorePosts(tid, function (posts) {
scrollTo = $('#post_anchor_' + pid); scrollTo = $('#post_anchor_' + pid);
if (tid && scrollTo.length) { if (tid && scrollTo.length) {
animateScroll(); animateScroll();
} }
if (!posts.length || scrollTo.length) if (!posts.length || scrollTo.length)
clearInterval(intervalID); clearInterval(intervalID);
}); });
}, 100); }, 100);
} else if (tid) { } else if (tid) {
animateScroll(); animateScroll();
}
} }
} }
function onNewPostPagination(data) {
var posts = data.posts;
socket.emit('topics.getPageCount', templates.get('topic_id'), function(err, newPageCount) {
pagination.recreatePaginationLinks(newPageCount);
if(pagination.currentPage === pagination.pageCount) {
createNewPosts(data);
} else if(data.posts && data.posts.length && parseInt(data.posts[0].uid, 10) === parseInt(app.uid, 10)) {
pagination.loadPage(pagination.pageCount);
}
});
}
function createNewPosts(data, infiniteLoaded) { function createNewPosts(data, infiniteLoaded) {
if(!data || (data.posts && !data.posts.length)) { if(!data || (data.posts && !data.posts.length)) {
return; return;
@ -1063,6 +1076,7 @@ define(['composer'], function(composer) {
function findInsertionPoint() { function findInsertionPoint() {
var after = null, var after = null,
firstPid = data.posts[0].pid; firstPid = data.posts[0].pid;
$('#post-container li[data-pid]').each(function() { $('#post-container li[data-pid]').each(function() {
if(parseInt(firstPid, 10) > parseInt($(this).attr('data-pid'), 10)) { if(parseInt(firstPid, 10) > parseInt($(this).attr('data-pid'), 10)) {
after = $(this); after = $(this);
@ -1083,11 +1097,7 @@ define(['composer'], function(composer) {
var insertAfter = findInsertionPoint(); var insertAfter = findInsertionPoint();
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data); parseAndTranslatePosts(data, function(translatedHTML) {
var regexp = new RegExp("<!--[\\s]*IF @first[\\s]*-->([\\s\\S]*?)<!--[\\s]*ENDIF @first[\\s]*-->", 'g');
html = html.replace(regexp, '');
translator.translate(html, function(translatedHTML) {
var translated = $(translatedHTML); var translated = $(translatedHTML);
if(!infiniteLoaded) { if(!infiniteLoaded) {
@ -1098,27 +1108,41 @@ define(['composer'], function(composer) {
.hide() .hide()
.fadeIn('slow'); .fadeIn('slow');
for (var x = 0, numPosts = data.posts.length; x < numPosts; x++) { onNewPostsLoaded(data.posts);
socket.emit('posts.getPrivileges', data.posts[x].pid, function(err, privileges) { });
if(err) { }
return app.alertError(err.message);
}
toggle_mod_tools(privileges.pid, privileges.editable);
});
}
infiniteLoaderActive = false; function parseAndTranslatePosts(data, callback) {
var html = templates.prepare(templates['topic'].blocks['posts']).parse(data);
var regexp = new RegExp("<!--[\\s]*IF @first[\\s]*-->([\\s\\S]*?)<!--[\\s]*ENDIF @first[\\s]*-->", 'g');
html = html.replace(regexp, '');
app.populateOnlineUsers(); translator.translate(html, callback);
app.createUserTooltips(); }
app.addCommasToNumbers();
$('span.timeago').timeago();
$('.post-content img').addClass('img-responsive'); function onNewPostsLoaded(posts) {
updatePostCount(); for (var x = 0, numPosts = posts.length; x < numPosts; x++) {
showBottomPostBar(); socket.emit('posts.getPrivileges', posts[x].pid, function(err, privileges) {
}); if(err) {
return app.alertError(err.message);
}
toggle_mod_tools(privileges.pid, privileges.editable);
});
}
infiniteLoaderActive = false;
app.populateOnlineUsers();
app.createUserTooltips();
app.addCommasToNumbers();
$('span.timeago').timeago();
$('.post-content img').addClass('img-responsive');
updatePostCount();
showBottomPostBar();
} }
function toggle_mod_tools(pid, state) { function toggle_mod_tools(pid, state) {
var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')), var postEl = $(document.querySelector('#post-container li[data-pid="' + pid + '"]')),
editEl = postEl.find('.edit'), editEl = postEl.find('.edit'),
@ -1150,7 +1174,6 @@ define(['composer'], function(composer) {
return; return;
} }
if (indicatorEl.attr('done') === '0') { if (indicatorEl.attr('done') === '0') {
infiniteLoaderActive = true; infiniteLoaderActive = true;
indicatorEl.fadeIn(); indicatorEl.fadeIn();

@ -1,53 +1,15 @@
define(function() { define(['forum/recent'], function(recent) {
var Unread = {}, var Unread = {},
loadingMoreTopics = false; loadingMoreTopics = false;
Unread.init = function() { Unread.init = function() {
app.enterRoom('recent_posts'); app.enterRoom('recent_posts');
ajaxify.register_events([
'event:new_topic',
'event:new_post',
'topics.markAllRead'
]);
var newTopicCount = 0,
newPostCount = 0;
$('#new-topics-alert').on('click', function() { $('#new-topics-alert').on('click', function() {
$(this).addClass('hide'); $(this).addClass('hide');
}); });
socket.on('event:new_topic', function(data) { recent.watchForNewPosts();
++newTopicCount;
updateAlertText();
});
function updateAlertText() {
var text = 'There';
if (newTopicCount > 1) {
text += ' are ' + newTopicCount + ' new topics';
} else if (newTopicCount === 1) {
text += ' is a new topic';
}
if (newPostCount > 1) {
text += (newTopicCount?' and ':' are ') + newPostCount + ' new posts';
} else if(newPostCount === 1) {
text += (newTopicCount?' and ':' is ') + ' a new post';
}
text += '. Click here to reload.';
$('#new-topics-alert').html(text).removeClass('hide').fadeIn('slow');
$('#category-no-topics').addClass('hidden');
}
socket.on('event:new_post', function(data) {
++newPostCount;
updateAlertText();
});
$('#mark-allread-btn').on('click', function() { $('#mark-allread-btn').on('click', function() {
var btn = $(this); var btn = $(this);
@ -67,24 +29,19 @@ define(function() {
}); });
}); });
function onTopicsLoaded(topics) { if ($("body").height() <= $(window).height() && $('#topics-container').children().length >= 20) {
$('#load-more-btn').show();
var html = templates.prepare(templates['unread'].blocks['topics']).parse({ }
topics: topics
});
translator.translate(html, function(translatedHTML) {
var container = $('#topics-container');
$('#category-no-topics').remove(); $('#load-more-btn').on('click', function() {
loadMoreTopics();
});
html = $(translatedHTML); app.enableInfiniteLoading(function() {
container.append(html); if(!loadingMoreTopics) {
$('span.timeago').timeago(); loadMoreTopics();
app.createUserTooltips(); }
app.makeNumbersHumanReadable(html.find('.human-readable-number')); });
});
}
function loadMoreTopics() { function loadMoreTopics() {
loadingMoreTopics = true; loadingMoreTopics = true;
@ -96,7 +53,7 @@ define(function() {
} }
if (data.topics && data.topics.length) { if (data.topics && data.topics.length) {
onTopicsLoaded(data.topics); recent.onTopicsLoaded('unread', data.topics);
$('#topics-container').attr('data-next-start', data.nextStart); $('#topics-container').attr('data-next-start', data.nextStart);
} else { } else {
$('#load-more-btn').hide(); $('#load-more-btn').hide();
@ -105,23 +62,6 @@ define(function() {
loadingMoreTopics = false; loadingMoreTopics = false;
}); });
} }
$(window).off('scroll').on('scroll', function() {
var bottom = ($(document).height() - $(window).height()) * 0.9;
if ($(window).scrollTop() > bottom && !loadingMoreTopics) {
loadMoreTopics();
}
});
if ($("body").height() <= $(window).height() && $('#topics-container').children().length >= 20) {
$('#load-more-btn').show();
}
$('#load-more-btn').on('click', function() {
loadMoreTopics();
});
}; };
return Unread; return Unread;

@ -352,7 +352,7 @@ define(['taskbar'], function(taskbar) {
} }
postContainer.css('visibility', 'visible') postContainer.css('visibility', 'visible')
.css('z-index', 1); .css('z-index', 2);
$('body').css({'margin-bottom': postContainer.css('height')}); $('body').css({'margin-bottom': postContainer.css('height')});

@ -79,7 +79,7 @@
template.prototype.parse = parse; template.prototype.parse = parse;
template.prototype.html = String(html); template.prototype.html = String(html);
global.templates[file] = new template; templates[file] = new template;
loaded--; loaded--;
if (loaded === 0) { if (loaded === 0) {
@ -196,6 +196,7 @@
template_data = data; template_data = data;
parse_template(); parse_template();
}).fail(function (data, textStatus) { }).fail(function (data, textStatus) {
jQuery('#content, #footer').stop(true, true).removeClass('ajaxifying');
if (data && data.status == 404) { if (data && data.status == 404) {
return ajaxify.go('404'); return ajaxify.go('404');
} else if (data && data.status === 403) { } else if (data && data.status === 403) {
@ -396,7 +397,7 @@
namespace = ''; namespace = '';
} else { } else {
// clean up all undefined conditionals // clean up all undefined conditionals
template = template.replace(/<!-- IF([^@]*?)ENDIF([^@]*?)-->/gi, ''); template = template.replace(/<!-- IF([^@]*?)ENDIF([^@]*?)-->/gi, '');
} }
return template; return template;

@ -7,6 +7,7 @@
<li><a href="#email" data-toggle="tab">Email</a></li> <li><a href="#email" data-toggle="tab">Email</a></li>
<li><a href="#user" data-toggle="tab">User</a></li> <li><a href="#user" data-toggle="tab">User</a></li>
<li><a href="#post" data-toggle="tab">Post</a></li> <li><a href="#post" data-toggle="tab">Post</a></li>
<li><a href="#pagination" data-toggle="tab">Pagination</a></li>
<li><a href="#web-crawler" data-toggle="tab">Web Crawler</a></li> <li><a href="#web-crawler" data-toggle="tab">Web Crawler</a></li>
</ul> </ul>
@ -133,6 +134,21 @@
</form> </form>
</div> </div>
<div class="tab-pane" id="pagination">
<form>
<div class="alert alert-warning">
<div class="checkbox">
<label>
<input type="checkbox" data-field="usePagination"> <strong>Paginate topics and posts instead of using infinite scroll.</strong>
</label>
</div>
<strong>Topics per Page</strong><br /> <input type="text" class="form-control" value="20" data-field="topicsPerPage"><br />
<strong>Posts per Page</strong><br /> <input type="text" class="form-control" value="20" data-field="postsPerPage"><br />
</div>
</form>
</div>
<div class="tab-pane" id="web-crawler"> <div class="tab-pane" id="web-crawler">
<form> <form>
<div class="alert alert-warning"> <div class="alert alert-warning">

@ -84,7 +84,16 @@
</li> </li>
<!-- END topics --> <!-- END topics -->
</ul> </ul>
<!-- IF usePagination -->
<div class="text-center">
<ul class="pagination">
<li class="previous pull-left"><a href="#"><i class="fa fa-chevron-left"></i> [[global:previouspage]]</a></li>
<li class="next pull-right"><a href="#">[[global:nextpage]] <i class="fa fa-chevron-right"></i></a></li>
</ul>
</div>
<!-- ENDIF usePagination -->
</div> </div>
<div class="col-md-3 col-xs-12 {show_sidebar} category-sidebar"> <div class="col-md-3 col-xs-12 {show_sidebar} category-sidebar">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">[[category:sidebar.recent_replies]]</div> <div class="panel-heading">[[category:sidebar.recent_replies]]</div>
@ -121,4 +130,6 @@
</div> </div>
<input type="hidden" template-variable="category_id" value="{category_id}" /> <input type="hidden" template-variable="category_id" value="{category_id}" />
<input type="hidden" template-variable="category_name" value="{category_name}" /> <input type="hidden" template-variable="category_name" value="{category_name}" />
<input type="hidden" template-variable="currentPage" value="{currentPage}" />
<input type="hidden" template-variable="pageCount" value="{pageCount}" />

@ -41,7 +41,10 @@
<div class="form-group"> <div class="form-group">
<div class="col-lg-offset-2 col-lg-10"> <div class="col-lg-offset-2 col-lg-10">
<hr /> <hr />
<button class="btn btn-primary btn-lg btn-block" id="login" type="submit">[[login:login]]</button> &nbsp; <a id="reset-link" class="hide" href="{relative_path}/reset">[[login:forgot_password]]</a> <button class="btn btn-primary btn-lg btn-block" id="login" type="submit">[[login:login]]</button>
<!-- IF showResetLink -->
&nbsp; <a id="reset-link" href="{relative_path}/reset">[[login:forgot_password]]</a>
<!-- ENDIF showResetLink -->
</div> </div>
</div> </div>
<input type="hidden" name="_csrf" value="{token}" id="csrf-token" /> <input type="hidden" name="_csrf" value="{token}" id="csrf-token" />

@ -1,5 +1,7 @@
<input type="hidden" template-variable="expose_tools" value="{expose_tools}" /> <input type="hidden" template-variable="expose_tools" value="{expose_tools}" />
<input type="hidden" template-variable="topic_id" value="{topic_id}" /> <input type="hidden" template-variable="topic_id" value="{topic_id}" />
<input type="hidden" template-variable="currentPage" value="{currentPage}" />
<input type="hidden" template-variable="pageCount" value="{pageCount}" />
<input type="hidden" template-variable="locked" value="{locked}" /> <input type="hidden" template-variable="locked" value="{locked}" />
<input type="hidden" template-variable="deleted" value="{deleted}" /> <input type="hidden" template-variable="deleted" value="{deleted}" />
<input type="hidden" template-variable="pinned" value="{pinned}" /> <input type="hidden" template-variable="pinned" value="{pinned}" />
@ -141,7 +143,7 @@
</li> </li>
<!-- IF @first --> <!-- IF @first -->
<li class="well post-bar"> <li class="well post-bar" data-index="{posts.index}">
<div class="inline-block"> <div class="inline-block">
<small class="topic-stats"> <small class="topic-stats">
<span>[[category:posts]]</span> <span>[[category:posts]]</span>
@ -176,15 +178,15 @@
<!-- END posts --> <!-- END posts -->
</ul> </ul>
<div class="well col-md-11 col-xs-12 pull-right hide"> <div class="well col-md-11 col-xs-12 pull-right post-bar bottom-post-bar hide">
<div class="topic-main-buttons pull-right inline-block hide"> <div class="topic-main-buttons pull-right inline-block">
<div class="loading-indicator" done="0" style="display:none;"> <div class="loading-indicator" done="0" style="display:none;">
<span class="hidden-xs-inline">[[topic:loading_more_posts]]</span> <i class="fa fa-refresh fa-spin"></i> <span class="hidden-xs-inline">[[topic:loading_more_posts]]</span> <i class="fa fa-refresh fa-spin"></i>
</div> </div>
<!-- IF privileges.write --> <!-- IF privileges.write -->
<button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button> <button class="btn btn-primary post_reply" type="button">[[topic:reply]]</button>
<!-- ENDIF privileges.write --> <!-- ENDIF privileges.write -->
<div class="btn-group thread-tools hide"> <div class="btn-group thread-tools hide dropup">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button> <button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[topic:thread_tools.title]] <span class="caret"></span></button>
<ul class="dropdown-menu pull-right"> <ul class="dropdown-menu pull-right">
<li><a href="#" class="markAsUnreadForAll"><i class="fa fa-inbox"></i> [[topic:thread_tools.markAsUnreadForAll]]</a></li> <li><a href="#" class="markAsUnreadForAll"><i class="fa fa-inbox"></i> [[topic:thread_tools.markAsUnreadForAll]]</a></li>
@ -201,6 +203,15 @@
<div style="clear:both;"></div> <div style="clear:both;"></div>
</div> </div>
<!-- IF usePagination -->
<div class="text-center">
<ul class="pagination">
<li class="previous pull-left"><a href="#"><i class="fa fa-chevron-left"></i> [[global:previouspage]]</a></li>
<li class="next pull-right"><a href="#">[[global:nextpage]] <i class="fa fa-chevron-right"></i></a></li>
</ul>
</div>
<!-- ENDIF usePagination -->
<div id="move_thread_modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="Move Topic" aria-hidden="true"> <div id="move_thread_modal" class="modal" tabindex="-1" role="dialog" aria-labelledby="Move Topic" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">

@ -5,6 +5,7 @@ var db = require('./database.js'),
topics = require('./topics.js'), topics = require('./topics.js'),
plugins = require('./plugins'), plugins = require('./plugins'),
CategoryTools = require('./categoryTools'), CategoryTools = require('./categoryTools'),
meta = require('./meta'),
async = require('async'), async = require('async'),
winston = require('winston'), winston = require('winston'),
@ -45,14 +46,14 @@ var db = require('./database.js'),
}); });
}; };
Categories.getCategoryById = function(category_id, current_user, callback) { Categories.getCategoryById = function(category_id, start, end, current_user, callback) {
Categories.getCategoryData(category_id, function(err, categoryData) { Categories.getCategoryData(category_id, function(err, categoryData) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
function getTopicIds(next) { function getTopicIds(next) {
Categories.getTopicIds(category_id, 0, 19, next); Categories.getTopicIds(category_id, start, end, next);
} }
function getActiveUsers(next) { function getActiveUsers(next) {
@ -65,10 +66,15 @@ var db = require('./database.js'),
}); });
} }
async.parallel([getTopicIds, getActiveUsers, getSidebars], function(err, results) { function getPageCount(next) {
Categories.getPageCount(category_id, next);
}
async.parallel([getTopicIds, getActiveUsers, getSidebars, getPageCount], function(err, results) {
var tids = results[0], var tids = results[0],
active_users = results[1], active_users = results[1],
sidebars = results[2]; sidebars = results[2],
pageCount = results[3];
var category = { var category = {
'category_name': categoryData.name, 'category_name': categoryData.name,
@ -82,6 +88,7 @@ var db = require('./database.js'),
'category_id': category_id, 'category_id': category_id,
'active_users': [], 'active_users': [],
'topics': [], 'topics': [],
'pageCount': pageCount,
'disableSocialButtons': meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false, 'disableSocialButtons': meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false,
'sidebars': sidebars 'sidebars': sidebars
}; };
@ -137,6 +144,18 @@ var db = require('./database.js'),
db.getSortedSetRevRange('categories:' + cid + ':tid', start, stop, callback); db.getSortedSetRevRange('categories:' + cid + ':tid', start, stop, callback);
}; };
Categories.getPageCount = function(cid, callback) {
db.sortedSetCard('categories:' + cid + ':tid', function(err, topicCount) {
if(err) {
return callback(err);
}
var topicsPerPage = parseInt(meta.config.topicsPerPage, 10);
topicsPerPage = topicsPerPage ? topicsPerPage : 20;
callback(null, Math.ceil(parseInt(topicCount, 10) / topicsPerPage));
});
};
Categories.getAllCategories = function(current_user, callback) { Categories.getAllCategories = function(current_user, callback) {
db.getListRange('categories:cid', 0, -1, function(err, cids) { db.getListRange('categories:cid', 0, -1, function(err, cids) {

@ -626,6 +626,19 @@
}); });
} }
module.sortedSetCard = function(key, callback) {
db.collection('objects').count({_key:key}, function(err, count) {
if(err) {
return callback(err);
}
if(!count) {
return callback(null, 0);
}
callback(null, count);
});
}
module.sortedSetRank = function(key, value, callback) { module.sortedSetRank = function(key, value, callback) {
if(value !== null && value !== undefined) { if(value !== null && value !== undefined) {
value = value.toString(); value = value.toString();

@ -374,6 +374,10 @@
redisClient.zcount(key, min, max, callback); redisClient.zcount(key, min, max, callback);
} }
module.sortedSetCard = function(key, callback) {
redisClient.zcard(key, callback);
}
module.sortedSetRank = function(key, value, callback) { module.sortedSetRank = function(key, value, callback) {
redisClient.zrank(key, value, callback); redisClient.zrank(key, value, callback);
} }

@ -1,16 +1,18 @@
var User = require('./user'), var fs = require('fs'),
async = require('async'),
path = require('path'),
User = require('./user'),
Plugins = require('./plugins'), Plugins = require('./plugins'),
Meta = require('./meta'), Meta = require('./meta'),
Translator = require('../public/src/translator'), Translator = require('../public/src/translator'),
templates = require('../public/src/templates'),
fs = require('fs'),
async = require('async'),
path = require('path'),
Emailer = {}; Emailer = {};
var render = function(template, params, callback) { var render = function(template, params, callback) {
if (templates[template] !== null) {
if (templates[template]) {
Translator.translate(templates[template].parse(params), function(template) { Translator.translate(templates[template].parse(params), function(template) {
callback(null, template); callback(null, template);
}); });

@ -84,7 +84,7 @@
}; };
Feed.updateCategory = function (cid, callback) { Feed.updateCategory = function (cid, callback) {
categories.getCategoryById(cid, 0, function (err, categoryData) { categories.getCategoryById(cid, 0, -1, 0, function (err, categoryData) {
if (err) return callback(new Error('category-invalid')); if (err) return callback(new Error('category-invalid'));
var feed = new rss({ var feed = new rss({

@ -1,7 +1,7 @@
var db = require('./database'), var db = require('./database'),
async = require('async'), async = require('async'),
user = require('./user'), user = require('./user'),
plugins = require('./plugins'); plugins = require('./plugins'),
meta = require('./meta'); meta = require('./meta');

@ -202,6 +202,10 @@ var fs = require('fs'),
} else return; } else return;
}; };
Plugins.hasListeners = function(hook) {
return (Plugins.loadedHooks[hook] && Plugins.loadedHooks[hook].length > 0);
};
Plugins.fireHook = function(hook, args, callback) { Plugins.fireHook = function(hook, args, callback) {
hookList = Plugins.loadedHooks[hook]; hookList = Plugins.loadedHooks[hook];

@ -194,7 +194,7 @@ var db = require('./database'),
post.user_rep = userData.reputation || 0; post.user_rep = userData.reputation || 0;
post.user_postcount = userData.postcount || 0; post.user_postcount = userData.postcount || 0;
post.user_banned = parseInt(userData.banned, 10) === 1; post.user_banned = parseInt(userData.banned, 10) === 1;
post.picture = userData.picture || gravatar.url('', {}, https = nconf.get('https')); post.picture = userData.picture || gravatar.url('', {}, true);
if(meta.config.disableSignatures === undefined || parseInt(meta.config.disableSignatures, 10) === 0) { if(meta.config.disableSignatures === undefined || parseInt(meta.config.disableSignatures, 10) === 0) {
post.signature = signature; post.signature = signature;
@ -468,4 +468,28 @@ var db = require('./database'),
}); });
} }
Posts.getPidPage = function(pid, callback) {
Posts.getPostField(pid, 'tid', function(err, tid) {
if(err) {
return callback(err);
}
topics.getPids(tid, function(err, pids) {
if(err) {
return callback(err);
}
var index = pids.indexOf(pid);
if(index === -1) {
return callback(new Error('pid not found'));
}
var postsPerPage = parseInt(meta.config.postsPerPage, 10);
postsPerPage = postsPerPage ? postsPerPage : 20;
var page = Math.ceil((index + 1) / postsPerPage);
callback(null, page);
});
});
}
}(exports)); }(exports));

@ -7,13 +7,14 @@ var nconf = require('nconf'),
user = require('./../user'), user = require('./../user'),
groups = require('../groups'), groups = require('../groups'),
topics = require('./../topics'), topics = require('./../topics'),
pkg = require('./../../package.json'), pkg = require('./../../package'),
categories = require('./../categories'), categories = require('./../categories'),
meta = require('../meta'), meta = require('../meta'),
plugins = require('../plugins'), plugins = require('../plugins'),
Languages = require('../languages'), Languages = require('../languages'),
events = require('./../events'), events = require('./../events'),
utils = require('./../../public/src/utils.js'); utils = require('./../../public/src/utils'),
templates = require('./../../public/src/templates');
(function (Admin) { (function (Admin) {
Admin.isAdmin = function (req, res, next) { Admin.isAdmin = function (req, res, next) {

@ -11,10 +11,11 @@ var path = require('path'),
posts = require('../posts'), posts = require('../posts'),
categories = require('../categories'), categories = require('../categories'),
categoryTools = require('../categoryTools') categoryTools = require('../categoryTools')
meta = require('../meta'),
Plugins = require('../plugins'),
utils = require('../../public/src/utils'), utils = require('../../public/src/utils'),
translator = require('../../public/src/translator'), translator = require('../../public/src/translator'),
pkg = require('../../package.json'), pkg = require('../../package.json');
meta = require('../meta');
(function (Api) { (function (Api) {
@ -42,8 +43,10 @@ var path = require('path'),
config.useOutgoingLinksPage = parseInt(meta.config.useOutgoingLinksPage, 10) === 1; config.useOutgoingLinksPage = parseInt(meta.config.useOutgoingLinksPage, 10) === 1;
config.allowGuestPosting = parseInt(meta.config.allowGuestPosting, 10) === 1; config.allowGuestPosting = parseInt(meta.config.allowGuestPosting, 10) === 1;
config.allowFileUploads = parseInt(meta.config.allowFileUploads, 10) === 1; config.allowFileUploads = parseInt(meta.config.allowFileUploads, 10) === 1;
config.usePagination = parseInt(meta.config.usePagination, 10) === 1;
config.topicsPerPage = meta.config.topicsPerPage || 20;
config.postsPerPage = meta.config.postsPerPage || 20;
config.maximumFileSize = meta.config.maximumFileSize; config.maximumFileSize = meta.config.maximumFileSize;
config.emailSetup = !!meta.config['email:from'];
config.defaultLang = meta.config.defaultLang || 'en'; config.defaultLang = meta.config.defaultLang || 'en';
res.json(200, config); res.json(200, config);
@ -113,7 +116,8 @@ var path = require('path'),
app.get('/login', function (req, res) { app.get('/login', function (req, res) {
var data = {}, var data = {},
login_strategies = auth.get_login_strategies(), login_strategies = auth.get_login_strategies(),
num_strategies = login_strategies.length; num_strategies = login_strategies.length,
emailersPresent = Plugins.hasListeners('action:email.send');
if (num_strategies == 0) { if (num_strategies == 0) {
data = { data = {
@ -128,8 +132,8 @@ var path = require('path'),
} }
data.authentication = login_strategies; data.authentication = login_strategies;
data.token = res.locals.csrf_token; data.token = res.locals.csrf_token;
data.showResetLink = emailersPresent;
res.json(data); res.json(data);
}); });
@ -161,20 +165,40 @@ var path = require('path'),
}); });
app.get('/topic/:id/:slug?', function (req, res, next) { app.get('/topic/:id/:slug?', function (req, res, next) {
var uid = (req.user) ? req.user.uid : 0; var uid = (req.user) ? req.user.uid : 0;
var page = 1;
if(req.query && req.query.page) {
page = req.query.page;
}
if(!utils.isNumber(page) || parseInt(page, 10) < 1) {
return res.send(404);
}
var postsPerPage = parseInt(meta.config.postsPerPage ? meta.config.postsPerPage : 20, 10);
var start = (page - 1) * postsPerPage;
var end = start + postsPerPage - 1;
ThreadTools.privileges(req.params.id, uid, function(err, privileges) { ThreadTools.privileges(req.params.id, uid, function(err, privileges) {
if (privileges.read) { if (privileges.read) {
topics.getTopicWithPosts(req.params.id, uid, 0, 10, false, function (err, data) { topics.getTopicWithPosts(req.params.id, uid, start, end, false, function (err, data) {
if (!err) { if(err) {
// Send in privilege data as well return next(err);
data.privileges = privileges; }
if (parseInt(data.deleted, 10) === 1 && parseInt(data.expose_tools, 10) === 0) { if(page > data.pageCount) {
return res.json(404, {}); return res.send(404);
} }
res.json(data); data.currentPage = page;
} else next(); data.privileges = privileges;
if (parseInt(data.deleted, 10) === 1 && parseInt(data.expose_tools, 10) === 0) {
return res.json(404, {});
}
res.json(data);
}); });
} else { } else {
res.send(403); res.send(403);
@ -184,16 +208,28 @@ var path = require('path'),
app.get('/category/:id/:slug?', function (req, res, next) { app.get('/category/:id/:slug?', function (req, res, next) {
var uid = (req.user) ? req.user.uid : 0; var uid = (req.user) ? req.user.uid : 0;
var page = 1;
if(req.query && req.query.page) {
page = req.query.page;
}
if(!utils.isNumber(page) || parseInt(page, 10) < 1) {
return res.send(404);
}
var topicsPerPage = parseInt(meta.config.topicsPerPage ? meta.config.topicsPerPage : 20, 10);
var start = (page - 1) * topicsPerPage;
var end = start + topicsPerPage - 1;
// Category Whitelisting // Category Whitelisting
categoryTools.privileges(req.params.id, uid, function(err, privileges) { categoryTools.privileges(req.params.id, uid, function(err, privileges) {
if (!err && privileges.read) { if (!err && privileges.read) {
categories.getCategoryById(req.params.id, uid, function (err, data) { categories.getCategoryById(req.params.id, start, end, uid, function (err, data) {
if(err) { if(err) {
return next(err); return next(err);
} }
// Add privilege data to template data data.currentPage = page;
data.privileges = privileges; data.privileges = privileges;
if (data && parseInt(data.disabled, 10) === 0) { if (data && parseInt(data.disabled, 10) === 0) {

@ -74,7 +74,13 @@ var DebugRoute = function(app) {
}); });
app.get('/test', function(req, res) { app.get('/test', function(req, res) {
res.send();
/*topics.getTopicPosts2(2, 0, 10, 5, function(err, data) {
res.json(data);
})*/
topics.getTopicWithPosts(2, 1, 0, -1, true, function (err, topicData) {
res.json(topicData);
});
}); });
}); });

@ -8,6 +8,7 @@ var fs = require('fs'),
posts = require('./../posts'), posts = require('./../posts'),
postTools = require('../postTools'), postTools = require('../postTools'),
utils = require('./../../public/src/utils'), utils = require('./../../public/src/utils'),
templates = require('./../../public/src/templates'),
meta = require('./../meta'), meta = require('./../meta'),
db = require('./../database'); db = require('./../database');

@ -1,4 +1,5 @@
var categories = require('../categories'), var categories = require('../categories'),
meta = require('./../meta'),
SocketCategories = {}; SocketCategories = {};
@ -15,8 +16,10 @@ SocketCategories.loadMore = function(socket, data, callback) {
return callback(new Error('invalid data')); return callback(new Error('invalid data'));
} }
var topicsPerPage = parseInt(meta.config.topicsPerPage, 10) || 20;
var start = data.after, var start = data.after,
end = start + 9; end = start + topicsPerPage - 1;
categories.getCategoryTopics(data.cid, start, end, socket.uid, function(err, topics) { categories.getCategoryTopics(data.cid, start, end, socket.uid, function(err, topics) {
callback(err, { callback(err, {
@ -25,4 +28,8 @@ SocketCategories.loadMore = function(socket, data, callback) {
}); });
}; };
SocketCategories.getPageCount = function(socket, cid, callback) {
categories.getPageCount(cid, callback);
}
module.exports = SocketCategories; module.exports = SocketCategories;

@ -30,9 +30,9 @@ Sockets.userSockets = {};
Sockets.rooms = {}; Sockets.rooms = {};
Sockets.init = function() { Sockets.init = function(server) {
io = socketioWildcard(SocketIO).listen(global.server, { io = socketioWildcard(SocketIO).listen(server, {
log: false, log: false,
transports: ['websocket', 'xhr-polling', 'jsonp-polling', 'flashsocket'], transports: ['websocket', 'xhr-polling', 'jsonp-polling', 'flashsocket'],
'browser client minification': true 'browser client minification': true

@ -58,7 +58,7 @@ SocketMeta.updateHeader = function(socket, data, callback) {
email: '', email: '',
picture: gravatar.url('', { picture: gravatar.url('', {
s: '24' s: '24'
}, nconf.get('https')), }, true),
config: { config: {
allowGuestSearching: meta.config.allowGuestSearching allowGuestSearching: meta.config.allowGuestSearching
} }

@ -93,7 +93,17 @@ SocketPosts.uploadFile = function(socket, data, callback) {
}; };
SocketPosts.getRawPost = function(socket, pid, callback) { SocketPosts.getRawPost = function(socket, pid, callback) {
posts.getPostField(pid, 'content', callback); posts.getPostFields(pid, ['content', 'deleted'], function(err, data) {
if(err) {
return callback(err);
}
if(data.deleted === '1') {
return callback(new Error('This post no longer exists'));
}
callback(null, data.content);
});
}; };
SocketPosts.edit = function(socket, data, callback) { SocketPosts.edit = function(socket, data, callback) {
@ -194,4 +204,8 @@ SocketPosts.getFavouritedUsers = function(socket, pid, callback) {
}); });
}; };
SocketPosts.getPidPage = function(socket, pid, callback) {
posts.getPidPage(pid, callback);
}
module.exports = SocketPosts; module.exports = SocketPosts;

@ -1,6 +1,9 @@
var topics = require('../topics'), var topics = require('../topics'),
threadTools = require('../threadTools'), threadTools = require('../threadTools'),
index = require('./index'), index = require('./index'),
async = require('async'),
SocketTopics = {}; SocketTopics = {};
SocketTopics.post = function(socket, data, callback) { SocketTopics.post = function(socket, data, callback) {
@ -230,11 +233,21 @@ SocketTopics.loadMore = function(socket, data, callback) {
return callback(new Error('invalid data')); return callback(new Error('invalid data'));
} }
var start = data.after, var postsPerPage = parseInt(meta.config.postsPerPage, 10);
end = start + 9; postsPerPage = postsPerPage ? postsPerPage : 20;
topics.getTopicPosts(data.tid, start, end, socket.uid, function(err, posts) { var start = data.after,
callback(err, {posts: posts}); end = start + postsPerPage - 1;
async.parallel({
posts: function(next) {
topics.getTopicPosts(data.tid, start, end, socket.uid, next);
},
privileges: function(next) {
threadTools.privileges(data.tid, socket.uid, next);
}
}, function(err, results) {
callback(err, results);
}); });
}; };
@ -256,4 +269,8 @@ SocketTopics.loadMoreUnreadTopics = function(socket, data, callback) {
topics.getUnreadTopics(socket.uid, start, end, callback); topics.getUnreadTopics(socket.uid, start, end, callback);
}; };
SocketTopics.getPageCount = function(socket, tid, callback) {
topics.getPageCount(tid, callback);
}
module.exports = SocketTopics; module.exports = SocketTopics;

@ -187,9 +187,20 @@ var winston = require('winston'),
ThreadTools.move = function(tid, cid, callback) { ThreadTools.move = function(tid, cid, callback) {
topics.getTopicFields(tid, ['cid', 'lastposttime'], function(err, topicData) { topics.getTopicFields(tid, ['cid', 'lastposttime'], function(err, topicData) {
if(err) {
return callback(err);
}
var oldCid = topicData.cid; var oldCid = topicData.cid;
if(!oldCid) {
return callback(new Error('invalid-topic'));
}
db.sortedSetRemove('categories:' + oldCid + ':tid', tid, function(err, result) { db.sortedSetRemove('categories:' + oldCid + ':tid', tid, function(err, result) {
if(err) {
return callback(err);
}
db.sortedSetAdd('categories:' + cid + ':tid', topicData.lastposttime, tid, function(err, result) { db.sortedSetAdd('categories:' + cid + ':tid', topicData.lastposttime, tid, function(err, result) {
if(err) { if(err) {

@ -330,10 +330,6 @@ var async = require('async'),
postData[i].index = start + i; postData[i].index = start + i;
} }
postData = postData.filter(function(post) {
return parseInt(current_user, 10) !== 0 || parseInt(post.deleted, 10) === 0;
});
pids = postData.map(function(post) { pids = postData.map(function(post) {
return post.pid; return post.pid;
}); });
@ -386,6 +382,9 @@ var async = require('async'),
postData[i].favourited = fav_data[pid]; postData[i].favourited = fav_data[pid];
postData[i].display_moderator_tools = (current_user != 0) && privileges[pid].editable; postData[i].display_moderator_tools = (current_user != 0) && privileges[pid].editable;
postData[i].display_move_tools = privileges[pid].move ? '' : 'hidden'; postData[i].display_move_tools = privileges[pid].move ? '' : 'hidden';
if(parseInt(postData[i].deleted, 10) === 1 && !privileges[pid].view_deleted) {
postData[i].content = 'This post is deleted!';
}
} }
callback(null, postData); callback(null, postData);
@ -393,6 +392,19 @@ var async = require('async'),
}); });
} }
Topics.getPageCount = function(tid, callback) {
db.sortedSetCard('tid:' + tid + ':posts', function(err, postCount) {
if(err) {
return callback(err);
}
var postsPerPage = parseInt(meta.config.postsPerPage, 10);
postsPerPage = postsPerPage ? postsPerPage : 20;
callback(null, Math.ceil(parseInt(postCount, 10) / postsPerPage));
});
}
Topics.getCategoryData = function(tid, callback) { Topics.getCategoryData = function(tid, callback) {
Topics.getTopicField(tid, 'cid', function(err, cid) { Topics.getTopicField(tid, 'cid', function(err, cid) {
if(err) { if(err) {
@ -713,14 +725,14 @@ var async = require('async'),
topicData.unreplied = parseInt(topicData.postcount, 10) === 1; topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
topicData.username = topicInfo.username || 'anonymous'; topicData.username = topicInfo.username || 'anonymous';
topicData.userslug = topicInfo.userslug || ''; topicData.userslug = topicInfo.userslug || '';
topicData.picture = topicInfo.picture || gravatar.url('', {}, https = nconf.get('https')); topicData.picture = topicInfo.picture || gravatar.url('', {}, true);
topicData.categoryIcon = topicInfo.categoryData.icon; topicData.categoryIcon = topicInfo.categoryData.icon;
topicData.categoryName = topicInfo.categoryData.name; topicData.categoryName = topicInfo.categoryData.name;
topicData.categorySlug = topicInfo.categoryData.slug; topicData.categorySlug = topicInfo.categoryData.slug;
topicData.badgeclass = (topicInfo.hasread && parseInt(current_user, 10) !== 0) ? '' : 'badge-important'; topicData.badgeclass = (topicInfo.hasread && parseInt(current_user, 10) !== 0) ? '' : 'badge-important';
topicData.teaser_username = topicInfo.teaserInfo.username || ''; topicData.teaser_username = topicInfo.teaserInfo.username || '';
topicData.teaser_userslug = topicInfo.teaserInfo.userslug || ''; topicData.teaser_userslug = topicInfo.teaserInfo.userslug || '';
topicData.teaser_userpicture = topicInfo.teaserInfo.picture || gravatar.url('', {}, https = nconf.get('https')); topicData.teaser_userpicture = topicInfo.teaserInfo.picture || gravatar.url('', {}, true);
topicData.teaser_pid = topicInfo.teaserInfo.pid; topicData.teaser_pid = topicInfo.teaserInfo.pid;
topicData.teaser_timestamp = utils.toISOString(topicInfo.teaserInfo.timestamp); topicData.teaser_timestamp = utils.toISOString(topicInfo.teaserInfo.timestamp);
@ -755,21 +767,25 @@ var async = require('async'),
function getTopicData(next) { function getTopicData(next) {
Topics.getTopicData(tid, next); Topics.getTopicData(tid, next);
}; }
function getTopicPosts(next) { function getTopicPosts(next) {
Topics.getTopicPosts(tid, start, end, current_user, next); Topics.getTopicPosts(tid, start, end, current_user, next);
}; }
function getPrivileges(next) { function getPrivileges(next) {
threadTools.privileges(tid, current_user, next); threadTools.privileges(tid, current_user, next);
}; }
function getCategoryData(next) { function getCategoryData(next) {
Topics.getCategoryData(tid, next); Topics.getCategoryData(tid, next);
}; }
async.parallel([getTopicData, getTopicPosts, getPrivileges, getCategoryData], function(err, results) { function getPageCount(next) {
Topics.getPageCount(tid, next);
}
async.parallel([getTopicData, getTopicPosts, getPrivileges, getCategoryData, getPageCount], function(err, results) {
if (err) { if (err) {
winston.error('[Topics.getTopicWithPosts] Could not retrieve topic data: ', err.message); winston.error('[Topics.getTopicWithPosts] Could not retrieve topic data: ', err.message);
return callback(err, null); return callback(err, null);
@ -778,7 +794,11 @@ var async = require('async'),
var topicData = results[0], var topicData = results[0],
topicPosts = results[1], topicPosts = results[1],
privileges = results[2], privileges = results[2],
categoryData = results[3]; categoryData = results[3],
pageCount = results[4];
var postsPerPage = parseInt(meta.config.postsPerPage, 10);
postsPerPage = postsPerPage ? postsPerPage : 20;
callback(null, { callback(null, {
'topic_name': topicData.title, 'topic_name': topicData.title,
@ -791,6 +811,7 @@ var async = require('async'),
'slug': topicData.slug, 'slug': topicData.slug,
'postcount': topicData.postcount, 'postcount': topicData.postcount,
'viewcount': topicData.viewcount, 'viewcount': topicData.viewcount,
'pageCount': pageCount,
'unreplied': parseInt(topicData.postcount, 10) > 1, 'unreplied': parseInt(topicData.postcount, 10) > 1,
'topic_id': tid, 'topic_id': tid,
'expose_tools': privileges.editable ? 1 : 0, 'expose_tools': privileges.editable ? 1 : 0,
@ -1018,7 +1039,7 @@ var async = require('async'),
pid: postData.pid, pid: postData.pid,
username: userData.username || 'anonymous', username: userData.username || 'anonymous',
userslug: userData.userslug, userslug: userData.userslug,
picture: userData.picture || gravatar.url('', {}, https = nconf.get('https')), picture: userData.picture || gravatar.url('', {}, true),
timestamp: postData.timestamp timestamp: postData.timestamp
}); });
}); });

@ -17,7 +17,7 @@ var db = require('./database'),
Upgrade.check = function(callback) { Upgrade.check = function(callback) {
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
var latestSchema = new Date(2014, 0, 23, 16, 5).getTime(); var latestSchema = new Date(2014, 0, 25, 0, 0).getTime();
db.get('schemaDate', function(err, value) { db.get('schemaDate', function(err, value) {
if (parseInt(value, 10) >= latestSchema) { if (parseInt(value, 10) >= latestSchema) {
@ -318,11 +318,46 @@ Upgrade.upgrade = function(callback) {
winston.info('[2014/1/23] Updating Administrators Group'); winston.info('[2014/1/23] Updating Administrators Group');
next(); next();
}); });
}) });
} else { } else {
winston.info('[2014/1/23] Updating Administrators Group -- skipped'); winston.info('[2014/1/23] Updating Administrators Group -- skipped');
next(); next();
} }
},
function(next) {
thisSchemaDate = new Date(2014, 0, 25, 0, 0).getTime();
if (schemaDate < thisSchemaDate) {
updatesMade = true;
db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) {
if(err) {
return next(err);
}
if(!uids || !uids.length) {
winston.info('[2014/1/25] Updating User Gravatars to HTTPS -- skipped');
return next();
}
var gravatar = require('gravatar');
function updateGravatar(uid, next) {
User.getUserFields(uid, ['email', 'picture', 'gravatarpicture'], function(err, userData) {
var gravatarPicture = User.createGravatarURLFromEmail(userData.email);
if(userData.picture === userData.gravatarpicture) {
User.setUserField(uid, 'picture', gravatarPicture);
}
User.setUserField(uid, 'gravatarpicture', gravatarPicture, next);
});
}
winston.info('[2014/1/25] Updating User Gravatars to HTTPS');
async.each(uids, updateGravatar, next);
});
} else {
winston.info('[2014/1/25] Updating User Gravatars to HTTPS -- skipped');
next();
}
} }
// Add new schema updates here // Add new schema updates here
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 17!!! // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 17!!!

@ -442,15 +442,14 @@ var bcrypt = require('bcrypt'),
size: '128', size: '128',
default: 'identicon', default: 'identicon',
rating: 'pg' rating: 'pg'
}, };
https = nconf.get('https');
if (!email) { if (!email) {
email = ''; email = '';
options.forcedefault = 'y'; options.forcedefault = 'y';
} }
return gravatar.url(email, options, https); return gravatar.url(email, options, true);
}; };
User.hashPassword = function(password, callback) { User.hashPassword = function(password, callback) {
@ -490,13 +489,13 @@ var bcrypt = require('bcrypt'),
User.search = function(query, callback) { User.search = function(query, callback) {
if (!query || query.length === 0) { if (!query || query.length === 0) {
return callback(null, []); return callback(null, {timing:0, users:[]});
} }
var start = process.hrtime(); var start = process.hrtime();
db.getObject('username:uid', function(err, usernamesHash) { db.getObject('username:uid', function(err, usernamesHash) {
if (err) { if (err) {
return callback(null, []); return callback(null, {timing: 0, users:[]});
} }
query = query.toLowerCase(); query = query.toLowerCase();
@ -516,7 +515,9 @@ var bcrypt = require('bcrypt'),
}); });
User.getDataForUsers(results, function(userdata) { User.getDataForUsers(results, function(userdata) {
callback(null, userdata); var diff = process.hrtime(start);
var timing = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(1);
callback(null, {timing: timing, users: userdata});
}); });
}); });
}; };
@ -885,6 +886,10 @@ var bcrypt = require('bcrypt'),
User.email = { User.email = {
verify: function(uid, email) { verify: function(uid, email) {
if (!plugins.hasListeners('action:email.send')) {
return;
}
var confirm_code = utils.generateUUID(), var confirm_code = utils.generateUUID(),
confirm_link = nconf.get('url') + '/confirm/' + confirm_code; confirm_link = nconf.get('url') + '/confirm/' + confirm_code;

@ -9,6 +9,7 @@ var path = require('path'),
validator = require('validator'), validator = require('validator'),
async = require('async'), async = require('async'),
S = require('string'), S = require('string'),
qs = require('querystring'),
pkg = require('../package.json'), pkg = require('../package.json'),
@ -28,7 +29,9 @@ var path = require('path'),
meta = require('./meta'), meta = require('./meta'),
feed = require('./feed'), feed = require('./feed'),
plugins = require('./plugins'), plugins = require('./plugins'),
logger = require('./logger'); logger = require('./logger'),
templates = require('./../public/src/templates'),
translator = require('./../public/src/translator');
if(nconf.get('ssl')) { if(nconf.get('ssl')) {
server = require('https').createServer({ server = require('https').createServer({
@ -39,13 +42,12 @@ if(nconf.get('ssl')) {
server = require('http').createServer(WebServer); server = require('http').createServer(WebServer);
} }
module.exports.server = server;
(function (app) { (function (app) {
"use strict"; "use strict";
var templates = null, var clientScripts;
clientScripts;
plugins.ready(function() { plugins.ready(function() {
// Minify client-side libraries // Minify client-side libraries
@ -60,9 +62,6 @@ if(nconf.get('ssl')) {
}); });
}); });
server.app = app;
/** /**
* `options` object requires: req, res * `options` object requires: req, res
* accepts: metaTags, linkTags * accepts: metaTags, linkTags
@ -200,7 +199,6 @@ if(nconf.get('ssl')) {
// Local vars, other assorted setup // Local vars, other assorted setup
app.use(function (req, res, next) { app.use(function (req, res, next) {
nconf.set('https', req.secure);
res.locals.csrf_token = req.session._csrf; res.locals.csrf_token = req.session._csrf;
// Disable framing // Disable framing
@ -376,8 +374,6 @@ if(nconf.get('ssl')) {
}); });
module.exports.init = function () { module.exports.init = function () {
templates = global.templates;
// translate all static templates served by webserver here. ex. footer, logout // translate all static templates served by webserver here. ex. footer, logout
plugins.fireHook('filter:footer.build', '', function(err, appendHTML) { plugins.fireHook('filter:footer.build', '', function(err, appendHTML) {
var footer = templates.footer.parse({ var footer = templates.footer.parse({
@ -649,6 +645,10 @@ if(nconf.get('ssl')) {
} }
var topic_url = tid + (req.params.slug ? '/' + req.params.slug : ''); var topic_url = tid + (req.params.slug ? '/' + req.params.slug : '');
var queryString = qs.stringify(req.query);
if(queryString.length) {
topic_url += '?' + queryString;
}
res.send( res.send(
data.header + data.header +
@ -706,7 +706,7 @@ if(nconf.get('ssl')) {
}); });
}, },
function (next) { function (next) {
categories.getCategoryById(cid, 0, function (err, categoryData) { categories.getCategoryById(cid, 0, -1, 0, function (err, categoryData) {
if (categoryData) { if (categoryData) {
if (parseInt(categoryData.disabled, 10) === 1) { if (parseInt(categoryData.disabled, 10) === 1) {
@ -763,6 +763,10 @@ if(nconf.get('ssl')) {
} }
var category_url = cid + (req.params.slug ? '/' + req.params.slug : ''); var category_url = cid + (req.params.slug ? '/' + req.params.slug : '');
var queryString = qs.stringify(req.query);
if(queryString.length) {
category_url += '?' + queryString;
}
res.send( res.send(
data.header + data.header +
@ -932,6 +936,3 @@ if(nconf.get('ssl')) {
}); });
}(WebServer)); }(WebServer));
global.server = server;

@ -31,7 +31,7 @@ describe('Categories', function() {
describe('.getCategoryById', function() { describe('.getCategoryById', function() {
it('should retrieve a newly created category by its ID', function(done) { it('should retrieve a newly created category by its ID', function(done) {
Categories.getCategoryById(categoryObj.cid, 0, function(err, categoryData) { Categories.getCategoryById(categoryObj.cid, 0, -1, 0, function(err, categoryData) {
assert(categoryData); assert(categoryData);
assert.equal(categoryObj.name, categoryData.category_name); assert.equal(categoryObj.name, categoryData.category_name);
assert.equal(categoryObj.description, categoryData.category_description); assert.equal(categoryObj.description, categoryData.category_description);

Loading…
Cancel
Save