feat: closes #12902, allow adding users as post editors

This commit is contained in:
Barış Soner Uşaklı
2024-11-06 11:36:53 -05:00
parent 65f64ebaed
commit bc00df3cd9
8 changed files with 161 additions and 4 deletions

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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]]');
}
}
};

View 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>