@xylogeist/twig
Version:
A lightweight TypeScript renderer that converts structured block-based content(from an editor) into HTML.
239 lines (237 loc) • 5.47 kB
JavaScript
// src/renderer.ts
var renderTwig = (content) => {
const MARK_RENDERERS = {
bold: (text) => `<strong class="twig-bold">${text}</strong>`,
italic: (text) => `<em class="twig-italic">${text}</em>`,
underline: (text) => `<span class="twig-underline">${text}</span>`,
strikethrough: (text) => `<s class="twig-strikethrough">${text}</s>`
};
const renderSpan = (span) => {
let html = span.text;
for (const mark of span.marks) {
const renderer = MARK_RENDERERS[mark];
if (renderer) {
html = renderer(html);
}
}
switch (span.type) {
case "text":
return html;
case "link":
return `<a href="${span.url}" class="twig-link">${html}</a>`;
default:
return html;
}
};
return content.map(renderSpan).join("");
};
var renderListBlock = (block) => {
const tag = block.listType === "ul" ? "ul" : "ol";
const className = `twig-${block.listType}`;
const items = block.listItems.map((listItem) => {
const itemContent = renderTwig(listItem.content);
return `<li class="twig-list-item">${itemContent}</li>`;
}).join("");
return `<${tag} class="${className}">${items}</${tag}>`;
};
var renderTwigToHtml = (blocks) => {
return blocks.map((block) => {
switch (block.type) {
case "paragraph":
return `<p>${renderTwig(block.content)}</p>`;
case "heading":
const level = block.level;
return `<h${level}>${renderTwig(block.content)}</h${level}>`;
case "list":
return renderListBlock(block);
default:
return "";
}
}).join(`
`);
};
// src/index.ts
if (import.meta.main) {
const testBlocks = [
{
type: "paragraph",
content: [
{
type: "text",
text: "This is some ",
marks: []
},
{
type: "text",
text: "bold",
marks: ["bold"]
},
{
type: "text",
text: " and ",
marks: []
},
{
type: "text",
text: "italic",
marks: ["italic"]
},
{
type: "text",
text: " text.",
marks: []
}
]
},
{
type: "heading",
level: 1,
content: [
{
type: "text",
text: "Main Title - Heading 1 (No marks)",
marks: []
}
]
},
{
type: "heading",
level: 2,
content: [
{
type: "text",
text: "Section Title - Heading 2 with Bold",
marks: ["bold"]
}
]
},
{
type: "heading",
level: 3,
content: [
{
type: "text",
text: "Subsection - Heading 3 with Italic",
marks: ["italic"]
}
]
},
{
type: "heading",
level: 4,
content: [
{
type: "text",
text: "Minor Section - Heading 4 with Underline",
marks: ["underline"]
}
]
},
{
type: "heading",
level: 5,
content: [
{
type: "text",
text: "Small Section - Heading 5 with Strikethrough",
marks: ["strikethrough"]
}
]
},
{
type: "heading",
level: 6,
content: [
{
type: "text",
text: "Tiny Section - Heading 6 with Multiple Marks",
marks: ["bold", "italic", "underline"]
}
]
},
{
type: "heading",
level: 2,
content: [
{
type: "link",
url: "https://www.google.com",
text: "Linked Heading - Heading 2 with Link and Bold",
marks: ["bold"]
}
]
},
{
type: "list",
listType: "ul",
listItems: [
{
type: "listItem",
content: [
{
type: "link",
url: "https://www.google.com",
text: "Go to google",
marks: ["bold"]
}
]
},
{
type: "listItem",
content: [{ type: "text", text: "Item 2", marks: ["italic"] }]
},
{
type: "listItem",
content: [{ type: "text", text: "Item 3", marks: [] }]
},
{
type: "listItem",
content: [
{
type: "link",
url: "https://www.google.com",
text: "Item 4",
marks: []
}
]
}
]
},
{
type: "list",
listType: "ol",
listItems: [
{
type: "listItem",
content: [{ type: "text", text: "Ordered Item 1", marks: ["bold"] }]
},
{
type: "listItem",
content: [{ type: "text", text: "Ordered Item 2", marks: ["italic"] }]
},
{
type: "listItem",
content: [{ type: "text", text: "Ordered Item 3", marks: ["underline"] }]
}
]
},
{
type: "quote",
quote: "This is a famous quote with important wisdom!"
}
];
const wrapper = (blocks) => {
return `<div class="twig-wrapper">${renderTwigToHtml(blocks)}</div>`;
};
if (typeof document !== "undefined") {
const renderer = document.getElementById("renderer");
if (renderer) {
renderer.innerHTML = wrapper(testBlocks);
}
} else {
console.log(wrapper(testBlocks));
}
}
export {
renderTwigToHtml
};