mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: store topic title and tags in diffs (#10900)
* feat: store topic title and tags in diffs allow restoring post diff if tags didn't change * test: fix tests, fast computer problems
This commit is contained in:
committed by
GitHub
parent
8e2129f858
commit
b5dd89e1c0
@@ -158,7 +158,7 @@ define('forum/topic/events', [
|
|||||||
hooks.fire('action:posts.edited', data);
|
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) {
|
Benchpress.render('partials/topic/tags', { tags: data.topic.tags }).then(function (html) {
|
||||||
const tags = $('.tags');
|
const tags = $('.tags');
|
||||||
|
|
||||||
@@ -171,19 +171,6 @@ define('forum/topic/events', [
|
|||||||
postTools.removeMenu(components.get('post', 'pid', data.post.pid));
|
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) {
|
function onPostPurged(postData) {
|
||||||
if (!postData || parseInt(postData.tid, 10) !== parseInt(ajaxify.data.tid, 10)) {
|
if (!postData || parseInt(postData.tid, 10) !== parseInt(ajaxify.data.tid, 10)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const db = require('../database');
|
|||||||
const meta = require('../meta');
|
const meta = require('../meta');
|
||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
const translator = require('../translator');
|
const translator = require('../translator');
|
||||||
|
const topics = require('../topics');
|
||||||
|
|
||||||
module.exports = function (Posts) {
|
module.exports = function (Posts) {
|
||||||
const Diffs = {};
|
const Diffs = {};
|
||||||
@@ -38,16 +38,24 @@ module.exports = function (Posts) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Diffs.save = async function (data) {
|
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 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([
|
await Promise.all([
|
||||||
db.listPrepend(`post:${pid}:diffs`, editTimestamp),
|
db.listPrepend(`post:${pid}:diffs`, editTimestamp),
|
||||||
db.setObject(`diff:${pid}.${editTimestamp}`, {
|
db.setObject(`diff:${pid}.${editTimestamp}`, diffData),
|
||||||
uid: uid,
|
|
||||||
pid: pid,
|
|
||||||
patch: patch,
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,6 +79,8 @@ module.exports = function (Posts) {
|
|||||||
content: post.content,
|
content: post.content,
|
||||||
req: req,
|
req: req,
|
||||||
timestamp: since,
|
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
|
// Replace content with re-constructed content from that point in time
|
||||||
post[0].content = diffs.reduce(applyPatch, validator.unescape(post[0].content));
|
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];
|
return post[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,9 +164,12 @@ module.exports = function (Posts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyPatch(content, aDiff) {
|
function applyPatch(content, aDiff) {
|
||||||
const result = diff.applyPatch(content, aDiff.patch, {
|
if (aDiff && aDiff.patch) {
|
||||||
fuzzFactor: 1,
|
const result = diff.applyPatch(content, aDiff.patch, {
|
||||||
});
|
fuzzFactor: 1,
|
||||||
return typeof result === 'string' ? result : content;
|
});
|
||||||
|
return typeof result === 'string' ? result : content;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ module.exports = function (Posts) {
|
|||||||
throw new Error('[[error:no-post]]');
|
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);
|
await scheduledTopicCheck(data, topicData);
|
||||||
|
|
||||||
@@ -53,7 +55,10 @@ module.exports = function (Posts) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await Posts.setPostFields(data.pid, result.post);
|
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) {
|
if (meta.config.enablePostHistory === 1 && contentChanged) {
|
||||||
await Posts.diffs.save({
|
await Posts.diffs.save({
|
||||||
pid: data.pid,
|
pid: data.pid,
|
||||||
@@ -61,6 +66,7 @@ module.exports = function (Posts) {
|
|||||||
oldContent: oldContent,
|
oldContent: oldContent,
|
||||||
newContent: data.content,
|
newContent: data.content,
|
||||||
edited: editPostData.edited,
|
edited: editPostData.edited,
|
||||||
|
topic,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await Posts.uploads.sync(data.pid);
|
await Posts.uploads.sync(data.pid);
|
||||||
@@ -109,6 +115,7 @@ module.exports = function (Posts) {
|
|||||||
title: validator.escape(String(topicData.title)),
|
title: validator.escape(String(topicData.title)),
|
||||||
isMainPost: false,
|
isMainPost: false,
|
||||||
renamed: false,
|
renamed: false,
|
||||||
|
tagsupdated: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,15 +131,16 @@ module.exports = function (Posts) {
|
|||||||
newTopicData.slug = `${tid}/${slugify(title) || 'topic'}`;
|
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);
|
const canTag = await privileges.categories.can('topics:tag', topicData.cid, data.uid);
|
||||||
if (!canTag) {
|
if (!canTag) {
|
||||||
throw new Error('[[error:no-privileges]]');
|
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', {
|
const results = await plugins.hooks.fire('filter:topic.edit', {
|
||||||
req: data.req,
|
req: data.req,
|
||||||
@@ -140,7 +148,9 @@ module.exports = function (Posts) {
|
|||||||
data: data,
|
data: data,
|
||||||
});
|
});
|
||||||
await db.setObject(`topic:${tid}`, results.topic);
|
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);
|
const tags = await topics.getTopicTagsObjects(tid);
|
||||||
|
|
||||||
if (rescheduling(data, topicData)) {
|
if (rescheduling(data, topicData)) {
|
||||||
@@ -149,7 +159,7 @@ module.exports = function (Posts) {
|
|||||||
|
|
||||||
newTopicData.tags = data.tags;
|
newTopicData.tags = data.tags;
|
||||||
newTopicData.oldTitle = topicData.title;
|
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 });
|
plugins.hooks.fire('action:topic.edit', { topic: newTopicData, uid: data.uid });
|
||||||
return {
|
return {
|
||||||
tid: tid,
|
tid: tid,
|
||||||
@@ -160,8 +170,10 @@ module.exports = function (Posts) {
|
|||||||
slug: newTopicData.slug || topicData.slug,
|
slug: newTopicData.slug || topicData.slug,
|
||||||
isMainPost: true,
|
isMainPost: true,
|
||||||
renamed: renamed,
|
renamed: renamed,
|
||||||
rescheduled: rescheduling(data, topicData),
|
tagsupdated: tagsupdated,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
|
oldTags: topicData.tags,
|
||||||
|
rescheduled: rescheduling(data, topicData),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,9 +76,15 @@ module.exports = function (Posts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getTopicAndCategories(tids) {
|
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 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 };
|
return { topics: topicsData, categories: categoriesData };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -194,8 +194,11 @@ describe('API', async () => {
|
|||||||
const socketAdmin = require('../src/socket.io/admin');
|
const socketAdmin = require('../src/socket.io/admin');
|
||||||
// export data for admin user
|
// export data for admin user
|
||||||
await socketUser.exportProfile({ uid: adminUid }, { uid: adminUid });
|
await socketUser.exportProfile({ uid: adminUid }, { uid: adminUid });
|
||||||
|
await wait(2000);
|
||||||
await socketUser.exportPosts({ uid: adminUid }, { uid: adminUid });
|
await socketUser.exportPosts({ uid: adminUid }, { uid: adminUid });
|
||||||
|
await wait(2000);
|
||||||
await socketUser.exportUploads({ uid: adminUid }, { uid: adminUid });
|
await socketUser.exportUploads({ uid: adminUid }, { uid: adminUid });
|
||||||
|
await wait(2000);
|
||||||
await socketAdmin.user.exportUsersCSV({ uid: adminUid }, {});
|
await socketAdmin.user.exportUsersCSV({ uid: adminUid }, {});
|
||||||
// wait for export child process to complete
|
// wait for export child process to complete
|
||||||
await wait(5000);
|
await wait(5000);
|
||||||
|
|||||||
@@ -425,6 +425,7 @@ describe('Post\'s', () => {
|
|||||||
cid: cid,
|
cid: cid,
|
||||||
title: 'topic to edit',
|
title: 'topic to edit',
|
||||||
content: 'A post to edit',
|
content: 'A post to edit',
|
||||||
|
tags: ['nodebb'],
|
||||||
}, (err, data) => {
|
}, (err, data) => {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
pid = data.postData.pid;
|
pid = data.postData.pid;
|
||||||
|
|||||||
Reference in New Issue
Block a user