mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-20 22:43:01 +01:00
Merge remote-tracking branch 'origin/develop' into bootstrap5
This commit is contained in:
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: 'postgres:10-alpine'
|
||||
image: 'postgres:14-alpine'
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,3 +1,45 @@
|
||||
#### v2.5.3 (2022-09-19)
|
||||
|
||||
##### Chores
|
||||
|
||||
* remove duplicate version increment lines in changelog (5dbcfef9)
|
||||
* bring back treding plugins (8aa1596d)
|
||||
* incrementing version number - v2.5.2 (babcd17e)
|
||||
* update changelog for v2.5.2 (84b6a7c7)
|
||||
* incrementing version number - v2.5.1 (ce3aa950)
|
||||
* incrementing version number - v2.5.0 (01d276cb)
|
||||
* incrementing version number - v2.4.5 (dd3e1a28)
|
||||
* incrementing version number - v2.4.4 (d5525c87)
|
||||
* incrementing version number - v2.4.3 (9c647c6c)
|
||||
* incrementing version number - v2.4.2 (3aa7b855)
|
||||
* incrementing version number - v2.4.1 (60cbd148)
|
||||
* incrementing version number - v2.4.0 (4834cde3)
|
||||
* incrementing version number - v2.3.1 (d2425942)
|
||||
* incrementing version number - v2.3.0 (046ea120)
|
||||
|
||||
##### New Features
|
||||
|
||||
* store topic title and tags in diffs (#10900) (b5dd89e1)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* #10906, allow `middleware.checkAccountPermissions` to be called with either uid or userslug in params (cf4f5447)
|
||||
* #10896, unescape / in taskbar (8e2129f8)
|
||||
* add back timeago to post history modal (d3e38df8)
|
||||
* **deps:** bump composer-default to v9.1.1 (1d80a07e)
|
||||
|
||||
##### Other Changes
|
||||
|
||||
* fix lint (3d5a6b39)
|
||||
|
||||
##### Performance Improvements
|
||||
|
||||
* disable trending plugins, too slow due to nbbpm (b392450c)
|
||||
|
||||
##### Tests
|
||||
|
||||
* add back /admin/extend/plugins test (1c9c39a8)
|
||||
|
||||
#### v2.5.2 (2022-09-04)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPL-3.0",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "2.5.2",
|
||||
"version": "2.5.3",
|
||||
"homepage": "http://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -30,10 +30,10 @@
|
||||
"dependencies": {
|
||||
"@adactive/bootstrap-tagsinput": "0.8.2",
|
||||
"@isaacs/ttlcache": "1.2.0",
|
||||
"ace-builds": "1.10.1",
|
||||
"ace-builds": "1.11.0",
|
||||
"archiver": "5.3.1",
|
||||
"async": "3.2.4",
|
||||
"autoprefixer": "10.4.8",
|
||||
"autoprefixer": "10.4.12",
|
||||
"bcryptjs": "2.4.3",
|
||||
"benchpressjs": "2.4.3",
|
||||
"body-parser": "1.20.0",
|
||||
@@ -46,7 +46,7 @@
|
||||
"clipboard": "2.0.11",
|
||||
"colors": "1.4.0",
|
||||
"commander": "9.4.0",
|
||||
"compare-versions": "4.1.4",
|
||||
"compare-versions": "5.0.1",
|
||||
"compression": "1.7.4",
|
||||
"connect-flash": "0.1.1",
|
||||
"connect-mongo": "4.6.0",
|
||||
@@ -68,7 +68,7 @@
|
||||
"helmet": "5.1.1",
|
||||
"html-to-text": "8.2.1",
|
||||
"ipaddr.js": "2.0.1",
|
||||
"jquery": "3.6.0",
|
||||
"jquery": "3.6.1",
|
||||
"jquery-deserialize": "2.0.0",
|
||||
"jquery-form": "4.3.0",
|
||||
"jquery-serializeobject": "1.0.0",
|
||||
@@ -83,7 +83,7 @@
|
||||
"material-design-lite": "1.3.0",
|
||||
"mime": "3.0.0",
|
||||
"mkdirp": "1.0.4",
|
||||
"mongodb": "4.9.0",
|
||||
"mongodb": "4.10.0",
|
||||
"morgan": "1.10.0",
|
||||
"mousetrap": "1.6.5",
|
||||
"multiparty": "4.2.3",
|
||||
@@ -97,7 +97,6 @@
|
||||
"nodebb-plugin-mentions": "4.0.1",
|
||||
"nodebb-plugin-spam-be-gone": "2.0.0",
|
||||
"nodebb-rewards-essentials": "0.2.1",
|
||||
"nodebb-theme-palette": "https://github.com/NodeBB/nodebb-theme-palette.git",
|
||||
"nodebb-theme-persona": "https://github.com/NodeBB/nodebb-theme-persona.git#bootstrap5",
|
||||
"nodebb-widget-essentials": "7.0.0",
|
||||
"nodemailer": "6.7.8",
|
||||
@@ -105,26 +104,26 @@
|
||||
"passport": "0.6.0",
|
||||
"passport-http-bearer": "1.0.1",
|
||||
"passport-local": "1.0.0",
|
||||
"pg": "8.7.3",
|
||||
"pg-cursor": "2.7.3",
|
||||
"postcss": "8.4.14",
|
||||
"pg": "8.8.0",
|
||||
"pg-cursor": "2.7.4",
|
||||
"postcss": "8.4.16",
|
||||
"postcss-clean": "1.2.0",
|
||||
"prompt": "1.3.0",
|
||||
"ioredis": "5.2.2",
|
||||
"ioredis": "5.2.3",
|
||||
"request": "2.88.2",
|
||||
"request-promise-native": "1.0.9",
|
||||
"rimraf": "3.0.2",
|
||||
"rss": "1.2.2",
|
||||
"rtlcss": "4.0.0",
|
||||
"sanitize-html": "2.7.1",
|
||||
"sanitize-html": "2.7.2",
|
||||
"sass": "1.54.9",
|
||||
"semver": "7.3.7",
|
||||
"serve-favicon": "2.5.0",
|
||||
"sharp": "0.30.7",
|
||||
"sharp": "0.31.0",
|
||||
"sitemap": "7.1.1",
|
||||
"slideout": "1.0.1",
|
||||
"socket.io": "4.5.1",
|
||||
"socket.io-client": "4.5.1",
|
||||
"socket.io": "4.5.2",
|
||||
"socket.io-client": "4.5.2",
|
||||
"@socket.io/redis-adapter": "7.2.0",
|
||||
"sortablejs": "1.15.0",
|
||||
"spdx-license-list": "6.6.0",
|
||||
@@ -138,7 +137,7 @@
|
||||
"validator": "13.7.0",
|
||||
"webpack": "5.74.0",
|
||||
"webpack-merge": "5.8.0",
|
||||
"winston": "3.8.1",
|
||||
"winston": "3.8.2",
|
||||
"xml": "1.0.1",
|
||||
"xregexp": "5.1.1",
|
||||
"yargs": "17.5.1",
|
||||
@@ -146,10 +145,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apidevtools/swagger-parser": "10.0.3",
|
||||
"@commitlint/cli": "17.0.3",
|
||||
"@commitlint/config-angular": "17.0.3",
|
||||
"@commitlint/cli": "17.1.2",
|
||||
"@commitlint/config-angular": "17.1.0",
|
||||
"coveralls": "3.1.1",
|
||||
"eslint": "8.22.0",
|
||||
"eslint": "8.23.1",
|
||||
"eslint-config-nodebb": "0.1.1",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"grunt": "1.5.3",
|
||||
@@ -186,4 +185,4 @@
|
||||
"url": "https://github.com/barisusakli"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@
|
||||
"logout": "登出",
|
||||
"view-forum": "查看论坛",
|
||||
|
||||
"search.placeholder": "Search settings",
|
||||
"search.placeholder": "搜索设置",
|
||||
"search.no-results": "没有可用结果…",
|
||||
"search.search-forum": "搜索论坛为<strong></strong>",
|
||||
"search.keep-typing": "输入更多以查看结果...",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"pagination": "分页设置",
|
||||
"enable": "在主题和帖子使用分页替代无限滚动浏览。",
|
||||
"posts": "Post Pagination",
|
||||
"posts": "帖子分页",
|
||||
"topics": "话题分页",
|
||||
"posts-per-page": "每页帖子数",
|
||||
"max-posts-per-page": "每页最多帖子数",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"close": "关闭",
|
||||
"pagination": "分页",
|
||||
"pagination.out_of": "%1 / %2",
|
||||
"pagination.enter_index": "Go to post index",
|
||||
"pagination.enter_index": "跳转到帖子",
|
||||
"header.admin": "管理",
|
||||
"header.categories": "版块",
|
||||
"header.recent": "最新",
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"chat.no_active": "暂无聊天",
|
||||
"chat.user_typing": "%1 正在输入……",
|
||||
"chat.user_has_messaged_you": "%1 向您发送了消息。",
|
||||
"chat.see_all": "All chats",
|
||||
"chat.mark_all_read": "Mark all read",
|
||||
"chat.see_all": "全部对话",
|
||||
"chat.mark_all_read": "标记全部已读",
|
||||
"chat.no-messages": "请选择接收人,以查看聊天消息历史",
|
||||
"chat.no-users-in-room": "此聊天室中没有用户",
|
||||
"chat.recent-chats": "最近聊天",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"title": "通知",
|
||||
"no_notifs": "您没有新的通知",
|
||||
"see_all": "All notifications",
|
||||
"mark_all_read": "Mark all read",
|
||||
"see_all": "全部通知",
|
||||
"mark_all_read": "标记全部已读",
|
||||
"back_to_home": "返回 %1",
|
||||
"outgoing_link": "站外链接",
|
||||
"outgoing_link_message": "您正在离开 %1",
|
||||
|
||||
@@ -44,9 +44,7 @@ ajaxify.widgets = { render: render };
|
||||
quiet = true;
|
||||
}
|
||||
|
||||
app.leaveCurrentRoom();
|
||||
|
||||
$(window).off('scroll');
|
||||
ajaxify.cleanup(url, ajaxify.data.template.name);
|
||||
|
||||
if ($('#content').hasClass('ajaxifying') && apiXHR) {
|
||||
apiXHR.abort();
|
||||
@@ -456,6 +454,12 @@ ajaxify.widgets = { render: render };
|
||||
});
|
||||
};
|
||||
|
||||
ajaxify.cleanup = (url, tpl_url) => {
|
||||
app.leaveCurrentRoom();
|
||||
$(window).off('scroll');
|
||||
hooks.fire('action:ajaxify.cleanup', { url, tpl_url });
|
||||
};
|
||||
|
||||
require(['translator', 'benchpress'], function (translator, Benchpress) {
|
||||
translator.translate('[[error:no-connection]]');
|
||||
translator.translate('[[error:socket-reconnect-failed]]');
|
||||
|
||||
@@ -73,17 +73,39 @@ define('forum/topic', [
|
||||
};
|
||||
|
||||
function handleTopicSearch() {
|
||||
if (config.topicSearchEnabled) {
|
||||
require(['mousetrap', 'search'], function (mousetrap, search) {
|
||||
mousetrap.bind(['command+f', 'ctrl+f'], function (e) {
|
||||
if (ajaxify.data.template.topic) {
|
||||
require(['mousetrap'], (mousetrap) => {
|
||||
if (config.topicSearchEnabled) {
|
||||
require(['search'], function (search) {
|
||||
mousetrap.bind(['command+f', 'ctrl+f'], function (e) {
|
||||
e.preventDefault();
|
||||
$('#search-fields input').val('in:topic-' + ajaxify.data.tid + ' ');
|
||||
search.showAndFocusInput();
|
||||
}
|
||||
});
|
||||
|
||||
hooks.onPage('action:ajaxify.cleanup', () => {
|
||||
mousetrap.unbind(['command+f', 'ctrl+f']);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
mousetrap.bind('j', () => {
|
||||
const index = navigator.getIndex();
|
||||
const count = navigator.getCount();
|
||||
if (index === count) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.scrollToIndex(index, true, 0);
|
||||
});
|
||||
}
|
||||
|
||||
mousetrap.bind('k', () => {
|
||||
const index = navigator.getIndex();
|
||||
if (index === 1) {
|
||||
return;
|
||||
}
|
||||
navigator.scrollToIndex(index - 2, true, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Topic.toTop = function () {
|
||||
|
||||
@@ -158,7 +158,7 @@ define('forum/topic/events', [
|
||||
hooks.fire('action:posts.edited', data);
|
||||
}
|
||||
|
||||
if (data.topic.tags && tagsUpdated(data.topic.tags)) {
|
||||
if (data.topic.tags && data.topic.tagsupdated) {
|
||||
Benchpress.render('partials/topic/tags', { tags: data.topic.tags }).then(function (html) {
|
||||
const tags = $('.tags');
|
||||
|
||||
@@ -171,19 +171,6 @@ define('forum/topic/events', [
|
||||
postTools.removeMenu(components.get('post', 'pid', data.post.pid));
|
||||
}
|
||||
|
||||
function tagsUpdated(tags) {
|
||||
if (tags.length !== $('.tags').first().children().length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tags.length; i += 1) {
|
||||
if (!$('.tags .tag-item[data-tag="' + tags[i].value + '"]').length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function onPostPurged(postData) {
|
||||
if (!postData || parseInt(postData.tid, 10) !== parseInt(ajaxify.data.tid, 10)) {
|
||||
return;
|
||||
|
||||
@@ -47,7 +47,6 @@ define('navigator', ['forum/pagination', 'components', 'hooks', 'alerts'], funct
|
||||
thumb = $('.scroller-thumb');
|
||||
thumbText = thumb.find('.thumb-text');
|
||||
|
||||
|
||||
$(window).off('scroll', navigator.delayedUpdate).on('scroll', navigator.delayedUpdate);
|
||||
|
||||
paginationBlockEl.find('.dropdown-menu').off('click').on('click', function (e) {
|
||||
@@ -331,6 +330,8 @@ define('navigator', ['forum/pagination', 'components', 'hooks', 'alerts'], funct
|
||||
return parts[1] + '/' + parts[2] + '/' + parts[3] + (index ? '/' + index : '');
|
||||
}
|
||||
|
||||
navigator.getCount = () => count;
|
||||
|
||||
navigator.setCount = function (value) {
|
||||
value = parseInt(value, 10);
|
||||
if (value === count) {
|
||||
@@ -440,6 +441,14 @@ define('navigator', ['forum/pagination', 'components', 'hooks', 'alerts'], funct
|
||||
toggle(!!count);
|
||||
};
|
||||
|
||||
navigator.getIndex = () => index;
|
||||
|
||||
navigator.setIndex = (newIndex) => {
|
||||
index = newIndex + 1;
|
||||
navigator.updateTextAndProgressBar();
|
||||
setThumbToIndex(index);
|
||||
};
|
||||
|
||||
navigator.updateTextAndProgressBar = function () {
|
||||
if (!utils.isNumber(index)) {
|
||||
return;
|
||||
@@ -544,20 +553,22 @@ define('navigator', ['forum/pagination', 'components', 'hooks', 'alerts'], funct
|
||||
|
||||
navigator.scrollToPostIndex = function (postIndex, highlight, duration) {
|
||||
const scrollTo = components.get('post', 'index', postIndex);
|
||||
navigator.scrollToElement(scrollTo, highlight, duration);
|
||||
navigator.scrollToElement(scrollTo, highlight, duration, postIndex);
|
||||
};
|
||||
|
||||
navigator.scrollToTopicIndex = function (topicIndex, highlight, duration) {
|
||||
const scrollTo = $('[component="category/topic"][data-index="' + topicIndex + '"]');
|
||||
navigator.scrollToElement(scrollTo, highlight, duration);
|
||||
navigator.scrollToElement(scrollTo, highlight, duration, topicIndex);
|
||||
};
|
||||
|
||||
navigator.scrollToElement = function (scrollTo, highlight, duration) {
|
||||
navigator.scrollToElement = async (scrollTo, highlight, duration, newIndex = null) => {
|
||||
if (!scrollTo.length) {
|
||||
navigator.scrollActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await hooks.fire('filter:navigator.scroll', { scrollTo, highlight, duration, newIndex });
|
||||
|
||||
const postHeight = scrollTo.outerHeight(true);
|
||||
const navbarHeight = components.get('navbar').outerHeight(true);
|
||||
const topicHeaderHeight = $('.topic-header').outerHeight(true) || 0;
|
||||
@@ -573,9 +584,11 @@ define('navigator', ['forum/pagination', 'components', 'hooks', 'alerts'], funct
|
||||
function animateScroll() {
|
||||
function reenableScroll() {
|
||||
// Re-enable onScroll behaviour
|
||||
$(window).on('scroll', navigator.delayedUpdate);
|
||||
const scrollToRect = scrollTo.get(0).getBoundingClientRect();
|
||||
navigator.update(scrollToRect.top);
|
||||
setTimeout(() => { // fixes race condition from jQuery — onAnimateComplete called too quickly
|
||||
$(window).on('scroll', navigator.delayedUpdate);
|
||||
|
||||
hooks.fire('action:navigator.scrolled', { scrollTo, highlight, duration, newIndex });
|
||||
}, 50);
|
||||
}
|
||||
function onAnimateComplete() {
|
||||
if (done) {
|
||||
@@ -586,8 +599,13 @@ define('navigator', ['forum/pagination', 'components', 'hooks', 'alerts'], funct
|
||||
|
||||
navigator.scrollActive = false;
|
||||
highlightPost();
|
||||
$('body').scrollTop($('body').scrollTop() - 1);
|
||||
$('html').scrollTop($('html').scrollTop() - 1);
|
||||
|
||||
const scrollToRect = scrollTo.get(0).getBoundingClientRect();
|
||||
if (!newIndex) {
|
||||
navigator.update(scrollToRect.top);
|
||||
} else {
|
||||
navigator.setIndex(newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
let scrollTop = 0;
|
||||
|
||||
@@ -151,7 +151,7 @@ define('taskbar', ['benchpress', 'translator', 'hooks'], function (Benchpress, t
|
||||
|
||||
const taskbarEl = $('<li></li>')
|
||||
.addClass(data.options.className)
|
||||
.html('<a href="#"' + (data.options.image ? ' style="background-image: url(\'' + data.options.image + '\'); background-size: cover;"' : '') + '>' +
|
||||
.html('<a href="#"' + (data.options.image ? ' style="background-image: url(\'' + data.options.image.replace(///g, '/') + '\'); background-size: cover;"' : '') + '>' +
|
||||
(data.options.icon ? '<i class="fa ' + data.options.icon + '"></i> ' : '') +
|
||||
'<span aria-label="' + title + '" component="taskbar/title">' + title + '</span>' +
|
||||
'</a>')
|
||||
@@ -186,7 +186,7 @@ define('taskbar', ['benchpress', 'translator', 'hooks'], function (Benchpress, t
|
||||
element.find('i').attr('class', 'fa fa-' + value);
|
||||
break;
|
||||
case 'image':
|
||||
element.find('a').css('background-image', value ? 'url("' + value + '")' : '');
|
||||
element.find('a').css('background-image', value ? 'url("' + value.replace(///g, '/') + '")' : '');
|
||||
break;
|
||||
case 'background-color':
|
||||
element.find('a').css('background-color', value);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const winston = require('winston');
|
||||
const _ = require('lodash');
|
||||
|
||||
const db = require('../database');
|
||||
@@ -11,11 +12,14 @@ const plugins = require('../plugins');
|
||||
const batch = require('../batch');
|
||||
|
||||
module.exports = function (Categories) {
|
||||
Categories.getRecentReplies = async function (cid, uid, count) {
|
||||
if (!parseInt(count, 10)) {
|
||||
return [];
|
||||
Categories.getRecentReplies = async function (cid, uid, start, stop) {
|
||||
// backwards compatibility, treat start as count
|
||||
if (stop === undefined && start > 0) {
|
||||
winston.warn('[Categories.getRecentReplies] 3 params deprecated please use Categories.getRecentReplies(cid, uid, start, stop)');
|
||||
stop = start - 1;
|
||||
start = 0;
|
||||
}
|
||||
let pids = await db.getSortedSetRevRange(`cid:${cid}:pids`, 0, count - 1);
|
||||
let pids = await db.getSortedSetRevRange(`cid:${cid}:pids`, start, stop);
|
||||
pids = await privileges.posts.filter('topics:read', pids, uid);
|
||||
return await posts.getPostSummaryByPids(pids, uid, { stripTags: true });
|
||||
};
|
||||
|
||||
@@ -266,6 +266,10 @@ program
|
||||
].join('\n')}`);
|
||||
})
|
||||
.action((scripts, options) => {
|
||||
if (program.opts().dev) {
|
||||
process.env.NODE_ENV = 'development';
|
||||
global.env = 'development';
|
||||
}
|
||||
require('./upgrade').upgrade(scripts.length ? scripts : true, options);
|
||||
});
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ pkgInstall.installAll = () => {
|
||||
command = `cnpm install ${prod ? ' --production' : ''}`;
|
||||
break;
|
||||
default:
|
||||
command += prod ? ' --production' : '';
|
||||
command += prod ? ' --omit=dev' : '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,11 @@ module.exports = function (middleware) {
|
||||
return controllers.helpers.notAllowed(req, res);
|
||||
}
|
||||
|
||||
const uid = await user.getUidByUserslug(req.params.userslug);
|
||||
if (!['uid', 'userslug'].some(param => req.params.hasOwnProperty(param))) {
|
||||
return controllers.helpers.notAllowed(req, res);
|
||||
}
|
||||
|
||||
const uid = req.params.uid || await user.getUidByUserslug(req.params.userslug);
|
||||
let allowed = await privileges.users.canEdit(req.uid, uid);
|
||||
if (allowed) {
|
||||
return next();
|
||||
|
||||
@@ -7,7 +7,7 @@ const db = require('../database');
|
||||
const meta = require('../meta');
|
||||
const plugins = require('../plugins');
|
||||
const translator = require('../translator');
|
||||
|
||||
const topics = require('../topics');
|
||||
|
||||
module.exports = function (Posts) {
|
||||
const Diffs = {};
|
||||
@@ -38,16 +38,24 @@ module.exports = function (Posts) {
|
||||
};
|
||||
|
||||
Diffs.save = async function (data) {
|
||||
const { pid, uid, oldContent, newContent, edited } = data;
|
||||
const { pid, uid, oldContent, newContent, edited, topic } = data;
|
||||
const editTimestamp = edited || Date.now();
|
||||
const patch = diff.createPatch('', newContent, oldContent);
|
||||
const diffData = {
|
||||
uid: uid,
|
||||
pid: pid,
|
||||
};
|
||||
if (oldContent !== newContent) {
|
||||
diffData.patch = diff.createPatch('', newContent, oldContent);
|
||||
}
|
||||
if (topic.renamed) {
|
||||
diffData.title = topic.oldTitle;
|
||||
}
|
||||
if (topic.tagsupdated && Array.isArray(topic.oldTags)) {
|
||||
diffData.tags = topic.oldTags.map(tag => tag && tag.value).filter(Boolean).join(',');
|
||||
}
|
||||
await Promise.all([
|
||||
db.listPrepend(`post:${pid}:diffs`, editTimestamp),
|
||||
db.setObject(`diff:${pid}.${editTimestamp}`, {
|
||||
uid: uid,
|
||||
pid: pid,
|
||||
patch: patch,
|
||||
}),
|
||||
db.setObject(`diff:${pid}.${editTimestamp}`, diffData),
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -71,6 +79,8 @@ module.exports = function (Posts) {
|
||||
content: post.content,
|
||||
req: req,
|
||||
timestamp: since,
|
||||
title: post.topic.title,
|
||||
tags: post.topic.tags.map(tag => tag.value),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -130,6 +140,16 @@ module.exports = function (Posts) {
|
||||
// Replace content with re-constructed content from that point in time
|
||||
post[0].content = diffs.reduce(applyPatch, validator.unescape(post[0].content));
|
||||
|
||||
const titleDiffs = diffs.filter(d => d.hasOwnProperty('title') && d.title);
|
||||
if (titleDiffs.length && post[0].topic) {
|
||||
post[0].topic.title = validator.unescape(String(titleDiffs[titleDiffs.length - 1].title));
|
||||
}
|
||||
const tagDiffs = diffs.filter(d => d.hasOwnProperty('tags') && d.tags);
|
||||
if (tagDiffs.length && post[0].topic) {
|
||||
const tags = tagDiffs[tagDiffs.length - 1].tags.split(',').map(tag => ({ value: tag }));
|
||||
post[0].topic.tags = await topics.getTagData(tags);
|
||||
}
|
||||
|
||||
return post[0];
|
||||
}
|
||||
|
||||
@@ -144,9 +164,12 @@ module.exports = function (Posts) {
|
||||
}
|
||||
|
||||
function applyPatch(content, aDiff) {
|
||||
const result = diff.applyPatch(content, aDiff.patch, {
|
||||
fuzzFactor: 1,
|
||||
});
|
||||
return typeof result === 'string' ? result : content;
|
||||
if (aDiff && aDiff.patch) {
|
||||
const result = diff.applyPatch(content, aDiff.patch, {
|
||||
fuzzFactor: 1,
|
||||
});
|
||||
return typeof result === 'string' ? result : content;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,7 +29,9 @@ module.exports = function (Posts) {
|
||||
throw new Error('[[error:no-post]]');
|
||||
}
|
||||
|
||||
const topicData = await topics.getTopicFields(postData.tid, ['cid', 'mainPid', 'title', 'timestamp', 'scheduled', 'slug']);
|
||||
const topicData = await topics.getTopicFields(postData.tid, [
|
||||
'cid', 'mainPid', 'title', 'timestamp', 'scheduled', 'slug', 'tags',
|
||||
]);
|
||||
|
||||
await scheduledTopicCheck(data, topicData);
|
||||
|
||||
@@ -53,7 +55,10 @@ module.exports = function (Posts) {
|
||||
]);
|
||||
|
||||
await Posts.setPostFields(data.pid, result.post);
|
||||
const contentChanged = data.content !== oldContent;
|
||||
const contentChanged = data.content !== oldContent ||
|
||||
topic.renamed ||
|
||||
topic.tagsupdated;
|
||||
|
||||
if (meta.config.enablePostHistory === 1 && contentChanged) {
|
||||
await Posts.diffs.save({
|
||||
pid: data.pid,
|
||||
@@ -61,6 +66,7 @@ module.exports = function (Posts) {
|
||||
oldContent: oldContent,
|
||||
newContent: data.content,
|
||||
edited: editPostData.edited,
|
||||
topic,
|
||||
});
|
||||
}
|
||||
await Posts.uploads.sync(data.pid);
|
||||
@@ -109,6 +115,7 @@ module.exports = function (Posts) {
|
||||
title: validator.escape(String(topicData.title)),
|
||||
isMainPost: false,
|
||||
renamed: false,
|
||||
tagsupdated: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,15 +131,16 @@ module.exports = function (Posts) {
|
||||
newTopicData.slug = `${tid}/${slugify(title) || 'topic'}`;
|
||||
}
|
||||
|
||||
data.tags = data.tags || [];
|
||||
const tagsupdated = Array.isArray(data.tags) &&
|
||||
!_.isEqual(data.tags, topicData.tags.map(tag => tag.value));
|
||||
|
||||
if (data.tags.length) {
|
||||
if (tagsupdated) {
|
||||
const canTag = await privileges.categories.can('topics:tag', topicData.cid, data.uid);
|
||||
if (!canTag) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
await topics.validateTags(data.tags, topicData.cid, data.uid, tid);
|
||||
}
|
||||
await topics.validateTags(data.tags, topicData.cid, data.uid, tid);
|
||||
|
||||
const results = await plugins.hooks.fire('filter:topic.edit', {
|
||||
req: data.req,
|
||||
@@ -140,7 +148,9 @@ module.exports = function (Posts) {
|
||||
data: data,
|
||||
});
|
||||
await db.setObject(`topic:${tid}`, results.topic);
|
||||
await topics.updateTopicTags(tid, data.tags);
|
||||
if (tagsupdated) {
|
||||
await topics.updateTopicTags(tid, data.tags);
|
||||
}
|
||||
const tags = await topics.getTopicTagsObjects(tid);
|
||||
|
||||
if (rescheduling(data, topicData)) {
|
||||
@@ -149,7 +159,7 @@ module.exports = function (Posts) {
|
||||
|
||||
newTopicData.tags = data.tags;
|
||||
newTopicData.oldTitle = topicData.title;
|
||||
const renamed = translator.escape(validator.escape(String(title))) !== topicData.title;
|
||||
const renamed = title && translator.escape(validator.escape(String(title))) !== topicData.title;
|
||||
plugins.hooks.fire('action:topic.edit', { topic: newTopicData, uid: data.uid });
|
||||
return {
|
||||
tid: tid,
|
||||
@@ -160,8 +170,10 @@ module.exports = function (Posts) {
|
||||
slug: newTopicData.slug || topicData.slug,
|
||||
isMainPost: true,
|
||||
renamed: renamed,
|
||||
rescheduled: rescheduling(data, topicData),
|
||||
tagsupdated: tagsupdated,
|
||||
tags: tags,
|
||||
oldTags: topicData.tags,
|
||||
rescheduled: rescheduling(data, topicData),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -76,9 +76,15 @@ module.exports = function (Posts) {
|
||||
}
|
||||
|
||||
async function getTopicAndCategories(tids) {
|
||||
const topicsData = await topics.getTopicsFields(tids, ['uid', 'tid', 'title', 'cid', 'slug', 'deleted', 'scheduled', 'postcount', 'mainPid', 'teaserPid']);
|
||||
const topicsData = await topics.getTopicsFields(tids, [
|
||||
'uid', 'tid', 'title', 'cid', 'tags', 'slug',
|
||||
'deleted', 'scheduled', 'postcount', 'mainPid', 'teaserPid',
|
||||
]);
|
||||
const cids = _.uniq(topicsData.map(topic => topic && topic.cid));
|
||||
const categoriesData = await categories.getCategoriesFields(cids, ['cid', 'name', 'icon', 'slug', 'parentCid', 'bgColor', 'color', 'backgroundImage', 'imageClass']);
|
||||
const categoriesData = await categories.getCategoriesFields(cids, [
|
||||
'cid', 'name', 'icon', 'slug', 'parentCid',
|
||||
'bgColor', 'color', 'backgroundImage', 'imageClass',
|
||||
]);
|
||||
return { topics: topicsData, categories: categoriesData };
|
||||
}
|
||||
|
||||
|
||||
@@ -307,7 +307,11 @@ async function generateForRecentPosts(req, res, next) {
|
||||
if (meta.config['feeds:disableRSS']) {
|
||||
return next();
|
||||
}
|
||||
const postData = await posts.getRecentPosts(req.uid, 0, 19, 'month');
|
||||
const page = parseInt(req.query.page, 10) || 1;
|
||||
const postsPerPage = 20;
|
||||
const start = Math.max(0, (page - 1) * postsPerPage);
|
||||
const stop = start + postsPerPage - 1;
|
||||
const postData = await posts.getRecentPosts(req.uid, start, stop, 'month');
|
||||
const feed = generateForPostsFeed({
|
||||
title: 'Recent Posts',
|
||||
description: 'A list of recent posts',
|
||||
@@ -323,11 +327,14 @@ async function generateForCategoryRecentPosts(req, res) {
|
||||
return controllers404.handle404(req, res);
|
||||
}
|
||||
const cid = req.params.category_id;
|
||||
|
||||
const page = parseInt(req.query.page, 10) || 1;
|
||||
const topicsPerPage = 20;
|
||||
const start = Math.max(0, (page - 1) * topicsPerPage);
|
||||
const stop = start + topicsPerPage - 1;
|
||||
const [userPrivileges, category, postData] = await Promise.all([
|
||||
privileges.categories.get(cid, req.uid),
|
||||
categories.getCategoryData(cid),
|
||||
categories.getRecentReplies(cid, req.uid || req.query.uid || 0, 20),
|
||||
categories.getRecentReplies(cid, req.uid || req.query.uid || 0, start, stop),
|
||||
]);
|
||||
|
||||
if (!category) {
|
||||
|
||||
@@ -10,7 +10,7 @@ const SocketCategories = module.exports;
|
||||
require('./categories/search')(SocketCategories);
|
||||
|
||||
SocketCategories.getRecentReplies = async function (socket, cid) {
|
||||
return await categories.getRecentReplies(cid, socket.uid, 4);
|
||||
return await categories.getRecentReplies(cid, socket.uid, 0, 4);
|
||||
};
|
||||
|
||||
SocketCategories.get = async function (socket) {
|
||||
|
||||
@@ -194,8 +194,11 @@ describe('API', async () => {
|
||||
const socketAdmin = require('../src/socket.io/admin');
|
||||
// export data for admin user
|
||||
await socketUser.exportProfile({ uid: adminUid }, { uid: adminUid });
|
||||
await wait(2000);
|
||||
await socketUser.exportPosts({ uid: adminUid }, { uid: adminUid });
|
||||
await wait(2000);
|
||||
await socketUser.exportUploads({ uid: adminUid }, { uid: adminUid });
|
||||
await wait(2000);
|
||||
await socketAdmin.user.exportUsersCSV({ uid: adminUid }, {});
|
||||
// wait for export child process to complete
|
||||
await wait(5000);
|
||||
|
||||
@@ -425,6 +425,7 @@ describe('Post\'s', () => {
|
||||
cid: cid,
|
||||
title: 'topic to edit',
|
||||
content: 'A post to edit',
|
||||
tags: ['nodebb'],
|
||||
}, (err, data) => {
|
||||
assert.ifError(err);
|
||||
pid = data.postData.pid;
|
||||
|
||||
@@ -212,7 +212,7 @@ describe('Upload Controllers', () => {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(res.statusCode, 500);
|
||||
assert(body && body.status && body.status.message);
|
||||
assert(body.status.message.startsWith('Input file has corrupt header: pngload: end of stream'));
|
||||
assert.strictEqual(body.status.message, 'Input file contains unsupported image format');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user