diff --git a/install/package.json b/install/package.json index 8a514d6e1b..9018846723 100644 --- a/install/package.json +++ b/install/package.json @@ -60,7 +60,7 @@ "mousetrap": "^1.6.1", "mubsub": "^1.4.0", "nconf": "^0.9.1", - "nodebb-plugin-composer-default": "6.0.8", + "nodebb-plugin-composer-default": "6.0.9", "nodebb-plugin-dbsearch": "2.0.9", "nodebb-plugin-emoji": "2.1.0", "nodebb-plugin-emoji-android": "2.0.0", @@ -70,9 +70,9 @@ "nodebb-plugin-spam-be-gone": "0.5.1", "nodebb-rewards-essentials": "0.0.11", "nodebb-theme-lavender": "5.0.1", - "nodebb-theme-persona": "7.2.21", + "nodebb-theme-persona": "7.2.20", "nodebb-theme-slick": "1.1.4", - "nodebb-theme-vanilla": "8.1.7", + "nodebb-theme-vanilla": "8.1.9", "nodebb-widget-essentials": "4.0.1", "nodemailer": "4.4.1", "passport": "^0.4.0", diff --git a/public/language/he/topic.json b/public/language/he/topic.json index 944883f525..41442aeb9f 100644 --- a/public/language/he/topic.json +++ b/public/language/he/topic.json @@ -20,7 +20,7 @@ "guest-login-reply": "התחבר כדי לפרסם תגובה", "edit": "עריכה", "delete": "מחק", - "purge": "מחק הכל", + "purge": "מחק לצמיתות", "restore": "שחזר", "move": "הזז", "fork": "פורק", @@ -94,7 +94,7 @@ "merge_topics_instruction": "Click the topics you want to merge", "composer.title_placeholder": "הכנס את כותרת הנושא כאן...", "composer.handle_placeholder": "שם", - "composer.discard": "מחק", + "composer.discard": "ביטול", "composer.submit": "שלח", "composer.replying_to": "מגיב ל %1", "composer.new_topic": "נושא חדש", diff --git a/public/language/ru/flags.json b/public/language/ru/flags.json index 4f105ca1d3..bb3372deb5 100644 --- a/public/language/ru/flags.json +++ b/public/language/ru/flags.json @@ -1,5 +1,5 @@ { - "state": "State", + "state": "Состояние", "reporter": "Reporter", "reported-at": "Reported At", "description": "Описание", @@ -9,7 +9,7 @@ "updated": "Обновлено", "target-purged": "The content this flag referred to has been purged and is no longer available.", - "quick-filters": "Quick Filters", + "quick-filters": "Быстрые фильтры", "filter-active": "There are one or more filters active in this list of flags", "filter-reset": "Убрать фильтры", "filters": "Filter Options", @@ -17,13 +17,13 @@ "filter-targetUid": "Flagged UID", "filter-type": "Flag Type", "filter-type-all": "Весь контент", - "filter-type-post": "Написать", - "filter-state": "State", + "filter-type-post": "Сообщение", + "filter-state": "Состояние", "filter-assignee": "Assignee UID", "filter-cid": "Категория", "filter-quick-mine": "Assigned to me", "filter-cid-all": "Все категории", - "apply-filters": "Apply Filters", + "apply-filters": "Применить фильтры", "quick-links": "Quick Links", "flagged-user": "Flagged User", @@ -32,7 +32,7 @@ "go-to-target": "View Flag Target", "user-view": "Просмотреть профиль", - "user-edit": "Изменить Профиль", + "user-edit": "Изменить профиль", "notes": "Flag Notes", "add-note": "Добавить примечание", @@ -42,8 +42,8 @@ "back": "Back to Flags List", "no-history": "No flag history.", - "state-all": "Все государства", - "state-open": "Новый/Открыть", + "state-all": "Все состояния", + "state-open": "Новый/Открытый", "state-wip": "Work in Progress", "state-resolved": "Решен", "state-rejected": "Отклонен", @@ -53,12 +53,12 @@ "modal-title": "Report Inappropriate Content", "modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.", "modal-reason-spam": "Спам", - "modal-reason-offensive": "Offensive", - "modal-reason-other": "Other (specify below)", - "modal-reason-custom": "Reason for reporting this content...", + "modal-reason-offensive": "Оскорбительный", + "modal-reason-other": "Другое (укажите ниже)", + "modal-reason-custom": "Причина жалобы на содержимое...", "modal-submit": "Представить отчет", "modal-submit-success": "Content has been flagged for moderation.", - "modal-submit-confirm": "Confirm Submission", + "modal-submit-confirm": "Подтвердить отправку", "modal-submit-confirm-text": "You have a custom reason specified already. Are you sure you wish to submit via quick-report?", "modal-submit-confirm-text-help": "Submitting a quick report will overwrite any custom reasons defined." } \ No newline at end of file diff --git a/public/src/client/popular.js b/public/src/client/popular.js index 02a1348156..b63f776bff 100644 --- a/public/src/client/popular.js +++ b/public/src/client/popular.js @@ -1,7 +1,7 @@ 'use strict'; -define('forum/popular', ['components'], function (components) { +define('forum/popular', ['forum/recent', 'components', 'forum/infinitescroll'], function (recent, components, infinitescroll) { var Popular = {}; Popular.init = function () { @@ -11,7 +11,30 @@ define('forum/popular', ['components'], function (components) { .removeClass('active') .find('a[href="' + window.location.pathname + '"]') .parent().addClass('active'); + + if (!config.usePagination) { + infinitescroll.init(loadMoreTopics); + } }; + function loadMoreTopics(direction) { + if (direction < 0 || !$('[component="category"]').length) { + return; + } + + infinitescroll.loadMore('topics.loadMorePopularTopics', { + after: $('[component="category"]').attr('data-nextstart'), + count: config.topicsPerPage, + term: ajaxify.data.term, + }, function (data, done) { + if (data.topics && data.topics.length) { + recent.onTopicsLoaded('popular', data.topics, false, done); + $('[component="category"]').attr('data-nextstart', data.nextStart); + } else { + done(); + } + }); + } + return Popular; }); diff --git a/src/controllers/accounts/helpers.js b/src/controllers/accounts/helpers.js index 8951976a78..bc43213de0 100644 --- a/src/controllers/accounts/helpers.js +++ b/src/controllers/accounts/helpers.js @@ -113,7 +113,7 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) { userData.fullname = ''; } - if (isAdmin || isSelf || (isGlobalModerator && !results.isTargetAdmin)) { + if (isAdmin || isSelf || ((isGlobalModerator || isModerator) && !results.isTargetAdmin)) { userData.ips = results.ips; } diff --git a/src/controllers/popular.js b/src/controllers/popular.js index b6a189383c..6c7c2e6011 100644 --- a/src/controllers/popular.js +++ b/src/controllers/popular.js @@ -3,9 +3,12 @@ var async = require('async'); var nconf = require('nconf'); + var topics = require('../topics'); var meta = require('../meta'); +var user = require('../user'); var helpers = require('./helpers'); +var pagination = require('../pagination'); var popularController = module.exports; @@ -19,6 +22,7 @@ var terms = { }; popularController.get = function (req, res, next) { + var page = parseInt(req.query.page, 10) || 1; var term = terms[req.params.term]; if (!term && req.params.term) { @@ -38,19 +42,25 @@ popularController.get = function (req, res, next) { return res.render('popular', anonCache[term]); } } - + var settings; async.waterfall([ function (next) { - topics.getPopular(term, req.uid, meta.config.topicsPerList, next); + user.getSettings(req.uid, next); }, - function (topics) { - var data = { - title: meta.config.homePageTitle || '[[pages:home]]', - topics: topics, - 'feeds:disableRSS': parseInt(meta.config['feeds:disableRSS'], 10) === 1, - rssFeedUrl: nconf.get('relative_path') + '/popular/' + (req.params.term || 'daily') + '.rss', - term: term, - }; + function (_settings, next) { + settings = _settings; + var start = Math.max(0, (page - 1) * settings.topicsPerPage); + var stop = start + settings.topicsPerPage - 1; + topics.getPopularTopics(term, req.uid, start, stop, next); + }, + function (data) { + var pageCount = Math.max(1, Math.ceil(data.topicCount / settings.topicsPerPage)); + + data.title = meta.config.homePageTitle || '[[pages:home]]'; + data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; + data.rssFeedUrl = nconf.get('relative_path') + '/popular/' + (req.params.term || 'alltime') + '.rss'; + data.term = term; + data.pagination = pagination.create(page, pageCount, req.query); if (req.originalUrl.startsWith(nconf.get('relative_path') + '/api/popular') || req.originalUrl.startsWith(nconf.get('relative_path') + '/popular')) { data.title = '[[pages:popular-' + term + ']]'; diff --git a/src/middleware/index.js b/src/middleware/index.js index cb91f82339..c0cce7623a 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -99,13 +99,14 @@ middleware.routeTouchIcon = function (req, res) { if (meta.config['brand:touchIcon'] && validator.isURL(meta.config['brand:touchIcon'])) { return res.redirect(meta.config['brand:touchIcon']); } - var iconPath = '../../public'; + var iconPath = ''; if (meta.config['brand:touchIcon']) { - iconPath += meta.config['brand:touchIcon'].replace(/assets\/uploads/, 'uploads'); + iconPath = path.join(nconf.get('upload_path'), meta.config['brand:touchIcon'].replace(/assets\/uploads/, '')); } else { - iconPath += '/logo.png'; + iconPath = path.join(nconf.get('base_dir'), 'public/logo.png'); } - return res.sendFile(path.join(__dirname, iconPath), { + + return res.sendFile(iconPath, { maxAge: req.app.enabled('cache') ? 5184000000 : 0, }); }; diff --git a/src/routes/feeds.js b/src/routes/feeds.js index eba2fee4ee..ccb5af1d76 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -13,6 +13,7 @@ var meta = require('../meta'); var helpers = require('../controllers/helpers'); var privileges = require('../privileges'); var db = require('../database'); +var utils = require('../utils'); var controllers404 = require('../controllers/404.js'); module.exports = function (app, middleware) { @@ -105,7 +106,7 @@ function generateForTopic(req, res, callback) { var author = topicData.posts.length ? topicData.posts[0].username : ''; var feed = new rss({ - title: topicData.title, + title: utils.stripHTMLTags(topicData.title, utils.stripTags), description: description, feed_url: nconf.get('url') + '/topic/' + tid + '.rss', site_url: nconf.get('url') + '/topic/' + topicData.slug, @@ -124,7 +125,7 @@ function generateForTopic(req, res, callback) { dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString(); feed.item({ - title: 'Reply to ' + topicData.title + ' on ' + dateStamp, + title: 'Reply to ' + utils.stripHTMLTags(topicData.title, utils.stripTags) + ' on ' + dateStamp, description: postData.content, url: nconf.get('url') + '/post/' + postData.pid, author: postData.user ? postData.user.username : '', @@ -252,16 +253,16 @@ function generateForPopular(req, res, next) { async.waterfall([ function (next) { - topics.getPopular(term, req.uid, 19, next); + topics.getPopularTopics(term, req.uid, 0, 19, next); }, - function (topics, next) { + function (result, next) { generateTopicsFeed({ uid: req.uid, title: 'Popular Topics', description: 'A list of topics that are sorted by post count', feed_url: '/popular/' + (req.params.term || 'daily') + '.rss', site_url: '/popular/' + (req.params.term || 'daily'), - }, topics, next); + }, result.topics, next); }, function (feed) { sendFeed(feed, res); @@ -300,7 +301,7 @@ function generateTopicsFeed(feedOptions, feedTopics, callback) { async.each(feedTopics, function (topicData, next) { var feedItem = { - title: topicData.title, + title: utils.stripHTMLTags(topicData.title, utils.stripTags), url: nconf.get('url') + '/topic/' + topicData.slug, date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString(), }; diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js index 18be5f6341..64fa087430 100644 --- a/src/socket.io/topics/infinitescroll.js +++ b/src/socket.io/topics/infinitescroll.js @@ -88,37 +88,37 @@ module.exports = function (SocketTopics) { }; SocketTopics.loadMoreUnreadTopics = function (socket, data, callback) { - if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { - 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); - - topics.getUnreadTopics({ cid: data.cid, uid: socket.uid, start: start, stop: stop, filter: data.filter }, callback); + loadData(data, callback, function (start, stop) { + topics.getUnreadTopics({ cid: data.cid, uid: socket.uid, start: start, stop: stop, filter: data.filter }, callback); + }); }; SocketTopics.loadMoreRecentTopics = function (socket, data, callback) { - if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { - 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); + loadData(data, callback, function (start, stop) { + topics.getRecentTopics(data.cid, socket.uid, start, stop, data.filter, callback); + }); + }; - topics.getRecentTopics(data.cid, socket.uid, start, stop, data.filter, callback); + SocketTopics.loadMorePopularTopics = function (socket, data, callback) { + loadData(data, callback, function (start, stop) { + topics.getPopularTopics(data.term, socket.uid, start, stop, callback); + }); }; SocketTopics.loadMoreTopTopics = function (socket, data, callback) { + loadData(data, callback, function (start, stop) { + topics.getTopTopics(data.cid, socket.uid, start, stop, data.filter, callback); + }); + }; + + function loadData(data, callback, loadFn) { if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { 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); - - topics.getTopTopics(data.cid, socket.uid, start, stop, data.filter, callback); - }; + loadFn(start, stop); + } SocketTopics.loadMoreFromSet = function (socket, data, callback) { if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0 || !data.set) { diff --git a/src/topics/popular.js b/src/topics/popular.js index 85d25abeaf..99855b17d3 100644 --- a/src/topics/popular.js +++ b/src/topics/popular.js @@ -2,39 +2,50 @@ 'use strict'; var async = require('async'); + +var db = require('../database'); var privileges = require('../privileges'); module.exports = function (Topics) { Topics.getPopular = function (term, uid, count, callback) { count = parseInt(count, 10) || 20; - - if (term === 'alltime') { - return getAllTimePopular(uid, count, callback); - } - async.waterfall([ function (next) { - Topics.getLatestTidsFromSet('topics:tid', 0, -1, term, next); + Topics.getPopularTopics(term, uid, 0, count - 1, next); }, - function (tids, next) { - getTopics(tids, uid, count, next); + function (data, next) { + next(null, data.topics); }, ], callback); }; - function getAllTimePopular(uid, count, callback) { + Topics.getPopularTopics = function (term, uid, start, stop, callback) { + var popularTopics = { + nextStart: 0, + topicCount: 0, + topics: [], + }; async.waterfall([ function (next) { - Topics.getTopicsFromSet('topics:posts', uid, 0, count - 1, next); + if (term === 'alltime') { + db.getSortedSetRevRange('topics:posts', 0, 199, next); + } else { + Topics.getLatestTidsFromSet('topics:tid', 0, -1, term, next); + } }, - function (data, next) { - data.topics.sort(sortPopular); - next(null, data.topics); + function (tids, next) { + popularTopics.topicCount = tids.length; + getTopics(tids, uid, start, stop, next); + }, + function (topics, next) { + popularTopics.topics = topics; + popularTopics.nextStart = stop + 1; + next(null, popularTopics); }, ], callback); - } + }; - function getTopics(tids, uid, count, callback) { + function getTopics(tids, uid, start, stop, callback) { async.waterfall([ function (next) { Topics.getTopicsFields(tids, ['tid', 'postcount', 'deleted'], next); @@ -42,7 +53,7 @@ module.exports = function (Topics) { function (topics, next) { tids = topics.filter(function (topic) { return topic && parseInt(topic.deleted, 10) !== 1; - }).sort(sortPopular).slice(0, count).map(function (topic) { + }).sort(sortPopular).slice(start, stop !== -1 ? stop - 1 : undefined).map(function (topic) { return topic.tid; }); privileges.topics.filterTids('read', tids, uid, next); diff --git a/src/upgrades/1.8.0/chat_privilege.js b/src/upgrades/1.7.4/chat_privilege.js similarity index 100% rename from src/upgrades/1.8.0/chat_privilege.js rename to src/upgrades/1.7.4/chat_privilege.js diff --git a/src/upgrades/1.8.0/fix_moved_topics_byvotes.js b/src/upgrades/1.7.4/fix_moved_topics_byvotes.js similarity index 100% rename from src/upgrades/1.8.0/fix_moved_topics_byvotes.js rename to src/upgrades/1.7.4/fix_moved_topics_byvotes.js diff --git a/src/upgrades/1.8.0/global_upload_privilege.js b/src/upgrades/1.7.4/global_upload_privilege.js similarity index 100% rename from src/upgrades/1.8.0/global_upload_privilege.js rename to src/upgrades/1.7.4/global_upload_privilege.js diff --git a/src/upgrades/1.8.0/rename_min_reputation_settings.js b/src/upgrades/1.7.4/rename_min_reputation_settings.js similarity index 100% rename from src/upgrades/1.8.0/rename_min_reputation_settings.js rename to src/upgrades/1.7.4/rename_min_reputation_settings.js diff --git a/src/upgrades/1.8.0/vote_privilege.js b/src/upgrades/1.7.4/vote_privilege.js similarity index 100% rename from src/upgrades/1.8.0/vote_privilege.js rename to src/upgrades/1.7.4/vote_privilege.js diff --git a/src/user/digest.js b/src/user/digest.js index c08a0afe93..b1562aab3f 100644 --- a/src/user/digest.js +++ b/src/user/digest.js @@ -106,14 +106,14 @@ Digest.send = function (data, callback) { function (next) { async.parallel({ notifications: async.apply(user.notifications.getDailyUnread, userObj.uid), - topics: async.apply(topics.getPopular, data.interval, userObj.uid, 10), + popular: async.apply(topics.getPopularTopics, data.interval, userObj.uid, 0, 9), }, next); }, function (data, next) { var notifications = data.notifications.filter(Boolean); // If there are no notifications and no new topics, don't bother sending a digest - if (!notifications.length && !data.topics.length) { + if (!notifications.length && !data.popular.topics.length) { return next(); } @@ -124,7 +124,7 @@ Digest.send = function (data, callback) { }); // Fix relative paths in topic data - data.topics = data.topics.map(function (topicObj) { + data.popular.topics = data.popular.topics.map(function (topicObj) { var user = topicObj.hasOwnProperty('teaser') && topicObj.teaser !== undefined ? topicObj.teaser.user : topicObj.user; if (user && user.picture && utils.isRelativeUrl(user.picture)) { user.picture = nconf.get('base_url') + user.picture; @@ -138,7 +138,7 @@ Digest.send = function (data, callback) { username: userObj.username, userslug: userObj.userslug, notifications: notifications, - recent: data.topics, + recent: data.popular.topics, interval: data.interval, showUnsubscribe: true, }, function (err) {