Repository: Milkdown/milkdown (2025-07-26T07:06:45Z)

Issue:
[Bug] MD files containing HTML tags cannot be fully rendered
### Initial checklist

- [x] I agree to follow the [code of conduct](https://github.com/Saul-Mirone/milkdown/blob/main/CODE_OF_CONDUCT.md)
- [x] I searched [issues](hhttps://github.com/Saul-Mirone/milkdown/issues) and [discussions](https://github.com/Saul-Mirone/milkdown/discussions) and couldn’t find anything (or linked relevant results below)

### Affected packages and versions

^7.15.1

### Link to runnable example

_No response_

### Steps to reproduce

When opening certain files containing HTML tags, some content cannot be displayed. This issue has been reported by users in our milkdown based desktop software

https://github.com/Auto-Plugin/milkup/issues/5

### Expected behavior

- Should be rendered in HTML tag style
- Or render directly as a string
- Or render in the form of code blocks

### Actual behavior

Render as blank

### Runtime

Electron

### OS

Windows

### Build and bundle tools

Vite


Discussion:
Very efficient, thank you very much.



Patch:
diff --git a/packages/plugins/preset-commonmark/src/plugin/remark-html-transformer.ts b/packages/plugins/preset-commonmark/src/plugin/remark-html-transformer.ts
index bdc45b7ea22..ba01d94ddca 100644
--- a/packages/plugins/preset-commonmark/src/plugin/remark-html-transformer.ts
+++ b/packages/plugins/preset-commonmark/src/plugin/remark-html-transformer.ts
@@ -38,6 +38,10 @@ function flatMapWithDepth(
   }
 }
 
+// List of container node types that can contain block-level content
+// and thus may need HTML content to be wrapped in paragraphs
+const BLOCK_CONTAINER_TYPES = ['root', 'blockquote', 'listItem']
+
 /// @internal
 /// This plugin should be deprecated after we support HTML.
 export const remarkHtmlTransformer = $remark(
@@ -46,7 +50,9 @@ export const remarkHtmlTransformer = $remark(
     flatMapWithDepth(tree, (node, _index, parent) => {
       if (!isHTML(node)) return [node]
 
-      if (parent?.type === 'root') {
+      // If the parent is a block container that expects block content,
+      // wrap the HTML in a paragraph node
+      if (parent && BLOCK_CONTAINER_TYPES.includes(parent.type)) {
         node.children = [{ ...node }]
         delete node.value
         ;(node as { type: string }).type = 'paragraph'


Test patch:
diff --git a/packages/plugins/preset-commonmark/src/__test__/html.spec.ts b/packages/plugins/preset-commonmark/src/__test__/html.spec.ts
new file mode 100644
index 00000000000..37ddd871a9f
--- /dev/null
+++ b/packages/plugins/preset-commonmark/src/__test__/html.spec.ts
@@ -0,0 +1,46 @@
+import '@testing-library/jest-dom/vitest'
+import type { EditorView } from '@milkdown/prose/view'
+
+import { defaultValueCtx, Editor, editorViewCtx } from '@milkdown/core'
+import { expect, it } from 'vitest'
+
+import { commonmark } from '..'
+
+function createEditor() {
+  const editor = Editor.make()
+  editor.use(commonmark)
+  return editor
+}
+
+const htmlInBlockquote = {
+  name: 'htmlInBlockquote',
+  defaultValue: `
+> <p>Hello, world!</p>
+`,
+  check: (view: EditorView) => {
+    expect(view.dom.querySelector('blockquote')).toBeInTheDocument()
+  },
+}
+
+const htmlInListItem = {
+  name: 'htmlInListItem',
+  defaultValue: `
+* <p>List item with HTML</p>
+`,
+  check: (view: EditorView) => {
+    expect(view.dom.querySelector('li')).toBeInTheDocument()
+  },
+}
+
+;[htmlInBlockquote, htmlInListItem].forEach(({ name, defaultValue, check }) => {
+  it(`should render html in ${name}`, async () => {
+    const editor = createEditor()
+    editor.config((ctx) => {
+      ctx.set(defaultValueCtx, defaultValue)
+    })
+
+    await editor.create()
+
+    check(editor.ctx.get(editorViewCtx))
+  })
+})
diff --git a/packages/plugins/preset-commonmark/vitest.config.ts b/packages/plugins/preset-commonmark/vitest.config.ts
new file mode 100644
index 00000000000..2fc41f01933
--- /dev/null
+++ b/packages/plugins/preset-commonmark/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+  test: {
+    globals: true,
+    environment: 'jsdom',
+  },
+})