Heading Node Type

Category: Basic • Detailed implementation guide with view integration notes

Schema Definition

Schema definition for the Heading node type:

{
  heading: {
    content: 'inline*',
    group: 'block',
    attrs: {
      level: { default: 1 }
    }
  }
}

Model Representation

Example model representation:

{
  type: 'heading',
  attrs: { level: 1 },
  children: [
    { type: 'text', text: 'Main Title' }
  ]
}

HTML Serialization

Converting model to HTML:

function serializeHeading(node) {
  const level = node.attrs?.level || 1;
  return '<h' + level + '>' + 
         serializeChildren(node.children) + 
         '</h' + level + '>';
}

HTML Deserialization

Parsing HTML to model:

function parseHeading(domNode) {
  const level = parseInt(domNode.tagName[1]) || 1;
  return {
    type: 'heading',
    attrs: { level },
    children: parseChildren(domNode.childNodes)
  };
}

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 level = node.attrs?.level || 1;
const h = document.createElement('h' + level);
h.contentEditable = 'true';
node.children.forEach(child => {
  h.appendChild(renderNode(child));
});

// Level change handling
function changeHeadingLevel(node, newLevel) {
  if (newLevel < 1 || newLevel > 6) return;
  return {
    ...node,
    attrs: { ...node.attrs, level: newLevel }
  };
}

Common Issues

Common Pitfalls: These are issues frequently encountered when implementing this node type. Review carefully before implementation.

Common issues and solutions:

// Issue: Heading level validation
// Solution: Always validate level
if (node.attrs.level < 1 || node.attrs.level > 6) {
  node.attrs.level = 1;
}

// Issue: Empty headings
// Solution: Prevent or handle empty headings
if (node.children.length === 0) {
  return '<h' + level + '><br></h' + level + '>';
}

// Issue: Heading hierarchy (h1 after h3)
// Solution: Validate document structure
function validateHeadingHierarchy(doc) {
  let lastLevel = 0;
  // Check heading order
}

Implementation

Complete implementation example:

class HeadingNode {
  constructor(attrs, children) {
    this.type = 'heading';
    this.attrs = { level: attrs?.level || 1 };
    this.children = children || [];
  }
  
  toDOM() {
    const level = this.attrs.level;
    const h = document.createElement('h' + level);
    this.children.forEach(child => {
      h.appendChild(child.toDOM());
    });
    return h;
  }
  
  static fromDOM(domNode) {
    const level = parseInt(domNode.tagName[1]) || 1;
    const children = Array.from(domNode.childNodes)
      .map(node => parseNode(node))
      .filter(Boolean);
    return new HeadingNode({ level }, children);
  }
}