순서 있는 목록 노드 타입

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

스키마 정의

순서 있는 목록 노드 타입의 스키마 정의:

{
  orderedList: {
    content: 'listItem+',
    group: 'block',
    attrs: {
      start: { default: 1 },
      order: { default: 1 }
    },
  }
}

모델 표현

모델 표현 예제:

{
  type: 'orderedList',
  attrs: { start: 1 },
  children: [
    {
      type: 'listItem',
      children: [
        {
          type: 'paragraph',
          children: [{ type: 'text', text: '첫 번째 항목' }]
        }
      ]
    }
  ]
}

HTML 직렬화

모델을 HTML로 변환:

function serializeOrderedList(node) {
  const attrs = node.attrs?.start !== 1 ? { start: node.attrs.start } : {};
  return '<ol' + serializeAttrs(attrs) + '>' + 
         serializeChildren(node.children) + '</ol>';
}

HTML 역직렬화

HTML을 모델로 파싱:

function parseOrderedList(domNode) {
  const start = parseInt(domNode.getAttribute('start')) || 1;
  return {
    type: 'orderedList',
    attrs: { start },
    children: Array.from(domNode.children)
      .map(child => parseNode(child))
      .filter(Boolean)
  };
}

뷰 연동

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

뷰 연동 코드:

// 순서 있는 목록 렌더링
const ol = document.createElement('ol');
ol.contentEditable = 'true';
if (node.attrs?.start !== 1) {
  ol.setAttribute('start', node.attrs.start);
}
node.children.forEach(item => {
  ol.appendChild(renderNode(item));
});

// 순서 있는 목록 생성
function createOrderedList() {
  return {
    type: 'orderedList',
    attrs: { start: 1 },
    children: [{
      type: 'listItem',
      children: [{
        type: 'paragraph',
        children: []
      }]
    }]
  };
}

일반적인 문제

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

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

// 문제: 중첩된 목록
// 해결: 중첩된 목록 항목을 올바르게 처리
function handleNestedList(list, nestedList) {
  // 올바른 중첩 구조 보장
  const lastItem = list.lastElementChild;
  if (lastItem) {
    lastItem.appendChild(nestedList);
  }
}

// 문제: 목록 번호 매기기
// 해결: 올바른 번호 매기기 유지
function updateListNumbering(list) {
  const items = list.querySelectorAll('li');
  items.forEach((item, index) => {
    // 필요시 번호 매기기 업데이트
  });
}

// 문제: 빈 목록 항목
// 해결: 목록 항목에 최소 하나의 단락 보장
function validateListItem(item) {
  if (!item.querySelector('p')) {
    const p = document.createElement('p');
    item.appendChild(p);
  }
}

구현

완전한 구현 예제:

class OrderedListNode {
  constructor(attrs, children) {
    this.type = 'orderedList';
    this.attrs = { start: attrs?.start || 1 };
    this.children = children || [];
  }
  
  toDOM() {
    const ol = document.createElement('ol');
    if (this.attrs.start !== 1) {
      ol.setAttribute('start', this.attrs.start);
    }
    this.children.forEach(child => {
      ol.appendChild(child.toDOM());
    });
    return ol;
  }
  
  static fromDOM(domNode) {
    const start = parseInt(domNode.getAttribute('start')) || 1;
    return new OrderedListNode(
      { start },
      Array.from(domNode.children)
        .map(child => parseNode(child))
        .filter(Boolean)
    );
  }
}