0
# Document Helpers
1
2
@tiptap/core provides a comprehensive set of helper functions for document manipulation, content generation, querying, and state inspection. These functions enable advanced document processing and analysis.
3
4
## Capabilities
5
6
### Content Generation
7
8
Functions for converting content between different formats and creating documents.
9
10
```typescript { .api }
11
/**
12
* Generate HTML string from JSON document content
13
* @param doc - JSON document content
14
* @param extensions - Array of extensions for schema generation
15
* @returns HTML string representation
16
*/
17
function generateHTML(doc: JSONContent, extensions: Extensions): string;
18
19
/**
20
* Generate JSON document from HTML string
21
* @param content - HTML string to parse
22
* @param extensions - Array of extensions for schema generation
23
* @returns JSONContent representation
24
*/
25
function generateJSON(content: string, extensions: Extensions): JSONContent;
26
27
/**
28
* Generate plain text from JSON document content
29
* @param doc - JSON document or ProseMirror Node
30
* @param options - Text generation options
31
* @returns Plain text string
32
*/
33
function generateText(
34
doc: JSONContent | ProseMirrorNode,
35
options?: GenerateTextOptions
36
): string;
37
38
interface GenerateTextOptions {
39
/** Separator between block elements */
40
blockSeparator?: string;
41
/** Include text from specific range */
42
from?: number;
43
to?: number;
44
}
45
```
46
47
**Usage Examples:**
48
49
```typescript
50
import { generateHTML, generateJSON, generateText } from '@tiptap/core';
51
52
// Convert JSON to HTML
53
const jsonDoc = {
54
type: 'doc',
55
content: [
56
{
57
type: 'paragraph',
58
content: [
59
{ type: 'text', text: 'Hello ' },
60
{ type: 'text', text: 'World', marks: [{ type: 'bold' }] }
61
]
62
}
63
]
64
};
65
66
const html = generateHTML(jsonDoc, extensions);
67
// '<p>Hello <strong>World</strong></p>'
68
69
// Convert HTML to JSON
70
const htmlContent = '<h1>Title</h1><p>Paragraph with <em>emphasis</em></p>';
71
const json = generateJSON(htmlContent, extensions);
72
73
// Extract plain text
74
const plainText = generateText(jsonDoc);
75
// 'Hello World'
76
77
const textWithSeparator = generateText(jsonDoc, {
78
blockSeparator: ' | '
79
});
80
// 'Hello World |'
81
82
// From ProseMirror node
83
const text = generateText(editor.state.doc, {
84
from: 0,
85
to: 100
86
});
87
```
88
89
### Document Creation
90
91
Functions for creating ProseMirror documents and nodes from various content sources.
92
93
```typescript { .api }
94
/**
95
* Create a ProseMirror document from content
96
* @param content - Content to convert to document
97
* @param schema - ProseMirror schema to use
98
* @param parseOptions - Options for parsing content
99
* @returns ProseMirror document node
100
*/
101
function createDocument(
102
content: Content,
103
schema: Schema,
104
parseOptions?: ParseOptions
105
): ProseMirrorNode;
106
107
/**
108
* Create ProseMirror node(s) from content
109
* @param content - Content to convert
110
* @param schema - ProseMirror schema to use
111
* @param options - Creation options
112
* @returns Single node or array of nodes
113
*/
114
function createNodeFromContent(
115
content: Content,
116
schema: Schema,
117
options?: CreateNodeFromContentOptions
118
): ProseMirrorNode | ProseMirrorNode[];
119
120
interface ParseOptions {
121
/** Preserve whitespace */
122
preserveWhitespace?: boolean | 'full';
123
/** Parse context */
124
context?: ResolvedPos;
125
/** Parse rules to use */
126
ruleFromNode?: (node: ProseMirrorNode) => ParseRule | null;
127
/** Top node name */
128
topNode?: string;
129
}
130
131
interface CreateNodeFromContentOptions extends ParseOptions {
132
/** Slice content to fragment */
133
slice?: boolean;
134
/** Parse as single node */
135
parseOptions?: ParseOptions;
136
}
137
138
type Content =
139
| string
140
| JSONContent
141
| JSONContent[]
142
| ProseMirrorNode
143
| ProseMirrorNode[]
144
| ProseMirrorFragment;
145
```
146
147
**Usage Examples:**
148
149
```typescript
150
import { createDocument, createNodeFromContent } from '@tiptap/core';
151
152
// Create document from HTML
153
const doc = createDocument(
154
'<h1>Title</h1><p>Content</p>',
155
schema
156
);
157
158
// Create document from JSON
159
const jsonContent = {
160
type: 'doc',
161
content: [
162
{ type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'Title' }] }
163
]
164
};
165
const docFromJson = createDocument(jsonContent, schema);
166
167
// Create nodes from content
168
const nodes = createNodeFromContent(
169
'<p>Paragraph 1</p><p>Paragraph 2</p>',
170
schema
171
);
172
173
// Create with options
174
const nodeWithOptions = createNodeFromContent(
175
'<pre> Code with spaces </pre>',
176
schema,
177
{ parseOptions: { preserveWhitespace: 'full' } }
178
);
179
180
// Create fragment
181
const fragment = createNodeFromContent(
182
[
183
{ type: 'paragraph', content: [{ type: 'text', text: 'First' }] },
184
{ type: 'paragraph', content: [{ type: 'text', text: 'Second' }] }
185
],
186
schema,
187
{ slice: true }
188
);
189
```
190
191
### Content Queries
192
193
Functions for finding and extracting content from documents.
194
195
```typescript { .api }
196
/**
197
* Find child nodes matching a predicate
198
* @param node - Parent node to search in
199
* @param predicate - Function to test each child
200
* @param descend - Whether to search recursively
201
* @returns Array of nodes with their positions
202
*/
203
function findChildren(
204
node: ProseMirrorNode,
205
predicate: (child: ProseMirrorNode) => boolean,
206
descend?: boolean
207
): NodeWithPos[];
208
209
/**
210
* Find child nodes in a specific range
211
* @param node - Parent node to search in
212
* @param range - Range to search within
213
* @param predicate - Function to test each child
214
* @param descend - Whether to search recursively
215
* @returns Array of nodes with their positions
216
*/
217
function findChildrenInRange(
218
node: ProseMirrorNode,
219
range: { from: number; to: number },
220
predicate: (child: ProseMirrorNode) => boolean,
221
descend?: boolean
222
): NodeWithPos[];
223
224
/**
225
* Find parent node matching predicate
226
* @param predicate - Function to test parent nodes
227
* @returns Function that takes selection and returns parent info
228
*/
229
function findParentNode(
230
predicate: (node: ProseMirrorNode) => boolean
231
): (selection: Selection) => NodeWithPos | null;
232
233
/**
234
* Find parent node closest to a position
235
* @param $pos - Resolved position
236
* @param predicate - Function to test parent nodes
237
* @returns Parent node info if found
238
*/
239
function findParentNodeClosestToPos(
240
$pos: ResolvedPos,
241
predicate: (node: ProseMirrorNode) => boolean
242
): NodeWithPos | null;
243
244
/**
245
* Get node at specific position
246
* @param state - Editor state
247
* @param pos - Document position
248
* @returns Node information at position
249
*/
250
function getNodeAtPosition(
251
state: EditorState,
252
pos: number
253
): { node: ProseMirrorNode; pos: number; depth: number };
254
255
/**
256
* Get text content between positions
257
* @param node - Node to extract text from
258
* @param from - Start position
259
* @param to - End position
260
* @param options - Extraction options
261
* @returns Text content
262
*/
263
function getTextBetween(
264
node: ProseMirrorNode,
265
from: number,
266
to: number,
267
options?: GetTextBetweenOptions
268
): string;
269
270
interface NodeWithPos {
271
node: ProseMirrorNode;
272
pos: number;
273
}
274
275
interface GetTextBetweenOptions {
276
/** Block separator string */
277
blockSeparator?: string;
278
/** Text serializers for custom nodes */
279
textSerializers?: Record<string, (props: { node: ProseMirrorNode }) => string>;
280
}
281
```
282
283
**Usage Examples:**
284
285
```typescript
286
import {
287
findChildren,
288
findChildrenInRange,
289
findParentNode,
290
getTextBetween
291
} from '@tiptap/core';
292
293
// Find all headings in document
294
const headings = findChildren(
295
editor.state.doc,
296
node => node.type.name === 'heading'
297
);
298
299
headings.forEach(({ node, pos }) => {
300
console.log(`H${node.attrs.level}: ${node.textContent} at ${pos}`);
301
});
302
303
// Find images in a specific range
304
const images = findChildrenInRange(
305
editor.state.doc,
306
{ from: 100, to: 200 },
307
node => node.type.name === 'image'
308
);
309
310
// Find all paragraphs recursively
311
const allParagraphs = findChildren(
312
editor.state.doc,
313
node => node.type.name === 'paragraph',
314
true // descend into nested structures
315
);
316
317
// Find parent list item
318
const findListItem = findParentNode(node => node.type.name === 'listItem');
319
const listItemInfo = findListItem(editor.state.selection);
320
321
if (listItemInfo) {
322
console.log('Inside list item at position:', listItemInfo.pos);
323
}
324
325
// Get text between positions
326
const textContent = getTextBetween(
327
editor.state.doc,
328
0,
329
editor.state.doc.content.size
330
);
331
332
// Custom text extraction
333
const textWithCustomSeparators = getTextBetween(
334
editor.state.doc,
335
0,
336
100,
337
{
338
blockSeparator: '\n\n',
339
textSerializers: {
340
image: ({ node }) => `[Image: ${node.attrs.alt || 'Untitled'}]`,
341
mention: ({ node }) => `@${node.attrs.label}`
342
}
343
}
344
);
345
```
346
347
### State Inspection
348
349
Functions for analyzing editor state and selection properties.
350
351
```typescript { .api }
352
/**
353
* Check if node or mark is active
354
* @param state - Editor state
355
* @param name - Node/mark name or type
356
* @param attributes - Optional attributes to match
357
* @returns Whether the node/mark is active
358
*/
359
function isActive(
360
state: EditorState,
361
name?: string | NodeType | MarkType,
362
attributes?: Record<string, any>
363
): boolean;
364
365
/**
366
* Check if a mark is active
367
* @param state - Editor state
368
* @param type - Mark type or name
369
* @param attributes - Optional attributes to match
370
* @returns Whether the mark is active
371
*/
372
function isMarkActive(
373
state: EditorState,
374
type: MarkType | string,
375
attributes?: Record<string, any>
376
): boolean;
377
378
/**
379
* Check if a node is active
380
* @param state - Editor state
381
* @param type - Node type or name
382
* @param attributes - Optional attributes to match
383
* @returns Whether the node is active
384
*/
385
function isNodeActive(
386
state: EditorState,
387
type: NodeType | string,
388
attributes?: Record<string, any>
389
): boolean;
390
391
/**
392
* Check if node is empty
393
* @param node - Node to check
394
* @param options - Check options
395
* @returns Whether the node is considered empty
396
*/
397
function isNodeEmpty(
398
node: ProseMirrorNode,
399
options?: { ignoreWhitespace?: boolean; checkChildren?: boolean }
400
): boolean;
401
402
/**
403
* Check if selection is text selection
404
* @param selection - Selection to check
405
* @returns Whether selection is TextSelection
406
*/
407
function isTextSelection(selection: Selection): selection is TextSelection;
408
409
/**
410
* Check if selection is node selection
411
* @param selection - Selection to check
412
* @returns Whether selection is NodeSelection
413
*/
414
function isNodeSelection(selection: Selection): selection is NodeSelection;
415
416
/**
417
* Check if cursor is at start of node
418
* @param state - Editor state
419
* @param types - Optional node types to check
420
* @returns Whether cursor is at start of specified node types
421
*/
422
function isAtStartOfNode(
423
state: EditorState,
424
types?: string[] | string
425
): boolean;
426
427
/**
428
* Check if cursor is at end of node
429
* @param state - Editor state
430
* @param types - Optional node types to check
431
* @returns Whether cursor is at end of specified node types
432
*/
433
function isAtEndOfNode(
434
state: EditorState,
435
types?: string[] | string
436
): boolean;
437
```
438
439
**Usage Examples:**
440
441
```typescript
442
import {
443
isActive,
444
isMarkActive,
445
isNodeActive,
446
isNodeEmpty,
447
isTextSelection,
448
isAtStartOfNode
449
} from '@tiptap/core';
450
451
// Check active formatting
452
const isBold = isMarkActive(editor.state, 'bold');
453
const isItalic = isMarkActive(editor.state, 'italic');
454
const isLink = isMarkActive(editor.state, 'link');
455
456
// Check active nodes
457
const isHeading = isNodeActive(editor.state, 'heading');
458
const isH1 = isNodeActive(editor.state, 'heading', { level: 1 });
459
const isBlockquote = isNodeActive(editor.state, 'blockquote');
460
461
// Generic active check
462
const isHeadingActive = isActive(editor.state, 'heading');
463
const isSpecificHeading = isActive(editor.state, 'heading', { level: 2 });
464
465
// Check if nodes are empty
466
const isEmpty = isNodeEmpty(editor.state.doc);
467
const isEmptyIgnoreSpaces = isNodeEmpty(editor.state.doc, {
468
ignoreWhitespace: true
469
});
470
471
// Check selection type
472
if (isTextSelection(editor.state.selection)) {
473
console.log('Text is selected');
474
const { from, to } = editor.state.selection;
475
console.log(`Selection from ${from} to ${to}`);
476
}
477
478
// Check cursor position
479
const atStart = isAtStartOfNode(editor.state);
480
const atStartOfParagraph = isAtStartOfNode(editor.state, 'paragraph');
481
const atStartOfMultiple = isAtStartOfNode(editor.state, ['paragraph', 'heading']);
482
483
// UI state management
484
function getToolbarState() {
485
const state = editor.state;
486
487
return {
488
bold: isMarkActive(state, 'bold'),
489
italic: isMarkActive(state, 'italic'),
490
heading: isNodeActive(state, 'heading'),
491
canIndent: !isAtStartOfNode(state, 'listItem'),
492
canOutdent: isNodeActive(state, 'listItem')
493
};
494
}
495
```
496
497
### Attributes and Schema
498
499
Functions for working with node and mark attributes and schema information.
500
501
```typescript { .api }
502
/**
503
* Get attributes from current selection
504
* @param state - Editor state
505
* @param nameOrType - Node/mark name or type
506
* @returns Attributes object
507
*/
508
function getAttributes(
509
state: EditorState,
510
nameOrType: string | NodeType | MarkType
511
): Record<string, any>;
512
513
/**
514
* Get node attributes from selection
515
* @param state - Editor state
516
* @param typeOrName - Node type or name
517
* @returns Node attributes
518
*/
519
function getNodeAttributes(
520
state: EditorState,
521
typeOrName: NodeType | string
522
): Record<string, any>;
523
524
/**
525
* Get mark attributes from selection
526
* @param state - Editor state
527
* @param typeOrName - Mark type or name
528
* @returns Mark attributes
529
*/
530
function getMarkAttributes(
531
state: EditorState,
532
typeOrName: MarkType | string
533
): Record<string, any>;
534
535
/**
536
* Generate ProseMirror schema from extensions
537
* @param extensions - Array of extensions
538
* @returns ProseMirror schema
539
*/
540
function getSchema(extensions: Extensions): Schema;
541
542
/**
543
* Get schema from resolved extensions
544
* @param extensions - Resolved extensions array
545
* @returns ProseMirror schema
546
*/
547
function getSchemaByResolvedExtensions(
548
extensions: AnyExtension[]
549
): Schema;
550
551
/**
552
* Split attributes by extension type
553
* @param extensionAttributes - Extension attributes
554
* @param typeName - Type name to match
555
* @param attributes - Attributes to split
556
* @returns Object with extension and node/mark attributes
557
*/
558
function getSplittedAttributes(
559
extensionAttributes: ExtensionAttribute[],
560
typeName: string,
561
attributes: Record<string, any>
562
): {
563
extensionAttributes: Record<string, any>;
564
nodeAttributes: Record<string, any>;
565
};
566
```
567
568
**Usage Examples:**
569
570
```typescript
571
import {
572
getAttributes,
573
getNodeAttributes,
574
getMarkAttributes,
575
getSchema
576
} from '@tiptap/core';
577
578
// Get current attributes
579
const headingAttrs = getNodeAttributes(editor.state, 'heading');
580
// { level: 1, id: 'intro' }
581
582
const linkAttrs = getMarkAttributes(editor.state, 'link');
583
// { href: 'https://example.com', target: '_blank' }
584
585
// Generic attribute getting
586
const attrs = getAttributes(editor.state, 'image');
587
// { src: 'image.jpg', alt: 'Description', width: 500 }
588
589
// Build schema from extensions
590
const customSchema = getSchema([
591
// your extensions
592
]);
593
594
// Use attributes in UI
595
function LinkDialog() {
596
const linkAttrs = getMarkAttributes(editor.state, 'link');
597
const [url, setUrl] = useState(linkAttrs.href || '');
598
const [target, setTarget] = useState(linkAttrs.target || '');
599
600
const applyLink = () => {
601
editor.commands.setMark('link', { href: url, target });
602
};
603
604
return (
605
<div>
606
<input value={url} onChange={e => setUrl(e.target.value)} />
607
<select value={target} onChange={e => setTarget(e.target.value)}>
608
<option value="">Same window</option>
609
<option value="_blank">New window</option>
610
</select>
611
<button onClick={applyLink}>Apply</button>
612
</div>
613
);
614
}
615
616
// Dynamic attribute handling
617
function updateNodeAttributes(nodeType: string, newAttributes: Record<string, any>) {
618
const currentAttrs = getNodeAttributes(editor.state, nodeType);
619
const mergedAttrs = { ...currentAttrs, ...newAttributes };
620
621
editor.commands.updateAttributes(nodeType, mergedAttrs);
622
}
623
```
624
625
### Utility Functions
626
627
Additional helper functions for document processing and analysis.
628
629
```typescript { .api }
630
/**
631
* Combine transaction steps from multiple transactions
632
* @param oldTr - Original transaction
633
* @param newTr - New transaction to combine
634
* @returns Combined transaction
635
*/
636
function combineTransactionSteps(
637
oldTr: Transaction,
638
newTr: Transaction
639
): Transaction;
640
641
/**
642
* Create chainable state for command chaining
643
* @param options - State and transaction
644
* @returns Chainable state object
645
*/
646
function createChainableState(options: {
647
state: EditorState;
648
transaction: Transaction;
649
}): EditorState;
650
651
/**
652
* Get ranges that were changed by a transaction
653
* @param tr - Transaction to analyze
654
* @returns Array of changed ranges
655
*/
656
function getChangedRanges(tr: Transaction): {
657
from: number;
658
to: number;
659
newFrom: number;
660
newTo: number;
661
}[];
662
663
/**
664
* Get debug representation of document
665
* @param doc - Document to debug
666
* @param schema - Schema for type information
667
* @returns Debug object with structure info
668
*/
669
function getDebugJSON(
670
doc: ProseMirrorNode,
671
schema: Schema
672
): Record<string, any>;
673
674
/**
675
* Convert document fragment to HTML
676
* @param fragment - ProseMirror fragment
677
* @param schema - Schema for serialization
678
* @returns HTML string
679
*/
680
function getHTMLFromFragment(
681
fragment: ProseMirrorFragment,
682
schema: Schema
683
): string;
684
685
/**
686
* Get DOM rectangle for position range
687
* @param view - Editor view
688
* @param from - Start position
689
* @param to - End position
690
* @returns DOM rectangle
691
*/
692
function posToDOMRect(
693
view: EditorView,
694
from: number,
695
to: number
696
): DOMRect;
697
```
698
699
**Usage Examples:**
700
701
```typescript
702
import {
703
getChangedRanges,
704
getDebugJSON,
705
posToDOMRect
706
} from '@tiptap/core';
707
708
// Track changes in transactions
709
editor.on('transaction', ({ transaction }) => {
710
const changes = getChangedRanges(transaction);
711
712
changes.forEach(range => {
713
console.log(`Changed: ${range.from}-${range.to} → ${range.newFrom}-${range.newTo}`);
714
});
715
});
716
717
// Debug document structure
718
const debugInfo = getDebugJSON(editor.state.doc, editor.schema);
719
console.log('Document structure:', debugInfo);
720
721
// Get position of selection for UI positioning
722
function positionTooltip() {
723
const { from, to } = editor.state.selection;
724
const rect = posToDOMRect(editor.view, from, to);
725
726
const tooltip = document.getElementById('tooltip');
727
if (tooltip && rect) {
728
tooltip.style.left = `${rect.left}px`;
729
tooltip.style.top = `${rect.bottom + 10}px`;
730
}
731
}
732
733
// Use in selection change handler
734
editor.on('selectionUpdate', () => {
735
if (!editor.state.selection.empty) {
736
positionTooltip();
737
}
738
});
739
```