Schema Definition
Schema definition for the Document node type:
{
document: {
content: 'block+',
// Document is the root node, always present
}
}Model Representation
Example model representation:
{
type: 'document',
children: [
{
type: 'paragraph',
children: [{ type: 'text', text: 'Hello' }]
}
]
}HTML Serialization
Converting model to HTML:
function serializeDocument(node) {
return serializeChildren(node.children);
}
// Document itself doesn't create a wrapper element
// It's the container for all top-level blocksHTML Deserialization
Parsing HTML to model:
function parseDocument(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return {
type: 'document',
children: Array.from(doc.body.childNodes)
.map(node => parseNode(node))
.filter(Boolean)
};
}View Integration
View Integration Notes: Pay special attention to contenteditable behavior, selection handling, and event management when implementing this node type in your view layer.
View integration code:
// Rendering
const container = document.createElement('div');
container.contentEditable = 'true';
node.children.forEach(child => {
container.appendChild(renderNode(child));
});
// Document-level event handling
container.addEventListener('input', handleDocumentInput);
container.addEventListener('paste', handleDocumentPaste);
container.addEventListener('keydown', handleDocumentKeydown);
// Selection management
function getDocumentSelection() {
const selection = window.getSelection();
if (!selection.rangeCount) return null;
return getModelPosition(selection);
}Common Issues
Common Pitfalls: These are issues frequently encountered when implementing this node type. Review carefully before implementation.
Common issues and solutions:
// Issue: Document must always have at least one block
// Solution: Ensure document always has content
if (node.children.length === 0) {
node.children.push({
type: 'paragraph',
children: []
});
}
// Issue: Document structure validation
// Solution: Validate all children are block nodes
function validateDocument(doc) {
return doc.children.every(child =>
isBlockNode(child)
);
}
// Issue: Empty document handling
// Solution: Always maintain at least one empty paragraph
function ensureDocumentNotEmpty(doc) {
if (doc.children.length === 0) {
doc.children.push(createEmptyParagraph());
}
}Implementation
Complete implementation example:
class DocumentNode {
constructor(children) {
this.type = 'document';
this.children = children || [];
}
toDOM() {
const fragment = document.createDocumentFragment();
this.children.forEach(child => {
fragment.appendChild(child.toDOM());
});
return fragment;
}
static fromDOM(domNode) {
const children = Array.from(domNode.childNodes)
.map(node => parseNode(node))
.filter(Boolean);
return new DocumentNode(children);
}
}