@bendyline/squisq-formats 2.8.7 → 2.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/chunk-4LIFEQKX.js +358 -0
- package/dist/{chunk-UXEANVBI.js → chunk-5HUHSLGA.js} +1 -84
- package/dist/{chunk-IYNDHW2C.js → chunk-6HUQFEP3.js} +75 -5
- package/dist/{chunk-BXWNU4T5.js → chunk-DTJQK7U7.js} +43 -0
- package/dist/{chunk-QU4EVC62.js → chunk-GFHUJ7L4.js} +1 -1
- package/dist/{chunk-TMOXUUTG.js → chunk-NQNIFIDP.js} +6 -4
- package/dist/{chunk-B6UQPW75.js → chunk-SZ3OIY6G.js} +697 -44
- package/dist/{chunk-KROH7FNT.js → chunk-VKLJUHC2.js} +6 -4
- package/dist/{chunk-C23JYVPG.js → chunk-WJTC5OD6.js} +2 -0
- package/dist/{chunk-ALDCFHZT.js → chunk-WVDAFJFS.js} +59 -21
- package/dist/chunk-YRFBWYAD.js +128 -0
- package/dist/{chunk-SVYTUYZM.js → chunk-ZJERBWTB.js} +6 -4
- package/dist/data/index.d.ts +27 -5
- package/dist/data/index.js +6 -2
- package/dist/diagrams/index.d.ts +19 -8
- package/dist/diagrams/index.js +7 -242
- package/dist/docx/index.d.ts +1 -1
- package/dist/docx/index.js +3 -3
- package/dist/epub/index.d.ts +1 -1
- package/dist/epub/index.js +3 -3
- package/dist/fontAwesome-C6FRDYTY.js +14 -0
- package/dist/html/index.d.ts +214 -2
- package/dist/html/index.js +25 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +15 -10
- package/dist/{materialize-G4WC5UG3.js → materialize-ICXNO4S4.js} +4 -1
- package/dist/outside-in/index.d.ts +21 -2
- package/dist/outside-in/index.js +7 -3
- package/dist/outside-in/layout.d.ts +7 -1
- package/dist/outside-in/layout.js +3 -1
- package/dist/pdf/index.d.ts +1 -1
- package/dist/pdf/index.js +3 -3
- package/dist/plainHtmlIcons-KBSIJ5EN.js +62 -0
- package/dist/pptx/index.js +2 -2
- package/dist/readers-3P5A6A2A.js +17 -0
- package/dist/registry/index.js +1 -1
- package/package.json +2 -2
- /package/dist/{chunk-LBXBRFBZ.js → chunk-DDMPZZJF.js} +0 -0
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findDiagrams,
|
|
3
|
+
rasterizeDiagrams
|
|
4
|
+
} from "./chunk-4LIFEQKX.js";
|
|
1
5
|
import {
|
|
2
6
|
arrayBufferToBase64DataUrl,
|
|
3
7
|
extractFilename,
|
|
@@ -8,8 +12,9 @@ import {
|
|
|
8
12
|
} from "./chunk-VCZUXQUL.js";
|
|
9
13
|
import {
|
|
10
14
|
FootnoteIndex,
|
|
11
|
-
footnoteIds
|
|
12
|
-
|
|
15
|
+
footnoteIds,
|
|
16
|
+
withoutBlockTags
|
|
17
|
+
} from "./chunk-DTJQK7U7.js";
|
|
13
18
|
import {
|
|
14
19
|
imageSizeAttributes
|
|
15
20
|
} from "./chunk-JZR32FON.js";
|
|
@@ -294,7 +299,9 @@ function escapeHtml(str) {
|
|
|
294
299
|
import {
|
|
295
300
|
findBlockVideoEmbed,
|
|
296
301
|
isSvgIslandLang,
|
|
302
|
+
mediaKindForUrl,
|
|
297
303
|
parseSvgIsland,
|
|
304
|
+
parseTimeSeconds,
|
|
298
305
|
sanitizeHtmlNodes,
|
|
299
306
|
svgIslandDataUrl,
|
|
300
307
|
svgSourceFromDataUrl,
|
|
@@ -304,8 +311,45 @@ import {
|
|
|
304
311
|
readFrontmatterThemeId as readFrontmatterThemeId2
|
|
305
312
|
} from "@bendyline/squisq/markdown";
|
|
306
313
|
import { resolveFontFamily, buildGoogleFontsUrl } from "@bendyline/squisq/schemas";
|
|
307
|
-
import {
|
|
314
|
+
import {
|
|
315
|
+
isMediaAnnotationName,
|
|
316
|
+
parseStandaloneAnnotation,
|
|
317
|
+
resolveThemeForDoc
|
|
318
|
+
} from "@bendyline/squisq/doc";
|
|
308
319
|
function markdownDocToPlainHtml(doc, options = {}) {
|
|
320
|
+
const { title, body, fontsLink, iconsLink, themedCss, featureCss } = renderPlainHtml(
|
|
321
|
+
doc,
|
|
322
|
+
options
|
|
323
|
+
);
|
|
324
|
+
const safeCss = escapeStyleText(`${themedCss}
|
|
325
|
+
${featureCss}`);
|
|
326
|
+
return `<!DOCTYPE html>
|
|
327
|
+
<html lang="en">
|
|
328
|
+
<head>
|
|
329
|
+
<meta charset="UTF-8">
|
|
330
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
331
|
+
<title>${escapeHtml2(title)}</title>
|
|
332
|
+
${fontsLink}${iconsLink}<style>
|
|
333
|
+
${safeCss}
|
|
334
|
+
</style>
|
|
335
|
+
</head>
|
|
336
|
+
<body>
|
|
337
|
+
${body}
|
|
338
|
+
</body>
|
|
339
|
+
</html>`;
|
|
340
|
+
}
|
|
341
|
+
function markdownDocToPlainHtmlParts(doc, options = {}) {
|
|
342
|
+
const { title, body, iconsLink, featureCss } = renderPlainHtml(doc, options);
|
|
343
|
+
return {
|
|
344
|
+
title,
|
|
345
|
+
bodyHtml: body,
|
|
346
|
+
featureHeadHtml: `${iconsLink}<style data-squisq>
|
|
347
|
+
${escapeStyleText(featureCss)}
|
|
348
|
+
</style>
|
|
349
|
+
`
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function renderPlainHtml(doc, options) {
|
|
309
353
|
const {
|
|
310
354
|
title = "Document",
|
|
311
355
|
images,
|
|
@@ -314,7 +358,8 @@ function markdownDocToPlainHtml(doc, options = {}) {
|
|
|
314
358
|
iconsCss,
|
|
315
359
|
htmlPolicy = "sanitize",
|
|
316
360
|
externalResources = "deny",
|
|
317
|
-
videoEmbeds
|
|
361
|
+
videoEmbeds,
|
|
362
|
+
icons
|
|
318
363
|
} = options;
|
|
319
364
|
const resolveId = themeId ?? readFrontmatterThemeId2(doc.frontmatter);
|
|
320
365
|
const theme = options.theme ?? (resolveId ? resolveThemeForDoc(doc, resolveId, options.themeRegistry) : void 0);
|
|
@@ -325,13 +370,14 @@ function markdownDocToPlainHtml(doc, options = {}) {
|
|
|
325
370
|
htmlPolicy,
|
|
326
371
|
footnotes,
|
|
327
372
|
videoEmbeds,
|
|
373
|
+
icons,
|
|
328
374
|
textColor: theme?.colors.text
|
|
329
375
|
};
|
|
330
|
-
const
|
|
376
|
+
const blocks = withoutBlockTags(doc, { keepMedia: true }).children;
|
|
377
|
+
const body = renderTopLevel(blocks, ctx) + renderFootnotesSection(footnotes, ctx);
|
|
331
378
|
const fontsLink = theme && externalResources === "allow" ? renderFontsLink(theme) : "";
|
|
332
|
-
const usesIcons = docUsesIcons(doc);
|
|
333
379
|
let iconsLink = "";
|
|
334
|
-
if (
|
|
380
|
+
if (ctx.usesFontIcons) {
|
|
335
381
|
iconsLink = iconsCss ? `<style data-fa-inline>
|
|
336
382
|
${escapeStyleText(iconsCss)}
|
|
337
383
|
</style>
|
|
@@ -342,40 +388,25 @@ ${escapeStyleText(iconsCss)}
|
|
|
342
388
|
${VIDEO_EMBED_CSS}` : "";
|
|
343
389
|
const svgCss = docUsesSvgIslands(doc) ? `
|
|
344
390
|
${SVG_ISLAND_CSS}` : "";
|
|
345
|
-
const
|
|
346
|
-
${
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
<body>
|
|
358
|
-
${body}
|
|
359
|
-
</body>
|
|
360
|
-
</html>`;
|
|
391
|
+
const mediaCss = ctx.usesMediaPlayers ? `
|
|
392
|
+
${MEDIA_PLAYER_CSS}` : "";
|
|
393
|
+
const iconCss = ctx.usesSvgIcons ? `
|
|
394
|
+
${SVG_ICON_CSS}` : "";
|
|
395
|
+
return {
|
|
396
|
+
title,
|
|
397
|
+
body,
|
|
398
|
+
fontsLink,
|
|
399
|
+
iconsLink,
|
|
400
|
+
themedCss,
|
|
401
|
+
featureCss: `${FEATURE_CSS}${videoCss}${svgCss}${mediaCss}${iconCss}`
|
|
402
|
+
};
|
|
361
403
|
}
|
|
362
404
|
function escapeStyleText(css) {
|
|
363
405
|
return css.replace(/</g, "\\3c ");
|
|
364
406
|
}
|
|
365
407
|
var FONT_AWESOME_LINK = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.3.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
|
|
366
408
|
`;
|
|
367
|
-
|
|
368
|
-
function visit(node) {
|
|
369
|
-
if (!node || typeof node !== "object") return false;
|
|
370
|
-
const n = node;
|
|
371
|
-
if (n.type === "inlineIcon") return true;
|
|
372
|
-
if (Array.isArray(n.children)) {
|
|
373
|
-
for (const child of n.children) if (visit(child)) return true;
|
|
374
|
-
}
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
return visit(doc);
|
|
378
|
-
}
|
|
409
|
+
var SVG_ICON_CSS = ` .squisq-icon { display: inline-block; height: 1em; vertical-align: -0.125em; overflow: visible; }`;
|
|
379
410
|
function docUsesVideoEmbeds(doc, htmlPolicy) {
|
|
380
411
|
const iframeIn = (nodes) => nodes.some(
|
|
381
412
|
(node) => node.type === "htmlElement" && (videoEmbedFromIframe(node) !== null || iframeIn(node.children))
|
|
@@ -416,15 +447,21 @@ function renderTopLevel(children, ctx) {
|
|
|
416
447
|
continue;
|
|
417
448
|
}
|
|
418
449
|
}
|
|
419
|
-
const
|
|
420
|
-
if (
|
|
421
|
-
out.push(videoEmbedToHtml(video.embed, video.title, ctx));
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
out.push(nodeToHtml(node, ctx));
|
|
450
|
+
const html = topLevelBlockToHtml(node, ctx);
|
|
451
|
+
if (html !== null) out.push(html);
|
|
425
452
|
}
|
|
426
453
|
return out.join("\n");
|
|
427
454
|
}
|
|
455
|
+
function topLevelBlockToHtml(node, ctx) {
|
|
456
|
+
if (node?.type !== "paragraph") return nodeToHtml(node, ctx);
|
|
457
|
+
const video = findBlockVideoEmbed(node);
|
|
458
|
+
if (video) return videoEmbedToHtml(video.embed, video.title, ctx);
|
|
459
|
+
const tag = parseStandaloneAnnotation(node);
|
|
460
|
+
if (tag?.template && isMediaAnnotationName(tag.template)) {
|
|
461
|
+
return mediaAnnotationToHtml(tag.template, tag.params, ctx);
|
|
462
|
+
}
|
|
463
|
+
return nodeToHtml(node, ctx);
|
|
464
|
+
}
|
|
428
465
|
function renderFootnotesSection(footnotes, ctx) {
|
|
429
466
|
if (footnotes.isEmpty) return "";
|
|
430
467
|
const items = footnotes.ordered().map((fn) => {
|
|
@@ -459,7 +496,7 @@ function renderFeatureSection(heading, bodyNodes, side, ctx) {
|
|
|
459
496
|
const headingHtml = `<${headingTag}>${childrenToHtml({ children: heading.children }, ctx)}</${headingTag}>`;
|
|
460
497
|
const featured = takeFirstImage(bodyNodes);
|
|
461
498
|
const media = featured.image ? renderFeatureImage(featured.image, ctx) : '<div class="squisq-feature__media squisq-feature__media--empty"></div>';
|
|
462
|
-
const textHtml = [headingHtml, ...featured.remaining.map((n) =>
|
|
499
|
+
const textHtml = [headingHtml, ...featured.remaining.map((n) => topLevelBlockToHtml(n, ctx))].filter((s) => !!s).join("\n");
|
|
463
500
|
const sideClass = side === "leftFeature" ? "squisq-feature--left" : "squisq-feature--right";
|
|
464
501
|
return `<section class="squisq-feature ${sideClass}">
|
|
465
502
|
${media}
|
|
@@ -782,6 +819,11 @@ function nodeToHtml(node, ctx) {
|
|
|
782
819
|
case "image": {
|
|
783
820
|
const original = node.url ?? "";
|
|
784
821
|
const resolved = ctx?.images?.get(original) ?? original;
|
|
822
|
+
const mediaKind = mediaKindForUrl(original);
|
|
823
|
+
if (mediaKind) {
|
|
824
|
+
const label = node.alt || node.title || void 0;
|
|
825
|
+
return mediaPlayerHtml(mediaKind, resolved, label, ctx) ?? escapeHtml2(node.alt ?? "");
|
|
826
|
+
}
|
|
785
827
|
const safeSrc = sanitizeUrl(resolved, "media") ?? svgDataImageToHtmlSrc(resolved);
|
|
786
828
|
if (!safeSrc) return escapeHtml2(node.alt ?? "");
|
|
787
829
|
return `<img src="${escapeAttr(safeSrc)}" alt="${escapeAttr(node.alt ?? "")}"${imageSizeAttributes(node)} />`;
|
|
@@ -791,9 +833,16 @@ function nodeToHtml(node, ctx) {
|
|
|
791
833
|
case "table":
|
|
792
834
|
return tableToHtml(node, ctx);
|
|
793
835
|
case "inlineIcon": {
|
|
836
|
+
const token = escapeAttr(node.token ?? `${node.family}:${node.name}`);
|
|
837
|
+
const svg = ctx?.icons?.get(`${node.family}:${node.name}`);
|
|
838
|
+
if (svg) {
|
|
839
|
+
ctx.usesSvgIcons = true;
|
|
840
|
+
const width = Number((svg.width / svg.height).toFixed(4));
|
|
841
|
+
return `<svg class="squisq-icon" viewBox="0 0 ${svg.width} ${svg.height}" width="${width}em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" data-icon="${token}"><path d="${escapeAttr(svg.path)}"/></svg>`;
|
|
842
|
+
}
|
|
843
|
+
if (ctx) ctx.usesFontIcons = true;
|
|
794
844
|
const family = escapeAttr(node.family ?? "solid");
|
|
795
845
|
const name = escapeAttr(node.name ?? "");
|
|
796
|
-
const token = escapeAttr(node.token ?? `${node.family}:${node.name}`);
|
|
797
846
|
return `<i class="fa-${family} fa-${name}" data-icon="${token}" aria-hidden="true"></i>`;
|
|
798
847
|
}
|
|
799
848
|
case "htmlBlock":
|
|
@@ -930,6 +979,28 @@ var VIDEO_EMBED_CSS = ` .squisq-video-embed { display: block; max-width: 720px;
|
|
|
930
979
|
.squisq-video-embed-play::after { content: ''; position: absolute; left: 26px; top: 12px; border-style: solid; border-width: 10px 0 10px 16px; border-color: transparent transparent transparent #fff; }
|
|
931
980
|
.squisq-video-embed-provider { position: absolute; left: 12px; bottom: 10px; color: #fff; font-size: 0.8em; font-weight: 600; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); }
|
|
932
981
|
.squisq-video-embed-caption { margin-top: 0.4em; font-size: 0.875em; opacity: 0.8; }`;
|
|
982
|
+
function mediaPlayerHtml(kind, src, label, ctx) {
|
|
983
|
+
const safeSrc = sanitizeUrl(src, "media");
|
|
984
|
+
if (!safeSrc) return null;
|
|
985
|
+
if (ctx) ctx.usesMediaPlayers = true;
|
|
986
|
+
const aria = label ? ` aria-label="${escapeAttr(label)}"` : "";
|
|
987
|
+
return `<${kind} class="squisq-media" src="${escapeAttr(safeSrc)}" controls preload="metadata"${aria}></${kind}>`;
|
|
988
|
+
}
|
|
989
|
+
function mediaAnnotationToHtml(template, params, ctx) {
|
|
990
|
+
if (!params.src) return null;
|
|
991
|
+
const kind = template === "video" ? "video" : "audio";
|
|
992
|
+
let src = ctx?.images?.get(params.src) ?? params.src;
|
|
993
|
+
const start = parseTimeSeconds(params.clipStart ?? "");
|
|
994
|
+
const end = parseTimeSeconds(params.clipEnd ?? "");
|
|
995
|
+
if ((start !== null || end !== null) && !src.includes("#")) {
|
|
996
|
+
src += `#t=${start ?? ""}${end !== null ? `,${end}` : ""}`;
|
|
997
|
+
}
|
|
998
|
+
const player = mediaPlayerHtml(kind, src, void 0, ctx);
|
|
999
|
+
return player ? `<figure class="squisq-media-clip">${player}</figure>` : null;
|
|
1000
|
+
}
|
|
1001
|
+
var MEDIA_PLAYER_CSS = ` .squisq-media-clip { margin: 1.25em 0; }
|
|
1002
|
+
video.squisq-media { display: block; width: 100%; max-width: 720px; height: auto; border-radius: 6px; }
|
|
1003
|
+
audio.squisq-media { display: block; width: 100%; max-width: 480px; }`;
|
|
933
1004
|
function svgDataImageToHtmlSrc(url) {
|
|
934
1005
|
const source = svgSourceFromDataUrl(url);
|
|
935
1006
|
const result = source === null ? null : parseSvgIsland(source);
|
|
@@ -951,6 +1022,80 @@ function escapeAttr(s) {
|
|
|
951
1022
|
return escapeHtml2(s).replace(/"/g, """);
|
|
952
1023
|
}
|
|
953
1024
|
|
|
1025
|
+
// src/html/plainHtmlPrepare.ts
|
|
1026
|
+
import { isDataFilePath } from "@bendyline/squisq/doc";
|
|
1027
|
+
var PAGE_DIAGRAM_KINDS = [
|
|
1028
|
+
"mermaid",
|
|
1029
|
+
"diagram",
|
|
1030
|
+
"timeline",
|
|
1031
|
+
"container",
|
|
1032
|
+
"chart"
|
|
1033
|
+
];
|
|
1034
|
+
function needsPlainHtmlPreparation(doc, options = {}) {
|
|
1035
|
+
let icons = false;
|
|
1036
|
+
let data = false;
|
|
1037
|
+
const visit = (node) => {
|
|
1038
|
+
if (!node || typeof node !== "object" || icons && data) return;
|
|
1039
|
+
const n = node;
|
|
1040
|
+
if (n.type === "inlineIcon") icons = true;
|
|
1041
|
+
if (n.type === "heading") {
|
|
1042
|
+
const src = n.templateAnnotation?.params?.src;
|
|
1043
|
+
if (src && isDataFilePath(src)) data = true;
|
|
1044
|
+
}
|
|
1045
|
+
if (Array.isArray(n.children)) n.children.forEach(visit);
|
|
1046
|
+
};
|
|
1047
|
+
doc.children.forEach(visit);
|
|
1048
|
+
if (icons || data && options.container) return true;
|
|
1049
|
+
if (!options.renderDiagram) return false;
|
|
1050
|
+
return findDiagrams(doc).some((diagram) => PAGE_DIAGRAM_KINDS.includes(diagram.kind));
|
|
1051
|
+
}
|
|
1052
|
+
async function prepareMarkdownDocForPlainHtml(doc, options = {}) {
|
|
1053
|
+
const { renderDiagram, container, signal, onWarning } = options;
|
|
1054
|
+
let markdownDoc = doc;
|
|
1055
|
+
if (container) {
|
|
1056
|
+
const { materializeDataReferences, DATA_PREVIEW_ROWS } = await import("./materialize-ICXNO4S4.js");
|
|
1057
|
+
markdownDoc = await materializeDataReferences(markdownDoc, container, onWarning, {
|
|
1058
|
+
maxRows: options.maxDataRows ?? DATA_PREVIEW_ROWS,
|
|
1059
|
+
tableCache: options.dataTableCache
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
signal?.throwIfAborted();
|
|
1063
|
+
const { plainHtmlIcons } = await import("./plainHtmlIcons-KBSIJ5EN.js");
|
|
1064
|
+
const icons = await plainHtmlIcons(markdownDoc);
|
|
1065
|
+
const images = /* @__PURE__ */ new Map();
|
|
1066
|
+
if (!renderDiagram) return { markdownDoc, images, icons };
|
|
1067
|
+
const pictured = await rasterizeDiagrams(markdownDoc, renderDiagram, {
|
|
1068
|
+
kinds: PAGE_DIAGRAM_KINDS,
|
|
1069
|
+
urlPrefix: "squisq-picture",
|
|
1070
|
+
signal,
|
|
1071
|
+
onWarning
|
|
1072
|
+
});
|
|
1073
|
+
const widths = /* @__PURE__ */ new Map();
|
|
1074
|
+
for (const [url, picture] of pictured.images) {
|
|
1075
|
+
const bytes = picture.data;
|
|
1076
|
+
const buffer = bytes.buffer.slice(
|
|
1077
|
+
bytes.byteOffset,
|
|
1078
|
+
bytes.byteOffset + bytes.byteLength
|
|
1079
|
+
);
|
|
1080
|
+
images.set(url, arrayBufferToBase64DataUrl(buffer, picture.contentType));
|
|
1081
|
+
widths.set(url, Math.round(picture.width));
|
|
1082
|
+
}
|
|
1083
|
+
return { markdownDoc: withPictureWidths(pictured.markdownDoc, widths), images, icons };
|
|
1084
|
+
}
|
|
1085
|
+
function withPictureWidths(doc, widths) {
|
|
1086
|
+
if (widths.size === 0) return doc;
|
|
1087
|
+
const visit = (node) => {
|
|
1088
|
+
if (node.type === "image") {
|
|
1089
|
+
const width = widths.get(node.url);
|
|
1090
|
+
return width === void 0 ? node : { ...node, width };
|
|
1091
|
+
}
|
|
1092
|
+
const children = node.children;
|
|
1093
|
+
if (!Array.isArray(children)) return node;
|
|
1094
|
+
return { ...node, children: children.map(visit) };
|
|
1095
|
+
};
|
|
1096
|
+
return { ...doc, children: doc.children.map(visit) };
|
|
1097
|
+
}
|
|
1098
|
+
|
|
954
1099
|
// src/html/plainHtmlBundle.ts
|
|
955
1100
|
import JSZip from "jszip";
|
|
956
1101
|
import { parseMarkdown, inferDocumentTitle } from "@bendyline/squisq/markdown";
|
|
@@ -1177,6 +1322,502 @@ function titleForFilename(path, mdDoc) {
|
|
|
1177
1322
|
return base.replace(/\.md$/i, "");
|
|
1178
1323
|
}
|
|
1179
1324
|
|
|
1325
|
+
// src/html/pageTemplate.ts
|
|
1326
|
+
var HTML_PAGE_TEMPLATE_CONTENT_START = "<!-- squisq:content -->";
|
|
1327
|
+
var HTML_PAGE_TEMPLATE_CONTENT_END = "<!-- /squisq:content -->";
|
|
1328
|
+
var HtmlPageTemplateError = class extends Error {
|
|
1329
|
+
constructor(issues) {
|
|
1330
|
+
super(
|
|
1331
|
+
issues.length === 1 ? `Invalid page template: ${describeIssue(issues[0])}` : `Invalid page template:
|
|
1332
|
+
${issues.map((issue) => `- ${describeIssue(issue)}`).join("\n")}`
|
|
1333
|
+
);
|
|
1334
|
+
this.name = "HtmlPageTemplateError";
|
|
1335
|
+
this.issues = issues;
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
function isHtmlPageTemplate(html) {
|
|
1339
|
+
return scanHtml(html).some(isLightSlot);
|
|
1340
|
+
}
|
|
1341
|
+
function inspectHtmlPageTemplate(html) {
|
|
1342
|
+
return analyzeTemplate(html).inspection;
|
|
1343
|
+
}
|
|
1344
|
+
function analyzeTemplate(html) {
|
|
1345
|
+
const tags = scanHtml(html);
|
|
1346
|
+
const { slots, issues } = collectSlots(tags);
|
|
1347
|
+
const lines = lineIndex(html);
|
|
1348
|
+
const errors = issues.map(({ message, offset }) => ({ message, line: lines(offset) }));
|
|
1349
|
+
const defaultSlots = slots.filter((slot) => slot.name === null);
|
|
1350
|
+
if (defaultSlots.length === 0) {
|
|
1351
|
+
errors.push({
|
|
1352
|
+
message: "The template has no unnamed <slot> for the document content.",
|
|
1353
|
+
line: 1
|
|
1354
|
+
});
|
|
1355
|
+
} else if (defaultSlots.length > 1) {
|
|
1356
|
+
for (const extra of defaultSlots.slice(1)) {
|
|
1357
|
+
errors.push({
|
|
1358
|
+
message: "The template has more than one unnamed <slot>; the content can appear only once.",
|
|
1359
|
+
line: lines(extra.start)
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
const namedSlots = [];
|
|
1364
|
+
for (const slot of slots) {
|
|
1365
|
+
if (slot.name !== null && !namedSlots.includes(slot.name)) namedSlots.push(slot.name);
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
tags,
|
|
1369
|
+
slots,
|
|
1370
|
+
inspection: {
|
|
1371
|
+
isTemplate: tags.some(isLightSlot),
|
|
1372
|
+
defaultSlotCount: defaultSlots.length,
|
|
1373
|
+
namedSlots,
|
|
1374
|
+
errors: errors.sort((a, b) => a.line - b.line)
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
function isLightSlot(tag) {
|
|
1379
|
+
return tag.kind === "start" && tag.name === "slot" && !tag.inert;
|
|
1380
|
+
}
|
|
1381
|
+
function applyHtmlPageTemplate(template, content, options = {}) {
|
|
1382
|
+
const { tags, slots, inspection } = analyzeTemplate(template);
|
|
1383
|
+
if (inspection.errors.length > 0) throw new HtmlPageTemplateError(inspection.errors);
|
|
1384
|
+
const values = content.values ?? {};
|
|
1385
|
+
const valueFor = (name) => name === "title" ? content.title : values[name];
|
|
1386
|
+
const edits = [];
|
|
1387
|
+
const replacements = /* @__PURE__ */ new Set();
|
|
1388
|
+
for (const slot of slots) {
|
|
1389
|
+
const value = slot.name === null ? void 0 : valueFor(slot.name);
|
|
1390
|
+
if (slot.name === null || value !== void 0) {
|
|
1391
|
+
const replacement = {
|
|
1392
|
+
start: slot.start,
|
|
1393
|
+
end: slot.end,
|
|
1394
|
+
text: slot.name === null ? `${HTML_PAGE_TEMPLATE_CONTENT_START}
|
|
1395
|
+
${content.bodyHtml}
|
|
1396
|
+
${HTML_PAGE_TEMPLATE_CONTENT_END}` : escapeHtml3(value ?? "")
|
|
1397
|
+
};
|
|
1398
|
+
edits.push(replacement);
|
|
1399
|
+
replacements.add(replacement);
|
|
1400
|
+
} else {
|
|
1401
|
+
edits.push({ start: slot.start, end: slot.contentStart, text: "" });
|
|
1402
|
+
if (slot.end > slot.contentEnd)
|
|
1403
|
+
edits.push({ start: slot.contentEnd, end: slot.end, text: "" });
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
const title = tags.find(
|
|
1407
|
+
(tag) => tag.kind === "start" && tag.name === "title" && !tag.inert
|
|
1408
|
+
);
|
|
1409
|
+
if (title?.text) {
|
|
1410
|
+
edits.push({
|
|
1411
|
+
start: title.text.start,
|
|
1412
|
+
end: title.text.end,
|
|
1413
|
+
text: fillTitleText(template.slice(title.text.start, title.text.end), valueFor)
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
for (const tag of tags) {
|
|
1417
|
+
if (tag.kind !== "start" || tag.name !== "meta" || tag.inert) continue;
|
|
1418
|
+
const key = (attributeValue(tag, "name") ?? attributeValue(tag, "property"))?.toLowerCase();
|
|
1419
|
+
const value = key ? metaValue(key, content.title, values.description) : void 0;
|
|
1420
|
+
if (value === void 0) continue;
|
|
1421
|
+
const contentAttribute = tag.attributes.find((attribute) => attribute.name === "content");
|
|
1422
|
+
const text = `content="${escapeAttr2(value)}"`;
|
|
1423
|
+
if (contentAttribute) {
|
|
1424
|
+
edits.push({ start: contentAttribute.start, end: contentAttribute.end, text });
|
|
1425
|
+
} else {
|
|
1426
|
+
edits.push({ start: tag.closeStart, end: tag.closeStart, text: ` ${text}` });
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
const fromDir = options.templatePath === void 0 ? null : posixDirname2(options.templatePath);
|
|
1430
|
+
const toDir = options.outputPath === void 0 ? null : posixDirname2(options.outputPath);
|
|
1431
|
+
if (fromDir !== null && toDir !== null && fromDir !== toDir) {
|
|
1432
|
+
for (const tag of tags) {
|
|
1433
|
+
if (tag.kind !== "start") continue;
|
|
1434
|
+
for (const attribute of tag.attributes) {
|
|
1435
|
+
if (attribute.value === null) continue;
|
|
1436
|
+
const rewrite = URL_ATTRIBUTES.has(attribute.name) ? rebaseUrl : SRCSET_ATTRIBUTES.has(attribute.name) ? rebaseSrcset : null;
|
|
1437
|
+
if (!rewrite) continue;
|
|
1438
|
+
const raw = template.slice(attribute.valueStart, attribute.valueEnd);
|
|
1439
|
+
const rebased = rewrite(raw, fromDir, toDir);
|
|
1440
|
+
if (rebased !== raw) {
|
|
1441
|
+
edits.push({ start: attribute.valueStart, end: attribute.valueEnd, text: rebased });
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
const headInsert = `${content.headHtml ?? ""}${title ? "" : `<title>${escapeHtml3(content.title)}</title>
|
|
1447
|
+
`}`;
|
|
1448
|
+
if (headInsert) {
|
|
1449
|
+
const at = headInsertionPoint(tags);
|
|
1450
|
+
edits.push({ start: at, end: at, text: headInsert });
|
|
1451
|
+
}
|
|
1452
|
+
const live = edits.filter(
|
|
1453
|
+
(edit) => replacements.has(edit) || ![...replacements].some(
|
|
1454
|
+
(slot) => edit.end > edit.start ? edit.start >= slot.start && edit.end <= slot.end : edit.start > slot.start && edit.start < slot.end
|
|
1455
|
+
)
|
|
1456
|
+
);
|
|
1457
|
+
return applyEdits(template, live);
|
|
1458
|
+
}
|
|
1459
|
+
function extractHtmlPageTemplateContent(html) {
|
|
1460
|
+
const start = html.indexOf(HTML_PAGE_TEMPLATE_CONTENT_START);
|
|
1461
|
+
if (start < 0) return null;
|
|
1462
|
+
const from = start + HTML_PAGE_TEMPLATE_CONTENT_START.length;
|
|
1463
|
+
const end = html.lastIndexOf(HTML_PAGE_TEMPLATE_CONTENT_END);
|
|
1464
|
+
return end < from ? null : html.slice(from, end);
|
|
1465
|
+
}
|
|
1466
|
+
var TEXT_ELEMENTS = /* @__PURE__ */ new Set([
|
|
1467
|
+
"script",
|
|
1468
|
+
"style",
|
|
1469
|
+
"title",
|
|
1470
|
+
"textarea",
|
|
1471
|
+
"xmp",
|
|
1472
|
+
"iframe",
|
|
1473
|
+
"noembed",
|
|
1474
|
+
"noframes",
|
|
1475
|
+
"noscript"
|
|
1476
|
+
]);
|
|
1477
|
+
function scanHtml(html) {
|
|
1478
|
+
const tags = [];
|
|
1479
|
+
let templateDepth = 0;
|
|
1480
|
+
let foreignDepth = 0;
|
|
1481
|
+
let index = 0;
|
|
1482
|
+
while (index < html.length) {
|
|
1483
|
+
const open = html.indexOf("<", index);
|
|
1484
|
+
if (open < 0) break;
|
|
1485
|
+
const next = html[open + 1];
|
|
1486
|
+
if (html.startsWith("<!--", open)) {
|
|
1487
|
+
const close = html.indexOf("-->", open + 4);
|
|
1488
|
+
index = close < 0 ? html.length : close + 3;
|
|
1489
|
+
} else if (next === "!" || next === "?") {
|
|
1490
|
+
const close = html.indexOf(">", open + 2);
|
|
1491
|
+
index = close < 0 ? html.length : close + 1;
|
|
1492
|
+
} else if (next === "/" && isAsciiLetter(html[open + 2])) {
|
|
1493
|
+
const nameEnd = scanName(html, open + 2);
|
|
1494
|
+
const name = html.slice(open + 2, nameEnd).toLowerCase();
|
|
1495
|
+
const close = html.indexOf(">", nameEnd);
|
|
1496
|
+
const end = close < 0 ? html.length : close + 1;
|
|
1497
|
+
if (name === "template" && templateDepth > 0) templateDepth--;
|
|
1498
|
+
if ((name === "svg" || name === "math") && foreignDepth > 0) foreignDepth--;
|
|
1499
|
+
tags.push({ kind: "end", name, start: open, end, inert: templateDepth + foreignDepth > 0 });
|
|
1500
|
+
index = end;
|
|
1501
|
+
} else if (isAsciiLetter(next)) {
|
|
1502
|
+
const tag = scanStartTag(html, open, templateDepth + foreignDepth > 0);
|
|
1503
|
+
tags.push(tag);
|
|
1504
|
+
index = tag.end;
|
|
1505
|
+
if (tag.name === "template") templateDepth++;
|
|
1506
|
+
else if ((tag.name === "svg" || tag.name === "math") && !tag.selfClosing) foreignDepth++;
|
|
1507
|
+
else if (TEXT_ELEMENTS.has(tag.name) && foreignDepth === 0) {
|
|
1508
|
+
const closing = new RegExp(`</${tag.name}(?=[\\s/>])`, "gi");
|
|
1509
|
+
closing.lastIndex = tag.end;
|
|
1510
|
+
const match = closing.exec(html);
|
|
1511
|
+
const textEnd = match ? match.index : html.length;
|
|
1512
|
+
tag.text = { start: tag.end, end: textEnd };
|
|
1513
|
+
index = textEnd;
|
|
1514
|
+
}
|
|
1515
|
+
} else {
|
|
1516
|
+
index = open + 1;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return tags;
|
|
1520
|
+
}
|
|
1521
|
+
function scanStartTag(html, start, inert) {
|
|
1522
|
+
const nameEnd = scanName(html, start + 1);
|
|
1523
|
+
const name = html.slice(start + 1, nameEnd).toLowerCase();
|
|
1524
|
+
const attributes = [];
|
|
1525
|
+
let index = nameEnd;
|
|
1526
|
+
for (; ; ) {
|
|
1527
|
+
index = skipSpace(html, index);
|
|
1528
|
+
if (index >= html.length) {
|
|
1529
|
+
return {
|
|
1530
|
+
kind: "start",
|
|
1531
|
+
name,
|
|
1532
|
+
start,
|
|
1533
|
+
end: html.length,
|
|
1534
|
+
closeStart: html.length,
|
|
1535
|
+
selfClosing: false,
|
|
1536
|
+
attributes,
|
|
1537
|
+
inert
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
const char = html[index];
|
|
1541
|
+
if (char === ">") {
|
|
1542
|
+
return {
|
|
1543
|
+
kind: "start",
|
|
1544
|
+
name,
|
|
1545
|
+
start,
|
|
1546
|
+
end: index + 1,
|
|
1547
|
+
closeStart: index,
|
|
1548
|
+
selfClosing: false,
|
|
1549
|
+
attributes,
|
|
1550
|
+
inert
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
if (char === "/") {
|
|
1554
|
+
if (html[index + 1] === ">") {
|
|
1555
|
+
return {
|
|
1556
|
+
kind: "start",
|
|
1557
|
+
name,
|
|
1558
|
+
start,
|
|
1559
|
+
end: index + 2,
|
|
1560
|
+
closeStart: index,
|
|
1561
|
+
selfClosing: true,
|
|
1562
|
+
attributes,
|
|
1563
|
+
inert
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
index++;
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
const attributeStart = index;
|
|
1570
|
+
index++;
|
|
1571
|
+
while (index < html.length && !isSpace(html[index]) && !"/>=".includes(html[index])) index++;
|
|
1572
|
+
const attributeName = html.slice(attributeStart, index).toLowerCase();
|
|
1573
|
+
const afterName = index;
|
|
1574
|
+
index = skipSpace(html, index);
|
|
1575
|
+
if (html[index] !== "=") {
|
|
1576
|
+
attributes.push({
|
|
1577
|
+
name: attributeName,
|
|
1578
|
+
value: null,
|
|
1579
|
+
start: attributeStart,
|
|
1580
|
+
end: afterName,
|
|
1581
|
+
valueStart: afterName,
|
|
1582
|
+
valueEnd: afterName
|
|
1583
|
+
});
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
index = skipSpace(html, index + 1);
|
|
1587
|
+
const quote = html[index];
|
|
1588
|
+
let valueStart;
|
|
1589
|
+
let valueEnd;
|
|
1590
|
+
if (quote === '"' || quote === "'") {
|
|
1591
|
+
valueStart = index + 1;
|
|
1592
|
+
const close = html.indexOf(quote, valueStart);
|
|
1593
|
+
valueEnd = close < 0 ? html.length : close;
|
|
1594
|
+
index = close < 0 ? html.length : close + 1;
|
|
1595
|
+
} else {
|
|
1596
|
+
valueStart = index;
|
|
1597
|
+
while (index < html.length && !isSpace(html[index]) && html[index] !== ">") index++;
|
|
1598
|
+
valueEnd = index;
|
|
1599
|
+
}
|
|
1600
|
+
attributes.push({
|
|
1601
|
+
name: attributeName,
|
|
1602
|
+
value: html.slice(valueStart, valueEnd),
|
|
1603
|
+
start: attributeStart,
|
|
1604
|
+
end: index,
|
|
1605
|
+
valueStart,
|
|
1606
|
+
valueEnd
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
function scanName(html, from) {
|
|
1611
|
+
let index = from;
|
|
1612
|
+
while (index < html.length && !isSpace(html[index]) && html[index] !== "/" && html[index] !== ">") {
|
|
1613
|
+
index++;
|
|
1614
|
+
}
|
|
1615
|
+
return index;
|
|
1616
|
+
}
|
|
1617
|
+
function skipSpace(html, from) {
|
|
1618
|
+
let index = from;
|
|
1619
|
+
while (index < html.length && isSpace(html[index])) index++;
|
|
1620
|
+
return index;
|
|
1621
|
+
}
|
|
1622
|
+
function isSpace(char) {
|
|
1623
|
+
return char === " " || char === "\n" || char === " " || char === "\r" || char === "\f";
|
|
1624
|
+
}
|
|
1625
|
+
function isAsciiLetter(char) {
|
|
1626
|
+
return char !== void 0 && /^[A-Za-z]$/.test(char);
|
|
1627
|
+
}
|
|
1628
|
+
function attributeValue(tag, name) {
|
|
1629
|
+
return tag.attributes.find((attribute) => attribute.name === name)?.value ?? void 0;
|
|
1630
|
+
}
|
|
1631
|
+
function collectSlots(tags) {
|
|
1632
|
+
const slots = [];
|
|
1633
|
+
const issues = [];
|
|
1634
|
+
let open = null;
|
|
1635
|
+
for (const tag of tags) {
|
|
1636
|
+
if (tag.name !== "slot" || tag.inert) continue;
|
|
1637
|
+
if (tag.kind === "start") {
|
|
1638
|
+
if (open) {
|
|
1639
|
+
issues.push({
|
|
1640
|
+
message: "A <slot> cannot be placed inside another <slot>.",
|
|
1641
|
+
offset: tag.start
|
|
1642
|
+
});
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
if (tag.selfClosing) {
|
|
1646
|
+
slots.push({
|
|
1647
|
+
name: slotName(tag),
|
|
1648
|
+
start: tag.start,
|
|
1649
|
+
end: tag.end,
|
|
1650
|
+
contentStart: tag.end,
|
|
1651
|
+
contentEnd: tag.end
|
|
1652
|
+
});
|
|
1653
|
+
} else {
|
|
1654
|
+
open = tag;
|
|
1655
|
+
}
|
|
1656
|
+
} else if (open) {
|
|
1657
|
+
slots.push({
|
|
1658
|
+
name: slotName(open),
|
|
1659
|
+
start: open.start,
|
|
1660
|
+
end: tag.end,
|
|
1661
|
+
contentStart: open.end,
|
|
1662
|
+
contentEnd: tag.start
|
|
1663
|
+
});
|
|
1664
|
+
open = null;
|
|
1665
|
+
} else {
|
|
1666
|
+
issues.push({ message: "A </slot> has no matching <slot>.", offset: tag.start });
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
if (open) {
|
|
1670
|
+
issues.push({
|
|
1671
|
+
message: "A <slot> is never closed; write <slot></slot> or <slot/>.",
|
|
1672
|
+
offset: open.start
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
return { slots, issues };
|
|
1676
|
+
}
|
|
1677
|
+
function slotName(tag) {
|
|
1678
|
+
const name = attributeValue(tag, "name")?.trim();
|
|
1679
|
+
return name ? name : null;
|
|
1680
|
+
}
|
|
1681
|
+
function fillTitleText(text, valueFor) {
|
|
1682
|
+
const { slots } = collectSlots(scanHtml(text));
|
|
1683
|
+
if (slots.length === 0) return escapeHtml3(valueFor("title") ?? "");
|
|
1684
|
+
const edits = slots.map((slot) => {
|
|
1685
|
+
const value = valueFor(slot.name ?? "title");
|
|
1686
|
+
return {
|
|
1687
|
+
start: slot.start,
|
|
1688
|
+
end: slot.end,
|
|
1689
|
+
text: value === void 0 ? text.slice(slot.contentStart, slot.contentEnd) : escapeHtml3(value)
|
|
1690
|
+
};
|
|
1691
|
+
});
|
|
1692
|
+
return applyEdits(text, edits);
|
|
1693
|
+
}
|
|
1694
|
+
var TITLE_META = /* @__PURE__ */ new Set(["og:title", "twitter:title"]);
|
|
1695
|
+
var DESCRIPTION_META = /* @__PURE__ */ new Set(["description", "og:description", "twitter:description"]);
|
|
1696
|
+
function metaValue(key, title, description) {
|
|
1697
|
+
if (TITLE_META.has(key)) return title;
|
|
1698
|
+
if (DESCRIPTION_META.has(key)) return description;
|
|
1699
|
+
return void 0;
|
|
1700
|
+
}
|
|
1701
|
+
function headInsertionPoint(tags) {
|
|
1702
|
+
for (const tag of tags) {
|
|
1703
|
+
if (tag.inert) continue;
|
|
1704
|
+
if (tag.kind === "end" && tag.name === "head") return tag.start;
|
|
1705
|
+
if (tag.kind !== "start") continue;
|
|
1706
|
+
if (tag.name === "body") return tag.start;
|
|
1707
|
+
if (tag.name === "style") return tag.start;
|
|
1708
|
+
if (tag.name === "link" && (attributeValue(tag, "rel") ?? "").toLowerCase().split(/\s+/).includes("stylesheet")) {
|
|
1709
|
+
return tag.start;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
const container = tags.find(
|
|
1713
|
+
(tag) => tag.kind === "start" && (tag.name === "head" || tag.name === "html")
|
|
1714
|
+
);
|
|
1715
|
+
return container?.end ?? 0;
|
|
1716
|
+
}
|
|
1717
|
+
var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
1718
|
+
"href",
|
|
1719
|
+
"src",
|
|
1720
|
+
"poster",
|
|
1721
|
+
"action",
|
|
1722
|
+
"formaction",
|
|
1723
|
+
"cite",
|
|
1724
|
+
"data",
|
|
1725
|
+
"background",
|
|
1726
|
+
"xlink:href"
|
|
1727
|
+
]);
|
|
1728
|
+
var SRCSET_ATTRIBUTES = /* @__PURE__ */ new Set(["srcset", "imagesrcset"]);
|
|
1729
|
+
function rebaseUrl(raw, fromDir, toDir) {
|
|
1730
|
+
const url = raw.trim();
|
|
1731
|
+
if (!url || url.startsWith("#") || url.startsWith("?") || url.startsWith("/") || url.startsWith("\\") || /^[a-z][a-z0-9+.-]*:/i.test(url) || url.includes("{{")) {
|
|
1732
|
+
return raw;
|
|
1733
|
+
}
|
|
1734
|
+
const cut = url.search(/[?#]/);
|
|
1735
|
+
const path = cut < 0 ? url : url.slice(0, cut);
|
|
1736
|
+
const suffix = cut < 0 ? "" : url.slice(cut);
|
|
1737
|
+
if (path.includes("&")) return raw;
|
|
1738
|
+
const target = normalizePath(fromDir ? `${fromDir}/${path}` : path);
|
|
1739
|
+
if (target === null) return raw;
|
|
1740
|
+
const isDirectory = path.endsWith("/") || /(?:^|\/)\.\.?$/.test(path);
|
|
1741
|
+
let relative = relativeTo(toDir, target);
|
|
1742
|
+
if (isDirectory) relative = relative ? `${relative}/` : "./";
|
|
1743
|
+
const leading = raw.slice(0, raw.length - raw.trimStart().length);
|
|
1744
|
+
const trailing = raw.slice(raw.trimEnd().length);
|
|
1745
|
+
return `${leading}${relative}${suffix}${trailing}`;
|
|
1746
|
+
}
|
|
1747
|
+
function rebaseSrcset(raw, fromDir, toDir) {
|
|
1748
|
+
let out = "";
|
|
1749
|
+
let index = 0;
|
|
1750
|
+
while (index < raw.length) {
|
|
1751
|
+
const separator = /^[\s,]*/.exec(raw.slice(index))?.[0] ?? "";
|
|
1752
|
+
out += separator;
|
|
1753
|
+
index += separator.length;
|
|
1754
|
+
if (index >= raw.length) break;
|
|
1755
|
+
let urlEnd = index;
|
|
1756
|
+
while (urlEnd < raw.length && !isSpace(raw[urlEnd])) urlEnd++;
|
|
1757
|
+
let url = raw.slice(index, urlEnd);
|
|
1758
|
+
const commas = /,+$/.exec(url)?.[0] ?? "";
|
|
1759
|
+
url = url.slice(0, url.length - commas.length);
|
|
1760
|
+
out += rebaseUrl(url, fromDir, toDir) + commas;
|
|
1761
|
+
index = urlEnd;
|
|
1762
|
+
if (commas) continue;
|
|
1763
|
+
let descriptorEnd = index;
|
|
1764
|
+
let depth = 0;
|
|
1765
|
+
while (descriptorEnd < raw.length) {
|
|
1766
|
+
const char = raw[descriptorEnd];
|
|
1767
|
+
if (char === "(") depth++;
|
|
1768
|
+
else if (char === ")") depth = Math.max(0, depth - 1);
|
|
1769
|
+
else if (char === "," && depth === 0) break;
|
|
1770
|
+
descriptorEnd++;
|
|
1771
|
+
}
|
|
1772
|
+
out += raw.slice(index, descriptorEnd);
|
|
1773
|
+
index = descriptorEnd;
|
|
1774
|
+
}
|
|
1775
|
+
return out;
|
|
1776
|
+
}
|
|
1777
|
+
function relativeTo(fromDir, target) {
|
|
1778
|
+
const from = fromDir ? fromDir.split("/") : [];
|
|
1779
|
+
const to = target ? target.split("/") : [];
|
|
1780
|
+
let shared = 0;
|
|
1781
|
+
while (shared < from.length && shared < to.length && from[shared] === to[shared]) shared++;
|
|
1782
|
+
return [...from.slice(shared).map(() => ".."), ...to.slice(shared)].join("/");
|
|
1783
|
+
}
|
|
1784
|
+
function posixDirname2(path) {
|
|
1785
|
+
const normalized = normalizePath(path.replace(/\\/g, "/")) ?? "";
|
|
1786
|
+
const slash = normalized.lastIndexOf("/");
|
|
1787
|
+
return slash < 0 ? "" : normalized.slice(0, slash);
|
|
1788
|
+
}
|
|
1789
|
+
function applyEdits(source, edits) {
|
|
1790
|
+
const ordered = [...edits].sort(
|
|
1791
|
+
(a, b) => a.start - b.start || Number(a.end > a.start) - Number(b.end > b.start)
|
|
1792
|
+
);
|
|
1793
|
+
let out = "";
|
|
1794
|
+
let cursor = 0;
|
|
1795
|
+
for (const edit of ordered) {
|
|
1796
|
+
if (edit.start < cursor) continue;
|
|
1797
|
+
out += source.slice(cursor, edit.start) + edit.text;
|
|
1798
|
+
cursor = edit.end;
|
|
1799
|
+
}
|
|
1800
|
+
return out + source.slice(cursor);
|
|
1801
|
+
}
|
|
1802
|
+
function lineIndex(source) {
|
|
1803
|
+
return (offset) => {
|
|
1804
|
+
let line = 1;
|
|
1805
|
+
for (let index = source.indexOf("\n"); index >= 0 && index < offset; index = source.indexOf("\n", index + 1)) {
|
|
1806
|
+
line++;
|
|
1807
|
+
}
|
|
1808
|
+
return line;
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
function describeIssue(issue) {
|
|
1812
|
+
return `${issue.message} (line ${issue.line})`;
|
|
1813
|
+
}
|
|
1814
|
+
function escapeHtml3(s) {
|
|
1815
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1816
|
+
}
|
|
1817
|
+
function escapeAttr2(s) {
|
|
1818
|
+
return escapeHtml3(s).replace(/"/g, """);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1180
1821
|
// src/html/docsHtmlBundle.ts
|
|
1181
1822
|
import JSZip2 from "jszip";
|
|
1182
1823
|
import { parseMarkdown as parseMarkdown2, inferDocumentTitle as inferDocumentTitle2 } from "@bendyline/squisq/markdown";
|
|
@@ -1997,11 +2638,13 @@ function htmlToMarkdownDocSync(html, options = {}) {
|
|
|
1997
2638
|
const embedded = findEmbeddedSquisqDoc(nodes);
|
|
1998
2639
|
if (embedded) return docToMarkdown(parseEmbeddedSquisqDoc(embedded));
|
|
1999
2640
|
const headMetadata = options.headMetadata === false ? {} : readHeadMetadata(nodes);
|
|
2641
|
+
const templated = extractHtmlPageTemplateContent(html);
|
|
2642
|
+
const contentNodes = templated === null ? nodes : parseHtmlToNodes(templated);
|
|
2000
2643
|
const {
|
|
2001
2644
|
nodes: bodyNodes,
|
|
2002
2645
|
bodies,
|
|
2003
2646
|
identifiers
|
|
2004
|
-
} = extractFootnoteSection(dropContentlessElements(
|
|
2647
|
+
} = extractFootnoteSection(dropContentlessElements(contentNodes));
|
|
2005
2648
|
const clean = (input) => options.sanitize === false ? input : sanitizeHtmlNodes2(input);
|
|
2006
2649
|
const children = blocksFromNodes(clean(bodyNodes));
|
|
2007
2650
|
if (bodies.size > 0) {
|
|
@@ -2121,8 +2764,18 @@ export {
|
|
|
2121
2764
|
collectImagePaths,
|
|
2122
2765
|
generateExternalHtml,
|
|
2123
2766
|
markdownDocToPlainHtml,
|
|
2767
|
+
markdownDocToPlainHtmlParts,
|
|
2768
|
+
needsPlainHtmlPreparation,
|
|
2769
|
+
prepareMarkdownDocForPlainHtml,
|
|
2124
2770
|
markdownDocsToPlainHtmlBundle,
|
|
2125
2771
|
collectLinkRefs,
|
|
2772
|
+
HTML_PAGE_TEMPLATE_CONTENT_START,
|
|
2773
|
+
HTML_PAGE_TEMPLATE_CONTENT_END,
|
|
2774
|
+
HtmlPageTemplateError,
|
|
2775
|
+
isHtmlPageTemplate,
|
|
2776
|
+
inspectHtmlPageTemplate,
|
|
2777
|
+
applyHtmlPageTemplate,
|
|
2778
|
+
extractHtmlPageTemplateContent,
|
|
2126
2779
|
markdownDocsToHtmlBundle,
|
|
2127
2780
|
htmlToMarkdownDocSync,
|
|
2128
2781
|
htmlToMarkdownDoc,
|