From c27be9db5a615dadde55941b98b27537b9e3bffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 27 Oct 2018 06:26:50 -0400 Subject: [PATCH] Recent refactor (#6879) * wip * fix inf scroll * remove duplicated code * remove dupe code in /unread * use topicList * update tag page to use topicList * fix tests * combine ifs * remove more dupe code * disable timeout --- public/src/client/category.js | 118 +--------- public/src/client/infinitescroll.js | 2 +- public/src/client/popular.js | 30 +-- public/src/client/recent.js | 260 +--------------------- public/src/client/tag.js | 33 +-- public/src/client/top.js | 45 +--- public/src/client/unread.js | 55 +---- public/src/modules/topicList.js | 290 +++++++++++++++++++++++++ src/categories/topics.js | 11 +- src/controllers/errors.js | 2 +- src/controllers/recent.js | 1 - src/controllers/tags.js | 10 +- src/database/mongo.js | 2 +- src/meta/js.js | 1 + src/socket.io/topics/infinitescroll.js | 50 ++--- src/topics/index.js | 3 +- src/topics/posts.js | 4 +- src/topics/sorted.js | 12 +- test/socket.io.js | 1 + test/topics.js | 8 +- 20 files changed, 365 insertions(+), 573 deletions(-) create mode 100644 public/src/modules/topicList.js diff --git a/public/src/client/category.js b/public/src/client/category.js index bea2ca0540..9fdd4dcd5b 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -1,18 +1,13 @@ 'use strict'; - define('forum/category', [ 'forum/infinitescroll', 'share', 'navigator', 'forum/category/tools', - 'forum/recent', + 'topicList', 'sort', - 'components', - 'translator', - 'topicSelect', - 'handleBack', -], function (infinitescroll, share, navigator, categoryTools, recent, sort, components, translator, topicSelect, handleBack) { +], function (infinitescroll, share, navigator, categoryTools, topicList, sort) { var Category = {}; $(window).on('action:ajaxify.start', function (ev, data) { @@ -25,7 +20,7 @@ define('forum/category', [ function removeListeners() { categoryTools.removeListeners(); - recent.removeListeners(); + topicList.removeListeners(); } Category.init = function () { @@ -36,15 +31,16 @@ define('forum/category', [ share.addShareHandlers(ajaxify.data.name); categoryTools.init(cid); - recent.watchForNewPosts(); - sort.handleSort('categoryTopicSort', 'user.setCategorySort', 'category/' + ajaxify.data.slug); + topicList.init('category', loadTopicsAfter); - enableInfiniteLoadingOrPagination(); + sort.handleSort('categoryTopicSort', 'user.setCategorySort', 'category/' + ajaxify.data.slug); - handleBack.init(function (after, cb) { - loadTopicsAfter(after, 1, cb); - }); + if (!config.usePagination) { + navigator.init('[component="category/topic"]', ajaxify.data.topic_count, Category.toTop, Category.toBottom, Category.navigatorCallback); + } else { + navigator.disable(); + } handleScrollToTopicIndex(); @@ -104,32 +100,8 @@ define('forum/category', [ return bottomIndex; }; - function enableInfiniteLoadingOrPagination() { - if (!config.usePagination) { - navigator.init('[component="category/topic"]', ajaxify.data.topic_count, Category.toTop, Category.toBottom, Category.navigatorCallback); - infinitescroll.init($('[component="category"]'), Category.loadMoreTopics); - } else { - navigator.disable(); - } - } - - Category.loadMoreTopics = function (direction) { - if (!$('[component="category"]').length || !$('[component="category"]').children().length) { - return; - } - - var topics = $('[component="category/topic"]'); - var afterEl = direction > 0 ? topics.last() : topics.first(); - var after = (parseInt(afterEl.attr('data-index'), 10) || 0) + (direction > 0 ? 1 : 0); - - loadTopicsAfter(after, direction); - }; - function loadTopicsAfter(after, direction, callback) { callback = callback || function () {}; - if (!utils.isNumber(after) || (after === 0 && components.get('category/topic', 'index', 0).length)) { - return callback(); - } $(window).trigger('action:category.loading'); var params = utils.params(); @@ -140,78 +112,10 @@ define('forum/category', [ query: params, categoryTopicSort: config.categoryTopicSort, }, function (data, done) { - if (data.topics && data.topics.length) { - Category.onTopicsLoaded(data, direction, done); - } else { - done(); - } - $(window).trigger('action:category.loaded'); - callback(); + callback(data, done); }); } - - Category.onTopicsLoaded = function (data, direction, callback) { - if (!data || !data.topics.length) { - return callback(); - } - - function removeAlreadyAddedTopics(topics) { - return topics.filter(function (topic) { - return components.get('category/topic', 'tid', topic.tid).length === 0; - }); - } - - data.topics = removeAlreadyAddedTopics(data.topics); - if (!data.topics.length) { - return callback(); - } - - data.showSelect = data.privileges.editable; - - var after; - var before; - var topics = $('[component="category/topic"]'); - - if (direction > 0 && topics.length) { - after = topics.last(); - } else if (direction < 0 && topics.length) { - before = topics.first(); - } - - app.parseAndTranslate('category', 'topics', data, function (html) { - $('[component="category"]').removeClass('hidden'); - $('.category-sidebar').removeClass('hidden'); - - $('#category-no-topics').remove(); - - if (after) { - html.insertAfter(after); - } else if (before) { - var height = $(document).height(); - var scrollTop = $(window).scrollTop(); - - html.insertBefore(before); - - $(window).scrollTop(scrollTop + ($(document).height() - height)); - } else { - $('[component="category"]').append(html); - } - - if (!topicSelect.getSelectedTids().length) { - infinitescroll.removeExtra($('[component="category/topic"]'), direction, config.topicsPerPage * 3); - } - - html.find('.timeago').timeago(); - app.createUserTooltips(); - utils.makeNumbersHumanReadable(html.find('.human-readable-number')); - - $(window).trigger('action:topics.loaded', { topics: data.topics }); - - callback(); - }); - }; - return Category; }); diff --git a/public/src/client/infinitescroll.js b/public/src/client/infinitescroll.js index e2b52548aa..1a7171dd0c 100644 --- a/public/src/client/infinitescroll.js +++ b/public/src/client/infinitescroll.js @@ -16,7 +16,7 @@ define('forum/infinitescroll', function () { callback = cb; container = el || $('body'); } - + previousScrollTop = $(window).scrollTop(); $(window).off('scroll', onScroll).on('scroll', onScroll); }; diff --git a/public/src/client/popular.js b/public/src/client/popular.js index f1ab3a5332..df9a656411 100644 --- a/public/src/client/popular.js +++ b/public/src/client/popular.js @@ -1,40 +1,14 @@ 'use strict'; -define('forum/popular', ['forum/recent', 'components', 'forum/infinitescroll'], function (recent, components, infinitescroll) { +define('forum/popular', ['topicList'], function (topicList) { var Popular = {}; Popular.init = function () { app.enterRoom('popular_topics'); - recent.handleCategorySelection(); - - if (!config.usePagination) { - infinitescroll.init(loadMoreTopics); - } + topicList.init('popular'); }; - function loadMoreTopics(direction) { - if (direction < 0 || !$('[component="category"]').length) { - return; - } - var query = utils.params(); - infinitescroll.loadMore('topics.loadMorePopularTopics', { - after: $('[component="category"]').attr('data-nextstart'), - count: config.topicsPerPage, - cid: query.cid, - query: query, - term: ajaxify.data.selectedTerm.term, - filter: ajaxify.data.selectedFilter.filter, - }, function (data, done) { - if (data.topics && data.topics.length) { - recent.onTopicsLoaded('popular', data.topics, false, direction, done); - $('[component="category"]').attr('data-nextstart', data.nextStart); - } else { - done(); - } - }); - } - return Popular; }); diff --git a/public/src/client/recent.js b/public/src/client/recent.js index 0f525a888c..35f886c582 100644 --- a/public/src/client/recent.js +++ b/public/src/client/recent.js @@ -1,268 +1,12 @@ 'use strict'; -define('forum/recent', ['forum/infinitescroll', 'components', 'handleBack'], function (infinitescroll, components, handleBack) { +define('forum/recent', ['topicList'], function (topicList) { var Recent = {}; - var newTopicCount = 0; - var newPostCount = 0; - - $(window).on('action:ajaxify.start', function (ev, data) { - if (ajaxify.currentPage !== data.url) { - Recent.removeListeners(); - } - }); - Recent.init = function () { app.enterRoom('recent_topics'); - Recent.watchForNewPosts(); - - Recent.handleCategorySelection(); - - handleBack.init(function (after, cb) { - loadTopicsAfter(after, 1, cb); - }); - - $('#new-topics-alert').on('click', function () { - $(this).addClass('hide'); - }); - - if (!config.usePagination) { - infinitescroll.init(Recent.loadMoreTopics); - } - - $(window).trigger('action:topics.loaded', { topics: ajaxify.data.topics }); - }; - - Recent.watchForNewPosts = function () { - newPostCount = 0; - newTopicCount = 0; - Recent.removeListeners(); - socket.on('event:new_topic', onNewTopic); - socket.on('event:new_post', onNewPost); - }; - - function onNewTopic(data) { - if (ajaxify.data.selectedCids && ajaxify.data.selectedCids.indexOf(parseInt(data.cid, 10)) === -1) { - return; - } - - if (ajaxify.data.selectedFilter && ajaxify.data.selectedFilter.filter === 'watched') { - return; - } - - if (ajaxify.data.template.category && parseInt(ajaxify.data.cid, 10) !== parseInt(data.cid, 10)) { - return; - } - - newTopicCount += 1; - Recent.updateAlertText(); - } - - function onNewPost(data) { - function showAlert() { - newPostCount += 1; - Recent.updateAlertText(); - } - - var post = data.posts[0]; - if (!post || !post.topic) { - return; - } - if (parseInt(post.topic.mainPid, 10) === parseInt(post.pid, 10)) { - return; - } - - if (ajaxify.data.selectedCids && ajaxify.data.selectedCids.indexOf(parseInt(post.topic.cid, 10)) === -1) { - return; - } - - if (ajaxify.data.selectedFilter && ajaxify.data.selectedFilter.filter === 'new') { - return; - } - - if (ajaxify.data.template.category && parseInt(ajaxify.data.cid, 10) !== parseInt(post.topic.cid, 10)) { - return; - } - - if (ajaxify.data.selectedFilter && ajaxify.data.selectedFilter.filter === 'watched') { - socket.emit('topics.isFollowed', post.tid, function (err, isFollowed) { - if (err) { - app.alertError(err.message); - } - if (isFollowed) { - showAlert(); - } - }); - return; - } - - showAlert(); - } - - Recent.handleCategorySelection = function () { - function getSelectedCids() { - var cids = []; - $('[component="category/list"] [data-cid]').each(function (index, el) { - if ($(el).find('i.fa-check').length) { - cids.push(parseInt($(el).attr('data-cid'), 10)); - } - }); - cids.sort(function (a, b) { - return a - b; - }); - return cids; - } - - $('[component="category/dropdown"]').on('hidden.bs.dropdown', function () { - var cids = getSelectedCids(); - var changed = ajaxify.data.selectedCids.length !== cids.length; - ajaxify.data.selectedCids.forEach(function (cid, index) { - if (cid !== cids[index]) { - changed = true; - } - }); - - if (changed) { - var url = window.location.pathname; - var currentParams = utils.params(); - if (cids.length) { - currentParams.cid = cids; - url += '?' + decodeURIComponent($.param(currentParams)); - } - ajaxify.go(url); - } - }); - - $('[component="category/list"]').on('click', '[data-cid]', function (ev) { - function selectChildren(parentCid, flag) { - $('[component="category/list"] [data-parent-cid="' + parentCid + '"] [component="category/select/icon"]').toggleClass('fa-check', flag); - $('[component="category/list"] [data-parent-cid="' + parentCid + '"]').each(function (index, el) { - selectChildren($(el).attr('data-cid'), flag); - }); - } - var categoryEl = $(this); - var cid = $(this).attr('data-cid'); - if (ev.ctrlKey) { - selectChildren(cid, !categoryEl.find('[component="category/select/icon"]').hasClass('fa-check')); - } - categoryEl.find('[component="category/select/icon"]').toggleClass('fa-check'); - $('[component="category/list"] li').first().find('i').toggleClass('fa-check', !getSelectedCids().length); - return false; - }); - }; - - Recent.removeListeners = function () { - socket.removeListener('event:new_topic', onNewTopic); - socket.removeListener('event:new_post', onNewPost); - }; - - Recent.updateAlertText = function () { - var text = ''; - - if (newTopicCount === 0) { - if (newPostCount === 1) { - text = '[[recent:there-is-a-new-post]]'; - } else if (newPostCount > 1) { - text = '[[recent:there-are-new-posts, ' + newPostCount + ']]'; - } - } else if (newTopicCount === 1) { - if (newPostCount === 0) { - text = '[[recent:there-is-a-new-topic]]'; - } else if (newPostCount === 1) { - text = '[[recent:there-is-a-new-topic-and-a-new-post]]'; - } else if (newPostCount > 1) { - text = '[[recent:there-is-a-new-topic-and-new-posts, ' + newPostCount + ']]'; - } - } else if (newTopicCount > 1) { - if (newPostCount === 0) { - text = '[[recent:there-are-new-topics, ' + newTopicCount + ']]'; - } else if (newPostCount === 1) { - text = '[[recent:there-are-new-topics-and-a-new-post, ' + newTopicCount + ']]'; - } else if (newPostCount > 1) { - text = '[[recent:there-are-new-topics-and-new-posts, ' + newTopicCount + ', ' + newPostCount + ']]'; - } - } - - text += ' [[recent:click-here-to-reload]]'; - - $('#new-topics-alert').translateText(text).removeClass('hide').fadeIn('slow'); - $('#category-no-topics').addClass('hide'); - }; - - Recent.loadMoreTopics = function (direction) { - if (!$('[component="category"]').length) { - return; - } - var topics = $('[component="category/topic"]'); - var afterEl = direction > 0 ? topics.last() : topics.first(); - var after = (parseInt(afterEl.attr('data-index'), 10) || 0) + (direction > 0 ? 1 : 0); - loadTopicsAfter(after, direction); - }; - - function loadTopicsAfter(after, direction, callback) { - callback = callback || function () {}; - var query = utils.params(); - infinitescroll.loadMore('topics.loadMoreRecentTopics', { - after: after, - direction: direction, - count: config.topicsPerPage, - cid: query.cid, - query: query, - filter: ajaxify.data.selectedFilter.filter, - set: $('[component="category"]').attr('data-set') ? $('[component="category"]').attr('data-set') : 'topics:recent', - }, function (data, done) { - if (data.topics && data.topics.length) { - Recent.onTopicsLoaded('recent', data.topics, false, direction, done); - } else { - done(); - } - $('[component="category"]').attr('data-nextstart', data.nextStart); - callback(); - }); - } - - Recent.onTopicsLoaded = function (templateName, topics, showSelect, direction, callback) { - topics = topics.filter(function (topic) { - return !components.get('category/topic', 'tid', topic.tid).length; - }); - - if (!topics.length) { - return callback(); - } - - var after; - var before; - var topicsList = $('[component="category/topic"]'); - - if (direction > 0 && topics.length) { - after = topicsList.last(); - } else if (direction < 0 && topics.length) { - before = topicsList.first(); - } - - app.parseAndTranslate(templateName, 'topics', { topics: topics, showSelect: showSelect }, function (html) { - $('#category-no-topics').remove(); - - if (after && after.length) { - html.insertAfter(after); - } else if (before && before.length) { - var height = $(document).height(); - var scrollTop = $(window).scrollTop(); - - html.insertBefore(before); - - $(window).scrollTop(scrollTop + ($(document).height() - height)); - } else { - $('[component="category"]').append(html); - } - - html.find('.timeago').timeago(); - app.createUserTooltips(); - utils.makeNumbersHumanReadable(html.find('.human-readable-number')); - $(window).trigger('action:topics.loaded', { topics: topics }); - callback(); - }); + topicList.init('recent'); }; return Recent; diff --git a/public/src/client/tag.js b/public/src/client/tag.js index 53867fd5b7..a23690ba68 100644 --- a/public/src/client/tag.js +++ b/public/src/client/tag.js @@ -1,42 +1,21 @@ 'use strict'; -define('forum/tag', ['forum/recent', 'forum/infinitescroll'], function (recent, infinitescroll) { +define('forum/tag', ['topicList', 'forum/infinitescroll'], function (topicList, infinitescroll) { var Tag = {}; Tag.init = function () { app.enterRoom('tags'); - if ($('body').height() <= $(window).height() && $('[component="category"]').children().length >= 20) { - $('#load-more-btn').show(); - } - - $('#load-more-btn').on('click', function () { - loadMoreTopics(); - }); - - if (!config.usePagination) { - infinitescroll.init(loadMoreTopics); - } - - function loadMoreTopics(direction) { - if (direction < 0 || !$('[component="category"]').length) { - return; - } + topicList.init('tag', loadMoreTopics); + function loadMoreTopics(after, direction, callback) { infinitescroll.loadMore('topics.loadMoreFromSet', { set: 'tag:' + ajaxify.data.tag + ':topics', - after: $('[component="category"]').attr('data-nextstart'), + after: after, + direction: direction, count: config.topicsPerPage, - }, function (data, done) { - if (data.topics && data.topics.length) { - recent.onTopicsLoaded('tag', data.topics, false, direction, done); - } else { - done(); - $('#load-more-btn').hide(); - } - $('[component="category"]').attr('data-nextstart', data.nextStart); - }); + }, callback); } }; diff --git a/public/src/client/top.js b/public/src/client/top.js index 18bc66558a..9ad2e7b886 100644 --- a/public/src/client/top.js +++ b/public/src/client/top.js @@ -1,54 +1,13 @@ 'use strict'; -define('forum/top', ['forum/recent', 'forum/infinitescroll'], function (recent, infinitescroll) { +define('forum/top', ['topicList'], function (topicList) { var Top = {}; - $(window).on('action:ajaxify.start', function (ev, data) { - if (ajaxify.currentPage !== data.url) { - recent.removeListeners(); - } - }); - Top.init = function () { app.enterRoom('top_topics'); - recent.watchForNewPosts(); - - recent.handleCategorySelection(); - - $('#new-topics-alert').on('click', function () { - $(this).addClass('hide'); - }); - - if (!config.usePagination) { - infinitescroll.init(loadMoreTopics); - } - - $(window).trigger('action:topics.loaded', { topics: ajaxify.data.topics }); + topicList.init('top'); }; - function loadMoreTopics(direction) { - if (direction < 0 || !$('[component="category"]').length) { - return; - } - var query = utils.params(); - infinitescroll.loadMore('topics.loadMoreTopTopics', { - after: $('[component="category"]').attr('data-nextstart'), - count: config.topicsPerPage, - cid: query.cid, - query: query, - term: ajaxify.data.selectedTerm.term, - filter: ajaxify.data.selectedFilter.filter, - }, function (data, done) { - if (data.topics && data.topics.length) { - recent.onTopicsLoaded('top', data.topics, true, direction, done); - $('[component="category"]').attr('data-nextstart', data.nextStart); - } else { - done(); - $('#load-more-btn').hide(); - } - }); - } - return Top; }); diff --git a/public/src/client/unread.js b/public/src/client/unread.js index e1824ff37a..09278b88de 100644 --- a/public/src/client/unread.js +++ b/public/src/client/unread.js @@ -1,27 +1,14 @@ 'use strict'; -define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll', 'components'], function (recent, topicSelect, infinitescroll, components) { +define('forum/unread', ['topicSelect', 'components', 'topicList'], function (topicSelect, components, topicList) { var Unread = {}; - $(window).on('action:ajaxify.start', function (ev, data) { - if (ajaxify.currentPage !== data.url) { - recent.removeListeners(); - } - }); - Unread.init = function () { app.enterRoom('unread_topics'); - $('#new-topics-alert').on('click', function () { - $(this).addClass('hide'); - }); - - recent.watchForNewPosts(); - - recent.handleCategorySelection(); - - $(window).trigger('action:topics.loaded', { topics: ajaxify.data.topics }); + topicList.init('unread'); + topicSelect.init(); $('#markSelectedRead').on('click', function () { var tids = topicSelect.getSelectedTids(); @@ -71,42 +58,6 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll', ' doneRemovingTids(tids); }); }); - - topicSelect.init(); - - if ($('body').height() <= $(window).height() && $('[component="category"]').children().length >= 20) { - $('#load-more-btn').show(); - } - - $('#load-more-btn').on('click', function () { - loadMoreTopics(); - }); - - if (!config.usePagination) { - infinitescroll.init(loadMoreTopics); - } - - function loadMoreTopics(direction) { - if (direction < 0 || !$('[component="category"]').length) { - return; - } - var query = utils.params(); - infinitescroll.loadMore('topics.loadMoreUnreadTopics', { - after: $('[component="category"]').attr('data-nextstart'), - count: config.topicsPerPage, - cid: query.cid, - query: query, - filter: ajaxify.data.selectedFilter.filter, - }, function (data, done) { - if (data.topics && data.topics.length) { - recent.onTopicsLoaded('unread', data.topics, true, direction, done); - $('[component="category"]').attr('data-nextstart', data.nextStart); - } else { - done(); - $('#load-more-btn').hide(); - } - }); - } }; function doneRemovingTids(tids) { diff --git a/public/src/modules/topicList.js b/public/src/modules/topicList.js new file mode 100644 index 0000000000..9f12e5fd89 --- /dev/null +++ b/public/src/modules/topicList.js @@ -0,0 +1,290 @@ +'use strict'; + +define('topicList', ['forum/infinitescroll', 'handleBack', 'topicSelect'], function (infinitescroll, handleBack, topicSelect) { + var TopicList = {}; + var templateName = ''; + + var tplToSort = { + recent: 'recent', + unread: 'unread', + popular: 'posts', + top: 'votes', + }; + + var newTopicCount = 0; + var newPostCount = 0; + + var loadTopicsCallback; + + $(window).on('action:ajaxify.start', function () { + TopicList.removeListeners(); + loadTopicsCallback = null; + }); + + TopicList.init = function (template, cb) { + templateName = template; + loadTopicsCallback = cb; + + TopicList.watchForNewPosts(); + + TopicList.handleCategorySelection(); + + if (!config.usePagination) { + infinitescroll.init(TopicList.loadMoreTopics); + } + + handleBack.init(function (after, cb) { + loadTopicsAfter(after, 1, cb); + }); + + if ($('body').height() <= $(window).height() && $('[component="category"]').children().length >= 20) { + $('#load-more-btn').show(); + } + + $('#load-more-btn').on('click', function () { + TopicList.loadMoreTopics(1); + }); + + $(window).trigger('action:topics.loaded', { topics: ajaxify.data.topics }); + }; + + TopicList.watchForNewPosts = function () { + $('#new-topics-alert').on('click', function () { + $(this).addClass('hide'); + }); + newPostCount = 0; + newTopicCount = 0; + TopicList.removeListeners(); + socket.on('event:new_topic', onNewTopic); + socket.on('event:new_post', onNewPost); + }; + + TopicList.removeListeners = function () { + socket.removeListener('event:new_topic', onNewTopic); + socket.removeListener('event:new_post', onNewPost); + }; + + function onNewTopic(data) { + if ((ajaxify.data.selectedCids && ajaxify.data.selectedCids.indexOf(parseInt(data.cid, 10)) === -1) || + (ajaxify.data.selectedFilter && ajaxify.data.selectedFilter.filter === 'watched') || + (ajaxify.data.template.category && parseInt(ajaxify.data.cid, 10) !== parseInt(data.cid, 10))) { + return; + } + + newTopicCount += 1; + updateAlertText(); + } + + function onNewPost(data) { + function showAlert() { + newPostCount += 1; + updateAlertText(); + } + + var post = data.posts[0]; + if ((!post || !post.topic) || + (parseInt(post.topic.mainPid, 10) === parseInt(post.pid, 10)) || + (ajaxify.data.selectedCids && ajaxify.data.selectedCids.indexOf(parseInt(post.topic.cid, 10)) === -1) || + (ajaxify.data.selectedFilter && ajaxify.data.selectedFilter.filter === 'new') || + (ajaxify.data.template.category && parseInt(ajaxify.data.cid, 10) !== parseInt(post.topic.cid, 10))) { + return; + } + + if (ajaxify.data.selectedFilter && ajaxify.data.selectedFilter.filter === 'watched') { + socket.emit('topics.isFollowed', post.tid, function (err, isFollowed) { + if (err) { + app.alertError(err.message); + } + if (isFollowed) { + showAlert(); + } + }); + return; + } + + showAlert(); + } + + function updateAlertText() { + var text = ''; + + if (newTopicCount === 0) { + if (newPostCount === 1) { + text = '[[recent:there-is-a-new-post]]'; + } else if (newPostCount > 1) { + text = '[[recent:there-are-new-posts, ' + newPostCount + ']]'; + } + } else if (newTopicCount === 1) { + if (newPostCount === 0) { + text = '[[recent:there-is-a-new-topic]]'; + } else if (newPostCount === 1) { + text = '[[recent:there-is-a-new-topic-and-a-new-post]]'; + } else if (newPostCount > 1) { + text = '[[recent:there-is-a-new-topic-and-new-posts, ' + newPostCount + ']]'; + } + } else if (newTopicCount > 1) { + if (newPostCount === 0) { + text = '[[recent:there-are-new-topics, ' + newTopicCount + ']]'; + } else if (newPostCount === 1) { + text = '[[recent:there-are-new-topics-and-a-new-post, ' + newTopicCount + ']]'; + } else if (newPostCount > 1) { + text = '[[recent:there-are-new-topics-and-new-posts, ' + newTopicCount + ', ' + newPostCount + ']]'; + } + } + + text += ' [[recent:click-here-to-reload]]'; + + $('#new-topics-alert').translateText(text).removeClass('hide').fadeIn('slow'); + $('#category-no-topics').addClass('hide'); + } + + TopicList.handleCategorySelection = function () { + function getSelectedCids() { + var cids = []; + $('[component="category/list"] [data-cid]').each(function (index, el) { + if ($(el).find('i.fa-check').length) { + cids.push(parseInt($(el).attr('data-cid'), 10)); + } + }); + cids.sort(function (a, b) { + return a - b; + }); + return cids; + } + + $('[component="category/dropdown"]').on('hidden.bs.dropdown', function () { + var cids = getSelectedCids(); + var changed = ajaxify.data.selectedCids.length !== cids.length; + ajaxify.data.selectedCids.forEach(function (cid, index) { + if (cid !== cids[index]) { + changed = true; + } + }); + + if (changed) { + var url = window.location.pathname; + var currentParams = utils.params(); + if (cids.length) { + currentParams.cid = cids; + url += '?' + decodeURIComponent($.param(currentParams)); + } + ajaxify.go(url); + } + }); + + $('[component="category/list"]').on('click', '[data-cid]', function (ev) { + function selectChildren(parentCid, flag) { + $('[component="category/list"] [data-parent-cid="' + parentCid + '"] [component="category/select/icon"]').toggleClass('fa-check', flag); + $('[component="category/list"] [data-parent-cid="' + parentCid + '"]').each(function (index, el) { + selectChildren($(el).attr('data-cid'), flag); + }); + } + var categoryEl = $(this); + var cid = $(this).attr('data-cid'); + if (ev.ctrlKey) { + selectChildren(cid, !categoryEl.find('[component="category/select/icon"]').hasClass('fa-check')); + } + categoryEl.find('[component="category/select/icon"]').toggleClass('fa-check'); + $('[component="category/list"] li').first().find('i').toggleClass('fa-check', !getSelectedCids().length); + return false; + }); + }; + + TopicList.loadMoreTopics = function (direction) { + if (!$('[component="category"]').length || !$('[component="category"]').children().length) { + return; + } + var topics = $('[component="category/topic"]'); + var afterEl = direction > 0 ? topics.last() : topics.first(); + var after = (parseInt(afterEl.attr('data-index'), 10) || 0) + (direction > 0 ? 1 : 0); + + if (!utils.isNumber(after) || (after === 0 && $('[component="category/topic"][data-index="0"]').length)) { + return; + } + + var loadFn = loadTopicsCallback || loadTopicsAfter; + loadFn(after, direction, function (data, done) { + if (data.topics && data.topics.length) { + TopicList.onTopicsLoaded(templateName, data.topics, data.privileges && data.privileges.editable, direction, done); + } else { + done(); + $('#load-more-btn').hide(); + } + }); + }; + + function loadTopicsAfter(after, direction, callback) { + callback = callback || function () {}; + var query = utils.params(); + infinitescroll.loadMore('topics.loadMoreSortedTopics', { + after: after, + direction: direction, + sort: tplToSort[templateName], + count: config.topicsPerPage, + cid: query.cid, + query: query, + term: ajaxify.data.selectedTerm.term, + filter: ajaxify.data.selectedFilter.filter, + set: $('[component="category"]').attr('data-set') ? $('[component="category"]').attr('data-set') : 'topics:recent', + }, callback); + } + + TopicList.onTopicsLoaded = function (templateName, topics, showSelect, direction, callback) { + topics = topics.filter(function (topic) { + return !$('[component="category/topic"][data-tid="' + topic.tid + '"]').length; + }); + + if (!topics.length) { + return callback(); + } + + var after; + var before; + var topicsList = $('[component="category/topic"]'); + + if (direction > 0 && topics.length) { + after = topicsList.last(); + } else if (direction < 0 && topics.length) { + before = topicsList.first(); + } + + var tplData = { + topics: topics, + showSelect: showSelect, + template: { + name: templateName, + }, + }; + tplData.template[templateName] = true; + + app.parseAndTranslate(templateName, 'topics', tplData, function (html) { + $('[component="category"]').removeClass('hidden'); + $('#category-no-topics').remove(); + + if (after && after.length) { + html.insertAfter(after); + } else if (before && before.length) { + var height = $(document).height(); + var scrollTop = $(window).scrollTop(); + + html.insertBefore(before); + + $(window).scrollTop(scrollTop + ($(document).height() - height)); + } else { + $('[component="category"]').append(html); + } + + if (!topicSelect.getSelectedTids().length) { + infinitescroll.removeExtra($('[component="category/topic"]'), direction, config.topicsPerPage * 3); + } + + html.find('.timeago').timeago(); + app.createUserTooltips(); + utils.makeNumbersHumanReadable(html.find('.human-readable-number')); + $(window).trigger('action:topics.loaded', { topics: topics, template: templateName }); + callback(); + }); + }; + + return TopicList; +}); diff --git a/src/categories/topics.js b/src/categories/topics.js index b7eafae95e..a9e3ab1229 100644 --- a/src/categories/topics.js +++ b/src/categories/topics.js @@ -22,16 +22,13 @@ module.exports = function (Categories) { topics.getTopicsByTids(tids, data.uid, next); }, async.apply(user.blocks.filter, data.uid), - function (topics, next) { - if (!topics.length) { + function (topicsData, next) { + if (!topicsData.length) { return next(null, { topics: [], uid: data.uid }); } + topics.calculateTopicIndices(topicsData, data.start); - for (var i = 0; i < topics.length; i += 1) { - topics[i].index = data.start + i; - } - - plugins.fireHook('filter:category.topics.get', { cid: data.cid, topics: topics, uid: data.uid }, next); + plugins.fireHook('filter:category.topics.get', { cid: data.cid, topics: topicsData, uid: data.uid }, next); }, function (results, next) { next(null, { topics: results.topics, nextStart: data.stop + 1 }); diff --git a/src/controllers/errors.js b/src/controllers/errors.js index 96bfda203c..dacca84e3c 100644 --- a/src/controllers/errors.js +++ b/src/controllers/errors.js @@ -52,7 +52,7 @@ exports.handleErrors = function (err, req, res, next) { // eslint-disable-line n return res.locals.isAPI ? res.set('X-Redirect', err.path).status(200).json(err.path) : res.redirect(err.path); } - winston.error(req.path + '\n', err.stack); + winston.error(req.path + '\n', err); res.status(status || 500); diff --git a/src/controllers/recent.js b/src/controllers/recent.js index b4d56e523d..aee3890067 100644 --- a/src/controllers/recent.js +++ b/src/controllers/recent.js @@ -79,7 +79,6 @@ recentController.getData = function (req, url, sort, callback) { data.allCategoriesUrl = url + helpers.buildQueryString('', filter, ''); data.selectedCategory = categoryData.selectedCategory; data.selectedCids = categoryData.selectedCids; - data.nextStart = stop + 1; data['feeds:disableRSS'] = meta.config['feeds:disableRSS']; data.rssFeedUrl = nconf.get('relative_path') + '/' + url + '.rss'; if (req.loggedIn) { diff --git a/src/controllers/tags.js b/src/controllers/tags.js index a35c00a72e..2dc54dd18a 100644 --- a/src/controllers/tags.js +++ b/src/controllers/tags.js @@ -23,15 +23,16 @@ tagsController.getTag = function (req, res, next) { }; var settings; var topicCount = 0; + var start; + async.waterfall([ function (next) { user.getSettings(req.uid, next); }, function (_settings, next) { settings = _settings; - var start = Math.max(0, (page - 1) * settings.topicsPerPage); + start = Math.max(0, (page - 1) * settings.topicsPerPage); var stop = start + settings.topicsPerPage - 1; - templateData.nextStart = stop + 1; async.parallel({ topicCount: function (next) { topics.getTagTopicCount(req.params.tag, next); @@ -48,7 +49,8 @@ tagsController.getTag = function (req, res, next) { topicCount = results.topicCount; topics.getTopics(results.tids, req.uid, next); }, - function (topics) { + function (topicsData) { + topics.calculateTopicIndices(topicsData, start); res.locals.metaTags = [ { name: 'title', @@ -59,7 +61,7 @@ tagsController.getTag = function (req, res, next) { content: tag, }, ]; - templateData.topics = topics; + templateData.topics = topicsData; var pageCount = Math.max(1, Math.ceil(topicCount / settings.topicsPerPage)); templateData.pagination = pagination.create(page, pageCount); diff --git a/src/database/mongo.js b/src/database/mongo.js index ec9d29bb19..47964fdd0d 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -100,7 +100,7 @@ mongoModule.getConnectionOptions = function () { reconnectTries: 3600, reconnectInterval: 1000, autoReconnect: true, - connectTimeoutMS: 60000, + connectTimeoutMS: 90000, useNewUrlParser: true, }; diff --git a/src/meta/js.js b/src/meta/js.js index 230d7ef54a..a691c0639c 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -67,6 +67,7 @@ JS.scripts = { 'public/src/modules/sort.js', 'public/src/modules/navigator.js', 'public/src/modules/topicSelect.js', + 'public/src/modules/topicList.js', 'public/src/modules/categorySelector.js', 'public/src/modules/share.js', 'public/src/modules/search.js', diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js index c35f188576..072a51d268 100644 --- a/src/socket.io/topics/infinitescroll.js +++ b/src/socket.io/topics/infinitescroll.js @@ -87,59 +87,43 @@ module.exports = function (SocketTopics) { ], callback); }; - SocketTopics.loadMoreUnreadTopics = function (socket, data, callback) { - loadData(socket.uid, data, 'unread', callback); - }; - - SocketTopics.loadMoreRecentTopics = function (socket, data, callback) { - loadData(socket.uid, data, 'recent', callback); - }; - - SocketTopics.loadMorePopularTopics = function (socket, data, callback) { - loadData(socket.uid, data, 'posts', callback); - }; - - SocketTopics.loadMoreTopTopics = function (socket, data, callback) { - loadData(socket.uid, data, 'votes', callback); - }; - - function loadData(uid, data, sort, callback) { + SocketTopics.loadMoreSortedTopics = function (socket, data, callback) { if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { return callback(new Error('[[error:invalid-data]]')); } - var itemsPerPage = Math.min(meta.config.topicsPerPage || 20, parseInt(data.count, 10) || meta.config.topicsPerPage || 20); - var start = Math.max(0, parseInt(data.after, 10)); - if (data.direction === -1) { - start -= itemsPerPage; - } - var stop = start + Math.max(0, itemsPerPage - 1); - start = Math.max(0, start); - stop = Math.max(0, stop); + const { start, stop } = calculateStartStop(data); const params = { - uid: uid, + uid: socket.uid, start: start, stop: stop, filter: data.filter, query: data.query, }; - if (sort === 'unread') { + if (data.sort === 'unread') { params.cid = data.cid; return topics.getUnreadTopics(params, callback); } params.cids = data.cid; - params.sort = sort; + params.sort = data.sort; params.term = data.term; topics.getSortedTopics(params, callback); - } + }; SocketTopics.loadMoreFromSet = function (socket, data, callback) { if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0 || !data.set) { return callback(new Error('[[error:invalid-data]]')); } - - var start = parseInt(data.after, 10); - var stop = start + Math.max(0, Math.min(meta.config.topicsPerPage || 20, parseInt(data.count, 10) || meta.config.topicsPerPage || 20) - 1); - + const { start, stop } = calculateStartStop(data); topics.getTopicsFromSet(data.set, socket.uid, start, stop, callback); }; + + function calculateStartStop(data) { + var itemsPerPage = Math.min(meta.config.topicsPerPage || 20, parseInt(data.count, 10) || meta.config.topicsPerPage || 20); + var start = Math.max(0, parseInt(data.after, 10)); + if (data.direction === -1) { + start -= itemsPerPage; + } + var stop = start + Math.max(0, itemsPerPage - 1); + return { start: Math.max(0, start), stop: Math.max(0, stop) }; + } }; diff --git a/src/topics/index.js b/src/topics/index.js index 0b54321eb1..005bb65b1e 100644 --- a/src/topics/index.js +++ b/src/topics/index.js @@ -46,6 +46,7 @@ Topics.getTopicsFromSet = function (set, uid, start, stop, callback) { Topics.getTopics(tids, uid, next); }, function (topics, next) { + Topics.calculateTopicIndices(topics, start); next(null, { topics: topics, nextStart: stop + 1 }); }, ], callback); @@ -237,7 +238,7 @@ function getMainPostAndReplies(topic, set, uid, start, stop, reverse, callback) replies = posts.slice(1); } - Topics.calculatePostIndices(replies, start, stop, topic.postcount, reverse); + Topics.calculatePostIndices(replies, start, topic.postcount, reverse); Topics.addPostData(posts, uid, next); }, diff --git a/src/topics/posts.js b/src/topics/posts.js index f9c0786064..fac337c0ea 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -37,7 +37,7 @@ module.exports = function (Topics) { }, next); }, function (results, next) { - Topics.calculatePostIndices(results.posts, start, stop, results.postCount, reverse); + Topics.calculatePostIndices(results.posts, start, results.postCount, reverse); Topics.addPostData(results.posts, uid, next); }, @@ -183,7 +183,7 @@ module.exports = function (Topics) { ], callback); }; - Topics.calculatePostIndices = function (posts, start, stop, postCount, reverse) { + Topics.calculatePostIndices = function (posts, start, postCount, reverse) { posts.forEach(function (post, index) { if (reverse) { post.index = postCount - (start + index + 1); diff --git a/src/topics/sorted.js b/src/topics/sorted.js index 2a94bfcbb8..2347134784 100644 --- a/src/topics/sorted.js +++ b/src/topics/sorted.js @@ -178,11 +178,17 @@ module.exports = function (Topics) { Topics.getTopicsByTids(tids, params.uid, next); }, function (topicData, next) { - topicData.forEach(function (topicObj, i) { - topicObj.index = params.start + i; - }); + Topics.calculateTopicIndices(topicData, params.start); next(null, topicData); }, ], callback); } + + Topics.calculateTopicIndices = function (topicData, start) { + topicData.forEach((topic, index) => { + if (topic) { + topic.index = start + index; + } + }); + }; }; diff --git a/test/socket.io.js b/test/socket.io.js index 31be159051..736f2a6378 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -468,6 +468,7 @@ describe('socket.io', function () { }); it('should toggle plugin install', function (done) { + this.timeout(0); socketAdmin.plugins.toggleInstall({ uid: adminUid }, { id: 'nodebb-plugin-location-to-map', version: 'latest' }, function (err, data) { assert.ifError(err); assert.equal(data.name, 'nodebb-plugin-location-to-map'); diff --git a/test/topics.js b/test/topics.js index c506c50312..633293739f 100644 --- a/test/topics.js +++ b/test/topics.js @@ -1064,7 +1064,7 @@ describe('Topic\'s', function () { }); it('should error with invalid data', function (done) { - socketTopics.loadMoreUnreadTopics({ uid: adminUid }, { after: 'invalid' }, function (err) { + socketTopics.loadMoreSortedTopics({ uid: adminUid }, { after: 'invalid' }, function (err) { assert.equal(err.message, '[[error:invalid-data]]'); done(); }); @@ -1073,7 +1073,7 @@ describe('Topic\'s', function () { it('should load more unread topics', function (done) { socketTopics.markUnread({ uid: adminUid }, tid, function (err) { assert.ifError(err); - socketTopics.loadMoreUnreadTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0, count: 10 }, function (err, data) { + socketTopics.loadMoreSortedTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0, count: 10, sort: 'unread' }, function (err, data) { assert.ifError(err); assert(data); assert(Array.isArray(data.topics)); @@ -1083,7 +1083,7 @@ describe('Topic\'s', function () { }); it('should error with invalid data', function (done) { - socketTopics.loadMoreRecentTopics({ uid: adminUid }, { after: 'invalid' }, function (err) { + socketTopics.loadMoreSortedTopics({ uid: adminUid }, { after: 'invalid' }, function (err) { assert.equal(err.message, '[[error:invalid-data]]'); done(); }); @@ -1091,7 +1091,7 @@ describe('Topic\'s', function () { it('should load more recent topics', function (done) { - socketTopics.loadMoreRecentTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0, count: 10 }, function (err, data) { + socketTopics.loadMoreSortedTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0, count: 10, sort: 'recent' }, function (err, data) { assert.ifError(err); assert(data); assert(Array.isArray(data.topics));