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