제목 노드 타입

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

스키마 정의

제목 노드 타입의 스키마 정의:

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

모델 표현

모델 표현 예제:

{
  type: 'heading',
  attrs: { level: 1 },
  children: [
    { type: 'text', text: '메인 제목' }
  ]
}

HTML 직렬화

모델을 HTML로 변환:

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

HTML 역직렬화

HTML을 모델로 파싱:

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

뷰 연동

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

뷰 연동 코드:

// 렌더링
const level = node.attrs?.level || 1;
const h = document.createElement('h' + level);
h.contentEditable = 'true';
node.children.forEach(child => {
  h.appendChild(renderNode(child));
});

// 레벨 변경 처리
function changeHeadingLevel(node, newLevel) {
  if (newLevel < 1 || newLevel > 6) return;
  return {
    ...node,
    attrs: { ...node.attrs, level: newLevel }
  };
}

일반적인 문제

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

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

// 문제: 제목 레벨 검증
// 해결: 항상 레벨 검증
if (node.attrs.level < 1 || node.attrs.level > 6) {
  node.attrs.level = 1;
}

// 문제: 빈 제목
// 해결: 빈 제목 방지 또는 처리
if (node.children.length === 0) {
  return '<h' + level + '><br></h' + level + '>';
}

// 문제: 제목 계층 구조 (h3 다음에 h1)
// 해결: 문서 구조 검증
function validateHeadingHierarchy(doc) {
  let lastLevel = 0;
  // 제목 순서 확인
}

구현

완전한 구현 예제:

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