@tiptap/static-renderer 3.0.0-next.1

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.
Files changed (57) hide show
  1. package/README.md +18 -0
  2. package/dist/index.cjs +62 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +52 -0
  5. package/dist/index.d.ts +52 -0
  6. package/dist/index.js +34 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/json/html-string/index.cjs +100 -0
  9. package/dist/json/html-string/index.cjs.map +1 -0
  10. package/dist/json/html-string/index.d.cts +205 -0
  11. package/dist/json/html-string/index.d.ts +205 -0
  12. package/dist/json/html-string/index.js +72 -0
  13. package/dist/json/html-string/index.js.map +1 -0
  14. package/dist/json/react/index.cjs +2306 -0
  15. package/dist/json/react/index.cjs.map +1 -0
  16. package/dist/json/react/index.d.cts +207 -0
  17. package/dist/json/react/index.d.ts +207 -0
  18. package/dist/json/react/index.js +2291 -0
  19. package/dist/json/react/index.js.map +1 -0
  20. package/dist/json/renderer.cjs +89 -0
  21. package/dist/json/renderer.cjs.map +1 -0
  22. package/dist/json/renderer.d.cts +182 -0
  23. package/dist/json/renderer.d.ts +182 -0
  24. package/dist/json/renderer.js +64 -0
  25. package/dist/json/renderer.js.map +1 -0
  26. package/dist/pm/html-string/index.cjs +359 -0
  27. package/dist/pm/html-string/index.cjs.map +1 -0
  28. package/dist/pm/html-string/index.d.cts +192 -0
  29. package/dist/pm/html-string/index.d.ts +192 -0
  30. package/dist/pm/html-string/index.js +332 -0
  31. package/dist/pm/html-string/index.js.map +1 -0
  32. package/dist/pm/react/index.cjs +2588 -0
  33. package/dist/pm/react/index.cjs.map +1 -0
  34. package/dist/pm/react/index.d.cts +181 -0
  35. package/dist/pm/react/index.d.ts +181 -0
  36. package/dist/pm/react/index.js +2576 -0
  37. package/dist/pm/react/index.js.map +1 -0
  38. package/package.json +82 -0
  39. package/src/helpers.example.ts +35 -0
  40. package/src/helpers.ts +65 -0
  41. package/src/index.ts +2 -0
  42. package/src/json/html-string/index.ts +2 -0
  43. package/src/json/html-string/string.example.ts +46 -0
  44. package/src/json/html-string/string.ts +22 -0
  45. package/src/json/react/index.ts +2 -0
  46. package/src/json/react/react.example.ts +45 -0
  47. package/src/json/react/react.tsx +35 -0
  48. package/src/json/renderer.ts +242 -0
  49. package/src/pm/extensionRenderer.ts +230 -0
  50. package/src/pm/html-string/html-string.example.ts +225 -0
  51. package/src/pm/html-string/html-string.ts +121 -0
  52. package/src/pm/html-string/index.ts +2 -0
  53. package/src/pm/markdown/markdown.example.ts +296 -0
  54. package/src/pm/react/index.ts +2 -0
  55. package/src/pm/react/react.example.tsx +306 -0
  56. package/src/pm/react/react.tsx +133 -0
  57. package/src/types.ts +57 -0
