diff --git a/install/package.json b/install/package.json index ff96015506..d8f81ebec6 100644 --- a/install/package.json +++ b/install/package.json @@ -108,7 +108,7 @@ "nodebb-plugin-spam-be-gone": "2.3.0", "nodebb-plugin-web-push": "0.7.2", "nodebb-rewards-essentials": "1.0.0", - "nodebb-theme-harmony": "2.0.14", + "nodebb-theme-harmony": "2.0.15", "nodebb-theme-lavender": "7.1.17", "nodebb-theme-peace": "2.2.38", "nodebb-theme-persona": "14.0.12", diff --git a/public/scss/chats.scss b/public/scss/chats.scss index e9bd116a90..d61965088e 100644 --- a/public/scss/chats.scss +++ b/public/scss/chats.scss @@ -50,6 +50,12 @@ body.page-user-chats { } } +[component="chat/message/parent"] { + [component="chat/message/parent/content"] > p:last-child { + margin-bottom: 0; + } +} + .expanded-chat { .chat-content { .message-body { diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 0bef2268df..c11ece6f94 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -264,15 +264,28 @@ define('forum/topic', [ } function addParentHandler() { - components.get('topic').on('click', '[component="post/parent"]', function (e) { - const toPid = $(this).attr('data-topid'); - + function gotoPost(event, toPid) { const toPost = $('[component="topic"]>[component="post"][data-pid="' + toPid + '"]'); if (toPost.length) { - e.preventDefault(); + event.preventDefault(); navigator.scrollToIndex(toPost.attr('data-index'), true); return false; } + } + components.get('topic').on('click', '[component="post/parent"]', function (e) { + const parentEl = $(this); + const contentEl = parentEl.find('[component="post/parent/content"]'); + if (contentEl.length) { + contentEl.toggleClass('line-clamp-1'); + parentEl.find('.timeago').toggleClass('hidden'); + parentEl.toggleClass('flex-column').toggleClass('flex-row'); + } else { + return gotoPost(e, parentEl.attr('data-topid')); + } + }); + + components.get('topic').on('click', '[component="post/parent"] .timeago', function (e) { + return gotoPost(e, $(this).parents('[data-parent-pid]').attr('data-parent-pid')); }); } @@ -298,7 +311,7 @@ define('forum/topic', [ destroyed = true; } $(window).one('action:ajaxify.start', destroyTooltip); - $('[component="topic"]').on('mouseenter', '[component="post/parent"], [component="post/content"] a, [component="topic/event"] a', async function () { + $('[component="topic"]').on('mouseenter', 'a[component="post/parent"], [component="post/content"] a, [component="topic/event"] a', async function () { const link = $(this); destroyed = false; diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index 73e1a91efc..4fdfcdbf4d 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -159,6 +159,17 @@ define('forum/topic/events', [ }); } }); + + const parentEl = $(`[component="post/parent"][data-parent-pid="${data.post.pid}"]`); + if (parentEl.length) { + parentEl.find('[component="post/parent/content"]').html( + translator.unescape(data.post.content) + ); + parentEl.find('img:not(.not-responsive)').addClass('img-fluid'); + parentEl.find('[component="post/parent/content]" img:not(.emoji)').each(function () { + images.wrapImageInLink($(this)); + }); + } } else { hooks.fire('action:posts.edited', data); } @@ -185,26 +196,39 @@ define('forum/topic/events', [ require(['forum/topic/replies'], function (replies) { replies.onPostPurged(postData); }); + $(`[component="post/parent"][data-parent-pid="${postData.pid}"]`).remove(); } function togglePostDeleteState(data) { const postEl = components.get('post', 'pid', data.pid); - if (!postEl.length) { - return; + const { isAdminOrMod } = ajaxify.data.privileges; + const isSelfPost = String(data.uid) === String(app.user.uid); + const isDeleted = !!data.deleted; + if (postEl.length) { + postEl.toggleClass('deleted'); + postTools.toggle(data.pid, isDeleted); + + if (!isAdminOrMod && !isSelfPost) { + postEl.find('[component="post/tools"]').toggleClass('hidden', isDeleted); + if (isDeleted) { + postEl.find('[component="post/content"]').translateHtml('[[topic:post-is-deleted]]'); + } else { + postEl.find('[component="post/content"]').html(translator.unescape(data.content)); + } + } } - postEl.toggleClass('deleted'); - const isDeleted = postEl.hasClass('deleted'); - postTools.toggle(data.pid, isDeleted); - - if (!ajaxify.data.privileges.isAdminOrMod && parseInt(data.uid, 10) !== parseInt(app.user.uid, 10)) { - postEl.find('[component="post/tools"]').toggleClass('hidden', isDeleted); - if (isDeleted) { - postEl.find('[component="post/content"]').translateHtml('[[topic:post-is-deleted]]'); - } else { - postEl.find('[component="post/content"]').html(translator.unescape(data.content)); - } + const parentEl = $(`[component="post/parent"][data-parent-pid="${data.pid}"]`); + if (parentEl.length) { + parentEl.each((i, el) => { + const $parent = $(el); + if (isDeleted) { + $parent.find('[component="post/parent/content"]').translateHtml('[[topic:post-is-deleted]]'); + } else { + $parent.find('[component="post/parent/content"]').html(translator.unescape(data.content)); + } + }); } } diff --git a/public/src/client/topic/replies.js b/public/src/client/topic/replies.js index 878c86f952..dc7ef8daef 100644 --- a/public/src/client/topic/replies.js +++ b/public/src/client/topic/replies.js @@ -36,6 +36,7 @@ define('forum/topic/replies', ['forum/topic/posts', 'hooks', 'alerts', 'api'], f 'downvote:disabled': ajaxify.data['downvote:disabled'], 'reputation:disabled': ajaxify.data['reputation:disabled'], loggedIn: !!app.user.uid, + hideParent: true, hideReplies: config.hasOwnProperty('showNestedReplies') ? !config.showNestedReplies : true, }; app.parseAndTranslate('topic', 'posts', tplData, async function (html) { diff --git a/src/messaging/data.js b/src/messaging/data.js index aff649f225..aa96e11a67 100644 --- a/src/messaging/data.js +++ b/src/messaging/data.js @@ -137,7 +137,7 @@ module.exports = function (Messaging) { parentMids = parentMids.filter((mid, idx) => canView[idx]); const parentMessages = await Messaging.getMessagesFields(parentMids, [ - 'fromuid', 'content', 'timestamp', 'deleted', + 'mid', 'fromuid', 'content', 'timestamp', 'deleted', ]); const parentUids = _.uniq(parentMessages.map(msg => msg && msg.fromuid)); const usersMap = _.zipObject( diff --git a/src/posts/delete.js b/src/posts/delete.js index a56fb17f3e..6ea4b55453 100644 --- a/src/posts/delete.js +++ b/src/posts/delete.js @@ -28,7 +28,7 @@ module.exports = function (Posts) { deleted: isDeleting ? 1 : 0, deleterUid: isDeleting ? uid : 0, }); - const postData = await Posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'timestamp']); + const postData = await Posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'timestamp', 'deleted']); const topicData = await topics.getTopicFields(postData.tid, ['tid', 'cid', 'pinned']); postData.cid = topicData.cid; await Promise.all([ diff --git a/src/privileges/posts.js b/src/privileges/posts.js index e8b68771de..e289cb8414 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -45,7 +45,7 @@ privsPosts.get = async function (pids, uid) { const privileges = cids.map((cid, i) => { const isAdminOrMod = results.isAdmin || isModerator[cid]; - const editable = (privData['posts:edit'][cid] && (results.isOwner[i] || results.isModerator)) || results.isAdmin; + const editable = (privData['posts:edit'][cid] && (results.isOwner[i] || results.isModerator[i])) || results.isAdmin; const viewDeletedPosts = results.isOwner[i] || privData['posts:view_deleted'][cid] || results.isAdmin; const viewHistory = results.isOwner[i] || privData['posts:history'][cid] || results.isAdmin; diff --git a/src/topics/create.js b/src/topics/create.js index 2d06d22123..96113c3e5a 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -233,7 +233,7 @@ module.exports = function (Topics) { posts.getUserInfoForPosts([postOwner], uid), ]); await Promise.all([ - Topics.addParentPosts([postData]), + Topics.addParentPosts([postData], uid), Topics.syncBacklinks(postData), Topics.markAsRead([tid], uid), ]); diff --git a/src/topics/posts.js b/src/topics/posts.js index 353dcddb1e..138b0dfa4a 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -12,6 +12,7 @@ const meta = require('../meta'); const activitypub = require('../activitypub'); const plugins = require('../plugins'); const utils = require('../utils'); +const privileges = require('../privileges'); const backlinkRegex = new RegExp(`(?:${nconf.get('url').replace('/', '\\/')}|\b|\\s)\\/topic\\/(\\d+)(?:\\/\\w+)?`, 'g'); @@ -129,7 +130,7 @@ module.exports = function (Topics) { getPostUserData('uid', async uids => await posts.getUserInfoForPosts(uids, uid)), getPostUserData('editor', async uids => await user.getUsersFields(uids, ['uid', 'username', 'userslug'])), getPostReplies(postData, uid), - Topics.addParentPosts(postData), + Topics.addParentPosts(postData, uid), ]); postData.forEach((postObj, i) => { @@ -178,7 +179,7 @@ module.exports = function (Topics) { }); }; - Topics.addParentPosts = async function (postData) { + Topics.addParentPosts = async function (postData, callerUid) { let parentPids = postData .filter(p => p && p.hasOwnProperty('toPid') && (activitypub.helpers.isUri(p.toPid) || utils.isNumber(p.toPid))) .map(postObj => postObj.toPid); @@ -187,16 +188,40 @@ module.exports = function (Topics) { return; } parentPids = _.uniq(parentPids); - const parentPosts = await posts.getPostsFields(parentPids, ['uid']); + const postPrivileges = await privileges.posts.get(parentPids, callerUid); + const pidToPrivs = _.zipObject(parentPids, postPrivileges); + + parentPids = parentPids.filter(p => pidToPrivs[p]['topics:read']); + const parentPosts = await posts.getPostsFields(parentPids, ['uid', 'pid', 'timestamp', 'content', 'deleted']); const parentUids = _.uniq(parentPosts.map(postObj => postObj && postObj.uid)); - const userData = await user.getUsersFields(parentUids, ['username']); + const userData = await user.getUsersFields(parentUids, ['username', 'userslug', 'picture']); const usersMap = _.zipObject(parentUids, userData); + + await Promise.all(parentPosts.map(async (parentPost) => { + const postPrivs = pidToPrivs[parentPost.pid]; + if (parentPost.deleted && String(parentPost.uid) !== String(callerUid, 10) && !postPrivs['posts:view_deleted']) { + parentPost.content = `
[[topic:post-is-deleted]]
`; + return; + } + const foundPost = postData.find(p => String(p.pid) === String(parentPost.pid)); + if (foundPost) { + parentPost.content = foundPost.content; + return; + } + parentPost = await posts.parsePost(parentPost); + })); + const parents = {}; parentPosts.forEach((post, i) => { if (usersMap[post.uid]) { parents[parentPids[i]] = { uid: post.uid, + pid: post.pid, + content: post.content, + user: usersMap[post.uid], + timestamp: post.timestamp, + timestampISO: post.timestampISO, username: usersMap[post.uid].username, displayname: usersMap[post.uid].displayname, }; diff --git a/src/views/partials/chats/parent.tpl b/src/views/partials/chats/parent.tpl index 33951788d4..2d2e66bf3b 100644 --- a/src/views/partials/chats/parent.tpl +++ b/src/views/partials/chats/parent.tpl @@ -8,6 +8,6 @@ -