Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped
146 lines
4.9 KiB
TypeScript
146 lines
4.9 KiB
TypeScript
/* oslint-disableno-explicit-any */
|
|
import type { DOMOutputSpecArray, Extensions, JSONContent } from '@tiptap/core'
|
|
import type { DOMOutputSpec, Mark, Node as PMNode } from '@tiptap/pm/model'
|
|
|
|
import {
|
|
escapeHTML,
|
|
renderJSONContentToString,
|
|
serializeAttrsToHTMLString,
|
|
serializeChildrenToHTMLString,
|
|
} from '../../json/html-string/string.js'
|
|
import type { TiptapStaticRendererOptions } from '../../json/renderer.js'
|
|
import type { StaticEditorOptions } from '../extensionRenderer.js'
|
|
import { applyStaticEditorOptionsToExtensions, renderToElement } from '../extensionRenderer.js'
|
|
|
|
export {
|
|
serializeAttrsToHTMLString,
|
|
serializeChildrenToHTMLString,
|
|
} from '../../json/html-string/string.js'
|
|
|
|
/**
|
|
* HTML elements that cannot be self-closing and must always have a closing tag.
|
|
* These elements must be rendered as <tag></tag> even when empty, not <tag />.
|
|
*/
|
|
const NON_SELF_CLOSING_TAGS = new Set([
|
|
'iframe',
|
|
'script',
|
|
'style',
|
|
'title',
|
|
'textarea',
|
|
'div',
|
|
'span',
|
|
'a',
|
|
'button',
|
|
])
|
|
|
|
/**
|
|
* Take a DOMOutputSpec and return a function that can render it to a string
|
|
* @param content The DOMOutputSpec to convert to a string
|
|
* @returns A function that can render the DOMOutputSpec to a string
|
|
*/
|
|
export function domOutputSpecToHTMLString(
|
|
content: DOMOutputSpec,
|
|
): (children?: string | string[]) => string {
|
|
if (typeof content === 'string') {
|
|
return () => escapeHTML(content)
|
|
}
|
|
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]}"`
|
|
}
|
|
|
|
if (attrs === undefined) {
|
|
return () => `<${tag}/>`
|
|
}
|
|
if (attrs === 0) {
|
|
return child => `<${tag}>${serializeChildrenToHTMLString(child)}</${tag}>`
|
|
}
|
|
if (typeof attrs === 'object') {
|
|
if (Array.isArray(attrs)) {
|
|
if (children === undefined) {
|
|
return child =>
|
|
`<${tag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}</${tag}>`
|
|
}
|
|
if (children === 0) {
|
|
return child =>
|
|
`<${tag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}</${tag}>`
|
|
}
|
|
return child =>
|
|
`<${tag}>${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 () => `<${tag}${serializeAttrsToHTMLString(attrs)}/>`
|
|
}
|
|
if (children === 0) {
|
|
return child =>
|
|
`<${tag}${serializeAttrsToHTMLString(attrs)}>${serializeChildrenToHTMLString(child)}</${tag}>`
|
|
}
|
|
|
|
return child =>
|
|
`<${tag}${serializeAttrsToHTMLString(attrs)}>${[children]
|
|
.concat(rest)
|
|
.map(a => domOutputSpecToHTMLString(a)(child))
|
|
.join('')}</${tag}>`
|
|
}
|
|
}
|
|
|
|
// TODO support DOM elements? How to handle them?
|
|
throw new Error(
|
|
'[tiptap error]: Unsupported DomOutputSpec type, check the `renderHTML` method output or implement a node mapping',
|
|
{
|
|
cause: content,
|
|
},
|
|
)
|
|
}
|
|
|
|
/**
|
|
* This function will statically render a Prosemirror Node to HTML using the provided extensions and options.
|
|
*
|
|
* Limitations: this function builds the schema and runs each extension's
|
|
* `renderHTML`, but does not instantiate an `Editor`. Extensions that mutate
|
|
* the document inside `addProseMirrorPlugins`, `onCreate`, or transaction
|
|
* hooks will not run. For UniqueID, pre-process the JSON with
|
|
* `generateUniqueIds` from `@tiptap/extension-unique-id`; for TableOfContents,
|
|
* pre-process with `generateTocIds` from `@tiptap/extension-table-of-contents`.
|
|
*
|
|
* @param content The content to render to HTML
|
|
* @param extensions The extensions to use for rendering
|
|
* @param staticEditorOptions Optional editor-level options that affect rendered output, currently `{ textDirection }`. Mirrors a subset of `EditorOptions`.
|
|
* @param options The options to use for rendering
|
|
* @returns The rendered HTML string
|
|
*/
|
|
export function renderToHTMLString({
|
|
content,
|
|
extensions,
|
|
staticEditorOptions,
|
|
options,
|
|
}: {
|
|
content: PMNode | JSONContent
|
|
extensions: Extensions
|
|
staticEditorOptions?: StaticEditorOptions
|
|
options?: Partial<TiptapStaticRendererOptions<string, Mark, PMNode>>
|
|
}): string {
|
|
return renderToElement<string>({
|
|
renderer: renderJSONContentToString,
|
|
domOutputSpecToElement: domOutputSpecToHTMLString,
|
|
mapDefinedTypes: {
|
|
// Map a doc node to concatenated children
|
|
doc: ({ children }) => serializeChildrenToHTMLString(children),
|
|
// Map a text node to its text content
|
|
text: ({ node }) => escapeHTML(node.text ?? ''),
|
|
},
|
|
content,
|
|
extensions: applyStaticEditorOptionsToExtensions(extensions, staticEditorOptions),
|
|
options,
|
|
})
|
|
}
|