스키마 정의
문서 노드 타입의 스키마 정의:
{
document: {
content: 'block+',
// 문서는 루트 노드, 항상 존재함
}
}모델 표현
모델 표현 예제:
{
type: 'document',
children: [
{
type: 'paragraph',
children: [{ type: 'text', text: '안녕하세요' }]
}
]
}HTML 직렬화
모델을 HTML로 변환:
function serializeDocument(node) {
return serializeChildren(node.children);
}
// 문서 자체는 래퍼 요소를 생성하지 않음
// 모든 최상위 블록의 컨테이너임HTML 역직렬화
HTML을 모델로 파싱:
function parseDocument(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return {
type: 'document',
children: Array.from(doc.body.childNodes)
.map(node => parseNode(node))
.filter(Boolean)
};
}뷰 연동
뷰 연동 노트: 이 노드 타입을 뷰 레이어에서 구현할 때 contenteditable 동작, 선택 처리, 이벤트 관리에 특히 주의하세요.
뷰 연동 코드:
// 렌더링
const container = document.createElement('div');
container.contentEditable = 'true';
node.children.forEach(child => {
container.appendChild(renderNode(child));
});
// 문서 레벨 이벤트 처리
container.addEventListener('input', handleDocumentInput);
container.addEventListener('paste', handleDocumentPaste);
container.addEventListener('keydown', handleDocumentKeydown);
// 선택 관리
function getDocumentSelection() {
const selection = window.getSelection();
if (!selection.rangeCount) return null;
return getModelPosition(selection);
}일반적인 문제
일반적인 함정: 이 노드 타입을 구현할 때 자주 발생하는 문제들입니다. 구현 전에 주의 깊게 검토하세요.
일반적인 문제 및 해결 방법:
// 문제: 문서는 항상 최소 하나의 블록을 가져야 함
// 해결: 문서가 항상 콘텐츠를 가지도록 보장
if (node.children.length === 0) {
node.children.push({
type: 'paragraph',
children: []
});
}
// 문제: 문서 구조 검증
// 해결: 모든 자식이 블록 노드인지 검증
function validateDocument(doc) {
return doc.children.every(child =>
isBlockNode(child)
);
}
// 문제: 빈 문서 처리
// 해결: 항상 최소 하나의 빈 단락 유지
function ensureDocumentNotEmpty(doc) {
if (doc.children.length === 0) {
doc.children.push(createEmptyParagraph());
}
}구현
완전한 구현 예제:
class DocumentNode {
constructor(children) {
this.type = 'document';
this.children = children || [];
}
toDOM() {
const fragment = document.createDocumentFragment();
this.children.forEach(child => {
fragment.appendChild(child.toDOM());
});
return fragment;
}
static fromDOM(domNode) {
const children = Array.from(domNode.childNodes)
.map(node => parseNode(node))
.filter(Boolean);
return new DocumentNode(children);
}
}