코드 블록 노드 타입

카테고리: 구조적 • 뷰 연동 노트를 포함한 상세 구현 가이드

스키마 정의

코드 블록 노드 타입의 스키마 정의:

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

모델 표현

모델 표현 예제:

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

HTML 직렬화

모델을 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 역직렬화

HTML을 모델로 파싱:

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
    }]
  };
}

뷰 연동

뷰 연동 노트: 이 노드 타입을 뷰 레이어에서 구현할 때 contenteditable 동작, 선택 처리, 이벤트 관리에 특히 주의하세요.

뷰 연동 코드:

// 코드 블록 렌더링
const pre = document.createElement('pre');
const code = document.createElement('code');
pre.contentEditable = 'false'; // 코드 블록은 일반적으로 읽기 전용
code.textContent = node.children[0].text;
if (node.attrs?.language) {
  code.setAttribute('data-language', node.attrs.language);
}
pre.appendChild(code);

// 코드 블록에서 Enter 키 처리
function handleEnterInCodeBlock(e) {
  e.preventDefault();
  insertText('\n');
}

일반적인 문제

일반적인 함정: 이 노드 타입을 구현할 때 자주 발생하는 문제들입니다. 구현 전에 주의 깊게 검토하세요.

일반적인 문제 및 해결 방법:

// 문제: 공백 보존
// 해결: pre 태그 사용 및 공백 보존
function preserveWhitespace(text) {
  return text.replace(/ /g, '&nbsp;').replace(/\n/g, '<br>');
}

// 문제: 구문 강조
// 해결: 렌더링 후 강조 적용
function applySyntaxHighlighting(codeElement, language) {
  // 구문 강조 라이브러리 사용
  // DOM 삽입 후 강조 적용
}

// 문제: Tab 키 처리
// 해결: 탭 대신 공백 삽입
function handleTabInCodeBlock(e) {
  e.preventDefault();
  insertText('  '); // 2개의 공백 삽입
}

구현

완전한 구현 예제:

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 }]
    );
  }
}