Schema Definition
Schema definition for the Bold mark type:
{
bold: {
// Marks don't have content, they wrap text nodes
}
}Model Representation
Example model representation:
{
type: 'text',
text: 'Bold text',
marks: [{ type: 'bold' }]
}HTML Serialization
Converting model to HTML:
function serializeBoldMark(text, mark) {
return '<strong>' + text + '</strong>';
}
// When multiple marks, apply in order
function applyMarks(text, marks) {
let html = text;
marks.forEach(mark => {
if (mark.type === 'bold') {
html = '<strong>' + html + '</strong>';
}
});
return html;
}HTML Deserialization
Parsing HTML to model:
function extractBoldMark(element) {
if (element.tagName === 'STRONG' || element.tagName === 'B') {
return { type: 'bold' };
}
return null;
}
// Extract all marks from parent chain
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;
}View Integration
View Integration Notes: Pay special attention to contenteditable behavior, selection handling, and event management when implementing this node type in your view layer.
View integration code:
// Toggling bold
function toggleBold() {
const selection = window.getSelection();
if (!selection.rangeCount) return;
const range = selection.getRangeAt(0);
const hasBold = hasMarkInSelection(range, 'bold');
if (hasBold) {
removeMark('bold');
} else {
addMark({ type: 'bold' });
}
}
// Checking if selection has bold
function hasBoldMark(selection) {
const range = selection.getRangeAt(0);
const commonAncestor = range.commonAncestorContainer;
if (commonAncestor.nodeType === Node.TEXT_NODE) {
return commonAncestor.parentElement?.tagName === 'STRONG';
}
return range.commonAncestorContainer.querySelector('strong');
}Common Issues
Common Pitfalls: These are issues frequently encountered when implementing this node type. Review carefully before implementation.
Common issues and solutions:
// Issue: <b> vs <strong> normalization
// Solution: Always convert <b> to <strong>
function normalizeBold(element) {
element.querySelectorAll('b').forEach(b => {
const strong = document.createElement('strong');
strong.innerHTML = b.innerHTML;
b.parentNode.replaceChild(strong, b);
});
}
// Issue: Nested bold tags
// Solution: Remove nested bold
function removeNestedBold(element) {
const strongs = element.querySelectorAll('strong');
strongs.forEach(strong => {
if (strong.closest('strong') !== strong) {
// Nested, unwrap inner
unwrapElement(strong);
}
});
}
// Issue: Bold mark with other marks
// Solution: Handle mark ordering
function applyMarksInOrder(text, marks) {
// Apply bold first, then other marks
const sortedMarks = sortMarks(marks);
let html = text;
sortedMarks.forEach(mark => {
html = wrapWithMark(html, mark);
});
return html;
}Implementation
Complete implementation example:
class BoldMark {
constructor() {
this.type = 'bold';
}
toDOM() {
return ['strong', 0];
}
static fromDOM(element) {
if (element.tagName === 'STRONG' || element.tagName === 'B') {
return new BoldMark();
}
return null;
}
}