From c8cd814023e3bad30e09cd17e2a581dbee2d8ab0 Mon Sep 17 00:00:00 2001 From: ancsemi Date: Wed, 9 Sep 2026 19:22:33 -0400 Subject: [PATCH] Forum topic cards carry the protection shield (#5622) A protected topic showed no shield on its card and, with no archived flag on the element, the context menu offered Protect again instead of Unprotect. The card now sets the flag and shows the shield, the protect and unprotect events update the card live, and the cached topic keeps the state across re-renders. --- CHANGELOG.md | 3 +++ public/js/app.js | 4 ++-- public/js/modules/app-forum.js | 5 ++++- public/js/modules/app-socket.js | 10 ++++++++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 425918b2..80e840d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). Haven uses [Sema - **Link cards in threads (#5620).** A link posted as a reply in a forum topic, or in any other thread, now gets the same preview card as it does in the channel. Reported by @quakeman00. +- **Forum topic cards show the protection shield (#5622).** A protected topic + now carries the shield on its card, and its right-click menu offers Unprotect + instead of Protect a second time. Reported by @quakeman00. ## [4.6.0] - 2026-09-09 diff --git a/public/js/app.js b/public/js/app.js index 752f0210..2ea9ffba 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -4,7 +4,7 @@ // notifications, volume sliders, status bar // ═══════════════════════════════════════════════════════════ -import SocketMethods from './modules/app-socket.js?v=4.5.5'; +import SocketMethods from './modules/app-socket.js?v=4.6.1'; import UIBindMethods from './modules/app-ui.js?v=4.6.1'; import MediaMethods from './modules/app-media.js?v=4.5.4'; import ContextMethods from './modules/app-context.js?v=3.44.6'; @@ -17,7 +17,7 @@ import AdminMethods from './modules/app-admin.js?v=4.6.1'; import PlatformMethods from './modules/app-platform.js?v=4.5.1'; import SearchMethods from './modules/app-search.js?v=3.49.0'; import FerryMethods from './modules/app-ferry.js?v=3.51.4'; -import ForumMethods from './modules/app-forum.js?v=4.5.0'; +import ForumMethods from './modules/app-forum.js?v=4.6.1'; class HavenApp { constructor() { diff --git a/public/js/modules/app-forum.js b/public/js/modules/app-forum.js index 3a29c29b..427e25b9 100644 --- a/public/js/modules/app-forum.js +++ b/public/js/modules/app-forum.js @@ -182,6 +182,9 @@ _createForumTopicEl(msg) { el.dataset.userId = msg.user_id; el.dataset.time = msg.created_at; el.dataset.username = msg.username || ''; + // Protected topics carry the same flag and shield as a message in chat, so + // the card shows it and the context menu offers Unprotect (#5622). + if (msg.is_archived) { el.classList.add('archived'); el.dataset.archived = '1'; } const tagsOf = this._forumTagsOf(); const tags = Array.isArray(msg.tags) ? msg.tags : []; const thumb = this._forumThumbOf(msg); @@ -191,7 +194,7 @@ _createForumTopicEl(msg) { el.innerHTML = ` ${thumb ? `
` : `
⬡
`}
-
${msg.pinned ? `📌 ${t('forum.pinned')}` : ''}${tags.map(name => { const tg = tagsOf.find(x => x.name === name); return `${tg && tg.emoji ? this._escapeHtml(tg.emoji) + ' ' : ''}${this._escapeHtml(name)}`; }).join('')}
+
${msg.is_archived ? `🛡️` : ''}${msg.pinned ? `📌 ${t('forum.pinned')}` : ''}${tags.map(name => { const tg = tagsOf.find(x => x.name === name); return `${tg && tg.emoji ? this._escapeHtml(tg.emoji) + ' ' : ''}${this._escapeHtml(name)}`; }).join('')}
${this._escapeHtml(this._forumTitleOf(msg))}
${this._escapeHtml(this._forumSnippetOf(msg))}
diff --git a/public/js/modules/app-socket.js b/public/js/modules/app-socket.js index ee56c697..fdcd4a81 100644 --- a/public/js/modules/app-socket.js +++ b/public/js/modules/app-socket.js @@ -2240,12 +2240,20 @@ _setupSocketListeners() { if (msgEl.classList.contains('message-compact') && content && !content.querySelector('.archived-tag')) { content.insertAdjacentHTML('afterbegin', `🛡️`); } + // A forum topic card shows the shield with its tags (#5622). + const forumTags = msgEl.classList.contains('forum-topic') ? msgEl.querySelector('.forum-topic-tags') : null; + if (forumTags && !forumTags.querySelector('.archived-tag')) { + forumTags.insertAdjacentHTML('afterbegin', `🛡️`); + } // Update toolbar: swap archive → unarchive const archBtn = msgEl.querySelector('[data-action="archive"]'); if (archBtn) { archBtn.dataset.action = 'unarchive'; archBtn.title = t('app.messages.unprotect_btn'); } } this._appendSystemMessage(`🛡️ ${t('header.messages.protected_by', { name: data.archivedBy })}`); } + // Keep the cached topic in step so a re-rendered card keeps its shield. + const topic = this._forumTopics && this._forumTopics.get(data.messageId); + if (topic) topic.is_archived = 1; }); this.socket.on('message-unarchived', (data) => { @@ -2265,6 +2273,8 @@ _setupSocketListeners() { } this._appendSystemMessage(`🛡️ ${t('header.messages.message_unprotected')}`); } + const topic = this._forumTopics && this._forumTopics.get(data.messageId); + if (topic) topic.is_archived = 0; }); // ── Admin moderation events ────────────────────────