mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-11 01:52:55 +01:00
feat: merge changes
allow selecting main topic to merge into allow specifying a new title for merge topic upon merge go to correct topic new tests for merging with options
This commit is contained in:
@@ -77,12 +77,19 @@ define('forum/topic/merge', function () {
|
|||||||
function mergeTopics(btn) {
|
function mergeTopics(btn) {
|
||||||
btn.attr('disabled', true);
|
btn.attr('disabled', true);
|
||||||
var tids = Object.keys(selectedTids);
|
var tids = Object.keys(selectedTids);
|
||||||
socket.emit('topics.merge', tids, function (err) {
|
var options = {};
|
||||||
|
if (modal.find('.merge-main-topic-radio').is(':checked')) {
|
||||||
|
options.mainTid = modal.find('.merge-main-topic-select').val();
|
||||||
|
} else if (modal.find('.merge-new-title-radio').is(':checked')) {
|
||||||
|
options.newTopicTitle = modal.find('.merge-new-title-input').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('topics.merge', { tids: tids, options: options }, function (err, tid) {
|
||||||
btn.removeAttr('disabled');
|
btn.removeAttr('disabled');
|
||||||
if (err) {
|
if (err) {
|
||||||
return app.alertError(err.message);
|
return app.alertError(err.message);
|
||||||
}
|
}
|
||||||
ajaxify.go('/topic/' + tids[0]);
|
ajaxify.go('/topic/' + tid);
|
||||||
closeModal();
|
closeModal();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -103,7 +110,7 @@ define('forum/topic/merge', function () {
|
|||||||
topics: topics,
|
topics: topics,
|
||||||
}, function (html) {
|
}, function (html) {
|
||||||
modal.find('.topics-section').html(html.find('.topics-section').html());
|
modal.find('.topics-section').html(html.find('.topics-section').html());
|
||||||
modal.find('.main-topic-select').html(html.find('.main-topic-select').html());
|
modal.find('.merge-main-topic-select').html(html.find('.merge-main-topic-select').html());
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
modal.find('.topics-section').translateHtml('[[error:no-topics-selected]]');
|
modal.find('.topics-section').translateHtml('[[error:no-topics-selected]]');
|
||||||
|
|||||||
@@ -4,14 +4,18 @@ const topics = require('../../topics');
|
|||||||
const privileges = require('../../privileges');
|
const privileges = require('../../privileges');
|
||||||
|
|
||||||
module.exports = function (SocketTopics) {
|
module.exports = function (SocketTopics) {
|
||||||
SocketTopics.merge = async function (socket, tids) {
|
SocketTopics.merge = async function (socket, data) {
|
||||||
if (!Array.isArray(tids)) {
|
if (!data || !Array.isArray(data.tids)) {
|
||||||
throw new Error('[[error:invalid-data]]');
|
throw new Error('[[error:invalid-data]]');
|
||||||
}
|
}
|
||||||
const allowed = await Promise.all(tids.map(tid => privileges.topics.isAdminOrMod(tid, socket.uid)));
|
const allowed = await Promise.all(data.tids.map(tid => privileges.topics.isAdminOrMod(tid, socket.uid)));
|
||||||
if (allowed.includes(false)) {
|
if (allowed.includes(false)) {
|
||||||
throw new Error('[[error:no-privileges]]');
|
throw new Error('[[error:no-privileges]]');
|
||||||
}
|
}
|
||||||
await topics.merge(tids, socket.uid);
|
if (data.options && data.options.mainTid && !data.tids.includes(data.options.mainTid)) {
|
||||||
|
throw new Error('[[error:invalid-data]]');
|
||||||
|
}
|
||||||
|
const mergeIntoTid = await topics.merge(data.tids, socket.uid, data.options);
|
||||||
|
return mergeIntoTid;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,10 +4,18 @@ const async = require('async');
|
|||||||
const plugins = require('../plugins');
|
const plugins = require('../plugins');
|
||||||
|
|
||||||
module.exports = function (Topics) {
|
module.exports = function (Topics) {
|
||||||
Topics.merge = async function (tids, uid) {
|
Topics.merge = async function (tids, uid, options) {
|
||||||
const mergeIntoTid = findOldestTopic(tids);
|
options = options || {};
|
||||||
|
const oldestTid = findOldestTopic(tids);
|
||||||
|
let mergeIntoTid = oldestTid;
|
||||||
|
if (options.mainTid) {
|
||||||
|
mergeIntoTid = options.mainTid;
|
||||||
|
} else if (options.newTopicTitle) {
|
||||||
|
mergeIntoTid = await createNewTopic(options.newTopicTitle, oldestTid);
|
||||||
|
}
|
||||||
|
|
||||||
const otherTids = tids.filter(tid => tid && parseInt(tid, 10) !== parseInt(mergeIntoTid, 10));
|
const otherTids = tids.sort((a, b) => a - b)
|
||||||
|
.filter(tid => tid && parseInt(tid, 10) !== parseInt(mergeIntoTid, 10));
|
||||||
|
|
||||||
await async.eachSeries(otherTids, async function (tid) {
|
await async.eachSeries(otherTids, async function (tid) {
|
||||||
const pids = await Topics.getPids(tid);
|
const pids = await Topics.getPids(tid);
|
||||||
@@ -25,8 +33,19 @@ module.exports = function (Topics) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
plugins.fireHook('action:topic.merge', { uid: uid, tids: tids, mergeIntoTid: mergeIntoTid, otherTids: otherTids });
|
plugins.fireHook('action:topic.merge', { uid: uid, tids: tids, mergeIntoTid: mergeIntoTid, otherTids: otherTids });
|
||||||
|
return mergeIntoTid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function createNewTopic(title, oldestTid) {
|
||||||
|
const topicData = await Topics.getTopicFields(oldestTid, ['uid', 'cid']);
|
||||||
|
const tid = await Topics.create({
|
||||||
|
uid: topicData.uid,
|
||||||
|
cid: topicData.cid,
|
||||||
|
title: title,
|
||||||
|
});
|
||||||
|
return tid;
|
||||||
|
}
|
||||||
|
|
||||||
function findOldestTopic(tids) {
|
function findOldestTopic(tids) {
|
||||||
return Math.min.apply(null, tids);
|
return Math.min.apply(null, tids);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2080,6 +2080,11 @@ describe('Topic\'s', function () {
|
|||||||
var topic1Data;
|
var topic1Data;
|
||||||
var topic2Data;
|
var topic2Data;
|
||||||
|
|
||||||
|
async function getTopic(tid) {
|
||||||
|
const topicData = await topics.getTopicData(tid);
|
||||||
|
return await topics.getTopicWithPosts(topicData, 'tid:' + topicData.tid + ':posts', adminUid, 0, 19, false);
|
||||||
|
}
|
||||||
|
|
||||||
before(function (done) {
|
before(function (done) {
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
function (next) {
|
function (next) {
|
||||||
@@ -2111,18 +2116,17 @@ describe('Topic\'s', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should error if user does not have privileges', function (done) {
|
it('should error if user does not have privileges', function (done) {
|
||||||
socketTopics.merge({ uid: 0 }, [topic2Data.tid, topic1Data.tid], function (err) {
|
socketTopics.merge({ uid: 0 }, { tids: [topic2Data.tid, topic1Data.tid] }, function (err) {
|
||||||
assert.equal(err.message, '[[error:no-privileges]]');
|
assert.equal(err.message, '[[error:no-privileges]]');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should merge 2 topics', async function () {
|
it('should merge 2 topics', async function () {
|
||||||
await socketTopics.merge({ uid: adminUid }, [topic2Data.tid, topic1Data.tid]);
|
await socketTopics.merge({ uid: adminUid }, {
|
||||||
async function getTopic(tid) {
|
tids: [topic2Data.tid, topic1Data.tid],
|
||||||
const topicData = await topics.getTopicData(tid);
|
});
|
||||||
return await topics.getTopicWithPosts(topicData, 'tid:' + topicData.tid + ':posts', adminUid, 0, 19, false);
|
|
||||||
}
|
|
||||||
const [topic1, topic2] = await Promise.all([
|
const [topic1, topic2] = await Promise.all([
|
||||||
getTopic(topic1Data.tid),
|
getTopic(topic1Data.tid),
|
||||||
getTopic(topic2Data.tid),
|
getTopic(topic2Data.tid),
|
||||||
@@ -2136,6 +2140,7 @@ describe('Topic\'s', function () {
|
|||||||
assert.equal(topic1.posts[1].content, 'topic 2 OP');
|
assert.equal(topic1.posts[1].content, 'topic 2 OP');
|
||||||
assert.equal(topic1.posts[2].content, 'topic 1 reply');
|
assert.equal(topic1.posts[2].content, 'topic 1 reply');
|
||||||
assert.equal(topic1.posts[3].content, 'topic 2 reply');
|
assert.equal(topic1.posts[3].content, 'topic 2 reply');
|
||||||
|
assert.equal(topic1.title, 'topic 1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return properly for merged topic', function (done) {
|
it('should return properly for merged topic', function (done) {
|
||||||
@@ -2147,6 +2152,65 @@ describe('Topic\'s', function () {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should merge 2 topics with options mainTid', async function () {
|
||||||
|
const topic1Result = await topics.post({ uid: uid, cid: categoryObj.cid, title: 'topic 1', content: 'topic 1 OP' });
|
||||||
|
const topic2Result = await topics.post({ uid: uid, cid: categoryObj.cid, title: 'topic 2', content: 'topic 2 OP' });
|
||||||
|
await topics.reply({ uid: uid, content: 'topic 1 reply', tid: topic1Result.topicData.tid });
|
||||||
|
await topics.reply({ uid: uid, content: 'topic 2 reply', tid: topic2Result.topicData.tid });
|
||||||
|
await socketTopics.merge({ uid: adminUid }, {
|
||||||
|
tids: [topic2Result.topicData.tid, topic1Result.topicData.tid],
|
||||||
|
options: {
|
||||||
|
mainTid: topic2Result.topicData.tid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [topic1, topic2] = await Promise.all([
|
||||||
|
getTopic(topic1Result.topicData.tid),
|
||||||
|
getTopic(topic2Result.topicData.tid),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(topic1.posts.length, 0);
|
||||||
|
assert.equal(topic2.posts.length, 4);
|
||||||
|
assert.equal(topic1.deleted, true);
|
||||||
|
|
||||||
|
assert.equal(topic2.posts[0].content, 'topic 2 OP');
|
||||||
|
assert.equal(topic2.posts[1].content, 'topic 1 OP');
|
||||||
|
assert.equal(topic2.posts[2].content, 'topic 1 reply');
|
||||||
|
assert.equal(topic2.posts[3].content, 'topic 2 reply');
|
||||||
|
assert.equal(topic2.title, 'topic 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge 2 topics with options newTopicTitle', async function () {
|
||||||
|
const topic1Result = await topics.post({ uid: uid, cid: categoryObj.cid, title: 'topic 1', content: 'topic 1 OP' });
|
||||||
|
const topic2Result = await topics.post({ uid: uid, cid: categoryObj.cid, title: 'topic 2', content: 'topic 2 OP' });
|
||||||
|
await topics.reply({ uid: uid, content: 'topic 1 reply', tid: topic1Result.topicData.tid });
|
||||||
|
await topics.reply({ uid: uid, content: 'topic 2 reply', tid: topic2Result.topicData.tid });
|
||||||
|
const mergeTid = await socketTopics.merge({ uid: adminUid }, {
|
||||||
|
tids: [topic2Result.topicData.tid, topic1Result.topicData.tid],
|
||||||
|
options: {
|
||||||
|
newTopicTitle: 'new merge topic',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [topic1, topic2, topic3] = await Promise.all([
|
||||||
|
getTopic(topic1Result.topicData.tid),
|
||||||
|
getTopic(topic2Result.topicData.tid),
|
||||||
|
getTopic(mergeTid),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.equal(topic1.posts.length, 0);
|
||||||
|
assert.equal(topic2.posts.length, 0);
|
||||||
|
assert.equal(topic3.posts.length, 4);
|
||||||
|
assert.equal(topic1.deleted, true);
|
||||||
|
assert.equal(topic2.deleted, true);
|
||||||
|
|
||||||
|
assert.equal(topic3.posts[0].content, 'topic 1 OP');
|
||||||
|
assert.equal(topic3.posts[1].content, 'topic 2 OP');
|
||||||
|
assert.equal(topic3.posts[2].content, 'topic 1 reply');
|
||||||
|
assert.equal(topic3.posts[3].content, 'topic 2 reply');
|
||||||
|
assert.equal(topic3.title, 'new merge topic');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sorted topics', function () {
|
describe('sorted topics', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user