@@ -0,0 +1,225 @@
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
+ )
@@ -0,0 +1,121 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { Extensions, JSONContent } from '@tiptap/core'
3
+ import type { DOMOutputSpec, Mark, Node } from '@tiptap/pm/model'
4
+
5
+ import { renderJSONContentToString } from '../../json/html-string/string.js'
6
+ import { TiptapStaticRendererOptions } from '../../json/renderer.js'
7
+ import type { DOMOutputSpecArray } from '../../types.js'
8
+ import { renderToElement } from '../extensionRenderer.js'
9
+
10
+ /**
11
+ * Serialize the attributes of a node or mark to a string
12
+ * @param attrs The attributes to serialize
13
+ * @returns The serialized attributes as a string
14
+ */
15
+ export function serializeAttrsToHTMLString(attrs: Record<string, any>): string {
16
+ const output = Object.entries(attrs)
17
+ .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
18
+ .join(' ')
19
+
20
+ return output ? ` ${output}` : ''
21
+ }
22
+
23
+ /**
24
+ * Serialize the children of a node or mark to a string
25
+ * @param children The children to serialize
26
+ * @returns The serialized children as a string
27
+ */
28
+ export function serializeChildrenToHTMLString(children?: string | string[]): string {
29
+ return ([] as string[])
30
+ .concat(children || '')
31
+ .filter(Boolean)
32
+ .join('')
33
+ }
34
+
35
+ /**
36
+ * Take a DOMOutputSpec and return a function that can render it to a string
37
+ * @param content The DOMOutputSpec to convert to a string
38
+ * @returns A function that can render the DOMOutputSpec to a string
39
+ */
40
+ export function domOutputSpecToHTMLString(
41
+ content: DOMOutputSpec,
42
+ ): (children?: string | string[]) => string {
43
+ if (typeof content === 'string') {
44
+ return () => content
45
+ }
46
+ if (typeof content === 'object' && 'length' in content) {
47
+ const [tag, attrs, children, ...rest] = content as DOMOutputSpecArray
48
+
49
+ if (attrs === undefined) {
50
+ return () => `<${tag}/>`
51
+ }
52
+ if (attrs === 0) {
53
+ return child => `<${tag}>${serializeChildrenToHTMLString(child)}</${tag}>`
54
+ }
55
+ if (typeof attrs === 'object') {
56
+ if (Array.isArray(attrs)) {
57
+ if (children === undefined) {
58
+ return child => `<${tag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}</${tag}>`
59
+ }
60
+ if (children === 0) {
61
+ return child => `<${tag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}</${tag}>`
62
+ }
63
+ return child => `<${tag}>${domOutputSpecToHTMLString(attrs as DOMOutputSpecArray)(child)}${[children]
64
+ .concat(rest)
65
+ .map(a => domOutputSpecToHTMLString(a)(child))}</${tag}>`
66
+ }
67
+ if (children === undefined) {
68
+ return () => `<${tag}${serializeAttrsToHTMLString(attrs)}/>`
69
+ }
70
+ if (children === 0) {
71
+ return child => `<${tag}${serializeAttrsToHTMLString(attrs)}>${serializeChildrenToHTMLString(
72
+ child,
73
+ )}</${tag}>`
74
+ }
75
+
76
+ return child => `<${tag}${serializeAttrsToHTMLString(attrs)}>${[children]
77
+ .concat(rest)
78
+ .map(a => domOutputSpecToHTMLString(a)(child))
79
+ .join('')}</${tag}>`
80
+ }
81
+ }
82
+
83
+ // TODO support DOM elements? How to handle them?
84
+ throw new Error(
85
+ '[tiptap error]: Unsupported DomOutputSpec type, check the `renderHTML` method output',
86
+ {
87
+ cause: content,
88
+ },
89
+ )
90
+ }
91
+
92
+ /**
93
+ * This function will statically render a Prosemirror Node to HTML using the provided extensions and options
94
+ * @param content The content to render to HTML
95
+ * @param extensions The extensions to use for rendering
96
+ * @param options The options to use for rendering
97
+ * @returns The rendered HTML string
98
+ */
99
+ export function renderToHTMLString({
100
+ content,
101
+ extensions,
102
+ options,
103
+ }: {
104
+ content: Node | JSONContent;
105
+ extensions: Extensions;
106
+ options?: Partial<TiptapStaticRendererOptions<string, Mark, Node>>;
107
+ }): string {
108
+ return renderToElement<string>({
109
+ renderer: renderJSONContentToString,
110
+ domOutputSpecToElement: domOutputSpecToHTMLString,
111
+ mapDefinedTypes: {
112
+ // Map a doc node to concatenated children
113
+ doc: ({ children }) => serializeChildrenToHTMLString(children),
114
+ // Map a text node to its text content
115
+ text: ({ node }) => node.text ?? '',
116
+ },
117
+ content,
118
+ extensions,
119
+ options,
120
+ })
121
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../extensionRenderer.js'
2
+ export * from './html-string.js'
@@ -0,0 +1,296 @@
1
+ import { JSONContent } from '@tiptap/core'
2
+ import StarterKit from '@tiptap/starter-kit'
3
+
4
+ import { renderToHTMLString, serializeChildrenToHTMLString } from '../html-string/html-string.js'
5
+
6
+ /**
7
+ * This code is just to show the flexibility of this renderer. We can potentially render content to any format we want.
8
+ * This is a simple example of how we can render content to markdown. This is not a full implementation of a markdown renderer.
9
+ */
10
+
11
+ const renderToMarkdown = ({ content }: { content: JSONContent | Node }) => renderToHTMLString({
12
+ content,
13
+ extensions: [StarterKit],
14
+ options: {
15
+ nodeMapping: {
16
+ bulletList({ children }) {
17
+ return `\n${serializeChildrenToHTMLString(children)}`
18
+ },
19
+ orderedList({ children }) {
20
+ return `\n${serializeChildrenToHTMLString(children)}`
21
+ },
22
+ listItem({ node, children, parent }) {
23
+ if (parent?.type.name === 'bulletList') {
24
+ return `- ${serializeChildrenToHTMLString(children).trim()}\n`
25
+ }
26
+ if (parent?.type.name === 'orderedList') {
27
+ let number = parent.attrs.start || 1
28
+
29
+ parent.forEach((parentChild, _offset, index) => {
30
+ if (node === parentChild) {
31
+ number = index + 1
32
+ }
33
+ })
34
+
35
+ return `${number}. ${serializeChildrenToHTMLString(children).trim()}\n`
36
+ }
37
+
38
+ return serializeChildrenToHTMLString(children)
39
+ },
40
+ paragraph({ children }) {
41
+ return `\n${serializeChildrenToHTMLString(children)}\n`
42
+ },
43
+ heading({ node, children }) {
44
+ const level = node.attrs.level as number
45
+
46
+ return `${new Array(level).fill('#').join('')} ${children}\n`
47
+ },
48
+ codeBlock({ node, children }) {
49
+ return `\n\`\`\`${node.attrs.language}\n${serializeChildrenToHTMLString(
50
+ children,
51
+ )}\n\`\`\`\n`
52
+ },
53
+ blockquote({ children }) {
54
+ return `\n${serializeChildrenToHTMLString(children)
55
+ .trim()
56
+ .split('\n')
57
+ .map(a => `> ${a}`)
58
+ .join('\n')}`
59
+ },
60
+ image({ node }) {
61
+ return `![${node.attrs.alt}](${node.attrs.src})`
62
+ },
63
+ hardBreak() {
64
+ return '\n'
65
+ },
66
+ },
67
+ markMapping: {
68
+ bold({ children }) {
69
+ return `**${serializeChildrenToHTMLString(children)}**`
70
+ },
71
+ italic({ children, node }) {
72
+ let isBoldToo = false
73
+
74
+ // Check if the node being wrapped also has a bold mark, if so, we need to use the bold markdown syntax
75
+ if (node?.marks.some(m => m.type.name === 'bold')) {
76
+ isBoldToo = true
77
+ }
78
+
79
+ if (isBoldToo) {
80
+ // If the content is bold, just wrap the bold content in italic markdown syntax with another set of asterisks
81
+ return `*${serializeChildrenToHTMLString(children)}*`
82
+ }
83
+
84
+ return `_${serializeChildrenToHTMLString(children)}_`
85
+ },
86
+ code({ children }) {
87
+ return `\`${serializeChildrenToHTMLString(children)}\``
88
+ },
89
+ },
90
+ },
91
+ })
92
+
93
+ // eslint-disable-next-line no-console
94
+ console.log(
95
+ renderToMarkdown({
96
+ content: {
97
+ type: 'doc',
98
+ from: 0,
99
+ to: 574,
100
+ content: [
101
+ {
102
+ type: 'heading',
103
+ from: 0,
104
+ to: 11,
105
+ attrs: {
106
+ level: 2,
107
+ },
108
+ content: [
109
+ {
110
+ type: 'text',
111
+ from: 1,
112
+ to: 10,
113
+ text: 'Hi there,',
114
+ },
115
+ ],
116
+ },
117
+ {
118
+ type: 'paragraph',
119
+ from: 11,
120
+ to: 169,
121
+ content: [
122
+ {
123
+ type: 'text',
124
+ from: 12,
125
+ to: 22,
126
+ text: 'this is a ',
127
+ },
128
+ {
129
+ type: 'text',
130
+ from: 22,
131
+ to: 27,
132
+ marks: [
133
+ {
134
+ type: 'italic',
135
+ },
136
+ ],
137
+ text: 'basic',
138
+ },
139
+ {
140
+ type: 'text',
141
+ from: 27,
142
+ to: 39,
143
+ text: ' example of ',
144
+ },
145
+ {
146
+ type: 'text',
147
+ from: 39,
148
+ to: 45,
149
+ marks: [
150
+ {
151
+ type: 'bold',
152
+ },
153
+ {
154
+ type: 'italic',
155
+ },
156
+ ],
157
+ text: 'Tiptap',
158
+ },
159
+ {
160
+ type: 'text',
161
+ from: 45,
162
+ to: 168,
163
+ 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:',
164
+ },
165
+ ],
166
+ },
167
+ {
168
+ type: 'bulletList',
169
+ from: 169,
170
+ to: 230,
171
+ content: [
172
+ {
173
+ type: 'listItem',
174
+ from: 170,
175
+ to: 205,
176
+ attrs: {
177
+ color: '',
178
+ },
179
+ content: [
180
+ {
181
+ type: 'paragraph',
182
+ from: 171,
183
+ to: 204,
184
+ content: [
185
+ {
186
+ type: 'text',
187
+ from: 172,
188
+ to: 203,
189
+ text: 'That’s a bullet list with one …',
190
+ },
191
+ ],
192
+ },
193
+ ],
194
+ },
195
+ {
196
+ type: 'listItem',
197
+ from: 205,
198
+ to: 229,
199
+ attrs: {
200
+ color: '',
201
+ },
202
+ content: [
203
+ {
204
+ type: 'paragraph',
205
+ from: 206,
206
+ to: 228,
207
+ content: [
208
+ {
209
+ type: 'text',
210
+ from: 207,
211
+ to: 227,
212
+ text: '… or two list items.',
213
+ },
214
+ ],
215
+ },
216
+ ],
217
+ },
218
+ ],
219
+ },
220
+ {
221
+ type: 'paragraph',
222
+ from: 230,
223
+ to: 326,
224
+ content: [
225
+ {
226
+ type: 'text',
227
+ from: 231,
228
+ to: 325,
229
+ text: 'Isn’t that great? And all of that is editable. But wait, there’s more. Let’s try a code block:',
230
+ },
231
+ ],
232
+ },
233
+ {
234
+ type: 'codeBlock',
235
+ from: 326,
236
+ to: 353,
237
+ attrs: {
238
+ language: 'css',
239
+ },
240
+ content: [
241
+ {
242
+ type: 'text',
243
+ from: 327,
244
+ to: 352,
245
+ text: 'body {\n display: none;\n}',
246
+ },
247
+ ],
248
+ },
249
+ {
250
+ type: 'paragraph',
251
+ from: 353,
252
+ to: 522,
253
+ content: [
254
+ {
255
+ type: 'text',
256
+ from: 354,
257
+ to: 521,
258
+ 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.',
259
+ },
260
+ ],
261
+ },
262
+ {
263
+ type: 'blockquote',
264
+ from: 522,
265
+ to: 572,
266
+ content: [
267
+ {
268
+ type: 'paragraph',
269
+ from: 523,
270
+ to: 571,
271
+ content: [
272
+ {
273
+ type: 'text',
274
+ from: 524,
275
+ to: 564,
276
+ text: 'Wow, that’s amazing. Good work, boy! 👏 ',
277
+ },
278
+ {
279
+ type: 'hardBreak',
280
+ from: 564,
281
+ to: 565,
282
+ },
283
+ {
284
+ type: 'text',
285
+ from: 565,
286
+ to: 570,
287
+ text: '— Mom',
288
+ },
289
+ ],
290
+ },
291
+ ],
292
+ },
293
+ ],
294
+ },
295
+ }),
296
+ )
@@ -0,0 +1,2 @@
1
+ export * from '../extensionRenderer.js'
2
+ export * from './react.js'