RTE Posts upgrade: TipTap editor + sanitization for posts
Extend the wiki's RichTextEditor to the Posts editor and close the stored-XSS gap on public post bodies. - RichTextEditor: add `variant` prop — `full` (wiki), `post` (no internal wiki-page link picker), `minimal` (image-only, for Screenshots captions). Toolbar sections rendered conditionally. - PostEditor: replace the body textarea with a lazy-loaded RichTextEditor in Suspense; variant chosen by category (minimal for screenshots, post otherwise). - posts.model: sanitize body via shared cleanBody on create/update, treat an empty TipTap `<p></p>` as null, and auto-derive the excerpt from the body (max 280 chars) when left blank. - sanitizeHtml util: add deriveExcerpt() helper. - FiveOnFriday / NewsletterIssue: wrap dangerouslySetInnerHTML with DOMPurify.sanitize() as defense-in-depth on render. No schema or dependency changes. Verified end-to-end against the local stack: 24/24 API assertions and a full UI round-trip across all four post categories. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import DOMPurify from 'dompurify'
|
||||
import PublicLayout from '../../components/PublicLayout.jsx'
|
||||
import PageHeader from '../../components/PageHeader.jsx'
|
||||
import { Loading, ErrorState, EmptyState } from '../../components/PageState.jsx'
|
||||
@@ -36,7 +37,7 @@ export default function FiveOnFriday() {
|
||||
{it.title}
|
||||
</h2>
|
||||
{it.body ? (
|
||||
<div className="prose" dangerouslySetInnerHTML={{ __html: it.body }} />
|
||||
<div className="prose" dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(it.body) }} />
|
||||
) : (
|
||||
it.excerpt && <p style={{ margin: 0, color: 'var(--text)' }}>{it.excerpt}</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user