Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-static-renderer-namespaced-tag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tiptap/static-renderer': patch
---

`renderToHTMLString` no longer copies the `xmlns` prefix of a namespaced `DOMOutputSpec` into the closing tag, and namespaced non-self-closing tags such as `div` or `iframe` are no longer self-closed.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
36 changes: 35 additions & 1 deletion packages/static-renderer/__tests__/json-string.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extensions as coreExtensions } from '@tiptap/core'
import { extensions as coreExtensions, Node as CoreNode } from '@tiptap/core'
import Bold from '@tiptap/extension-bold'
import CodeBlock from '@tiptap/extension-code-block'
import Document from '@tiptap/extension-document'
Expand Down Expand Up @@ -562,3 +562,37 @@ describe('static render json to string (with prosemirror)', () => {
expect(html).toMatch(/data-toc-id="[^"]+"/)
})
})

describe('namespaced DOMOutputSpec', () => {
// ProseMirror encodes a namespaced spec as `"<namespace> <localName>"`.
const Diagram = CoreNode.create({
name: 'diagram',
group: 'block',
content: 'text*',
renderHTML: () => ['http://www.w3.org/2000/svg svg', { width: '100' }, 0],
})

it('keeps the xmlns attribute out of the closing tag', () => {
const html = renderToHTMLString({
content: {
type: 'doc',
content: [{ type: 'diagram', content: [{ type: 'text', text: 'hi' }] }],
},
extensions: [Document, Paragraph, Text, Diagram],
})

// The namespace belongs on the opening tag only.
expect(html).toContain('<svg xmlns="http://www.w3.org/2000/svg"')
expect(html).toMatch(/<\/svg>$/)
})

it('still resolves non-self-closing tags by their local name', () => {
// `div` is in NON_SELF_CLOSING_TAGS, so the namespaced spec must not self-close.
const html = domOutputSpecToHTMLString([
'http://www.w3.org/1999/xhtml div',
{ class: 'wrapper' },
])()

expect(html).toContain('></div')
})
})
30 changes: 15 additions & 15 deletions packages/static-renderer/src/pm/html-string/html-string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,47 +46,47 @@ export function domOutputSpecToHTMLString(
}
if (typeof content === 'object' && 'length' in content) {
const [_tag, attrs, children, ...rest] = content as DOMOutputSpecArray
let tag = _tag
const parts = tag.split(' ')

if (parts.length > 1) {
tag = `${parts[1]} xmlns="${parts[0]}"`
}
// ProseMirror encodes a namespaced spec as `"<namespace> <localName>"`.
// Only the opening tag carries the `xmlns` attribute: the closing tag and the
// `NON_SELF_CLOSING_TAGS` lookup below both need the bare local name.
const parts = _tag.split(' ')
const tag = parts.length > 1 ? parts[1] : _tag
const openTag = parts.length > 1 ? `${parts[1]} xmlns="${parts[0]}"` : _tag

if (attrs === undefined) {
return () => `<${tag}/>`
return () => `<${openTag}/>`
}
if (attrs === 0) {
return child => `<${tag}>${serializeChildrenToHTMLString(child)}</${tag}>`
return child => `<${openTag}>${serializeChildrenToHTMLString(child)}</${tag}>`
}
if (typeof attrs === 'object') {
if (Array.isArray(attrs)) {
if (children === undefined) {
return child =>
`<${tag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}</${tag}>`
`<${openTag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}</${tag}>`
}
if (children === 0) {
return child =>
`<${tag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}</${tag}>`
`<${openTag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}</${tag}>`
}
return child =>
`<${tag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}${[children]
`<${openTag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}${[children]
.concat(rest)
.map(a => domOutputSpecToHTMLString(a)(child))}</${tag}>`
}
if (children === undefined) {
if (NON_SELF_CLOSING_TAGS.has(tag)) {
return () => `<${tag}${serializeAttrsToHTMLString(attrs)}></${tag}>`
return () => `<${openTag}${serializeAttrsToHTMLString(attrs)}></${tag}>`
}
return () => `<${tag}${serializeAttrsToHTMLString(attrs)}/>`
return () => `<${openTag}${serializeAttrsToHTMLString(attrs)}/>`
}
if (children === 0) {
return child =>
`<${tag}${serializeAttrsToHTMLString(attrs)}>${serializeChildrenToHTMLString(child)}</${tag}>`
`<${openTag}${serializeAttrsToHTMLString(attrs)}>${serializeChildrenToHTMLString(child)}</${tag}>`
}

return child =>
`<${tag}${serializeAttrsToHTMLString(attrs)}>${[children]
`<${openTag}${serializeAttrsToHTMLString(attrs)}>${[children]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
.concat(rest)
.map(a => domOutputSpecToHTMLString(a)(child))
.join('')}</${tag}>`
Expand Down