스키마 정의
코드 블록 노드 타입의 스키마 정의:
{
codeBlock: {
content: 'text*',
group: 'block',
attrs: {
language: { default: null }
},
}
}모델 표현
모델 표현 예제:
{
type: 'codeBlock',
attrs: { language: 'javascript' },
children: [
{ type: 'text', text: 'const x = 1;\nconst y = 2;' }
]
}HTML 직렬화
모델을 HTML로 변환:
function serializeCodeBlock(node) {
const language = node.attrs?.language || '';
const code = node.children.map(child => child.text).join('');
const attrs = language ? { 'data-language': language } : {};
return '<pre' + serializeAttrs(attrs) + '><code>' +
escapeHtml(code) + '</code></pre>';
}HTML 역직렬화
HTML을 모델로 파싱:
function parseCodeBlock(domNode) {
const pre = domNode.tagName === 'PRE' ? domNode : domNode.closest('pre');
if (!pre) return null;
const code = pre.querySelector('code') || pre;
const language = pre.getAttribute('data-language') ||
code.getAttribute('data-language') || null;
return {
type: 'codeBlock',
attrs: { language },
children: [{
type: 'text',
text: code.textContent
}]
};
}뷰 연동
뷰 연동 노트: 이 노드 타입을 뷰 레이어에서 구현할 때 contenteditable 동작, 선택 처리, 이벤트 관리에 특히 주의하세요.
뷰 연동 코드:
// 코드 블록 렌더링
const pre = document.createElement('pre');
const code = document.createElement('code');
pre.contentEditable = 'false'; // 코드 블록은 일반적으로 읽기 전용
code.textContent = node.children[0].text;
if (node.attrs?.language) {
code.setAttribute('data-language', node.attrs.language);
}
pre.appendChild(code);
// 코드 블록에서 Enter 키 처리
function handleEnterInCodeBlock(e) {
e.preventDefault();
insertText('\n');
}일반적인 문제
일반적인 함정: 이 노드 타입을 구현할 때 자주 발생하는 문제들입니다. 구현 전에 주의 깊게 검토하세요.
일반적인 문제 및 해결 방법:
// 문제: 공백 보존
// 해결: pre 태그 사용 및 공백 보존
function preserveWhitespace(text) {
return text.replace(/ /g, ' ').replace(/\n/g, '<br>');
}
// 문제: 구문 강조
// 해결: 렌더링 후 강조 적용
function applySyntaxHighlighting(codeElement, language) {
// 구문 강조 라이브러리 사용
// DOM 삽입 후 강조 적용
}
// 문제: Tab 키 처리
// 해결: 탭 대신 공백 삽입
function handleTabInCodeBlock(e) {
e.preventDefault();
insertText(' '); // 2개의 공백 삽입
}구현
완전한 구현 예제:
class CodeBlockNode {
constructor(attrs, children) {
this.type = 'codeBlock';
this.attrs = { language: attrs?.language || null };
this.children = children || [];
}
toDOM() {
const pre = document.createElement('pre');
const code = document.createElement('code');
code.textContent = this.children[0]?.text || '';
if (this.attrs.language) {
code.setAttribute('data-language', this.attrs.language);
}
pre.appendChild(code);
return pre;
}
static fromDOM(domNode) {
const pre = domNode.tagName === 'PRE' ? domNode : domNode.closest('pre');
if (!pre) return null;
const code = pre.querySelector('code') || pre;
return new CodeBlockNode(
{ language: pre.getAttribute('data-language') },
[{ type: 'text', text: code.textContent }]
);
}
}