forked from UKSOURCE/ipv6
init
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Converts an EditorJS JSON output (string or object) into an HTML string.
|
||||
* Supports all block types used in the CMS blog editor.
|
||||
*/
|
||||
export function editorjsToHtml(raw: string | object | null | undefined): string {
|
||||
if (!raw) return "";
|
||||
|
||||
let data: { blocks?: Array<{ type: string; data: Record<string, unknown> }> };
|
||||
|
||||
if (typeof raw === "string") {
|
||||
try {
|
||||
data = JSON.parse(raw);
|
||||
} catch {
|
||||
// Not JSON — treat as plain HTML string (legacy content)
|
||||
return raw;
|
||||
}
|
||||
} else {
|
||||
data = raw as typeof data;
|
||||
}
|
||||
|
||||
if (!data || !Array.isArray(data.blocks) || data.blocks.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return data.blocks.map(renderBlock).join("\n");
|
||||
}
|
||||
|
||||
function esc(text: unknown): string {
|
||||
return String(text ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
function renderBlock(block: { type: string; data: Record<string, unknown> }): string {
|
||||
const { type, data } = block;
|
||||
|
||||
switch (type) {
|
||||
case "paragraph": {
|
||||
const text = data.text as string ?? "";
|
||||
return `<p>${text}</p>`;
|
||||
}
|
||||
|
||||
case "header": {
|
||||
const level = Number(data.level) || 2;
|
||||
const tag = `h${Math.min(Math.max(level, 1), 6)}`;
|
||||
return `<${tag}>${data.text}</${tag}>`;
|
||||
}
|
||||
|
||||
case "list": {
|
||||
const style = data.style === "ordered" ? "ol" : "ul";
|
||||
const items = (data.items as string[]) ?? [];
|
||||
const lis = items.map((item) => `<li>${item}</li>`).join("\n");
|
||||
return `<${style}>\n${lis}\n</${style}>`;
|
||||
}
|
||||
|
||||
case "checklist": {
|
||||
const items = (data.items as Array<{ text: string; checked: boolean }>) ?? [];
|
||||
const lis = items
|
||||
.map(
|
||||
(item) =>
|
||||
`<li class="editorjs-checklist-item${item.checked ? " checked" : ""}">` +
|
||||
`<span class="editorjs-checkbox">${item.checked ? "✓" : "○"}</span> ${item.text}` +
|
||||
`</li>`
|
||||
)
|
||||
.join("\n");
|
||||
return `<ul class="editorjs-checklist">\n${lis}\n</ul>`;
|
||||
}
|
||||
|
||||
case "image": {
|
||||
const file = (data.file as { url?: string }) ?? {};
|
||||
const url = file.url ?? (data.url as string) ?? "";
|
||||
const caption = (data.caption as string) ?? "";
|
||||
const stretched = data.stretched ? ' class="editorjs-image-stretched"' : "";
|
||||
const withBg = data.withBackground ? ' style="background:#f5f5f5; padding:1rem;"' : "";
|
||||
return (
|
||||
`<figure${withBg}>` +
|
||||
`<img src="${esc(url)}" alt="${esc(caption)}"${stretched} loading="lazy" />` +
|
||||
(caption ? `<figcaption>${caption}</figcaption>` : "") +
|
||||
`</figure>`
|
||||
);
|
||||
}
|
||||
|
||||
case "quote": {
|
||||
const text = data.text as string ?? "";
|
||||
const caption = data.caption as string ?? "";
|
||||
return (
|
||||
`<blockquote class="editorjs-quote">` +
|
||||
`<p>${text}</p>` +
|
||||
(caption ? `<cite>${caption}</cite>` : "") +
|
||||
`</blockquote>`
|
||||
);
|
||||
}
|
||||
|
||||
case "code": {
|
||||
return `<pre><code>${esc(data.code)}</code></pre>`;
|
||||
}
|
||||
|
||||
case "delimiter": {
|
||||
return `<hr class="editorjs-delimiter" />`;
|
||||
}
|
||||
|
||||
case "table": {
|
||||
const content = (data.content as string[][]) ?? [];
|
||||
const withHeadings = data.withHeadings as boolean;
|
||||
const rows = content.map((row, rowIdx) => {
|
||||
const tag = withHeadings && rowIdx === 0 ? "th" : "td";
|
||||
const cells = row.map((cell) => `<${tag}>${cell}</${tag}>`).join("");
|
||||
return `<tr>${cells}</tr>`;
|
||||
});
|
||||
return `<table class="editorjs-table">\n${rows.join("\n")}\n</table>`;
|
||||
}
|
||||
|
||||
case "embed": {
|
||||
const service = (data.service as string ?? "").toLowerCase();
|
||||
const embedUrl = (data.embed as string) ?? "";
|
||||
const caption = (data.caption as string) ?? "";
|
||||
const width = (data.width as number) ?? 560;
|
||||
const height = (data.height as number) ?? 315;
|
||||
|
||||
// EditorJS Embed stores the ready-to-use iframe src in data.embed
|
||||
let iframeSrc = esc(embedUrl);
|
||||
|
||||
// Fallback: if data.embed is the watch URL, convert it
|
||||
if (service === "youtube" && iframeSrc.includes("watch?v=")) {
|
||||
iframeSrc = iframeSrc.replace("watch?v=", "embed/").split("&")[0];
|
||||
} else if (service === "vimeo" && !iframeSrc.includes("player.vimeo.com")) {
|
||||
const vimeoId = iframeSrc.split("/").pop() ?? "";
|
||||
iframeSrc = `https://player.vimeo.com/video/${vimeoId}`;
|
||||
}
|
||||
|
||||
return (
|
||||
`<figure class="editorjs-embed">` +
|
||||
`<iframe src="${iframeSrc}" width="${width}" height="${height}" frameborder="0" allowfullscreen loading="lazy"></iframe>` +
|
||||
(caption ? `<figcaption>${caption}</figcaption>` : "") +
|
||||
`</figure>`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
// Unknown block — skip silently
|
||||
return "";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user