Bold Node Type

Category: Formatting • Detailed implementation guide with view integration notes

Schema Definition

Schema definition for the Bold mark type:

{
  bold: {
    // Marks don't have content, they wrap text nodes
  }
}

Model Representation

Example model representation:

{
  type: 'text',
  text: 'Bold text',
  marks: [{ type: 'bold' }]
}

HTML Serialization

Converting model to HTML:

function serializeBoldMark(text, mark) {
  return '<strong>' + text + '</strong>';
}

// When multiple marks, apply in order
function applyMarks(text, marks) {
  let html = text;
  marks.forEach(mark => {
    if (mark.type === 'bold') {
      html = '<strong>' + html + '</strong>';
    }
  });
  return html;
}

HTML Deserialization

Parsing HTML to model:

function extractBoldMark(element) {
  if (element.tagName === 'STRONG' || element.tagName === 'B') {
    return { type: 'bold' };
  }
  return null;
}

// Extract all marks from parent chain
function extractMarks(textNode) {
  const marks = [];
  let current = textNode.parentElement;
  
  while (current && current !== editor) {
    const mark = getMarkFromElement(current);
    if (mark) marks.push(mark);
    current = current.parentElement;
  }
  
  return marks;
}

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:

// Toggling bold
function toggleBold() {
  const selection = window.getSelection();
  if (!selection.rangeCount) return;
  
  const range = selection.getRangeAt(0);
  const hasBold = hasMarkInSelection(range, 'bold');
  
  if (hasBold) {
    removeMark('bold');
  } else {
    addMark({ type: 'bold' });
  }
}

// Checking if selection has bold
function hasBoldMark(selection) {
  const range = selection.getRangeAt(0);
  const commonAncestor = range.commonAncestorContainer;
  
  if (commonAncestor.nodeType === Node.TEXT_NODE) {
    return commonAncestor.parentElement?.tagName === 'STRONG';
  }
  
  return range.commonAncestorContainer.querySelector('strong');
}

Common Issues

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

Common issues and solutions:

// Issue: <b> vs <strong> normalization
// Solution: Always convert <b> to <strong>
function normalizeBold(element) {
  element.querySelectorAll('b').forEach(b => {
    const strong = document.createElement('strong');
    strong.innerHTML = b.innerHTML;
    b.parentNode.replaceChild(strong, b);
  });
}

// Issue: Nested bold tags
// Solution: Remove nested bold
function removeNestedBold(element) {
  const strongs = element.querySelectorAll('strong');
  strongs.forEach(strong => {
    if (strong.closest('strong') !== strong) {
      // Nested, unwrap inner
      unwrapElement(strong);
    }
  });
}

// Issue: Bold mark with other marks
// Solution: Handle mark ordering
function applyMarksInOrder(text, marks) {
  // Apply bold first, then other marks
  const sortedMarks = sortMarks(marks);
  let html = text;
  sortedMarks.forEach(mark => {
    html = wrapWithMark(html, mark);
  });
  return html;
}

Implementation

Complete implementation example:

class BoldMark {
  constructor() {
    this.type = 'bold';
  }
  
  toDOM() {
    return ['strong', 0];
  }
  
  static fromDOM(element) {
    if (element.tagName === 'STRONG' || element.tagName === 'B') {
      return new BoldMark();
    }
    return null;
  }
}