Code Block Node Type

Category: Structural • Detailed implementation guide with view integration notes

Schema Definition

Schema definition for the Code Block node type:

{
  codeBlock: {
    content: 'text*',
    group: 'block',
    attrs: {
      language: { default: null }
    },
  }
}

Model Representation

Example model representation:

{
  type: 'codeBlock',
  attrs: { language: 'javascript' },
  children: [
    { type: 'text', text: 'const x = 1;\nconst y = 2;' }
  ]
}

HTML Serialization

Converting model to HTML:

function serializeCodeBlock(node) {
  const language = node.attrs?.language || '';
  const code = node.children.map(child => child.text).join('');
  const attrs = language ? { 'data-language': language } : {};
  return '<pre' + serializeAttrs(attrs) + '><code>' + 
         escapeHtml(code) + '</code></pre>';
}

HTML Deserialization

Parsing HTML to model:

function parseCodeBlock(domNode) {
  const pre = domNode.tagName === 'PRE' ? domNode : domNode.closest('pre');
  if (!pre) return null;
  
  const code = pre.querySelector('code') || pre;
  const language = pre.getAttribute('data-language') || 
                   code.getAttribute('data-language') || null;
  
  return {
    type: 'codeBlock',
    attrs: { language },
    children: [{
      type: 'text',
      text: code.textContent
    }]
  };
}

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 code block
const pre = document.createElement('pre');
const code = document.createElement('code');
pre.contentEditable = 'false'; // Code blocks are typically read-only
code.textContent = node.children[0].text;
if (node.attrs?.language) {
  code.setAttribute('data-language', node.attrs.language);
}
pre.appendChild(code);

// Handling Enter key in code block
function handleEnterInCodeBlock(e) {
  e.preventDefault();
  insertText('\n');
}

Common Issues

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

Common issues and solutions:

// Issue: Whitespace preservation
// Solution: Use pre tag and preserve whitespace
function preserveWhitespace(text) {
  return text.replace(/ /g, '&nbsp;').replace(/\n/g, '<br>');
}

// Issue: Syntax highlighting
// Solution: Apply highlighting after rendering
function applySyntaxHighlighting(codeElement, language) {
  // Use a syntax highlighter library
  // Highlight after DOM insertion
}

// Issue: Tab key handling
// Solution: Insert spaces instead of tab
function handleTabInCodeBlock(e) {
  e.preventDefault();
  insertText('  '); // Insert 2 spaces
}

Implementation

Complete implementation example:

class CodeBlockNode {
  constructor(attrs, children) {
    this.type = 'codeBlock';
    this.attrs = { language: attrs?.language || null };
    this.children = children || [];
  }
  
  toDOM() {
    const pre = document.createElement('pre');
    const code = document.createElement('code');
    code.textContent = this.children[0]?.text || '';
    if (this.attrs.language) {
      code.setAttribute('data-language', this.attrs.language);
    }
    pre.appendChild(code);
    return pre;
  }
  
  static fromDOM(domNode) {
    const pre = domNode.tagName === 'PRE' ? domNode : domNode.closest('pre');
    if (!pre) return null;
    
    const code = pre.querySelector('code') || pre;
    return new CodeBlockNode(
      { language: pre.getAttribute('data-language') },
      [{ type: 'text', text: code.textContent }]
    );
  }
}