Render markdown in Vue chat TextContent#730
Conversation
TextContent's prompt/description claims markdown support, but the component rendered text with plain interpolation, showing literal markdown syntax (e.g. **NVDA**) instead of formatted output. Parse text with marked and sanitize with DOMPurify before binding via v-html. Scoped to examples/vue-chat only; @openuidev/vue-lang runtime is unchanged. Fixes thesysdev#728
Adds the lockfile entries for the marked/dompurify dependencies added to examples/vue-chat/package.json in the previous commit, so pnpm install --frozen-lockfile (CI) matches the package.json specs. Both packages were already fully resolved elsewhere in the lockfile as transitive deps, so this only adds the importer specifiers for examples/vue-chat.
|
I would be happy to see this feature merged asap 🙏 |
vishxrad
left a comment
There was a problem hiding this comment.
Thanks for fixing #728. I am requesting changes for two issues before merge: model-produced text should stay within a Markdown-only rendering surface, and the component should consistently support either inline Markdown or correctly structured and styled block Markdown. The dependency and lockfile changes look good, and both CI jobs pass.
|
|
||
| const html = computed(() => { | ||
| const raw = marked.parse(props.text ?? "", { async: false }); | ||
| return DOMPurify.sanitize(raw); |
There was a problem hiding this comment.
[P2] Please disable or escape raw HTML before sanitizing. marked.parse() preserves embedded HTML, while the default DOMPurify policy still permits forms, inputs, external form actions and images, and fixed-position inline styles; a model response can therefore render UI outside the component library controls. HTML-looking plain text such as <script> also loses content instead of being displayed. Prefer treating Marked HTML tokens as text, then keep DOMPurify with a Markdown-specific tag and attribute allowlist.
|
|
||
| <template> | ||
| <p class="text-sm leading-relaxed text-zinc-700 dark:text-zinc-300">{{ props.text ?? "" }}</p> | ||
| <p class="text-sm leading-relaxed text-zinc-700 dark:text-zinc-300" v-html="html"></p> |
There was a problem hiding this comment.
[P2] marked.parse() emits block elements; even plain text becomes
...
, so injecting the result into an outercreates invalid nested or block markup. Tailwind preflight also removes list markers, heading and link styling, and default spacing, leaving most block Markdown visually unformatted. If this component only needs inline formatting, use marked.parseInline(); if it should support full Markdown, use a
|
@shogun444 maybe https://github.com/Simon-He95/markstream-vue would be more suitable for this case |
- Escape raw HTML tokens in marked renderer (html override) to prevent model-produced UI outside component controls - Add strict DOMPurify allowlist as defense-in-depth - Change host from <p> to <div> to fix invalid nested block markup - Add scoped CSS for markdown descendants (lists, headings, code blocks, blockquotes, tables, links) to counter Tailwind preflight resets
|
@AlexQuidditch Thanks for the suggestion! markstream-vue looks great for streaming use cases. For now I've addressed the @vishxrad feedback with a minimal fix (HTML escaping, proper block structure, scoped markdown styles). If there's interest in a fuller streaming markdown solution for the Vue chat example, that could be a follow-up. Sorry for the late PR. |

Fixes #728
What
The Vue chat example claims that
TextContentsupports markdown, but the component rendered text using plain Vue interpolation, causing markdown syntax (e.g.**NVDA**) to appear literally instead of being formatted.This PR updates
TextContentto render markdown as the prompt already describes.Changes
TextContentusingmarkedDOMPurifyv-htmlmarkedanddompurifyas direct dependencies for the Vue chat exampleWhy
The issue proposed two possible fixes:
This PR chooses the first approach so the implementation matches the documented behavior instead of reducing functionality.
The change is intentionally scoped to
examples/vue-chatonly. No changes were made to@openuidev/vue-langsince the runtime is functioning correctly; the issue was limited to the example's rendering implementation.Test Plan
DOMPurify)Checklist