57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
import { marked } from "marked";
|
|
import { mediaUrl } from "@/app/lib/media";
|
|
|
|
export interface EbookChunk {
|
|
html: string;
|
|
estimatedHeight: number;
|
|
}
|
|
|
|
export interface EbookData {
|
|
chunks: EbookChunk[];
|
|
headers: string[];
|
|
}
|
|
|
|
function rewriteMediaTag(tag: string): string {
|
|
return tag.replace(/\s(src|poster)=(["'])(.*?)\2/gi, (match, attr: string, quote: string, value: string) => {
|
|
const rewritten = mediaUrl(value);
|
|
return rewritten ? ` ${attr}=${quote}${rewritten}${quote}` : match;
|
|
});
|
|
}
|
|
|
|
function rewriteEmbeddedMedia(html: string): string {
|
|
return html.replace(/<(img|video|audio|source)\b[^>]*>/gi, rewriteMediaTag);
|
|
}
|
|
|
|
export function parseEbookMarkdown(source: string): EbookData {
|
|
const html = marked.parse(source, { breaks: true, gfm: true }) as string;
|
|
const mediaHtml = rewriteEmbeddedMedia(html);
|
|
const lazyHtml = mediaHtml.replace(/<img /g, '<img loading="lazy" decoding="async" ');
|
|
|
|
let rawChunks = lazyHtml.split(/(?=<h1[\s>])/i).filter((c) => c.trim());
|
|
if (rawChunks.length <= 1) {
|
|
rawChunks = lazyHtml.split(/(?=<h2[\s>])/i).filter((c) => c.trim());
|
|
}
|
|
|
|
const headers: string[] = [];
|
|
const headerRegex = /<h1[^>]*>(.*?)<\/h1>/gi;
|
|
let m;
|
|
while ((m = headerRegex.exec(lazyHtml)) !== null) {
|
|
headers.push(m[1].replace(/<[^>]+>/g, ""));
|
|
}
|
|
if (headers.length === 0) {
|
|
const h2Regex = /<h2[^>]*>(.*?)<\/h2>/gi;
|
|
while ((m = h2Regex.exec(lazyHtml)) !== null) {
|
|
headers.push(m[1].replace(/<[^>]+>/g, ""));
|
|
}
|
|
}
|
|
|
|
const chunks: EbookChunk[] = rawChunks.map((chunkHtml) => {
|
|
const imgCount = (chunkHtml.match(/<img\b/g) || []).length;
|
|
const textLen = chunkHtml.replace(/<[^>]+>/g, "").length;
|
|
const estimatedHeight = Math.max(300, Math.round(textLen * 0.6 + imgCount * 350));
|
|
return { html: chunkHtml, estimatedHeight };
|
|
});
|
|
|
|
return { chunks, headers };
|
|
}
|