feat(markdown): add MarkdownRenderer with code/Mermaid/CSV/Survey/HtmlPreview blocks#233
Conversation
Deploying ui with
|
| Latest commit: |
3a55f97
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://61a1b55c.ui-6d0.pages.dev |
| Branch Preview URL: | https://markdown-components.ui-6d0.pages.dev |
There was a problem hiding this comment.
Pull request overview
Adds a new Markdown rendering subsystem to @mieweb/ui that converts markdown to sanitized HTML, highlights code blocks, and mounts React-powered “special fence blocks” (Mermaid/CSV/Survey/HTML preview) via portals—plus a separate exported stylesheet (@mieweb/ui/markdown.css) for highlight.js + fence styling.
Changes:
- Introduces
MarkdownRenderer+useMarkdownpipeline using marked + DOMPurify + highlight.js, including placeholder emission for special fenced blocks. - Adds block components (
FenceBlock,MermaidBlock,CsvBlock,SurveyBlock,HtmlPreviewBlock) and bundled highlight/fence CSS. - Updates build/package exports and dependency config to ship a tree-shakeable Markdown subpath and a dedicated
markdown.cssexport.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| tsup.config.ts | Adds a tree-shakeable Markdown entry point and marks optional block deps as externals. |
| src/index.ts | Re-exports Markdown components from the package root. |
| src/components/Markdown/useMarkdown.ts | Implements marked→sanitized HTML rendering, highlight.js integration, caching, placeholder emission, and delegated copy handler. |
| src/components/Markdown/MarkdownRenderer.tsx | Renders sanitized HTML and mounts block components into placeholders via portals. |
| src/components/Markdown/FenceBlock.tsx | Provides a reusable UI wrapper for fenced blocks (copy + raw/rendered toggle + error state). |
| src/components/Markdown/MermaidBlock.tsx | Adds Mermaid diagram rendering (dynamic import) into FenceBlock. |
| src/components/Markdown/CsvBlock.tsx | Adds CSV parsing + sortable/exportable table block. |
| src/components/Markdown/SurveyBlock.tsx | Adds JSON/YAML survey preview block. |
| src/components/Markdown/HtmlPreviewBlock.tsx | Adds sandboxed iframe HTML preview with expand-to-modal behavior. |
| src/components/Markdown/styles.css | Bundles/scopes highlight.js themes and fence-block styles; exported as @mieweb/ui/markdown.css. |
| src/components/Markdown/index.ts | Exposes the Markdown module’s public exports. |
| package.json | Adds markdown.css export + build copy step; adds hard deps (marked/dompurify/highlight.js) and optional peer deps metadata. |
| pnpm-lock.yaml | Locks new dependencies introduced by the Markdown feature. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
src/components/Markdown/MermaidBlock.tsx:76
dangerouslySetInnerHTMLis used to inject Mermaid’s generated SVG. Even with a safersecurityLevel, it’s worth sanitizing/validating the SVG string (or rendering via a DOM API) before injection to reduce XSS risk from malformed output or upstream changes.
<div
ref={containerRef}
className="flex justify-center p-4"
dangerouslySetInnerHTML={{ __html: svg }}
/>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
7ff2d4f to
ffb15f2
Compare
garrity-miepub
left a comment
There was a problem hiding this comment.
Rebase needed
10 PRs have landed on main since this branch was cut (May 14). Merging as-is will silently revert #240, #241, #242, #245, and 6bfe832. Please rebase on origin/main and force-push.
Post-rebase nits
- HTML Preview — consider dropping
allow-scriptsfrom the iframe sandbox if interactive JS isn't needed - SSR — guard
DOMPurify.addHook()inuseMarkdown.tswithtypeof document !== 'undefined' - A11y — add
aria-sorton CsvBlock<th>androle="img" aria-label="..."on Mermaid SVG container
Otherwise the markdown work looks solid — sanitization, lazy loading, and caching are all well done.
…lPreview blocks
Adds a self-contained Markdown rendering component with:
- marked + DOMPurify pipeline
- highlight.js syntax highlighting (eager: js/ts/py/json/xml/css/bash/sql/yaml/md;
lazy: java/cpp/c/csharp/php/ruby/go/rust/perl/r/makefile/dockerfile)
- Copy-to-clipboard button on fenced code blocks
- React-portal blocks for mermaid diagrams, CSV tables, survey previews, and
sandboxed HTML previews
- atom-one-light / atom-one-dark theme bundled and scoped via [data-theme]
Exports:
- '@mieweb/ui' — { MarkdownRenderer, FenceBlock, MermaidBlock, CsvBlock,
SurveyBlock, HtmlPreviewBlock, useMarkdown }
- '@mieweb/ui/components/Markdown' — same, as tree-shakeable subpath
- '@mieweb/ui/markdown.css' — bundled hljs themes + fence block styles
mermaid, papaparse, and js-yaml are optional peer dependencies so consumers
that don't use those block types don't pay the bundle cost.
- Render plain code fences via React portal + FenceBlock (new CodeBlock component) instead of injecting raw HTML + a global click listener. Removes COPY_BTN_HTML, the document-level click handler, and the .fence-block / .fence-copy-btn CSS — code blocks now use the same copy/raw-toggle UX as Mermaid/CSV/Survey/HTML blocks. - useMarkdown: bound the render cache (LRU, 200 entries), reset the block-id counter per render, use marked's RendererThis instead of unsafe `unknown` casts, register js/ts/sh hljs aliases. - MermaidBlock: drop unused containerRef. - HtmlPreviewBlock: drop unused iframeRef and unreachable Escape branch in handleCloseExpanded. - Export CodeBlock and highlightCode helper.
…act 19 portal fix, stories
…s, Mermaid dark mode, clipboard fallback
…header), fix prettier formatting
…, SSR guard - styles.css: darken light-mode (title/subst/string/literal/built_in/comment) and lighten dark-mode (subst/comment) hljs tokens to meet 4.5:1 contrast - CsvBlock: make sort headers real buttons with scope=col + aria-sort - MermaidBlock: add role=img and derived aria-label to SVG container - useMarkdown: guard DOMPurify.addHook with typeof document for SSR - HtmlPreviewBlock: document sandbox security posture (allow-scripts in null origin)
… in CodeBlock and FenceBlock components
garrity-miepub
left a comment
There was a problem hiding this comment.
looks good! thanks for fixing the a11y issues!



