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