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.
This commit is contained in:
ancsemi committed 2026-09-09 19:22:33 -04:00
1 parent a5d8cdc53f
commit c8cd814023
4 files changed
+19 -3

No files matched your search

+3
View File
@@ -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
+2 -2
View File
@@ -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() {
+4 -1
View File
@@ -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 ? `<div class="forum-topic-thumb"><img ${this._lazySrcAttr ? this._lazySrcAttr(`src="${this._escapeHtml(thumb)}"`) : `src="${this._escapeHtml(thumb)}"`} class="chat-image forum-thumb-img" alt=""></div>` : `<div class="forum-topic-thumb forum-topic-thumb-empty"><span>⬡</span></div>`}
<div class="forum-topic-body">
<div class="forum-topic-tags">${msg.pinned ? `<span class="forum-tag forum-tag-pinned">📌 ${t('forum.pinned')}</span>` : ''}${tags.map(name => { const tg = tagsOf.find(x => x.name === name); return `<span class="forum-tag">${tg && tg.emoji ? this._escapeHtml(tg.emoji) + ' ' : ''}${this._escapeHtml(name)}</span>`; }).join('')}</div>
<div class="forum-topic-tags">${msg.is_archived ? `<span class="forum-tag forum-tag-protected archived-tag" title="${this._escapeHtml(t('app.messages.protected'))}">🛡️</span>` : ''}${msg.pinned ? `<span class="forum-tag forum-tag-pinned">📌 ${t('forum.pinned')}</span>` : ''}${tags.map(name => { const tg = tagsOf.find(x => x.name === name); return `<span class="forum-tag">${tg && tg.emoji ? this._escapeHtml(tg.emoji) + ' ' : ''}${this._escapeHtml(name)}</span>`; }).join('')}</div>
<div class="forum-topic-title">${this._escapeHtml(this._forumTitleOf(msg))}</div>
<div class="forum-topic-snippet message-content">${this._escapeHtml(this._forumSnippetOf(msg))}</div>
<div class="forum-topic-meta">
+10
View File
@@ -2240,12 +2240,20 @@ _setupSocketListeners() {
if (msgEl.classList.contains('message-compact') && content && !content.querySelector('.archived-tag')) {
content.insertAdjacentHTML('afterbegin', `<span class="archived-tag" title="${t('app.messages.protected')}">🛡️</span>`);
}
// 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', `<span class="forum-tag forum-tag-protected archived-tag" title="${t('app.messages.protected')}">🛡️</span>`);
}
// 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 ────────────────────────