스키마 정의
링크 마크 타입의 스키마 정의:
{
link: {
attrs: {
href: { default: '' },
title: { default: '' }
}
}
}모델 표현
모델 표현 예제:
{
type: 'text',
text: '여기를 클릭하세요',
marks: [{
type: 'link',
attrs: { href: 'https://example.com', title: '예제' }
}]
}HTML 직렬화
모델을 HTML로 변환:
function serializeLinkMark(text, mark) {
const href = escapeHtml(mark.attrs?.href || '');
const title = mark.attrs?.title ?
' title="' + escapeHtml(mark.attrs.title) + '"' : '';
return '<a href="' + href + '"' + title + '>' + text + '</a>';
}HTML 역직렬화
HTML을 모델로 파싱:
function extractLinkMark(element) {
if (element.tagName === 'A' && element.hasAttribute('href')) {
return {
type: 'link',
attrs: {
href: element.getAttribute('href') || '',
title: element.getAttribute('title') || ''
}
};
}
return null;
}뷰 연동
뷰 연동 노트: 이 노드 타입을 뷰 레이어에서 구현할 때 contenteditable 동작, 선택 처리, 이벤트 관리에 특히 주의하세요.
뷰 연동 코드:
// 링크 생성/편집
function createLink(url, text) {
return {
type: 'text',
text: text,
marks: [{
type: 'link',
attrs: { href: url }
}]
};
}
// 링크 URL 업데이트
function updateLinkUrl(linkMark, newUrl) {
return {
...linkMark,
attrs: { ...linkMark.attrs, href: newUrl }
};
}
// 링크 클릭 처리
function handleLinkClick(e) {
const link = e.target.closest('a');
if (link && e.ctrlKey || e.metaKey) {
// 기본 동작 허용 (새 탭에서 열기)
return;
}
e.preventDefault();
// 에디터에서 링크 네비게이션 처리
}일반적인 문제
일반적인 함정: 이 노드 타입을 구현할 때 자주 발생하는 문제들입니다. 구현 전에 주의 깊게 검토하세요.
일반적인 문제 및 해결 방법:
// 문제: 빈 href 링크
// 해결: 링크 생성 전 href 검증
function validateLink(link) {
if (!link.attrs.href || link.attrs.href.trim() === '') {
return false;
}
return true;
}
// 문제: 다른 마크와 함께 링크
// 해결: 링크는 볼드, 이탤릭 등과 공존할 수 있음
// 하지만 코드 마크는 링크를 배제함
function canApplyLinkWithMarks(marks) {
return !marks.some(m => m.type === 'code');
}
// 문제: 상대 URL vs 절대 URL
// 해결: URL 정규화
function normalizeUrl(url) {
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
if (url.startsWith('/')) {
return url; // 루트에 대한 상대 경로
}
return 'https://' + url; // 외부로 가정
}
// 문제: 링크 보안 (XSS)
// 해결: href 정화
function sanitizeUrl(url) {
// javascript:, data: 등 제거
if (url.startsWith('javascript:') || url.startsWith('data:')) {
return '#';
}
return url;
}구현
완전한 구현 예제:
class LinkMark {
constructor(attrs) {
this.type = 'link';
this.attrs = {
href: attrs?.href || '',
title: attrs?.title || ''
};
}
toDOM() {
const attrs = {};
if (this.attrs.href) attrs.href = this.attrs.href;
if (this.attrs.title) attrs.title = this.attrs.title;
return ['a', attrs, 0];
}
static fromDOM(element) {
if (element.tagName === 'A' && element.hasAttribute('href')) {
return new LinkMark({
href: element.getAttribute('href') || '',
title: element.getAttribute('title') || ''
});
}
return null;
}
}