Closes the last two Learning Hub follow-ups flagged in the earlier
commit body: rich-HTML body rendering (instead of pre-wrap plain text)
and in-React Marp slide playback (instead of the legacy-viewer link).
client/src/lib/sanitize.ts (new)
Inline HTML sanitizer. Parses via DOMParser (sandboxed — no scripts
run), walks the tree and:
• Removes script / style / iframe / object / embed / link / meta /
base / form / input / button / select / textarea.
• Drops every on* event-handler attribute.
• Drops href / src / xlink:href values starting with javascript:
or data:text/html.
• Drops any attribute whose value contains "javascript:".
• Falls back to entity-escaping the raw string if parsing throws.
Admin-authored Learning Hub content is the trust model here —
essentially CMS content. A full DOMPurify dep would be strictly
better but adding a package requires a network install; the inline
sanitizer covers the realistic XSS vectors without the dep bump.
client/src/pages/Learning.tsx
• Body rendering (non-presentation content_type) now runs through
sanitizeHtml + dangerouslySetInnerHTML with a `prose prose-sm`
Tailwind-typography class. Markdown/HTML formatting from admin
content now appears correctly (headings, lists, code, bold,
italics, links) instead of raw text.
• SlideViewer component (new) replaces the "Open in legacy viewer"
button for content_type === 'presentation'. Fetches
/api/learning/content/:slug/slides (returns { css, slides[] } —
server-side Marp output), sanitizes each slide's HTML + the CSS
block, renders one slide at a time with:
- prev/next buttons
- keyboard ←/→ and PageUp/PageDown navigation
- slide counter (N/total)
- fullscreen toggle (Escape exits)
Marp's own CSS is injected scoped-ish via sanitizer so slide
theming survives.
shared/types.ts + client/src/shared/types.ts — additive:
LearningSlidesOk { css: string; slides: string[] }
Client tsc + vite build clean. Initial bundle unchanged
(342.86 kB / 106.03 kB gz) since Learning.tsx was already lazy-loaded;
the sanitizer + SlideViewer roll into its chunk.
15 lines
554 B
HTML
15 lines
554 B
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<link rel="icon" type="image/svg+xml" href="/app/favicon.svg" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>client</title>
|
|
<script type="module" crossorigin src="/app/assets/index-DTb5Y4u0.js"></script>
|
|
<link rel="modulepreload" crossorigin href="/app/assets/jsx-runtime-ByY1xr43.js">
|
|
<link rel="stylesheet" crossorigin href="/app/assets/index-CyWXS7DQ.css">
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
</body>
|
|
</html>
|