diff --git a/nodebb-theme-harmony/templates/partials/topic/post-menu-list.tpl b/nodebb-theme-harmony/templates/partials/topic/post-menu-list.tpl index 6599da7..7fb32ed 100644 --- a/nodebb-theme-harmony/templates/partials/topic/post-menu-list.tpl +++ b/nodebb-theme-harmony/templates/partials/topic/post-menu-list.tpl @@ -70,6 +70,11 @@ {{{end}}} +
  • + + Endorse + +
  • {{{end}}} {{{ if !posts.deleted }}} diff --git a/src/privileges/posts.js b/src/privileges/posts.js index fbd6858..e002814 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -168,6 +168,7 @@ privsPosts.canDelete = async function (pid, uid) { isMod: posts.isModerator([pid], uid), isLocked: topics.isLocked(postData.tid), isOwner: posts.isOwner(pid, uid), + isTopicOwner: topics.isOwner(postData.tid, uid), 'posts:delete': privsPosts.can('posts:delete', pid, uid), }); results.isMod = results.isMod[0]; @@ -184,7 +185,7 @@ privsPosts.canDelete = async function (pid, uid) { return { flag: false, message: `[[error:post-delete-duration-expired, ${meta.config.postDeleteDuration}]]` }; } const { deleterUid } = postData; - const flag = results['posts:delete'] && ((results.isOwner && (deleterUid === 0 || deleterUid === postData.uid)) || results.isMod); + const flag = results['posts:delete'] && ((results.isOwner && (deleterUid === 0 || deleterUid === postData.uid)) || results.isMod || results.isTopicOwner); return { flag: flag, message: '[[error:no-privileges]]' }; }; diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index 5e195ac..408f481 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -46,7 +46,7 @@ module.exports = function (SocketPosts) { postData.display_purge_tools = results.canPurge; postData.display_flag_tools = socket.uid && results.canFlag.flag; postData.display_topic_owner_tools = socket.uid === topicsData.uid; - postData.display_moderator_tools = postData.display_edit_tools || postData.display_delete_tools; + postData.display_moderator_tools = results.isAdmin || results.isModerator; postData.display_move_tools = results.isAdmin || results.isModerator; postData.display_change_owner_tools = results.isAdmin || results.isModerator; postData.display_ip_ban = (results.isAdmin || results.isGlobalMod) && !postData.selfPost; diff --git a/test/posts.js b/test/posts.js index 20403e2..2e74b5c 100644 --- a/test/posts.js +++ b/test/posts.js @@ -1215,3 +1215,59 @@ describe('Posts\'', async () => { }); }); }); + +describe('Post deletion by topic owner', () => { + let topicOwnerUid; + let posterUid; + let cid; + let tid; + let mainPid; + let replyPid; + + before(async () => { + topicOwnerUid = await user.create({ username: 'topicowner' }); + posterUid = await user.create({ username: 'regularuser' }); + + ({ cid } = await categories.create({ + name: 'Test Category', + description: 'Test category for topic owner deletion tests', + })); + + const topicData = await topics.post({ + uid: topicOwnerUid, + cid: cid, + title: 'Test Topic for Owner Deletion Rights', + content: 'This is the main post of the topic', + }); + + tid = topicData.topicData.tid; + mainPid = topicData.postData.pid; + + const replyData = await topics.reply({ + uid: posterUid, + tid: tid, + content: 'This is a reply posted by another user', + }); + + replyPid = replyData.pid; + }); + + it('should allow topic owner to delete any post under their topic', async () => { + assert(await user.exists(topicOwnerUid), 'Topic owner user should exist'); + assert(await user.exists(posterUid), 'Poster user should exist'); + + const isOwner = await topics.isOwner(tid, topicOwnerUid); + assert.strictEqual(isOwner, true, 'User should be the topic owner'); + + const isPostOwner = await posts.isOwner(replyPid, topicOwnerUid); + assert.strictEqual(isPostOwner, false, 'Topic owner should not be the post owner'); + + await apiPosts.delete({ uid: topicOwnerUid }, { pid: replyPid, tid: tid }); + + const isDeleted = await posts.getPostField(replyPid, 'deleted'); + assert.strictEqual(isDeleted, 1, 'Post should be marked as deleted'); + + const postExists = await posts.exists(replyPid); + assert.strictEqual(postExists, true, 'Post should still exist in database after deletion'); + }); +});