Adds a self-contained Markdown rendering component so consumer apps (e.g. ozwell-workspace) don't have to re-implement the marked + DOMPurify + highlight.js pipeline themselves.
What's included
MarkdownRenderer— full pipeline (marked GFM + DOMPurify sanitisation + hljs highlighting + React portals for special blocks)FenceBlock— base wrapper (copy button, raw/rendered toggle, error state)MermaidBlock— sandboxed Mermaid diagram renderingCsvBlock— sortable / exportable CSV tableSurveyBlock— JSON/YAML survey previewHtmlPreviewBlock— sandboxed iframe HTML preview with expanduseMarkdown— hook (render/renderAsync/clearCache)[data-theme]Exports
@mieweb/ui— all of the above (named exports)@mieweb/ui/components/Markdown— tree-shakeable subpath@mieweb/ui/markdown.css— bundled hljs themes + fence-block stylesDependencies
marked,dompurify,highlight.js— small, always-needed for the markdown pipelinemermaid,papaparse,js-yaml— only loaded when the matching block type is used. Apps that never render Mermaid/CSV/Survey blocks don't pay the bundle cost.Why
Originally landed in ozwell-workspace; centralising here so other products consuming
@mieweb/ui'sAIChat(viarenderTextContent) can get full Markdown + code-block rendering for free.Usage