mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: closes #12902, allow adding users as post editors
This commit is contained in:
@@ -107,10 +107,10 @@
|
||||
"nodebb-plugin-ntfy": "1.7.7",
|
||||
"nodebb-plugin-spam-be-gone": "2.2.2",
|
||||
"nodebb-rewards-essentials": "1.0.0",
|
||||
"nodebb-theme-harmony": "1.2.77",
|
||||
"nodebb-theme-harmony": "1.2.78",
|
||||
"nodebb-theme-lavender": "7.1.10",
|
||||
"nodebb-theme-peace": "2.2.8",
|
||||
"nodebb-theme-persona": "13.3.40",
|
||||
"nodebb-theme-persona": "13.3.41",
|
||||
"nodebb-widget-essentials": "7.0.30",
|
||||
"nodemailer": "6.9.16",
|
||||
"nprogress": "0.2.0",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"restore": "Restore",
|
||||
"move": "Move",
|
||||
"change-owner": "Change Owner",
|
||||
"manage-editors": "Manage Editors",
|
||||
"fork": "Fork",
|
||||
"link": "Link",
|
||||
"share": "Share",
|
||||
@@ -116,6 +117,7 @@
|
||||
"thread-tools.move-posts": "Move Posts",
|
||||
"thread-tools.move-all": "Move All",
|
||||
"thread-tools.change-owner": "Change Owner",
|
||||
"thread-tools.manage-editors": "Manage Editors",
|
||||
"thread-tools.select-category": "Select Category",
|
||||
"thread-tools.fork": "Fork Topic",
|
||||
"thread-tools.tag": "Tag Topic",
|
||||
@@ -177,6 +179,7 @@
|
||||
"move-posts-instruction": "Click the posts you want to move then enter a topic ID or go to the target topic",
|
||||
"move-topic-instruction": "Select the target category and then click move",
|
||||
"change-owner-instruction": "Click the posts you want to assign to another user",
|
||||
"manage-editors-instruction": "Manage the users who can edit this post below.",
|
||||
|
||||
"composer.title-placeholder": "Enter your topic title here...",
|
||||
"composer.handle-placeholder": "Enter your name/handle here",
|
||||
|
||||
77
public/src/client/topic/manage-editors.js
Normal file
77
public/src/client/topic/manage-editors.js
Normal file
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
define('forum/topic/manage-editors', [
|
||||
'autocomplete',
|
||||
'alerts',
|
||||
], function (autocomplete, alerts) {
|
||||
const ManageEditors = {};
|
||||
|
||||
let modal;
|
||||
|
||||
ManageEditors.init = async function (postEl) {
|
||||
if (modal) {
|
||||
return;
|
||||
}
|
||||
const pid = postEl.attr('data-pid');
|
||||
|
||||
let editors = await socket.emit('posts.getEditors', { pid: pid });
|
||||
app.parseAndTranslate('modals/manage-editors', {
|
||||
editors: editors,
|
||||
}, function (html) {
|
||||
modal = html;
|
||||
|
||||
const commitEl = modal.find('#manage_editors_commit');
|
||||
|
||||
$('body').append(modal);
|
||||
|
||||
modal.find('#manage_editors_cancel').on('click', closeModal);
|
||||
|
||||
commitEl.on('click', function () {
|
||||
saveEditors(pid);
|
||||
});
|
||||
|
||||
autocomplete.user(modal.find('#username'), { filters: ['notbanned'] }, function (ev, ui) {
|
||||
const isInEditors = editors.find(e => String(e.uid) === String(ui.item.user.uid));
|
||||
if (!isInEditors) {
|
||||
editors.push(ui.item.user);
|
||||
app.parseAndTranslate('modals/manage-editors', 'editors', {
|
||||
editors: editors,
|
||||
}, function (html) {
|
||||
modal.find('[component="topic/editors"]').html(html);
|
||||
modal.find('#username').val('');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
modal.on('click', 'button.remove-user-icon', function () {
|
||||
const el = $(this).parents('[data-uid]');
|
||||
const uid = el.attr('data-uid');
|
||||
editors = editors.filter(e => String(e.uid) === String(uid));
|
||||
el.remove();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function saveEditors(pid) {
|
||||
const uids = modal.find('[component="topic/editors"]>[data-uid]')
|
||||
.map((i, el) => $(el).attr('data-uid')).get();
|
||||
|
||||
socket.emit('posts.saveEditors', { pid: pid, uids: uids }, function (err) {
|
||||
if (err) {
|
||||
return alerts.error(err);
|
||||
}
|
||||
|
||||
closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
if (modal) {
|
||||
modal.remove();
|
||||
modal = null;
|
||||
}
|
||||
}
|
||||
|
||||
return ManageEditors;
|
||||
});
|
||||
@@ -253,6 +253,13 @@ define('forum/topic/postTools', [
|
||||
});
|
||||
});
|
||||
|
||||
postContainer.on('click', '[component="post/manage-editors"]', function () {
|
||||
const btn = $(this);
|
||||
require(['forum/topic/manage-editors'], function (manageEditors) {
|
||||
manageEditors.init(btn.parents('[data-pid]'));
|
||||
});
|
||||
});
|
||||
|
||||
postContainer.on('click', '[component="post/ban-ip"]', function () {
|
||||
const ip = $(this).attr('data-ip');
|
||||
socket.emit('blacklist.addRule', ip, function (err) {
|
||||
|
||||
@@ -81,6 +81,7 @@ module.exports = function (Posts) {
|
||||
deleteDiffs(pids),
|
||||
deleteFromUploads(pids),
|
||||
db.sortedSetsRemove(['posts:pid', 'posts:votes', 'posts:flagged'], pids),
|
||||
db.deleteAll(pids.map(pid => `pid:${pid}:editors`)),
|
||||
]);
|
||||
|
||||
await resolveFlags(postData, uid);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const db = require('../database');
|
||||
const meta = require('../meta');
|
||||
const posts = require('../posts');
|
||||
const topics = require('../topics');
|
||||
@@ -118,7 +119,8 @@ privsPosts.canEdit = async function (pid, uid) {
|
||||
const results = await utils.promiseParallel({
|
||||
isAdmin: user.isAdministrator(uid),
|
||||
isMod: posts.isModerator([pid], uid),
|
||||
owner: posts.isOwner(pid, uid),
|
||||
isOwner: posts.isOwner(pid, uid),
|
||||
isEditor: db.isSetMember(`pid:${pid}:editors`, uid),
|
||||
edit: privsPosts.can('posts:edit', pid, uid),
|
||||
postData: posts.getPostFields(pid, ['tid', 'timestamp', 'deleted', 'deleterUid']),
|
||||
userData: user.getUserFields(uid, ['reputation']),
|
||||
@@ -158,7 +160,10 @@ privsPosts.canEdit = async function (pid, uid) {
|
||||
results.uid = uid;
|
||||
|
||||
const result = await plugins.hooks.fire('filter:privileges.posts.edit', results);
|
||||
return { flag: result.edit && (result.owner || result.isMod), message: '[[error:no-privileges]]' };
|
||||
return {
|
||||
flag: result.edit && (result.isOwner || result.isEditor || result.isMod),
|
||||
message: '[[error:no-privileges]]',
|
||||
};
|
||||
};
|
||||
|
||||
privsPosts.canDelete = async function (pid, uid) {
|
||||
|
||||
@@ -46,6 +46,7 @@ module.exports = function (SocketPosts) {
|
||||
postData.display_moderator_tools = postData.display_edit_tools || postData.display_delete_tools;
|
||||
postData.display_move_tools = results.isAdmin || results.isModerator;
|
||||
postData.display_change_owner_tools = results.isAdmin || results.isModerator;
|
||||
postData.display_manage_editors_tools = results.isAdmin || results.isModerator || postData.selfPost;
|
||||
postData.display_ip_ban = (results.isAdmin || results.isGlobalMod) && !postData.selfPost;
|
||||
postData.display_history = results.history && results.canViewHistory;
|
||||
postData.flags = {
|
||||
@@ -92,4 +93,35 @@ module.exports = function (SocketPosts) {
|
||||
|
||||
await Promise.all(logs);
|
||||
};
|
||||
|
||||
SocketPosts.getEditors = async function (socket, data) {
|
||||
if (!data || !data.pid) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
await checkEditorPrivilege(socket.uid, data.pid);
|
||||
const editorUids = await db.getSetMembers(`pid:${data.pid}:editors`);
|
||||
const userData = await user.getUsersFields(editorUids, ['username', 'userslug', 'picture']);
|
||||
return userData;
|
||||
};
|
||||
|
||||
SocketPosts.saveEditors = async function (socket, data) {
|
||||
if (!data || !data.pid || !Array.isArray(data.uids)) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
await checkEditorPrivilege(socket.uid, data.pid);
|
||||
await db.delete(`pid:${data.pid}:editors`);
|
||||
await db.setAdd(`pid:${data.pid}:editors`, data.uids);
|
||||
};
|
||||
|
||||
async function checkEditorPrivilege(uid, pid) {
|
||||
const cid = await posts.getCidByPid(pid);
|
||||
const [isAdminOrMod, owner] = await Promise.all([
|
||||
privileges.categories.isAdminOrMod(cid, uid),
|
||||
posts.getPostField(pid, 'uid'),
|
||||
]);
|
||||
const isSelfPost = String(uid) === String(owner);
|
||||
if (!isAdminOrMod && !isSelfPost) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
32
src/views/modals/manage-editors.tpl
Normal file
32
src/views/modals/manage-editors.tpl
Normal file
@@ -0,0 +1,32 @@
|
||||
<div class="card tool-modal shadow">
|
||||
<h5 class="card-header">[[topic:thread-tools.manage-editors]]</h5>
|
||||
<div class="card-body">
|
||||
<p>
|
||||
[[topic:manage-editors-instruction]]
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="username"><strong>[[user:username]]</strong></label>
|
||||
<div class="input-group">
|
||||
<input id="username" type="text" class="form-control" name="username">
|
||||
<span class="input-group-text" type="button">
|
||||
<i class="fa fa-search"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap" component="topic/editors">
|
||||
{{{ each editors }}}
|
||||
<div class="badge text-bg-light m-1 p-1 border d-inline-flex gap-1 align-items-center" data-uid="{./uid}">
|
||||
{buildAvatar(@value, "24px", true)}
|
||||
<a href="{config.relative_path}/user/{./userslug}">{./username}</a>
|
||||
<button class="btn btn-ghost btn-sm p-0 remove-user-icon">
|
||||
<i class="fa fa-fw fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
{{{ end }}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<button class="btn btn-link btn-sm" id="manage_editors_cancel">[[global:buttons.close]]</button>
|
||||
<button class="btn btn-primary btn-sm" id="manage_editors_commit">[[global:save]]</button>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user