단락 노드 타입

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

스키마 정의

단락 노드 타입의 스키마 정의:

{
  paragraph: {
    content: 'inline*',
    group: 'block'
  }
}

모델 표현

모델 표현 예제:

{
  type: 'paragraph',
  children: [
    { type: 'text', text: '이것은 단락입니다.' }
  ]
}

HTML 직렬화

모델을 HTML로 변환:

function serializeParagraph(node) {
  return '<p>' + serializeChildren(node.children) + '</p>';
}

HTML 역직렬화

HTML을 모델로 파싱:

function parseParagraph(domNode) {
  return {
    type: 'paragraph',
    children: parseChildren(domNode.childNodes)
  };
}

뷰 연동

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

뷰 연동 코드:

// 렌더링
const p = document.createElement('p');
p.contentEditable = 'true';
node.children.forEach(child => {
  p.appendChild(renderNode(child));
});

// 입력 처리
p.addEventListener('input', (e) => {
  const newModel = parseDOMToModel(p);
  updateModel(newModel);
});

// 선택 매핑
function getParagraphPosition(selection) {
  const range = selection.getRangeAt(0);
  const offset = getTextOffset(range.startContainer, range.startOffset);
  return { path: [0], offset };
}

일반적인 문제

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

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

// 문제: 빈 단락이 축소됨
// 해결: 항상 최소한 <br> 또는 &nbsp; 렌더링
if (node.children.length === 0) {
  return '<p><br></p>';
}

// 문제: 브라우저가 추가 <br> 태그를 추가함
// 해결: 역직렬화 시 정규화
function normalizeParagraph(domNode) {
  const brs = domNode.querySelectorAll('br');
  if (brs.length > 1) {
    // 추가 <br> 태그 제거
  }
}

// 문제: 붙여넣기가 중첩된 단락을 생성함
// 해결: 붙여넣기 시 언래핑
function handlePaste(e) {
  const pasted = e.clipboardData.getData('text/html');
  const parsed = parseHTML(pasted);
  // 중첩된 단락 언래핑
  return unwrapNestedParagraphs(parsed);
}

구현

완전한 구현 예제:

class ParagraphNode {
  constructor(attrs, children) {
    this.type = 'paragraph';
    this.attrs = attrs || {};
    this.children = children || [];
  }
  
  toDOM() {
    const p = document.createElement('p');
    this.children.forEach(child => {
      p.appendChild(child.toDOM());
    });
    return p;
  }
  
  static fromDOM(domNode) {
    const children = Array.from(domNode.childNodes)
      .map(node => parseNode(node))
      .filter(Boolean);
    return new ParagraphNode({}, children);
  }
}