@tiptap/static-renderer 3.0.0-next.1 → 3.0.0-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +1 -1
- package/dist/index.cjs +573 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +307 -32
- package/dist/index.d.ts +307 -32
- package/dist/index.js +552 -2
- package/dist/index.js.map +1 -1
- package/dist/json/html-string/index.cjs +16 -5
- package/dist/json/html-string/index.cjs.map +1 -1
- package/dist/json/html-string/index.d.cts +18 -22
- package/dist/json/html-string/index.d.ts +18 -22
- package/dist/json/html-string/index.js +10 -1
- package/dist/json/html-string/index.js.map +1 -1
- package/dist/json/react/index.cjs +12 -2201
- package/dist/json/react/index.cjs.map +1 -1
- package/dist/json/react/index.d.cts +5 -22
- package/dist/json/react/index.d.ts +5 -22
- package/dist/json/react/index.js +9 -2221
- package/dist/json/react/index.js.map +1 -1
- package/dist/json/renderer.cjs.map +1 -1
- package/dist/json/renderer.d.cts +5 -21
- package/dist/json/renderer.d.ts +5 -21
- package/dist/json/renderer.js.map +1 -1
- package/dist/pm/html-string/index.cjs +22 -37
- package/dist/pm/html-string/index.cjs.map +1 -1
- package/dist/pm/html-string/index.d.cts +7 -24
- package/dist/pm/html-string/index.d.ts +7 -24
- package/dist/pm/html-string/index.js +19 -34
- package/dist/pm/html-string/index.js.map +1 -1
- package/dist/pm/markdown/index.cjs +473 -0
- package/dist/pm/markdown/index.cjs.map +1 -0
- package/dist/pm/markdown/index.d.cts +153 -0
- package/dist/pm/markdown/index.d.ts +153 -0
- package/dist/pm/markdown/index.js +449 -0
- package/dist/pm/markdown/index.js.map +1 -0
- package/dist/pm/react/index.cjs +41 -2230
- package/dist/pm/react/index.cjs.map +1 -1
- package/dist/pm/react/index.d.cts +5 -23
- package/dist/pm/react/index.d.ts +5 -23
- package/dist/pm/react/index.js +47 -2259
- package/dist/pm/react/index.js.map +1 -1
- package/package.json +27 -8
- package/src/helpers.ts +5 -16
- package/src/index.ts +5 -1
- package/src/json/html-string/string.ts +39 -13
- package/src/json/react/react.tsx +12 -15
- package/src/json/renderer.ts +50 -51
- package/src/pm/extensionRenderer.ts +16 -34
- package/src/pm/html-string/html-string.ts +29 -45
- package/src/pm/markdown/index.ts +2 -0
- package/src/pm/markdown/markdown.ts +142 -0
- package/src/pm/react/react.tsx +49 -30
- package/src/helpers.example.ts +0 -35
- package/src/json/html-string/string.example.ts +0 -46
- package/src/json/react/react.example.ts +0 -45
- package/src/pm/html-string/html-string.example.ts +0 -225
- package/src/pm/markdown/markdown.example.ts +0 -296
- package/src/pm/react/react.example.tsx +0 -306
- package/src/types.ts +0 -57
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Extensions, JSONContent } from '@tiptap/core'
|
|
2
|
+
import type { Mark, Node } from '@tiptap/pm/model'
|
|
3
|
+
|
|
4
|
+
import { TiptapStaticRendererOptions } from '../../json/renderer.js'
|
|
5
|
+
import { renderToHTMLString, serializeChildrenToHTMLString } from '../html-string/html-string.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* This code is just to show the flexibility of this renderer. We can potentially render content to any format we want.
|
|
9
|
+
* This is a simple example of how we can render content to markdown. This is not a full implementation of a markdown renderer.
|
|
10
|
+
*/
|
|
11
|
+
export function renderToMarkdown({
|
|
12
|
+
content,
|
|
13
|
+
extensions,
|
|
14
|
+
options,
|
|
15
|
+
}: {
|
|
16
|
+
content: Node | JSONContent
|
|
17
|
+
extensions: Extensions
|
|
18
|
+
options?: Partial<TiptapStaticRendererOptions<string, Mark, Node>>
|
|
19
|
+
}) {
|
|
20
|
+
return renderToHTMLString({
|
|
21
|
+
content,
|
|
22
|
+
extensions,
|
|
23
|
+
options: {
|
|
24
|
+
nodeMapping: {
|
|
25
|
+
bulletList({ children }) {
|
|
26
|
+
return `\n${serializeChildrenToHTMLString(children)}`
|
|
27
|
+
},
|
|
28
|
+
orderedList({ children }) {
|
|
29
|
+
return `\n${serializeChildrenToHTMLString(children)}`
|
|
30
|
+
},
|
|
31
|
+
listItem({ node, children, parent }) {
|
|
32
|
+
if (parent?.type.name === 'bulletList') {
|
|
33
|
+
return `- ${serializeChildrenToHTMLString(children).trim()}\n`
|
|
34
|
+
}
|
|
35
|
+
if (parent?.type.name === 'orderedList') {
|
|
36
|
+
let number = parent.attrs.start || 1
|
|
37
|
+
|
|
38
|
+
parent.forEach((parentChild, _offset, index) => {
|
|
39
|
+
if (node === parentChild) {
|
|
40
|
+
number = index + 1
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return `${number}. ${serializeChildrenToHTMLString(children).trim()}\n`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return serializeChildrenToHTMLString(children)
|
|
48
|
+
},
|
|
49
|
+
paragraph({ children }) {
|
|
50
|
+
return `\n${serializeChildrenToHTMLString(children)}\n`
|
|
51
|
+
},
|
|
52
|
+
heading({ node, children }) {
|
|
53
|
+
const level = node.attrs.level as number
|
|
54
|
+
|
|
55
|
+
return `${new Array(level).fill('#').join('')} ${children}\n`
|
|
56
|
+
},
|
|
57
|
+
codeBlock({ node, children }) {
|
|
58
|
+
return `\n\`\`\`${node.attrs.language}\n${serializeChildrenToHTMLString(children)}\n\`\`\`\n`
|
|
59
|
+
},
|
|
60
|
+
blockquote({ children }) {
|
|
61
|
+
return `\n${serializeChildrenToHTMLString(children)
|
|
62
|
+
.trim()
|
|
63
|
+
.split('\n')
|
|
64
|
+
.map(a => `> ${a}`)
|
|
65
|
+
.join('\n')}`
|
|
66
|
+
},
|
|
67
|
+
image({ node }) {
|
|
68
|
+
return ``
|
|
69
|
+
},
|
|
70
|
+
hardBreak() {
|
|
71
|
+
return '\n'
|
|
72
|
+
},
|
|
73
|
+
horizontalRule() {
|
|
74
|
+
return '\n---\n'
|
|
75
|
+
},
|
|
76
|
+
table({ children, node }) {
|
|
77
|
+
if (!Array.isArray(children)) {
|
|
78
|
+
return `\n${serializeChildrenToHTMLString(children)}\n`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return `\n${serializeChildrenToHTMLString(children[0])}| ${new Array(node.childCount - 2).fill('---').join(' | ')} |\n${serializeChildrenToHTMLString(children.slice(1))}\n`
|
|
82
|
+
},
|
|
83
|
+
tableRow({ children }) {
|
|
84
|
+
if (Array.isArray(children)) {
|
|
85
|
+
return `| ${children.join(' | ')} |\n`
|
|
86
|
+
}
|
|
87
|
+
return `${serializeChildrenToHTMLString(children)}\n`
|
|
88
|
+
},
|
|
89
|
+
tableHeader({ children }) {
|
|
90
|
+
return serializeChildrenToHTMLString(children).trim()
|
|
91
|
+
},
|
|
92
|
+
tableCell({ children }) {
|
|
93
|
+
return serializeChildrenToHTMLString(children).trim()
|
|
94
|
+
},
|
|
95
|
+
...options?.nodeMapping,
|
|
96
|
+
},
|
|
97
|
+
markMapping: {
|
|
98
|
+
bold({ children }) {
|
|
99
|
+
return `**${serializeChildrenToHTMLString(children)}**`
|
|
100
|
+
},
|
|
101
|
+
italic({ children, node }) {
|
|
102
|
+
let isBoldToo = false
|
|
103
|
+
|
|
104
|
+
// Check if the node being wrapped also has a bold mark, if so, we need to use the bold markdown syntax
|
|
105
|
+
if (node?.marks.some(m => m.type.name === 'bold')) {
|
|
106
|
+
isBoldToo = true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isBoldToo) {
|
|
110
|
+
// If the content is bold, just wrap the bold content in italic markdown syntax with another set of asterisks
|
|
111
|
+
return `*${serializeChildrenToHTMLString(children)}*`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return `_${serializeChildrenToHTMLString(children)}_`
|
|
115
|
+
},
|
|
116
|
+
code({ children }) {
|
|
117
|
+
return `\`${serializeChildrenToHTMLString(children)}\``
|
|
118
|
+
},
|
|
119
|
+
strike({ children }) {
|
|
120
|
+
return `~~${serializeChildrenToHTMLString(children)}~~`
|
|
121
|
+
},
|
|
122
|
+
underline({ children }) {
|
|
123
|
+
return `<u>${serializeChildrenToHTMLString(children)}</u>`
|
|
124
|
+
},
|
|
125
|
+
subscript({ children }) {
|
|
126
|
+
return `<sub>${serializeChildrenToHTMLString(children)}</sub>`
|
|
127
|
+
},
|
|
128
|
+
superscript({ children }) {
|
|
129
|
+
return `<sup>${serializeChildrenToHTMLString(children)}</sup>`
|
|
130
|
+
},
|
|
131
|
+
link({ node, children }) {
|
|
132
|
+
return `[${serializeChildrenToHTMLString(children)}](${node.attrs.href})`
|
|
133
|
+
},
|
|
134
|
+
highlight({ children }) {
|
|
135
|
+
return `==${serializeChildrenToHTMLString(children)}==`
|
|
136
|
+
},
|
|
137
|
+
...options?.markMapping,
|
|
138
|
+
},
|
|
139
|
+
...options,
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
}
|
package/src/pm/react/react.tsx
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/* eslint-disable no-plusplus, @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { Extensions, JSONContent } from '@tiptap/core'
|
|
2
|
+
import type { DOMOutputSpecArray, Extensions, JSONContent } from '@tiptap/core'
|
|
3
3
|
import type { DOMOutputSpec, Mark, Node } from '@tiptap/pm/model'
|
|
4
4
|
import React from 'react'
|
|
5
5
|
|
|
6
6
|
import { renderJSONContentToReactElement } from '../../json/react/react.js'
|
|
7
7
|
import { TiptapStaticRendererOptions } from '../../json/renderer.js'
|
|
8
|
-
import type { DOMOutputSpecArray } from '../../types.js'
|
|
9
8
|
import { renderToElement } from '../extensionRenderer.js'
|
|
10
9
|
|
|
11
10
|
/**
|
|
@@ -42,7 +41,27 @@ export function domOutputSpecToReactElement(
|
|
|
42
41
|
return () => content
|
|
43
42
|
}
|
|
44
43
|
if (typeof content === 'object' && 'length' in content) {
|
|
45
|
-
|
|
44
|
+
// eslint-disable-next-line prefer-const
|
|
45
|
+
let [tag, attrs, children, ...rest] = content as DOMOutputSpecArray
|
|
46
|
+
const parts = tag.split(' ')
|
|
47
|
+
|
|
48
|
+
if (parts.length > 1) {
|
|
49
|
+
tag = parts[1]
|
|
50
|
+
if (attrs === undefined) {
|
|
51
|
+
attrs = {
|
|
52
|
+
xmlns: parts[0],
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (attrs === 0) {
|
|
56
|
+
attrs = {
|
|
57
|
+
xmlns: parts[0],
|
|
58
|
+
}
|
|
59
|
+
children = 0
|
|
60
|
+
}
|
|
61
|
+
if (typeof attrs === 'object') {
|
|
62
|
+
attrs = Object.assign(attrs, { xmlns: parts[0] })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
46
65
|
|
|
47
66
|
if (attrs === undefined) {
|
|
48
67
|
return () => React.createElement(tag, mapAttrsToHTMLAttributes(undefined, key.toString()))
|
|
@@ -53,27 +72,28 @@ export function domOutputSpecToReactElement(
|
|
|
53
72
|
if (typeof attrs === 'object') {
|
|
54
73
|
if (Array.isArray(attrs)) {
|
|
55
74
|
if (children === undefined) {
|
|
56
|
-
return child =>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
return child =>
|
|
76
|
+
React.createElement(
|
|
77
|
+
tag,
|
|
78
|
+
mapAttrsToHTMLAttributes(undefined, key.toString()),
|
|
79
|
+
domOutputSpecToReactElement(attrs as DOMOutputSpecArray, key++)(child),
|
|
80
|
+
)
|
|
61
81
|
}
|
|
62
82
|
if (children === 0) {
|
|
63
|
-
return child =>
|
|
83
|
+
return child =>
|
|
84
|
+
React.createElement(
|
|
85
|
+
tag,
|
|
86
|
+
mapAttrsToHTMLAttributes(undefined, key.toString()),
|
|
87
|
+
domOutputSpecToReactElement(attrs as DOMOutputSpecArray, key++)(child),
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
return child =>
|
|
91
|
+
React.createElement(
|
|
64
92
|
tag,
|
|
65
93
|
mapAttrsToHTMLAttributes(undefined, key.toString()),
|
|
66
|
-
domOutputSpecToReactElement(attrs as DOMOutputSpecArray
|
|
94
|
+
domOutputSpecToReactElement(attrs as DOMOutputSpecArray)(child),
|
|
95
|
+
[children].concat(rest).map(outputSpec => domOutputSpecToReactElement(outputSpec, key++)(child)),
|
|
67
96
|
)
|
|
68
|
-
}
|
|
69
|
-
return child => React.createElement(
|
|
70
|
-
tag,
|
|
71
|
-
mapAttrsToHTMLAttributes(undefined, key.toString()),
|
|
72
|
-
domOutputSpecToReactElement(attrs as DOMOutputSpecArray)(child),
|
|
73
|
-
[children]
|
|
74
|
-
.concat(rest)
|
|
75
|
-
.map(outputSpec => domOutputSpecToReactElement(outputSpec, key++)(child)),
|
|
76
|
-
)
|
|
77
97
|
}
|
|
78
98
|
if (children === undefined) {
|
|
79
99
|
return () => React.createElement(tag, mapAttrsToHTMLAttributes(attrs, key.toString()))
|
|
@@ -82,19 +102,18 @@ export function domOutputSpecToReactElement(
|
|
|
82
102
|
return child => React.createElement(tag, mapAttrsToHTMLAttributes(attrs, key.toString()), child)
|
|
83
103
|
}
|
|
84
104
|
|
|
85
|
-
return child =>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.concat(rest)
|
|
90
|
-
|
|
91
|
-
)
|
|
105
|
+
return child =>
|
|
106
|
+
React.createElement(
|
|
107
|
+
tag,
|
|
108
|
+
mapAttrsToHTMLAttributes(attrs, key.toString()),
|
|
109
|
+
[children].concat(rest).map(outputSpec => domOutputSpecToReactElement(outputSpec, key++)(child)),
|
|
110
|
+
)
|
|
92
111
|
}
|
|
93
112
|
}
|
|
94
113
|
|
|
95
114
|
// TODO support DOM elements? How to handle them?
|
|
96
115
|
throw new Error(
|
|
97
|
-
'[tiptap error]: Unsupported DomOutputSpec type, check the `renderHTML` method output',
|
|
116
|
+
'[tiptap error]: Unsupported DomOutputSpec type, check the `renderHTML` method output or implement a node mapping',
|
|
98
117
|
{
|
|
99
118
|
cause: content,
|
|
100
119
|
},
|
|
@@ -113,9 +132,9 @@ export function renderToReactElement({
|
|
|
113
132
|
extensions,
|
|
114
133
|
options,
|
|
115
134
|
}: {
|
|
116
|
-
content: Node | JSONContent
|
|
117
|
-
extensions: Extensions
|
|
118
|
-
options?: Partial<TiptapStaticRendererOptions<React.ReactNode, Mark, Node
|
|
135
|
+
content: Node | JSONContent
|
|
136
|
+
extensions: Extensions
|
|
137
|
+
options?: Partial<TiptapStaticRendererOptions<React.ReactNode, Mark, Node>>
|
|
119
138
|
}): React.ReactNode {
|
|
120
139
|
return renderToElement<React.ReactNode>({
|
|
121
140
|
renderer: renderJSONContentToReactElement,
|
package/src/helpers.example.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { getAttributesFromExtensions, resolveExtensions } from '@tiptap/core'
|
|
3
|
-
import { TextAlign } from '@tiptap/extension-text-align'
|
|
4
|
-
import { TextStyle } from '@tiptap/extension-text-style'
|
|
5
|
-
import StarterKit from '@tiptap/starter-kit'
|
|
6
|
-
|
|
7
|
-
import { getAttributes } from './helpers.js'
|
|
8
|
-
|
|
9
|
-
const extensionAttributes = getAttributesFromExtensions(
|
|
10
|
-
resolveExtensions([
|
|
11
|
-
StarterKit,
|
|
12
|
-
TextAlign.configure({
|
|
13
|
-
types: ['paragraph', 'heading'],
|
|
14
|
-
}),
|
|
15
|
-
TextStyle,
|
|
16
|
-
]),
|
|
17
|
-
)
|
|
18
|
-
const attributes = getAttributes(
|
|
19
|
-
{
|
|
20
|
-
type: 'heading',
|
|
21
|
-
attrs: {
|
|
22
|
-
textAlign: 'right',
|
|
23
|
-
},
|
|
24
|
-
content: [
|
|
25
|
-
{
|
|
26
|
-
type: 'text',
|
|
27
|
-
text: 'hello world',
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
},
|
|
31
|
-
extensionAttributes,
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
// eslint-disable-next-line no-console
|
|
35
|
-
console.log(attributes)
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { renderJSONContentToString } from './string.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* This example demonstrates how to render a JSON representation of a node to a string
|
|
5
|
-
* It does so without including Prosemirror or Tiptap, it is the lightest possible way to render JSON content
|
|
6
|
-
* But, since it doesn't include Prosemirror or Tiptap, it cannot automatically render marks or nodes for you.
|
|
7
|
-
* If you need that, you should use the `renderToHTMLString` from `@tiptap/static-renderer`
|
|
8
|
-
*
|
|
9
|
-
* You have complete control over the rendering process. And can replace how each Node/Mark is rendered.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
// eslint-disable-next-line no-console
|
|
13
|
-
console.log(
|
|
14
|
-
renderJSONContentToString({
|
|
15
|
-
nodeMapping: {
|
|
16
|
-
text({ node }) {
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
18
|
-
return node.text!
|
|
19
|
-
},
|
|
20
|
-
heading({ node, children }) {
|
|
21
|
-
const level = node.attrs?.level
|
|
22
|
-
const attrs = Object.entries(node.attrs || {})
|
|
23
|
-
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
|
|
24
|
-
.join(' ')
|
|
25
|
-
|
|
26
|
-
return `<h${level}${attrs ? ` ${attrs}` : ''}>${([] as string[])
|
|
27
|
-
.concat(children || '')
|
|
28
|
-
.filter(Boolean)
|
|
29
|
-
.join('\n')}</h${level}>`
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
markMapping: {},
|
|
33
|
-
})({
|
|
34
|
-
content: {
|
|
35
|
-
type: 'heading',
|
|
36
|
-
content: [
|
|
37
|
-
{
|
|
38
|
-
type: 'text',
|
|
39
|
-
text: 'hello world',
|
|
40
|
-
marks: [],
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
attrs: { level: 2 },
|
|
44
|
-
},
|
|
45
|
-
}),
|
|
46
|
-
)
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
|
|
3
|
-
import { NodeType } from '../../types.js'
|
|
4
|
-
import { NodeProps } from '../renderer.js'
|
|
5
|
-
import { renderJSONContentToReactElement } from './react.js'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* This example demonstrates how to render a JSON representation of a node to a React element
|
|
9
|
-
* It does so without including Prosemirror or Tiptap, it is the lightest possible way to render JSON content
|
|
10
|
-
* But, since it doesn't include Prosemirror or Tiptap, it cannot automatically render marks or nodes for you.
|
|
11
|
-
* If you need that, you should use the `renderToReactElement` from `@tiptap/static-renderer`
|
|
12
|
-
*
|
|
13
|
-
* You have complete control over the rendering process. And can replace how each Node/Mark is rendered.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
// eslint-disable-next-line no-console
|
|
17
|
-
console.log(renderJSONContentToReactElement({
|
|
18
|
-
nodeMapping: {
|
|
19
|
-
text({ node }) {
|
|
20
|
-
return node.text ?? null
|
|
21
|
-
},
|
|
22
|
-
heading({
|
|
23
|
-
node,
|
|
24
|
-
children,
|
|
25
|
-
}: NodeProps<NodeType<'heading', { level: number }>, React.ReactNode>) {
|
|
26
|
-
const level = node.attrs.level
|
|
27
|
-
const hTag = `h${level}`
|
|
28
|
-
|
|
29
|
-
return React.createElement(hTag, node.attrs, children)
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
markMapping: {},
|
|
33
|
-
})({
|
|
34
|
-
content: {
|
|
35
|
-
type: 'heading',
|
|
36
|
-
content: [
|
|
37
|
-
{
|
|
38
|
-
type: 'text',
|
|
39
|
-
text: 'hello world',
|
|
40
|
-
marks: [],
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
attrs: { level: 2 },
|
|
44
|
-
},
|
|
45
|
-
}))
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import StarterKit from '@tiptap/starter-kit'
|
|
2
|
-
|
|
3
|
-
import { renderToHTMLString, serializeAttrsToHTMLString, serializeChildrenToHTMLString } from './html-string.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* This example demonstrates how to render a Prosemirror Node (or JSON Content) to an HTML string.
|
|
7
|
-
* It will use your extensions to render the content based on each Node's/Mark's `renderHTML` method.
|
|
8
|
-
* This can be useful if you want to render content to HTML without having an actual editor instance.
|
|
9
|
-
*
|
|
10
|
-
* You have complete control over the rendering process. And can replace how each Node/Mark is rendered.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// eslint-disable-next-line no-console
|
|
14
|
-
console.log(
|
|
15
|
-
renderToHTMLString({
|
|
16
|
-
extensions: [StarterKit],
|
|
17
|
-
options: {
|
|
18
|
-
nodeMapping: {
|
|
19
|
-
heading({ node, children }) {
|
|
20
|
-
const level = node.attrs.level
|
|
21
|
-
|
|
22
|
-
return `<h${level}${serializeAttrsToHTMLString(node.attrs)}>THIS IS AN EXAMPLE OF CUSTOM HTML STRING RENDERING${serializeChildrenToHTMLString(children)}</h${level}>`
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
markMapping: {},
|
|
26
|
-
},
|
|
27
|
-
content:
|
|
28
|
-
{
|
|
29
|
-
type: 'doc',
|
|
30
|
-
from: 0,
|
|
31
|
-
to: 574,
|
|
32
|
-
content: [
|
|
33
|
-
{
|
|
34
|
-
type: 'heading',
|
|
35
|
-
from: 0,
|
|
36
|
-
to: 11,
|
|
37
|
-
attrs: {
|
|
38
|
-
level: 2,
|
|
39
|
-
},
|
|
40
|
-
content: [
|
|
41
|
-
{
|
|
42
|
-
type: 'text',
|
|
43
|
-
from: 1,
|
|
44
|
-
to: 10,
|
|
45
|
-
text: 'Hi there,',
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
type: 'paragraph',
|
|
51
|
-
from: 11,
|
|
52
|
-
to: 169,
|
|
53
|
-
content: [
|
|
54
|
-
{
|
|
55
|
-
type: 'text',
|
|
56
|
-
from: 12,
|
|
57
|
-
to: 22,
|
|
58
|
-
text: 'this is a ',
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
type: 'text',
|
|
62
|
-
from: 22,
|
|
63
|
-
to: 27,
|
|
64
|
-
marks: [
|
|
65
|
-
{
|
|
66
|
-
type: 'italic',
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
text: 'basic',
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
type: 'text',
|
|
73
|
-
from: 27,
|
|
74
|
-
to: 39,
|
|
75
|
-
text: ' example of ',
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
type: 'text',
|
|
79
|
-
from: 39,
|
|
80
|
-
to: 45,
|
|
81
|
-
marks: [
|
|
82
|
-
{
|
|
83
|
-
type: 'bold',
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
text: 'Tiptap',
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
type: 'text',
|
|
90
|
-
from: 45,
|
|
91
|
-
to: 168,
|
|
92
|
-
text: '. Sure, there are all kind of basic text styles you’d probably expect from a text editor. But wait until you see the lists:',
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
type: 'bulletList',
|
|
98
|
-
from: 169,
|
|
99
|
-
to: 230,
|
|
100
|
-
content: [
|
|
101
|
-
{
|
|
102
|
-
type: 'listItem',
|
|
103
|
-
from: 170,
|
|
104
|
-
to: 205,
|
|
105
|
-
attrs: {
|
|
106
|
-
color: '',
|
|
107
|
-
},
|
|
108
|
-
content: [
|
|
109
|
-
{
|
|
110
|
-
type: 'paragraph',
|
|
111
|
-
from: 171,
|
|
112
|
-
to: 204,
|
|
113
|
-
content: [
|
|
114
|
-
{
|
|
115
|
-
type: 'text',
|
|
116
|
-
from: 172,
|
|
117
|
-
to: 203,
|
|
118
|
-
text: 'That’s a bullet list with one …',
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
type: 'listItem',
|
|
126
|
-
from: 205,
|
|
127
|
-
to: 229,
|
|
128
|
-
attrs: {
|
|
129
|
-
color: '',
|
|
130
|
-
},
|
|
131
|
-
content: [
|
|
132
|
-
{
|
|
133
|
-
type: 'paragraph',
|
|
134
|
-
from: 206,
|
|
135
|
-
to: 228,
|
|
136
|
-
content: [
|
|
137
|
-
{
|
|
138
|
-
type: 'text',
|
|
139
|
-
from: 207,
|
|
140
|
-
to: 227,
|
|
141
|
-
text: '… or two list items.',
|
|
142
|
-
},
|
|
143
|
-
],
|
|
144
|
-
},
|
|
145
|
-
],
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
type: 'paragraph',
|
|
151
|
-
from: 230,
|
|
152
|
-
to: 326,
|
|
153
|
-
content: [
|
|
154
|
-
{
|
|
155
|
-
type: 'text',
|
|
156
|
-
from: 231,
|
|
157
|
-
to: 325,
|
|
158
|
-
text: 'Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:',
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
type: 'codeBlock',
|
|
164
|
-
from: 326,
|
|
165
|
-
to: 353,
|
|
166
|
-
attrs: {
|
|
167
|
-
language: 'css',
|
|
168
|
-
},
|
|
169
|
-
content: [
|
|
170
|
-
{
|
|
171
|
-
type: 'text',
|
|
172
|
-
from: 327,
|
|
173
|
-
to: 352,
|
|
174
|
-
text: 'body {\n display: none;\n}',
|
|
175
|
-
},
|
|
176
|
-
],
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
type: 'paragraph',
|
|
180
|
-
from: 353,
|
|
181
|
-
to: 522,
|
|
182
|
-
content: [
|
|
183
|
-
{
|
|
184
|
-
type: 'text',
|
|
185
|
-
from: 354,
|
|
186
|
-
to: 521,
|
|
187
|
-
text: 'I know, I know, this is impressive. It’s only the tip of the iceberg though. Give it a try and click a little bit around. Don’t forget to check the other examples too.',
|
|
188
|
-
},
|
|
189
|
-
],
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
type: 'blockquote',
|
|
193
|
-
from: 522,
|
|
194
|
-
to: 572,
|
|
195
|
-
content: [
|
|
196
|
-
{
|
|
197
|
-
type: 'paragraph',
|
|
198
|
-
from: 523,
|
|
199
|
-
to: 571,
|
|
200
|
-
content: [
|
|
201
|
-
{
|
|
202
|
-
type: 'text',
|
|
203
|
-
from: 524,
|
|
204
|
-
to: 564,
|
|
205
|
-
text: 'Wow, that’s amazing. Good work, boy! 👏 ',
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
type: 'hardBreak',
|
|
209
|
-
from: 564,
|
|
210
|
-
to: 565,
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
type: 'text',
|
|
214
|
-
from: 565,
|
|
215
|
-
to: 570,
|
|
216
|
-
text: '— Mom',
|
|
217
|
-
},
|
|
218
|
-
],
|
|
219
|
-
},
|
|
220
|
-
],
|
|
221
|
-
},
|
|
222
|
-
],
|
|
223
|
-
},
|
|
224
|
-
}),
|
|
225
|
-
)
|