From 75b6b8056e9f944255104126655925a50db784e4 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 28 May 2026 18:18:34 -0400 Subject: [PATCH] feat(posts): plain bold titles; reserve View-original to the post card; provenance links to the posts feed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Post titles arrived as stored HTML (e.g. ) rendering as literal markup. New toPlainText() strips tags; titles render plain + bold (ProvenancePanel and PostCard). - Removed "View original post" from the provenance panel (modal) — the open-original button lives on the PostCard (the post view). - Provenance "View post" now navigates to the /posts feed (post_id query), not the gallery image grid. (Feed in-context landing lands next.) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/modal/ProvenancePanel.vue | 20 +++++++++++-------- frontend/src/components/posts/PostCard.vue | 18 ++++++++++------- frontend/src/utils/htmlSanitize.js | 11 ++++++++++ 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/modal/ProvenancePanel.vue b/frontend/src/components/modal/ProvenancePanel.vue index 507a9e6..b4d641e 100644 --- a/frontend/src/components/modal/ProvenancePanel.vue +++ b/frontend/src/components/modal/ProvenancePanel.vue @@ -19,7 +19,7 @@ {{ postDate(e) }}
- {{ e.post.title || `Post ${e.post.external_post_id}` }} + {{ postTitle(e) }}
@@ -30,12 +30,8 @@
-

- {{ post.post_title }} +

+ {{ plainTitle }}

Post {{ post.external_post_id }} @@ -80,8 +80,8 @@
-

- {{ post.post_title }} +

+ {{ plainTitle }}

Post {{ post.external_post_id }} @@ -125,7 +125,7 @@ import { computed, ref } from 'vue' import { RouterLink } from 'vue-router' import { usePostsStore } from '../../stores/posts.js' -import { sanitizeHtml } from '../../utils/htmlSanitize.js' +import { sanitizeHtml, toPlainText } from '../../utils/htmlSanitize.js' import PostEmptyThumbs from './PostEmptyThumbs.vue' import PostImageGrid from './PostImageGrid.vue' @@ -149,6 +149,10 @@ const merged = computed(() => detail.value || props.post) const images = computed(() => merged.value.thumbnails || []) const attachments = computed(() => merged.value.attachments || []) +// Titles can arrive as stored HTML (e.g. ""); render as +// plain text — the CSS makes the title bold. +const plainTitle = computed(() => toPlainText(props.post.post_title)) + // Compact-view hero+rail derived from the feed-shape (capped 6). const hero = computed(() => props.post.thumbnails?.[0]) const rail = computed(() => (props.post.thumbnails || []).slice(1, 4)) @@ -312,7 +316,7 @@ function formatBytes (n) { .fc-post-card__title { font-family: 'Fraunces', Georgia, serif; - font-size: 18px; font-weight: 500; + font-size: 18px; font-weight: 700; margin: 0 0 8px 0; color: rgb(var(--v-theme-on-surface)); display: -webkit-box; @@ -369,7 +373,7 @@ function formatBytes (n) { .fc-post-card__title-full { font-family: 'Fraunces', Georgia, serif; font-size: 22px; - font-weight: 500; + font-weight: 700; margin: 0; color: rgb(var(--v-theme-on-surface)); } diff --git a/frontend/src/utils/htmlSanitize.js b/frontend/src/utils/htmlSanitize.js index 7978fca..5733c52 100644 --- a/frontend/src/utils/htmlSanitize.js +++ b/frontend/src/utils/htmlSanitize.js @@ -29,6 +29,17 @@ export function sanitizeHtml (html) { return doc.body.innerHTML } +// Strip all tags + decode entities to plain text. Used for titles that +// arrive as stored HTML (e.g. "Edelgard at the Sex-Arcade") +// which must render as text, not literal markup. DOMParser yields an inert +// document (no script execution, no resource loads), so reading textContent +// is safe. +export function toPlainText (html) { + if (typeof html !== 'string' || !html) return '' + const doc = new DOMParser().parseFromString(html, 'text/html') + return (doc.body.textContent || '').trim() +} + function _scrubNode (node) { const children = Array.from(node.children) for (const child of children) {