@nldoc/tiptap-extensions
v1.1.0
Published
Custom TipTap extensions, nodes, marks, and schemas for the NLdoc editor
Downloads
225
Readme
@nldoc/tiptap-extensions
Custom TipTap extensions, nodes, marks, and schemas for accessible, validated rich-text editing.
Features
- NLDocStarterKit: Pre-configured bundle of extensions for accessible, validated rich-text editing
- Custom Nodes: Document with asset management, headings (including ARIA levels > 6), images, tables, definition lists, and more
- Custom Marks: Metadata tracking, link with purpose (aria-label), inline quotations
- Custom Extensions: Automatic UUID identifier assignment, validation findings integration, enhanced content setting
- Zod Schemas: Runtime validation and TypeScript types for all TipTap JSON content
- Accessibility First: ARIA attributes, semantic HTML, decorative image support
- Configurable: All hardcoded text is configurable, no forced theming
Installation
npm install @nldoc/tiptap-extensionsPeer Dependencies
This package requires the following peer dependencies:
npm install @tiptap/core @tiptap/pm @tiptap/react prosemirror-model prosemirror-state zod reactQuick Start
Using NLDocStarterKit (Recommended)
The simplest way to get started is with NLDocStarterKit, which bundles all the core NLdoc extensions together:
import { useEditor, EditorContent } from '@tiptap/react'
import { NLDocStarterKit } from '@nldoc/tiptap-extensions'
// Import structural CSS (optional, but recommended)
import '@nldoc/tiptap-extensions/styles'
const editor = useEditor({
extensions: [
NLDocStarterKit.configure({
// Configure heading levels (default: 1-6)
heading: {
levels: [1, 2, 3, 4, 5, 6],
},
// Configure image options
image: {
// Image configuration options
},
// Configure node types for identifier system
identifier: {
nodeTypes: ['paragraph', 'heading', 'image', 'listItem', 'blockquote'],
},
// Configure node types for validation system
validation: {
nodeTypes: ['paragraph', 'heading', 'image', 'listItem', 'blockquote'],
},
}),
],
content: '<p>Start editing...</p>',
})
return <EditorContent editor={editor} />What's included in NLDocStarterKit:
- TipTap StarterKit (Bold, Italic, Strike, Code, BulletList, OrderedList, ListItem, Blockquote, CodeBlock, HorizontalRule, HardBreak, History, Dropcursor, Gapcursor)
- Custom Document with asset management
- Custom Paragraph with role handling
- Custom Heading with ARIA levels > 6 support
- Custom Image with asset management and decorative support
- MetaMark for metadata tracking
- LinkMark with purpose (aria-label) support
- IdentifierExtension for automatic UUID assignment
- ValidationFindingsExtension for accessibility validation
- SetContentAndDocAttributes for content loading
Extending NLDocStarterKit
You can easily extend NLDocStarterKit with additional TipTap extensions or your own custom extensions:
import { NLDocStarterKit, Table, DefinitionList, DefinitionTerm, DefinitionDetails, InlineQuotationMark } from '@nldoc/tiptap-extensions'
import TableRow from '@tiptap/extension-table-row'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
import Subscript from '@tiptap/extension-subscript'
import Superscript from '@tiptap/extension-superscript'
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import { createLowlight } from 'lowlight'
const lowlight = createLowlight()
// Additional node types beyond what NLDocStarterKit uses by default
const additionalNodeTypes = [
'table',
'tableRow',
'tableCell',
'tableHeader',
'definitionList',
'definitionTerm',
'definitionDetails',
'codeBlock',
]
const editor = useEditor({
extensions: [
// Start with NLDocStarterKit
NLDocStarterKit.configure({
heading: {
levels: [1, 2, 3, 4, 5, 6],
},
image: {},
codeBlock: false, // Disable default codeBlock to use CodeBlockLowlight instead
identifier: {
nodeTypes: [
'paragraph',
'heading',
'image',
'listItem',
'blockquote',
...additionalNodeTypes,
],
},
validation: {
nodeTypes: [
'paragraph',
'heading',
'image',
'listItem',
'blockquote',
...additionalNodeTypes,
],
},
}),
// Add additional marks
Subscript,
Superscript,
InlineQuotationMark,
// Add code block with syntax highlighting
CodeBlockLowlight.configure({ lowlight }),
// Add table support
Table,
TableHeader,
TableRow,
TableCell,
// Add definition list support
DefinitionList.configure({
defaultTermText: 'Term',
defaultDetailsText: 'Description',
}),
DefinitionTerm,
DefinitionDetails,
],
content: '<p>Start editing...</p>',
})This pattern allows you to:
- Start with a solid foundation of NLdoc-specific extensions
- Disable specific StarterKit extensions if needed (e.g.,
codeBlock: false) - Add standard TipTap extensions (Subscript, Superscript, etc.)
- Add custom NLdoc extensions (Table, DefinitionList, etc.)
- Extend the identifier and validation systems to cover your additional node types
Using DEFAULT_TRACKED_NODE_TYPES
To easily extend the default tracked node types without repeating them, use the exported DEFAULT_TRACKED_NODE_TYPES constant:
import { NLDocStarterKit, DEFAULT_TRACKED_NODE_TYPES } from '@nldoc/tiptap-extensions'
const additionalNodeTypes = ['table', 'tableRow', 'tableCell', 'codeBlock']
const editor = useEditor({
extensions: [
NLDocStarterKit.configure({
identifier: {
nodeTypes: [...DEFAULT_TRACKED_NODE_TYPES, ...additionalNodeTypes],
},
validation: {
nodeTypes: [...DEFAULT_TRACKED_NODE_TYPES, ...additionalNodeTypes],
},
}),
// ... your additional extensions
],
})DEFAULT_TRACKED_NODE_TYPES includes:
paragraphheadingimagebulletListorderedListlistItemblockquote
Configuration
NLDocStarterKit Options
interface NLDocStarterKitOptions {
// All StarterKit options are supported
// See: https://tiptap.dev/docs/editor/api/extensions/starter-kit
// Custom heading HTML attributes
customHeadingHTMLAttributes?: HeadingOptions['HTMLAttributes']
// Image configuration
image: Partial<ImageOptions>
// Node types for identifier system
identifier?: {
nodeTypes: string[]
}
// Node types for validation system
validation?: {
nodeTypes: string[]
}
// Link configuration (set to false to disable)
link?: false | Partial<LinkOptions>
}Disabling StarterKit Extensions
You can disable any StarterKit extension by setting it to false:
NLDocStarterKit.configure({
codeBlock: false, // Disable codeBlock
dropcursor: false, // Disable dropcursor
gapcursor: false, // Disable gapcursor
})Configuring Definition List Text
The DefinitionList node allows you to configure the default text for new terms and descriptions:
import { DefinitionList } from '@nldoc/tiptap-extensions'
DefinitionList.configure({
defaultTermText: 'Nieuw term', // Dutch
defaultDetailsText: 'Beschrijving', // Dutch
})
// Or in English
DefinitionList.configure({
defaultTermText: 'New term',
defaultDetailsText: 'Description',
})Working with Validation
Setting Validation Findings
import type { ValidationFinding } from '@nldoc/types'
// Fetch validation findings from your API
const findings: ValidationFinding[] = [
{
type: 'https://spec.validation.nldoc.nl/Resource/ValidationFinding',
resourceId: 'node-uuid',
severity: 'error',
rule: 'image-alt',
ruleset: 'wcag',
rulesetVersion: '2.1',
},
]
// Apply to editor
editor.commands.setValidationFindings(findings)This will:
- Map findings to nodes by
resourceId(UUID) - Add
validationFindingIdsarray to each node (rendered asaria-describedby) - Add
highestSeverityclass (error/warning/notice) to each node - Update text marks with validation info
Required Validation CSS
If you use the ValidationFindingsExtension, style these classes in your application:
/* Severity indicators */
.tiptap .error {
border-bottom: 2px solid red;
}
.tiptap .warning {
border-bottom: 2px dashed orange;
}
.tiptap .notice {
border-bottom: 2px dotted blue;
}
/* Focus states */
.tiptap .error.has-focus {
background-color: rgba(255, 0, 0, 0.1);
}
.tiptap .warning.has-focus {
background-color: rgba(255, 165, 0, 0.1);
}
.tiptap .notice.has-focus {
background-color: rgba(0, 0, 255, 0.1);
}Styling
Import Structural CSS
import '@nldoc/tiptap-extensions/styles'The provided CSS contains only structural styles (layout, positioning, basic formatting). All theming (colors, fonts, spacing) should be provided by your application.
Table Styling
.table-wrapper {
overflow-x: auto;
border-radius: 8px;
}
.tiptap table {
border-collapse: collapse;
}
.tiptap th,
.tiptap td {
border: 1px solid #ddd;
padding: 8px;
}
.tiptap th {
background-color: #f0f0f0;
font-weight: bold;
}
.tiptap .selectedCell {
background-color: #b3d4fc;
}Zod Schemas
All TipTap JSON content can be validated at runtime using the provided Zod schemas.
import {
TipTapDocumentDto,
TipTapHeadingDto,
TipTapParagraphDto,
TipTapImageDto,
} from '@nldoc/tiptap-extensions'
// Validate document structure
const document = TipTapDocumentDto.parse(jsonContent)
// TypeScript types are automatically inferred
type Document = z.infer<typeof TipTapDocumentDto>API Reference
Below is detailed documentation for all individual extensions, nodes, and marks included in this package. Most users should start with NLDocStarterKit (see Quick Start above), but these references are useful for advanced configuration or when building custom extensions.
Custom Nodes
Document
Extends TipTap's Document with an assets attribute for storing base64-encoded images.
import { Document, hasAssets } from '@nldoc/tiptap-extensions'
// Check if document has assets
if (hasAssets(doc)) {
console.log(doc.attrs.assets) // Record<string, string>
}Heading
Supports heading levels 1-6 as native HTML elements (<h1> through <h6>), and levels 7+ using <p role="heading" aria-level="N"> for accessibility.
editor.commands.setHeading({ level: 1 }) // <h1>
editor.commands.setHeading({ level: 7 }) // <p role="heading" aria-level="7">
// Keyboard shortcuts: Mod-Alt-1 through Mod-Alt-6
// Input rules: # through ######Image
Image node with asset management and decorative image support.
// Add an image with alt text
editor.commands.setImage({
base64: 'data:image/png;base64,...',
alt: 'Description of image',
decorative: false,
})
// Add a decorative image (alt will be set to empty string)
editor.commands.setImage({
base64: 'data:image/png;base64,...',
decorative: true,
})Images are automatically stored in the document's assets attribute with UUID keys. Unused assets are automatically cleaned up.
Table
Table with wrapper div for responsive scrolling.
import { Table } from '@nldoc/tiptap-extensions'
import TableRow from '@tiptap/extension-table-row'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
// Use with standard TipTap table extensions
extensions: [
Table,
TableRow,
TableCell,
TableHeader,
]Definition Lists
Semantic HTML definition lists (<dl>, <dt>, <dd>).
// Insert a definition list
editor.commands.insertDefinitionList()
// Keyboard behavior:
// - Press Enter at end of term → creates definition details
// - Press Enter at end of details → creates new term + details pairParagraph
Extends TipTap's Paragraph to prevent conflicts with Heading's role="heading" attribute.
Custom Marks
MetaMark
Invisible mark that wraps text nodes with metadata (ID, validation severity, validation finding IDs). Essential for the identifier and validation systems.
import { MetaMark, getMetaMark, upsertMark } from '@nldoc/tiptap-extensions'
// Get meta mark from a node
const mark = getMetaMark(node)
console.log(mark.attrs.id)
console.log(mark.attrs.highestSeverity) // 'error' | 'warning' | 'notice' | null
console.log(mark.attrs.validationFindingIds) // string[]
// Update meta mark attributes
const transaction = upsertMark(editorState, transaction, { node, pos }, {
id: 'some-uuid',
highestSeverity: 'error',
validationFindingIds: ['finding-1', 'finding-2'],
})LinkMark
Extends TipTap's Link with a purpose attribute (rendered as aria-label) for better accessibility.
editor.commands.setLink({
href: 'https://example.com',
purpose: 'Visit our documentation',
})InlineQuotationMark
Renders as <q> element with optional cite attribute.
editor.commands.setInlineQuotation({
cite: 'https://example.com/source',
})
editor.commands.toggleInlineQuotation()
editor.commands.unsetInlineQuotation()Custom Extensions
IdentifierExtension
Automatically assigns UUID identifiers to all nodes and text (via MetaMark). Prevents duplicate IDs when nodes are split (e.g., splitting a paragraph).
import { IdentifierExtension, getIdentifier } from '@nldoc/tiptap-extensions'
IdentifierExtension.configure({
nodeTypes: ['heading', 'paragraph', 'image'],
})
// Get identifier from any node
const id = getIdentifier(node) // Returns string | nullValidationFindingsExtension
Integrates accessibility validation findings into the editor. Maps findings to nodes/text and applies visual styling via CSS classes.
import { ValidationFindingsExtension } from '@nldoc/tiptap-extensions'
import type { ValidationFinding } from '@nldoc/types'
ValidationFindingsExtension.configure({
nodeTypes: ['heading', 'paragraph', 'image'],
})
// Set validation findings
const findings: ValidationFinding[] = [
{
type: 'https://spec.validation.nldoc.nl/Resource/ValidationFinding',
resourceId: 'node-uuid',
severity: 'error',
rule: 'missing-alt-text',
ruleset: 'wcag',
rulesetVersion: '2.1',
},
]
editor.commands.setValidationFindings(findings)This adds the following attributes to nodes:
validationFindingIds: Array of finding IDs (rendered asaria-describedby)highestSeverity: 'error' | 'warning' | 'notice' (rendered as CSS class)
SetContentAndDocAttributes
Custom command to set editor content while preserving document-level attributes (useful for loading documents from storage).
editor.commands.setContentAndDocAttributes(
jsonContent,
true, // emitUpdate
{}, // parseOptions
{ errorOnInvalidContent: false }
)Utilities
findingHTMLId
Creates unique HTML IDs for validation findings.
import { findingHTMLId } from '@nldoc/tiptap-extensions'
const id = findingHTMLId(finding)
// Returns: "comment-{resourceId}-{encodedRuleInfo}-{severity}"Index and IndexWithDefault
Simple index data structures for efficient lookups.
import { Index, IndexWithDefault } from '@nldoc/tiptap-extensions'
const index = new Index({ key1: 'value1', key2: 'value2' })
index.get('key1') // 'value1'
index.get('key3') // undefined
const indexWithDefault = new IndexWithDefault({ key1: 'value1' }, 'default')
indexWithDefault.get('key1') // 'value1'
indexWithDefault.get('key3') // 'default'Transaction Metadata Constants
import { TransactionMeta } from '@nldoc/tiptap-extensions'
// Prevent validation from being triggered
transaction.setMeta(TransactionMeta.blockValidation, true)
// Prevent transaction from being added to history
transaction.setMeta(TransactionMeta.addToHistory, false)TypeScript
This package is fully typed. All exports include TypeScript definitions.
import type {
NLDocStarterKitOptions,
HeadingOptions,
ImageOptions,
InlineQuotationOptions,
DefinitionListOptions,
MetaMarkAttributes,
} from '@nldoc/tiptap-extensions'License
EUPL-1.2
Contributing
This package is part of the NLdoc project. Contributions are welcome!
Credits
Built with TipTap and ProseMirror.
