스키마 정의
텍스트 노드 타입의 스키마 정의:
{
text: {
group: 'inline',
// 텍스트 노드는 리프 노드, 자식 없음
}
}모델 표현
모델 표현 예제:
{
type: 'text',
text: '안녕하세요',
marks: [
{ type: 'bold' },
{ type: 'italic' }
]
}HTML 직렬화
모델을 HTML로 변환:
function serializeText(node) {
let html = escapeHtml(node.text);
// 순서대로 마크 적용
if (node.marks && node.marks.length > 0) {
node.marks.forEach(mark => {
html = wrapWithMark(html, mark);
});
}
return html;
}
function wrapWithMark(html, mark) {
const tagMap = {
bold: 'strong',
italic: 'em',
underline: 'u',
code: 'code'
};
const tag = tagMap[mark.type];
return tag ? '<' + tag + '>' + html + '</' + tag + '>' : html;
}HTML 역직렬화
HTML을 모델로 파싱:
function parseText(domNode) {
return {
type: 'text',
text: domNode.textContent,
marks: extractMarks(domNode)
};
}
function extractMarks(textNode) {
const marks = [];
let current = textNode.parentElement;
while (current && current !== editor) {
const mark = getMarkFromElement(current);
if (mark) marks.push(mark);
current = current.parentElement;
}
return marks;
}뷰 연동
뷰 연동 노트: 이 노드 타입을 뷰 레이어에서 구현할 때 contenteditable 동작, 선택 처리, 이벤트 관리에 특히 주의하세요.
뷰 연동 코드:
// 렌더링
const textNode = document.createTextNode(node.text);
// 마크는 직렬화 중 부모 요소로 래핑하여 적용됨
// 텍스트 노드 편집
textNode.addEventListener('input', (e) => {
// 텍스트 콘텐츠 업데이트
const newText = textNode.textContent;
updateTextNode(node, newText);
});
// 텍스트 노드의 선택
function getTextPosition(textNode, offset) {
return {
path: getPathToNode(textNode),
offset: offset
};
}일반적인 문제
일반적인 함정: 이 노드 타입을 구현할 때 자주 발생하는 문제들입니다. 구현 전에 주의 깊게 검토하세요.
일반적인 문제 및 해결 방법:
// 문제: 텍스트 노드는 블록 또는 인라인 노드 내부에 있어야 함
// 해결: 부모 노드 타입 검증
function validateTextNode(textNode) {
const parent = textNode.parentNode;
if (!isBlockNode(parent) && !isInlineNode(parent)) {
// 단락으로 래핑
wrapInParagraph(textNode);
}
}
// 문제: 빈 텍스트 노드
// 해결: 빈 텍스트 노드 제거 또는 병합
function normalizeTextNodes(element) {
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT
);
const toRemove = [];
let node;
while (node = walker.nextNode()) {
if (node.textContent.trim() === '') {
toRemove.push(node);
}
}
toRemove.forEach(n => n.remove());
}
// 문제: 인접한 텍스트 노드
// 해결: 인접한 텍스트 노드 병합
function mergeAdjacentTextNodes(element) {
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT
);
let prevNode = null;
let node;
while (node = walker.nextNode()) {
if (prevNode && prevNode.parentNode === node.parentNode) {
prevNode.textContent += node.textContent;
node.remove();
} else {
prevNode = node;
}
}
}구현
완전한 구현 예제:
class TextNode {
constructor(text, marks = []) {
this.type = 'text';
this.text = text;
this.marks = marks;
}
toDOM() {
const textNode = document.createTextNode(this.text);
// 마크는 직렬화 중 부모에 의해 적용됨
return textNode;
}
static fromDOM(domNode) {
const text = domNode.textContent;
const marks = extractMarks(domNode);
return new TextNode(text, marks);
}
}