@bendyline/squisq-formats 2.8.6 → 2.8.8
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-IYNDHW2C.js → chunk-6ALSRHD7.js} +15 -2
- package/dist/{chunk-B6UQPW75.js → chunk-LK4KCVN6.js} +548 -17
- package/dist/{chunk-C23JYVPG.js → chunk-WJTC5OD6.js} +2 -0
- package/dist/html/index.d.ts +130 -2
- package/dist/html/index.js +17 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -4
- package/dist/outside-in/index.d.ts +9 -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/registry/index.js +1 -1
- package/package.json +2 -2
- /package/dist/{chunk-ALDCFHZT.js → chunk-GDUZJ75A.js} +0 -0
package/README.md
CHANGED
|
@@ -141,6 +141,27 @@ const page = markdownDocToPlainHtml(markdownDoc, { themeId: 'warm-earth' });
|
|
|
141
141
|
const imported = await htmlToMarkdownDoc(htmlString);
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
To place a document inside your own page — a site header, navigation, footer, and stylesheet — write the page as an HTML template with standard `<slot>` elements and fill it:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { applyHtmlPageTemplate, markdownDocToPlainHtmlParts } from '@bendyline/squisq-formats/html';
|
|
148
|
+
|
|
149
|
+
// template: <title><slot name="title">Post</slot> · My Blog</title> … <h1><slot name="title"></slot></h1> <slot></slot>
|
|
150
|
+
const parts = markdownDocToPlainHtmlParts(markdownDoc);
|
|
151
|
+
const page = applyHtmlPageTemplate(
|
|
152
|
+
template,
|
|
153
|
+
{
|
|
154
|
+
bodyHtml: parts.bodyHtml,
|
|
155
|
+
headHtml: parts.featureHeadHtml,
|
|
156
|
+
title: 'Hello',
|
|
157
|
+
values: { date: '2026-10-28' },
|
|
158
|
+
},
|
|
159
|
+
{ templatePath: '_templates/post.html', outputPath: 'posts/hello.html' },
|
|
160
|
+
);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The unnamed `<slot>` receives the body (between `<!-- squisq:content -->` markers, which `htmlToMarkdownDoc` honors on re-import), named slots receive text values or keep their fallback children, relative URLs are rebased from the template's folder to the page's, and everything else in the template is copied byte for byte.
|
|
164
|
+
|
|
144
165
|
The subpath also exports `markdownDocsToPlainHtmlBundle` / `markdownDocsToHtmlBundle` (recursive multi-doc ZIP bundles with `.md` links rewritten to `.html`), `htmlToMarkdownDocSync` / `htmlToMarkdown`, and helpers (`collectImagePaths`, `collectLinkRefs`, `inferMimeType`).
|
|
145
166
|
|
|
146
167
|
### EPUB
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
convert,
|
|
3
3
|
defaultRegistry
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GDUZJ75A.js";
|
|
5
5
|
import {
|
|
6
6
|
ConversionError
|
|
7
7
|
} from "./chunk-KXOZMWBS.js";
|
|
8
8
|
import {
|
|
9
9
|
OUTSIDE_IN_FORMAT_IDS,
|
|
10
|
+
OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
|
|
10
11
|
resolveOutsideInLayout
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WJTC5OD6.js";
|
|
12
13
|
|
|
13
14
|
// src/outside-in/index.ts
|
|
14
15
|
import {
|
|
@@ -63,6 +64,17 @@ function isOutsideInMarkdownEditingEnabled(source) {
|
|
|
63
64
|
const frontmatter = yaml === null ? null : parseFrontmatter(yaml);
|
|
64
65
|
return frontmatter?.[OUTSIDE_IN_UPDATE_FROM_MARKDOWN_KEY] === true;
|
|
65
66
|
}
|
|
67
|
+
function readOutsideInOutputTemplate(source) {
|
|
68
|
+
const frontmatter = typeof source === "string" ? (() => {
|
|
69
|
+
const yaml = rawFrontmatter(source);
|
|
70
|
+
return yaml === null ? null : parseFrontmatter(yaml);
|
|
71
|
+
})() : source.frontmatter;
|
|
72
|
+
const value = frontmatter?.[OUTSIDE_IN_OUTPUT_TEMPLATE_KEY];
|
|
73
|
+
if (typeof value !== "string") return null;
|
|
74
|
+
const trimmed = value.trim();
|
|
75
|
+
if (!trimmed) return null;
|
|
76
|
+
return trimmed.toLowerCase() === "none" ? "none" : trimmed;
|
|
77
|
+
}
|
|
66
78
|
function withOutsideInMarkdownEditing(source, layout, enabled = true) {
|
|
67
79
|
return setFrontmatterValues(withOutsideInMetadata(source, layout), {
|
|
68
80
|
[OUTSIDE_IN_UPDATE_FROM_MARKDOWN_KEY]: enabled
|
|
@@ -205,6 +217,7 @@ export {
|
|
|
205
217
|
readOutsideInMetadata,
|
|
206
218
|
withOutsideInMetadata,
|
|
207
219
|
isOutsideInMarkdownEditingEnabled,
|
|
220
|
+
readOutsideInOutputTemplate,
|
|
208
221
|
withOutsideInMarkdownEditing,
|
|
209
222
|
importOutsideInDocument,
|
|
210
223
|
renderOutsideInDocument
|
|
@@ -306,6 +306,39 @@ import {
|
|
|
306
306
|
import { resolveFontFamily, buildGoogleFontsUrl } from "@bendyline/squisq/schemas";
|
|
307
307
|
import { resolveThemeForDoc } from "@bendyline/squisq/doc";
|
|
308
308
|
function markdownDocToPlainHtml(doc, options = {}) {
|
|
309
|
+
const { title, body, fontsLink, iconsLink, themedCss, featureCss } = renderPlainHtml(
|
|
310
|
+
doc,
|
|
311
|
+
options
|
|
312
|
+
);
|
|
313
|
+
const safeCss = escapeStyleText(`${themedCss}
|
|
314
|
+
${featureCss}`);
|
|
315
|
+
return `<!DOCTYPE html>
|
|
316
|
+
<html lang="en">
|
|
317
|
+
<head>
|
|
318
|
+
<meta charset="UTF-8">
|
|
319
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
320
|
+
<title>${escapeHtml2(title)}</title>
|
|
321
|
+
${fontsLink}${iconsLink}<style>
|
|
322
|
+
${safeCss}
|
|
323
|
+
</style>
|
|
324
|
+
</head>
|
|
325
|
+
<body>
|
|
326
|
+
${body}
|
|
327
|
+
</body>
|
|
328
|
+
</html>`;
|
|
329
|
+
}
|
|
330
|
+
function markdownDocToPlainHtmlParts(doc, options = {}) {
|
|
331
|
+
const { title, body, iconsLink, featureCss } = renderPlainHtml(doc, options);
|
|
332
|
+
return {
|
|
333
|
+
title,
|
|
334
|
+
bodyHtml: body,
|
|
335
|
+
featureHeadHtml: `${iconsLink}<style data-squisq>
|
|
336
|
+
${escapeStyleText(featureCss)}
|
|
337
|
+
</style>
|
|
338
|
+
`
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function renderPlainHtml(doc, options) {
|
|
309
342
|
const {
|
|
310
343
|
title = "Document",
|
|
311
344
|
images,
|
|
@@ -342,22 +375,14 @@ ${escapeStyleText(iconsCss)}
|
|
|
342
375
|
${VIDEO_EMBED_CSS}` : "";
|
|
343
376
|
const svgCss = docUsesSvgIslands(doc) ? `
|
|
344
377
|
${SVG_ISLAND_CSS}` : "";
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
${fontsLink}${iconsLink}<style>
|
|
354
|
-
${safeCss}
|
|
355
|
-
</style>
|
|
356
|
-
</head>
|
|
357
|
-
<body>
|
|
358
|
-
${body}
|
|
359
|
-
</body>
|
|
360
|
-
</html>`;
|
|
378
|
+
return {
|
|
379
|
+
title,
|
|
380
|
+
body,
|
|
381
|
+
fontsLink,
|
|
382
|
+
iconsLink,
|
|
383
|
+
themedCss,
|
|
384
|
+
featureCss: `${FEATURE_CSS}${videoCss}${svgCss}`
|
|
385
|
+
};
|
|
361
386
|
}
|
|
362
387
|
function escapeStyleText(css) {
|
|
363
388
|
return css.replace(/</g, "\\3c ");
|
|
@@ -1177,6 +1202,502 @@ function titleForFilename(path, mdDoc) {
|
|
|
1177
1202
|
return base.replace(/\.md$/i, "");
|
|
1178
1203
|
}
|
|
1179
1204
|
|
|
1205
|
+
// src/html/pageTemplate.ts
|
|
1206
|
+
var HTML_PAGE_TEMPLATE_CONTENT_START = "<!-- squisq:content -->";
|
|
1207
|
+
var HTML_PAGE_TEMPLATE_CONTENT_END = "<!-- /squisq:content -->";
|
|
1208
|
+
var HtmlPageTemplateError = class extends Error {
|
|
1209
|
+
constructor(issues) {
|
|
1210
|
+
super(
|
|
1211
|
+
issues.length === 1 ? `Invalid page template: ${describeIssue(issues[0])}` : `Invalid page template:
|
|
1212
|
+
${issues.map((issue) => `- ${describeIssue(issue)}`).join("\n")}`
|
|
1213
|
+
);
|
|
1214
|
+
this.name = "HtmlPageTemplateError";
|
|
1215
|
+
this.issues = issues;
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
function isHtmlPageTemplate(html) {
|
|
1219
|
+
return scanHtml(html).some(isLightSlot);
|
|
1220
|
+
}
|
|
1221
|
+
function inspectHtmlPageTemplate(html) {
|
|
1222
|
+
return analyzeTemplate(html).inspection;
|
|
1223
|
+
}
|
|
1224
|
+
function analyzeTemplate(html) {
|
|
1225
|
+
const tags = scanHtml(html);
|
|
1226
|
+
const { slots, issues } = collectSlots(tags);
|
|
1227
|
+
const lines = lineIndex(html);
|
|
1228
|
+
const errors = issues.map(({ message, offset }) => ({ message, line: lines(offset) }));
|
|
1229
|
+
const defaultSlots = slots.filter((slot) => slot.name === null);
|
|
1230
|
+
if (defaultSlots.length === 0) {
|
|
1231
|
+
errors.push({
|
|
1232
|
+
message: "The template has no unnamed <slot> for the document content.",
|
|
1233
|
+
line: 1
|
|
1234
|
+
});
|
|
1235
|
+
} else if (defaultSlots.length > 1) {
|
|
1236
|
+
for (const extra of defaultSlots.slice(1)) {
|
|
1237
|
+
errors.push({
|
|
1238
|
+
message: "The template has more than one unnamed <slot>; the content can appear only once.",
|
|
1239
|
+
line: lines(extra.start)
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
const namedSlots = [];
|
|
1244
|
+
for (const slot of slots) {
|
|
1245
|
+
if (slot.name !== null && !namedSlots.includes(slot.name)) namedSlots.push(slot.name);
|
|
1246
|
+
}
|
|
1247
|
+
return {
|
|
1248
|
+
tags,
|
|
1249
|
+
slots,
|
|
1250
|
+
inspection: {
|
|
1251
|
+
isTemplate: tags.some(isLightSlot),
|
|
1252
|
+
defaultSlotCount: defaultSlots.length,
|
|
1253
|
+
namedSlots,
|
|
1254
|
+
errors: errors.sort((a, b) => a.line - b.line)
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
function isLightSlot(tag) {
|
|
1259
|
+
return tag.kind === "start" && tag.name === "slot" && !tag.inert;
|
|
1260
|
+
}
|
|
1261
|
+
function applyHtmlPageTemplate(template, content, options = {}) {
|
|
1262
|
+
const { tags, slots, inspection } = analyzeTemplate(template);
|
|
1263
|
+
if (inspection.errors.length > 0) throw new HtmlPageTemplateError(inspection.errors);
|
|
1264
|
+
const values = content.values ?? {};
|
|
1265
|
+
const valueFor = (name) => name === "title" ? content.title : values[name];
|
|
1266
|
+
const edits = [];
|
|
1267
|
+
const replacements = /* @__PURE__ */ new Set();
|
|
1268
|
+
for (const slot of slots) {
|
|
1269
|
+
const value = slot.name === null ? void 0 : valueFor(slot.name);
|
|
1270
|
+
if (slot.name === null || value !== void 0) {
|
|
1271
|
+
const replacement = {
|
|
1272
|
+
start: slot.start,
|
|
1273
|
+
end: slot.end,
|
|
1274
|
+
text: slot.name === null ? `${HTML_PAGE_TEMPLATE_CONTENT_START}
|
|
1275
|
+
${content.bodyHtml}
|
|
1276
|
+
${HTML_PAGE_TEMPLATE_CONTENT_END}` : escapeHtml3(value ?? "")
|
|
1277
|
+
};
|
|
1278
|
+
edits.push(replacement);
|
|
1279
|
+
replacements.add(replacement);
|
|
1280
|
+
} else {
|
|
1281
|
+
edits.push({ start: slot.start, end: slot.contentStart, text: "" });
|
|
1282
|
+
if (slot.end > slot.contentEnd)
|
|
1283
|
+
edits.push({ start: slot.contentEnd, end: slot.end, text: "" });
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
const title = tags.find(
|
|
1287
|
+
(tag) => tag.kind === "start" && tag.name === "title" && !tag.inert
|
|
1288
|
+
);
|
|
1289
|
+
if (title?.text) {
|
|
1290
|
+
edits.push({
|
|
1291
|
+
start: title.text.start,
|
|
1292
|
+
end: title.text.end,
|
|
1293
|
+
text: fillTitleText(template.slice(title.text.start, title.text.end), valueFor)
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
for (const tag of tags) {
|
|
1297
|
+
if (tag.kind !== "start" || tag.name !== "meta" || tag.inert) continue;
|
|
1298
|
+
const key = (attributeValue(tag, "name") ?? attributeValue(tag, "property"))?.toLowerCase();
|
|
1299
|
+
const value = key ? metaValue(key, content.title, values.description) : void 0;
|
|
1300
|
+
if (value === void 0) continue;
|
|
1301
|
+
const contentAttribute = tag.attributes.find((attribute) => attribute.name === "content");
|
|
1302
|
+
const text = `content="${escapeAttr2(value)}"`;
|
|
1303
|
+
if (contentAttribute) {
|
|
1304
|
+
edits.push({ start: contentAttribute.start, end: contentAttribute.end, text });
|
|
1305
|
+
} else {
|
|
1306
|
+
edits.push({ start: tag.closeStart, end: tag.closeStart, text: ` ${text}` });
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
const fromDir = options.templatePath === void 0 ? null : posixDirname2(options.templatePath);
|
|
1310
|
+
const toDir = options.outputPath === void 0 ? null : posixDirname2(options.outputPath);
|
|
1311
|
+
if (fromDir !== null && toDir !== null && fromDir !== toDir) {
|
|
1312
|
+
for (const tag of tags) {
|
|
1313
|
+
if (tag.kind !== "start") continue;
|
|
1314
|
+
for (const attribute of tag.attributes) {
|
|
1315
|
+
if (attribute.value === null) continue;
|
|
1316
|
+
const rewrite = URL_ATTRIBUTES.has(attribute.name) ? rebaseUrl : SRCSET_ATTRIBUTES.has(attribute.name) ? rebaseSrcset : null;
|
|
1317
|
+
if (!rewrite) continue;
|
|
1318
|
+
const raw = template.slice(attribute.valueStart, attribute.valueEnd);
|
|
1319
|
+
const rebased = rewrite(raw, fromDir, toDir);
|
|
1320
|
+
if (rebased !== raw) {
|
|
1321
|
+
edits.push({ start: attribute.valueStart, end: attribute.valueEnd, text: rebased });
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
const headInsert = `${content.headHtml ?? ""}${title ? "" : `<title>${escapeHtml3(content.title)}</title>
|
|
1327
|
+
`}`;
|
|
1328
|
+
if (headInsert) {
|
|
1329
|
+
const at = headInsertionPoint(tags);
|
|
1330
|
+
edits.push({ start: at, end: at, text: headInsert });
|
|
1331
|
+
}
|
|
1332
|
+
const live = edits.filter(
|
|
1333
|
+
(edit) => replacements.has(edit) || ![...replacements].some(
|
|
1334
|
+
(slot) => edit.end > edit.start ? edit.start >= slot.start && edit.end <= slot.end : edit.start > slot.start && edit.start < slot.end
|
|
1335
|
+
)
|
|
1336
|
+
);
|
|
1337
|
+
return applyEdits(template, live);
|
|
1338
|
+
}
|
|
1339
|
+
function extractHtmlPageTemplateContent(html) {
|
|
1340
|
+
const start = html.indexOf(HTML_PAGE_TEMPLATE_CONTENT_START);
|
|
1341
|
+
if (start < 0) return null;
|
|
1342
|
+
const from = start + HTML_PAGE_TEMPLATE_CONTENT_START.length;
|
|
1343
|
+
const end = html.lastIndexOf(HTML_PAGE_TEMPLATE_CONTENT_END);
|
|
1344
|
+
return end < from ? null : html.slice(from, end);
|
|
1345
|
+
}
|
|
1346
|
+
var TEXT_ELEMENTS = /* @__PURE__ */ new Set([
|
|
1347
|
+
"script",
|
|
1348
|
+
"style",
|
|
1349
|
+
"title",
|
|
1350
|
+
"textarea",
|
|
1351
|
+
"xmp",
|
|
1352
|
+
"iframe",
|
|
1353
|
+
"noembed",
|
|
1354
|
+
"noframes",
|
|
1355
|
+
"noscript"
|
|
1356
|
+
]);
|
|
1357
|
+
function scanHtml(html) {
|
|
1358
|
+
const tags = [];
|
|
1359
|
+
let templateDepth = 0;
|
|
1360
|
+
let foreignDepth = 0;
|
|
1361
|
+
let index = 0;
|
|
1362
|
+
while (index < html.length) {
|
|
1363
|
+
const open = html.indexOf("<", index);
|
|
1364
|
+
if (open < 0) break;
|
|
1365
|
+
const next = html[open + 1];
|
|
1366
|
+
if (html.startsWith("<!--", open)) {
|
|
1367
|
+
const close = html.indexOf("-->", open + 4);
|
|
1368
|
+
index = close < 0 ? html.length : close + 3;
|
|
1369
|
+
} else if (next === "!" || next === "?") {
|
|
1370
|
+
const close = html.indexOf(">", open + 2);
|
|
1371
|
+
index = close < 0 ? html.length : close + 1;
|
|
1372
|
+
} else if (next === "/" && isAsciiLetter(html[open + 2])) {
|
|
1373
|
+
const nameEnd = scanName(html, open + 2);
|
|
1374
|
+
const name = html.slice(open + 2, nameEnd).toLowerCase();
|
|
1375
|
+
const close = html.indexOf(">", nameEnd);
|
|
1376
|
+
const end = close < 0 ? html.length : close + 1;
|
|
1377
|
+
if (name === "template" && templateDepth > 0) templateDepth--;
|
|
1378
|
+
if ((name === "svg" || name === "math") && foreignDepth > 0) foreignDepth--;
|
|
1379
|
+
tags.push({ kind: "end", name, start: open, end, inert: templateDepth + foreignDepth > 0 });
|
|
1380
|
+
index = end;
|
|
1381
|
+
} else if (isAsciiLetter(next)) {
|
|
1382
|
+
const tag = scanStartTag(html, open, templateDepth + foreignDepth > 0);
|
|
1383
|
+
tags.push(tag);
|
|
1384
|
+
index = tag.end;
|
|
1385
|
+
if (tag.name === "template") templateDepth++;
|
|
1386
|
+
else if ((tag.name === "svg" || tag.name === "math") && !tag.selfClosing) foreignDepth++;
|
|
1387
|
+
else if (TEXT_ELEMENTS.has(tag.name) && foreignDepth === 0) {
|
|
1388
|
+
const closing = new RegExp(`</${tag.name}(?=[\\s/>])`, "gi");
|
|
1389
|
+
closing.lastIndex = tag.end;
|
|
1390
|
+
const match = closing.exec(html);
|
|
1391
|
+
const textEnd = match ? match.index : html.length;
|
|
1392
|
+
tag.text = { start: tag.end, end: textEnd };
|
|
1393
|
+
index = textEnd;
|
|
1394
|
+
}
|
|
1395
|
+
} else {
|
|
1396
|
+
index = open + 1;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return tags;
|
|
1400
|
+
}
|
|
1401
|
+
function scanStartTag(html, start, inert) {
|
|
1402
|
+
const nameEnd = scanName(html, start + 1);
|
|
1403
|
+
const name = html.slice(start + 1, nameEnd).toLowerCase();
|
|
1404
|
+
const attributes = [];
|
|
1405
|
+
let index = nameEnd;
|
|
1406
|
+
for (; ; ) {
|
|
1407
|
+
index = skipSpace(html, index);
|
|
1408
|
+
if (index >= html.length) {
|
|
1409
|
+
return {
|
|
1410
|
+
kind: "start",
|
|
1411
|
+
name,
|
|
1412
|
+
start,
|
|
1413
|
+
end: html.length,
|
|
1414
|
+
closeStart: html.length,
|
|
1415
|
+
selfClosing: false,
|
|
1416
|
+
attributes,
|
|
1417
|
+
inert
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
const char = html[index];
|
|
1421
|
+
if (char === ">") {
|
|
1422
|
+
return {
|
|
1423
|
+
kind: "start",
|
|
1424
|
+
name,
|
|
1425
|
+
start,
|
|
1426
|
+
end: index + 1,
|
|
1427
|
+
closeStart: index,
|
|
1428
|
+
selfClosing: false,
|
|
1429
|
+
attributes,
|
|
1430
|
+
inert
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
if (char === "/") {
|
|
1434
|
+
if (html[index + 1] === ">") {
|
|
1435
|
+
return {
|
|
1436
|
+
kind: "start",
|
|
1437
|
+
name,
|
|
1438
|
+
start,
|
|
1439
|
+
end: index + 2,
|
|
1440
|
+
closeStart: index,
|
|
1441
|
+
selfClosing: true,
|
|
1442
|
+
attributes,
|
|
1443
|
+
inert
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
index++;
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
const attributeStart = index;
|
|
1450
|
+
index++;
|
|
1451
|
+
while (index < html.length && !isSpace(html[index]) && !"/>=".includes(html[index])) index++;
|
|
1452
|
+
const attributeName = html.slice(attributeStart, index).toLowerCase();
|
|
1453
|
+
const afterName = index;
|
|
1454
|
+
index = skipSpace(html, index);
|
|
1455
|
+
if (html[index] !== "=") {
|
|
1456
|
+
attributes.push({
|
|
1457
|
+
name: attributeName,
|
|
1458
|
+
value: null,
|
|
1459
|
+
start: attributeStart,
|
|
1460
|
+
end: afterName,
|
|
1461
|
+
valueStart: afterName,
|
|
1462
|
+
valueEnd: afterName
|
|
1463
|
+
});
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
index = skipSpace(html, index + 1);
|
|
1467
|
+
const quote = html[index];
|
|
1468
|
+
let valueStart;
|
|
1469
|
+
let valueEnd;
|
|
1470
|
+
if (quote === '"' || quote === "'") {
|
|
1471
|
+
valueStart = index + 1;
|
|
1472
|
+
const close = html.indexOf(quote, valueStart);
|
|
1473
|
+
valueEnd = close < 0 ? html.length : close;
|
|
1474
|
+
index = close < 0 ? html.length : close + 1;
|
|
1475
|
+
} else {
|
|
1476
|
+
valueStart = index;
|
|
1477
|
+
while (index < html.length && !isSpace(html[index]) && html[index] !== ">") index++;
|
|
1478
|
+
valueEnd = index;
|
|
1479
|
+
}
|
|
1480
|
+
attributes.push({
|
|
1481
|
+
name: attributeName,
|
|
1482
|
+
value: html.slice(valueStart, valueEnd),
|
|
1483
|
+
start: attributeStart,
|
|
1484
|
+
end: index,
|
|
1485
|
+
valueStart,
|
|
1486
|
+
valueEnd
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
function scanName(html, from) {
|
|
1491
|
+
let index = from;
|
|
1492
|
+
while (index < html.length && !isSpace(html[index]) && html[index] !== "/" && html[index] !== ">") {
|
|
1493
|
+
index++;
|
|
1494
|
+
}
|
|
1495
|
+
return index;
|
|
1496
|
+
}
|
|
1497
|
+
function skipSpace(html, from) {
|
|
1498
|
+
let index = from;
|
|
1499
|
+
while (index < html.length && isSpace(html[index])) index++;
|
|
1500
|
+
return index;
|
|
1501
|
+
}
|
|
1502
|
+
function isSpace(char) {
|
|
1503
|
+
return char === " " || char === "\n" || char === " " || char === "\r" || char === "\f";
|
|
1504
|
+
}
|
|
1505
|
+
function isAsciiLetter(char) {
|
|
1506
|
+
return char !== void 0 && /^[A-Za-z]$/.test(char);
|
|
1507
|
+
}
|
|
1508
|
+
function attributeValue(tag, name) {
|
|
1509
|
+
return tag.attributes.find((attribute) => attribute.name === name)?.value ?? void 0;
|
|
1510
|
+
}
|
|
1511
|
+
function collectSlots(tags) {
|
|
1512
|
+
const slots = [];
|
|
1513
|
+
const issues = [];
|
|
1514
|
+
let open = null;
|
|
1515
|
+
for (const tag of tags) {
|
|
1516
|
+
if (tag.name !== "slot" || tag.inert) continue;
|
|
1517
|
+
if (tag.kind === "start") {
|
|
1518
|
+
if (open) {
|
|
1519
|
+
issues.push({
|
|
1520
|
+
message: "A <slot> cannot be placed inside another <slot>.",
|
|
1521
|
+
offset: tag.start
|
|
1522
|
+
});
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
if (tag.selfClosing) {
|
|
1526
|
+
slots.push({
|
|
1527
|
+
name: slotName(tag),
|
|
1528
|
+
start: tag.start,
|
|
1529
|
+
end: tag.end,
|
|
1530
|
+
contentStart: tag.end,
|
|
1531
|
+
contentEnd: tag.end
|
|
1532
|
+
});
|
|
1533
|
+
} else {
|
|
1534
|
+
open = tag;
|
|
1535
|
+
}
|
|
1536
|
+
} else if (open) {
|
|
1537
|
+
slots.push({
|
|
1538
|
+
name: slotName(open),
|
|
1539
|
+
start: open.start,
|
|
1540
|
+
end: tag.end,
|
|
1541
|
+
contentStart: open.end,
|
|
1542
|
+
contentEnd: tag.start
|
|
1543
|
+
});
|
|
1544
|
+
open = null;
|
|
1545
|
+
} else {
|
|
1546
|
+
issues.push({ message: "A </slot> has no matching <slot>.", offset: tag.start });
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
if (open) {
|
|
1550
|
+
issues.push({
|
|
1551
|
+
message: "A <slot> is never closed; write <slot></slot> or <slot/>.",
|
|
1552
|
+
offset: open.start
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
return { slots, issues };
|
|
1556
|
+
}
|
|
1557
|
+
function slotName(tag) {
|
|
1558
|
+
const name = attributeValue(tag, "name")?.trim();
|
|
1559
|
+
return name ? name : null;
|
|
1560
|
+
}
|
|
1561
|
+
function fillTitleText(text, valueFor) {
|
|
1562
|
+
const { slots } = collectSlots(scanHtml(text));
|
|
1563
|
+
if (slots.length === 0) return escapeHtml3(valueFor("title") ?? "");
|
|
1564
|
+
const edits = slots.map((slot) => {
|
|
1565
|
+
const value = valueFor(slot.name ?? "title");
|
|
1566
|
+
return {
|
|
1567
|
+
start: slot.start,
|
|
1568
|
+
end: slot.end,
|
|
1569
|
+
text: value === void 0 ? text.slice(slot.contentStart, slot.contentEnd) : escapeHtml3(value)
|
|
1570
|
+
};
|
|
1571
|
+
});
|
|
1572
|
+
return applyEdits(text, edits);
|
|
1573
|
+
}
|
|
1574
|
+
var TITLE_META = /* @__PURE__ */ new Set(["og:title", "twitter:title"]);
|
|
1575
|
+
var DESCRIPTION_META = /* @__PURE__ */ new Set(["description", "og:description", "twitter:description"]);
|
|
1576
|
+
function metaValue(key, title, description) {
|
|
1577
|
+
if (TITLE_META.has(key)) return title;
|
|
1578
|
+
if (DESCRIPTION_META.has(key)) return description;
|
|
1579
|
+
return void 0;
|
|
1580
|
+
}
|
|
1581
|
+
function headInsertionPoint(tags) {
|
|
1582
|
+
for (const tag of tags) {
|
|
1583
|
+
if (tag.inert) continue;
|
|
1584
|
+
if (tag.kind === "end" && tag.name === "head") return tag.start;
|
|
1585
|
+
if (tag.kind !== "start") continue;
|
|
1586
|
+
if (tag.name === "body") return tag.start;
|
|
1587
|
+
if (tag.name === "style") return tag.start;
|
|
1588
|
+
if (tag.name === "link" && (attributeValue(tag, "rel") ?? "").toLowerCase().split(/\s+/).includes("stylesheet")) {
|
|
1589
|
+
return tag.start;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
const container = tags.find(
|
|
1593
|
+
(tag) => tag.kind === "start" && (tag.name === "head" || tag.name === "html")
|
|
1594
|
+
);
|
|
1595
|
+
return container?.end ?? 0;
|
|
1596
|
+
}
|
|
1597
|
+
var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
1598
|
+
"href",
|
|
1599
|
+
"src",
|
|
1600
|
+
"poster",
|
|
1601
|
+
"action",
|
|
1602
|
+
"formaction",
|
|
1603
|
+
"cite",
|
|
1604
|
+
"data",
|
|
1605
|
+
"background",
|
|
1606
|
+
"xlink:href"
|
|
1607
|
+
]);
|
|
1608
|
+
var SRCSET_ATTRIBUTES = /* @__PURE__ */ new Set(["srcset", "imagesrcset"]);
|
|
1609
|
+
function rebaseUrl(raw, fromDir, toDir) {
|
|
1610
|
+
const url = raw.trim();
|
|
1611
|
+
if (!url || url.startsWith("#") || url.startsWith("?") || url.startsWith("/") || url.startsWith("\\") || /^[a-z][a-z0-9+.-]*:/i.test(url) || url.includes("{{")) {
|
|
1612
|
+
return raw;
|
|
1613
|
+
}
|
|
1614
|
+
const cut = url.search(/[?#]/);
|
|
1615
|
+
const path = cut < 0 ? url : url.slice(0, cut);
|
|
1616
|
+
const suffix = cut < 0 ? "" : url.slice(cut);
|
|
1617
|
+
if (path.includes("&")) return raw;
|
|
1618
|
+
const target = normalizePath(fromDir ? `${fromDir}/${path}` : path);
|
|
1619
|
+
if (target === null) return raw;
|
|
1620
|
+
const isDirectory = path.endsWith("/") || /(?:^|\/)\.\.?$/.test(path);
|
|
1621
|
+
let relative = relativeTo(toDir, target);
|
|
1622
|
+
if (isDirectory) relative = relative ? `${relative}/` : "./";
|
|
1623
|
+
const leading = raw.slice(0, raw.length - raw.trimStart().length);
|
|
1624
|
+
const trailing = raw.slice(raw.trimEnd().length);
|
|
1625
|
+
return `${leading}${relative}${suffix}${trailing}`;
|
|
1626
|
+
}
|
|
1627
|
+
function rebaseSrcset(raw, fromDir, toDir) {
|
|
1628
|
+
let out = "";
|
|
1629
|
+
let index = 0;
|
|
1630
|
+
while (index < raw.length) {
|
|
1631
|
+
const separator = /^[\s,]*/.exec(raw.slice(index))?.[0] ?? "";
|
|
1632
|
+
out += separator;
|
|
1633
|
+
index += separator.length;
|
|
1634
|
+
if (index >= raw.length) break;
|
|
1635
|
+
let urlEnd = index;
|
|
1636
|
+
while (urlEnd < raw.length && !isSpace(raw[urlEnd])) urlEnd++;
|
|
1637
|
+
let url = raw.slice(index, urlEnd);
|
|
1638
|
+
const commas = /,+$/.exec(url)?.[0] ?? "";
|
|
1639
|
+
url = url.slice(0, url.length - commas.length);
|
|
1640
|
+
out += rebaseUrl(url, fromDir, toDir) + commas;
|
|
1641
|
+
index = urlEnd;
|
|
1642
|
+
if (commas) continue;
|
|
1643
|
+
let descriptorEnd = index;
|
|
1644
|
+
let depth = 0;
|
|
1645
|
+
while (descriptorEnd < raw.length) {
|
|
1646
|
+
const char = raw[descriptorEnd];
|
|
1647
|
+
if (char === "(") depth++;
|
|
1648
|
+
else if (char === ")") depth = Math.max(0, depth - 1);
|
|
1649
|
+
else if (char === "," && depth === 0) break;
|
|
1650
|
+
descriptorEnd++;
|
|
1651
|
+
}
|
|
1652
|
+
out += raw.slice(index, descriptorEnd);
|
|
1653
|
+
index = descriptorEnd;
|
|
1654
|
+
}
|
|
1655
|
+
return out;
|
|
1656
|
+
}
|
|
1657
|
+
function relativeTo(fromDir, target) {
|
|
1658
|
+
const from = fromDir ? fromDir.split("/") : [];
|
|
1659
|
+
const to = target ? target.split("/") : [];
|
|
1660
|
+
let shared = 0;
|
|
1661
|
+
while (shared < from.length && shared < to.length && from[shared] === to[shared]) shared++;
|
|
1662
|
+
return [...from.slice(shared).map(() => ".."), ...to.slice(shared)].join("/");
|
|
1663
|
+
}
|
|
1664
|
+
function posixDirname2(path) {
|
|
1665
|
+
const normalized = normalizePath(path.replace(/\\/g, "/")) ?? "";
|
|
1666
|
+
const slash = normalized.lastIndexOf("/");
|
|
1667
|
+
return slash < 0 ? "" : normalized.slice(0, slash);
|
|
1668
|
+
}
|
|
1669
|
+
function applyEdits(source, edits) {
|
|
1670
|
+
const ordered = [...edits].sort(
|
|
1671
|
+
(a, b) => a.start - b.start || Number(a.end > a.start) - Number(b.end > b.start)
|
|
1672
|
+
);
|
|
1673
|
+
let out = "";
|
|
1674
|
+
let cursor = 0;
|
|
1675
|
+
for (const edit of ordered) {
|
|
1676
|
+
if (edit.start < cursor) continue;
|
|
1677
|
+
out += source.slice(cursor, edit.start) + edit.text;
|
|
1678
|
+
cursor = edit.end;
|
|
1679
|
+
}
|
|
1680
|
+
return out + source.slice(cursor);
|
|
1681
|
+
}
|
|
1682
|
+
function lineIndex(source) {
|
|
1683
|
+
return (offset) => {
|
|
1684
|
+
let line = 1;
|
|
1685
|
+
for (let index = source.indexOf("\n"); index >= 0 && index < offset; index = source.indexOf("\n", index + 1)) {
|
|
1686
|
+
line++;
|
|
1687
|
+
}
|
|
1688
|
+
return line;
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function describeIssue(issue) {
|
|
1692
|
+
return `${issue.message} (line ${issue.line})`;
|
|
1693
|
+
}
|
|
1694
|
+
function escapeHtml3(s) {
|
|
1695
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1696
|
+
}
|
|
1697
|
+
function escapeAttr2(s) {
|
|
1698
|
+
return escapeHtml3(s).replace(/"/g, """);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1180
1701
|
// src/html/docsHtmlBundle.ts
|
|
1181
1702
|
import JSZip2 from "jszip";
|
|
1182
1703
|
import { parseMarkdown as parseMarkdown2, inferDocumentTitle as inferDocumentTitle2 } from "@bendyline/squisq/markdown";
|
|
@@ -1997,11 +2518,13 @@ function htmlToMarkdownDocSync(html, options = {}) {
|
|
|
1997
2518
|
const embedded = findEmbeddedSquisqDoc(nodes);
|
|
1998
2519
|
if (embedded) return docToMarkdown(parseEmbeddedSquisqDoc(embedded));
|
|
1999
2520
|
const headMetadata = options.headMetadata === false ? {} : readHeadMetadata(nodes);
|
|
2521
|
+
const templated = extractHtmlPageTemplateContent(html);
|
|
2522
|
+
const contentNodes = templated === null ? nodes : parseHtmlToNodes(templated);
|
|
2000
2523
|
const {
|
|
2001
2524
|
nodes: bodyNodes,
|
|
2002
2525
|
bodies,
|
|
2003
2526
|
identifiers
|
|
2004
|
-
} = extractFootnoteSection(dropContentlessElements(
|
|
2527
|
+
} = extractFootnoteSection(dropContentlessElements(contentNodes));
|
|
2005
2528
|
const clean = (input) => options.sanitize === false ? input : sanitizeHtmlNodes2(input);
|
|
2006
2529
|
const children = blocksFromNodes(clean(bodyNodes));
|
|
2007
2530
|
if (bodies.size > 0) {
|
|
@@ -2121,8 +2644,16 @@ export {
|
|
|
2121
2644
|
collectImagePaths,
|
|
2122
2645
|
generateExternalHtml,
|
|
2123
2646
|
markdownDocToPlainHtml,
|
|
2647
|
+
markdownDocToPlainHtmlParts,
|
|
2124
2648
|
markdownDocsToPlainHtmlBundle,
|
|
2125
2649
|
collectLinkRefs,
|
|
2650
|
+
HTML_PAGE_TEMPLATE_CONTENT_START,
|
|
2651
|
+
HTML_PAGE_TEMPLATE_CONTENT_END,
|
|
2652
|
+
HtmlPageTemplateError,
|
|
2653
|
+
isHtmlPageTemplate,
|
|
2654
|
+
inspectHtmlPageTemplate,
|
|
2655
|
+
applyHtmlPageTemplate,
|
|
2656
|
+
extractHtmlPageTemplateContent,
|
|
2126
2657
|
markdownDocsToHtmlBundle,
|
|
2127
2658
|
htmlToMarkdownDocSync,
|
|
2128
2659
|
htmlToMarkdownDoc,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/outside-in/layout.ts
|
|
2
2
|
var OUTSIDE_IN_FORMAT_IDS = ["html", "docx", "pdf", "pptx", "xlsx", "csv"];
|
|
3
|
+
var OUTSIDE_IN_OUTPUT_TEMPLATE_KEY = "squisq-output-template";
|
|
3
4
|
var OUTSIDE_IN_FORMAT_SET = new Set(OUTSIDE_IN_FORMAT_IDS);
|
|
4
5
|
function normalizePath(path) {
|
|
5
6
|
const slash = path.replace(/\\/g, "/");
|
|
@@ -83,6 +84,7 @@ function chooseOutsideInMarkdownPath(layout, paths) {
|
|
|
83
84
|
|
|
84
85
|
export {
|
|
85
86
|
OUTSIDE_IN_FORMAT_IDS,
|
|
87
|
+
OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
|
|
86
88
|
resolveOutsideInLayout,
|
|
87
89
|
isOutsideInTargetPath,
|
|
88
90
|
chooseOutsideInMarkdownPath
|
package/dist/html/index.d.ts
CHANGED
|
@@ -118,7 +118,7 @@ interface PlainHtmlExportOptions {
|
|
|
118
118
|
htmlPolicy?: HtmlPolicy;
|
|
119
119
|
/**
|
|
120
120
|
* How a hosted video (a paragraph that is only a link to a YouTube, Vimeo,
|
|
121
|
-
* Loom
|
|
121
|
+
* Loom or Wistia page, or that provider's `<iframe>` embed
|
|
122
122
|
* code) renders:
|
|
123
123
|
*
|
|
124
124
|
* - `player` (default) — the provider's player iframe;
|
|
@@ -138,6 +138,134 @@ interface PlainHtmlExportOptions {
|
|
|
138
138
|
* renders consistently when opened standalone.
|
|
139
139
|
*/
|
|
140
140
|
declare function markdownDocToPlainHtml(doc: MarkdownDocument, options?: PlainHtmlExportOptions): string;
|
|
141
|
+
/**
|
|
142
|
+
* The pieces of a plain HTML page that belong to the document itself, for a
|
|
143
|
+
* host that places the document inside a page of its own (see
|
|
144
|
+
* `applyHtmlPageTemplate`).
|
|
145
|
+
*/
|
|
146
|
+
interface PlainHtmlParts {
|
|
147
|
+
/** The `title` option, unescaped. */
|
|
148
|
+
title: string;
|
|
149
|
+
/** The rendered document body, footnotes included. */
|
|
150
|
+
bodyHtml: string;
|
|
151
|
+
/**
|
|
152
|
+
* `<head>` markup for the CSS the body's own constructs need — feature
|
|
153
|
+
* sections, video embeds, SVG islands, icons — and nothing that styles the
|
|
154
|
+
* page around them: no theme typography, colors, fonts, or `body` rules.
|
|
155
|
+
*/
|
|
156
|
+
featureHeadHtml: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Render a document's body and the functional CSS it depends on, without the
|
|
160
|
+
* page shell or theme stylesheet `markdownDocToPlainHtml` wraps around them.
|
|
161
|
+
* The theme (when one resolves) still informs body rendering, such as the
|
|
162
|
+
* color an SVG island's `currentColor` follows.
|
|
163
|
+
*/
|
|
164
|
+
declare function markdownDocToPlainHtmlParts(doc: MarkdownDocument, options?: PlainHtmlExportOptions): PlainHtmlParts;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* HTML Page Templates
|
|
168
|
+
*
|
|
169
|
+
* Places a rendered document inside a page of the author's own — a site
|
|
170
|
+
* header, navigation, footer, and stylesheet — instead of the fixed shell
|
|
171
|
+
* `markdownDocToPlainHtml` emits. A template is an ordinary HTML file that
|
|
172
|
+
* marks where content goes with the HTML standard's `<slot>` element:
|
|
173
|
+
*
|
|
174
|
+
* - the one unnamed `<slot>` receives the document body;
|
|
175
|
+
* - `<slot name="title">` receives the document title, and
|
|
176
|
+
* `<slot name="KEY">` the caller's value for `KEY` (typically a
|
|
177
|
+
* frontmatter field), inserted as text;
|
|
178
|
+
* - a slot with no value is replaced by its children — the standard fallback
|
|
179
|
+
* content — so a template opened directly in a browser previews with its
|
|
180
|
+
* placeholders.
|
|
181
|
+
*
|
|
182
|
+
* `<slot/>` is accepted as an empty slot (the spelling Astro and JSX authors
|
|
183
|
+
* use), although HTML itself has no self-closing `<slot>`.
|
|
184
|
+
*
|
|
185
|
+
* `<title>` cannot contain elements, so slots written inside it are matched
|
|
186
|
+
* as text; a `<title>` without one is replaced by the document title.
|
|
187
|
+
* Existing description and Open Graph / Twitter title and description
|
|
188
|
+
* `<meta>` tags receive the document's values. Relative URLs in the template
|
|
189
|
+
* are rewritten so they still resolve from the generated page's location.
|
|
190
|
+
*
|
|
191
|
+
* The template is scanned, never re-serialized: everything outside the
|
|
192
|
+
* replaced ranges is emitted byte for byte, so its formatting, comments, and
|
|
193
|
+
* scripts survive unchanged.
|
|
194
|
+
*/
|
|
195
|
+
/** Marks the start of the injected document body in a templated page. */
|
|
196
|
+
declare const HTML_PAGE_TEMPLATE_CONTENT_START = "<!-- squisq:content -->";
|
|
197
|
+
/** Marks the end of the injected document body in a templated page. */
|
|
198
|
+
declare const HTML_PAGE_TEMPLATE_CONTENT_END = "<!-- /squisq:content -->";
|
|
199
|
+
interface HtmlPageTemplateContent {
|
|
200
|
+
/** Rendered document body. Inserted as-is; the caller sanitizes it. */
|
|
201
|
+
bodyHtml: string;
|
|
202
|
+
/**
|
|
203
|
+
* Markup the body needs in `<head>`, such as
|
|
204
|
+
* `PlainHtmlParts.featureHeadHtml`. Inserted before the template's own
|
|
205
|
+
* stylesheets so the template's CSS cascades last.
|
|
206
|
+
*/
|
|
207
|
+
headHtml?: string;
|
|
208
|
+
/** Document title, as text. */
|
|
209
|
+
title: string;
|
|
210
|
+
/**
|
|
211
|
+
* Text for named slots, keyed by slot name. `description` also fills
|
|
212
|
+
* description `<meta>` tags. `title` comes from {@link title}.
|
|
213
|
+
*/
|
|
214
|
+
values?: Readonly<Record<string, string | undefined>>;
|
|
215
|
+
}
|
|
216
|
+
interface HtmlPageTemplateOptions {
|
|
217
|
+
/**
|
|
218
|
+
* Container-relative path of the template file: the location its relative
|
|
219
|
+
* URLs were written against.
|
|
220
|
+
*/
|
|
221
|
+
templatePath?: string;
|
|
222
|
+
/**
|
|
223
|
+
* Container-relative path of the page being generated. With
|
|
224
|
+
* {@link templatePath}, relative URLs in the template are rewritten to
|
|
225
|
+
* resolve from here.
|
|
226
|
+
*/
|
|
227
|
+
outputPath?: string;
|
|
228
|
+
}
|
|
229
|
+
interface HtmlPageTemplateIssue {
|
|
230
|
+
message: string;
|
|
231
|
+
/** 1-based line in the template. */
|
|
232
|
+
line: number;
|
|
233
|
+
}
|
|
234
|
+
interface HtmlPageTemplateInspection {
|
|
235
|
+
/** True when the HTML has a `<slot>` outside any `<template>`. */
|
|
236
|
+
isTemplate: boolean;
|
|
237
|
+
/** Unnamed slots outside `<template>`; a usable template has exactly one. */
|
|
238
|
+
defaultSlotCount: number;
|
|
239
|
+
/** Distinct named slots, in document order. */
|
|
240
|
+
namedSlots: string[];
|
|
241
|
+
/** Problems that make {@link applyHtmlPageTemplate} refuse the template. */
|
|
242
|
+
errors: HtmlPageTemplateIssue[];
|
|
243
|
+
}
|
|
244
|
+
/** Thrown by {@link applyHtmlPageTemplate} for a template it cannot use. */
|
|
245
|
+
declare class HtmlPageTemplateError extends Error {
|
|
246
|
+
readonly issues: readonly HtmlPageTemplateIssue[];
|
|
247
|
+
constructor(issues: readonly HtmlPageTemplateIssue[]);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* True when `html` is a page template: it has a `<slot>` outside any
|
|
251
|
+
* `<template>`. Outside a shadow tree a slot does nothing, so an ordinary
|
|
252
|
+
* page has no reason to carry one.
|
|
253
|
+
*/
|
|
254
|
+
declare function isHtmlPageTemplate(html: string): boolean;
|
|
255
|
+
/** Describe a template's slots and any problems that would prevent its use. */
|
|
256
|
+
declare function inspectHtmlPageTemplate(html: string): HtmlPageTemplateInspection;
|
|
257
|
+
/**
|
|
258
|
+
* Generate a page by filling `template`'s slots with `content`.
|
|
259
|
+
*
|
|
260
|
+
* @throws {HtmlPageTemplateError} when the template has no single unnamed
|
|
261
|
+
* slot, or its slots are unbalanced or nested.
|
|
262
|
+
*/
|
|
263
|
+
declare function applyHtmlPageTemplate(template: string, content: HtmlPageTemplateContent, options?: HtmlPageTemplateOptions): string;
|
|
264
|
+
/**
|
|
265
|
+
* The document body a templated page carries between its content markers, or
|
|
266
|
+
* null when `html` has none. Lets an importer skip the template's chrome.
|
|
267
|
+
*/
|
|
268
|
+
declare function extractHtmlPageTemplateContent(html: string): string | null;
|
|
141
269
|
|
|
142
270
|
/**
|
|
143
271
|
* Recursive plain-HTML bundle export.
|
|
@@ -349,4 +477,4 @@ declare function docToHtml(doc: Doc, options: HtmlExportOptions): string;
|
|
|
349
477
|
*/
|
|
350
478
|
declare function docToHtmlZip(doc: Doc, options: HtmlZipExportOptions): Promise<Blob>;
|
|
351
479
|
|
|
352
|
-
export { type HtmlBundleOptions, HtmlExportOptions, type HtmlZipExportOptions, type PlainHtmlBundleOptions, type PlainHtmlExportOptions, arrayBufferToBase64DataUrl, collectLinkRefs, docToHtml, docToHtmlZip, extractFilename, inferMimeType, markdownDocToPlainHtml, markdownDocsToHtmlBundle, markdownDocsToPlainHtmlBundle };
|
|
480
|
+
export { HTML_PAGE_TEMPLATE_CONTENT_END, HTML_PAGE_TEMPLATE_CONTENT_START, type HtmlBundleOptions, HtmlExportOptions, type HtmlPageTemplateContent, HtmlPageTemplateError, type HtmlPageTemplateInspection, type HtmlPageTemplateIssue, type HtmlPageTemplateOptions, type HtmlZipExportOptions, type PlainHtmlBundleOptions, type PlainHtmlExportOptions, type PlainHtmlParts, applyHtmlPageTemplate, arrayBufferToBase64DataUrl, collectLinkRefs, docToHtml, docToHtmlZip, extractFilename, extractHtmlPageTemplateContent, inferMimeType, inspectHtmlPageTemplate, isHtmlPageTemplate, markdownDocToPlainHtml, markdownDocToPlainHtmlParts, markdownDocsToHtmlBundle, markdownDocsToPlainHtmlBundle };
|
package/dist/html/index.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
+
HTML_PAGE_TEMPLATE_CONTENT_END,
|
|
3
|
+
HTML_PAGE_TEMPLATE_CONTENT_START,
|
|
4
|
+
HtmlPageTemplateError,
|
|
5
|
+
applyHtmlPageTemplate,
|
|
2
6
|
collectImagePaths,
|
|
3
7
|
collectLinkRefs,
|
|
4
8
|
docToHtml,
|
|
5
9
|
docToHtmlZip,
|
|
10
|
+
extractHtmlPageTemplateContent,
|
|
6
11
|
generateExternalHtml,
|
|
7
12
|
htmlToMarkdown,
|
|
8
13
|
htmlToMarkdownDoc,
|
|
9
14
|
htmlToMarkdownDocSync,
|
|
15
|
+
inspectHtmlPageTemplate,
|
|
16
|
+
isHtmlPageTemplate,
|
|
10
17
|
markdownDocToPlainHtml,
|
|
18
|
+
markdownDocToPlainHtmlParts,
|
|
11
19
|
markdownDocsToHtmlBundle,
|
|
12
20
|
markdownDocsToPlainHtmlBundle
|
|
13
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-LK4KCVN6.js";
|
|
14
22
|
import {
|
|
15
23
|
arrayBufferToBase64DataUrl,
|
|
16
24
|
extractFilename,
|
|
@@ -20,18 +28,26 @@ import "../chunk-VCZUXQUL.js";
|
|
|
20
28
|
import "../chunk-BXWNU4T5.js";
|
|
21
29
|
import "../chunk-JZR32FON.js";
|
|
22
30
|
export {
|
|
31
|
+
HTML_PAGE_TEMPLATE_CONTENT_END,
|
|
32
|
+
HTML_PAGE_TEMPLATE_CONTENT_START,
|
|
33
|
+
HtmlPageTemplateError,
|
|
34
|
+
applyHtmlPageTemplate,
|
|
23
35
|
arrayBufferToBase64DataUrl,
|
|
24
36
|
collectImagePaths,
|
|
25
37
|
collectLinkRefs,
|
|
26
38
|
docToHtml,
|
|
27
39
|
docToHtmlZip,
|
|
28
40
|
extractFilename,
|
|
41
|
+
extractHtmlPageTemplateContent,
|
|
29
42
|
generateExternalHtml,
|
|
30
43
|
htmlToMarkdown,
|
|
31
44
|
htmlToMarkdownDoc,
|
|
32
45
|
htmlToMarkdownDocSync,
|
|
33
46
|
inferMimeType,
|
|
47
|
+
inspectHtmlPageTemplate,
|
|
48
|
+
isHtmlPageTemplate,
|
|
34
49
|
markdownDocToPlainHtml,
|
|
50
|
+
markdownDocToPlainHtmlParts,
|
|
35
51
|
markdownDocsToHtmlBundle,
|
|
36
52
|
markdownDocsToPlainHtmlBundle
|
|
37
53
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -8,10 +8,10 @@ export { EpubExportOptions, docToEpub, markdownDocToEpub } from './epub/index.js
|
|
|
8
8
|
export { ExtractedFileTheme, InferSourceFormat, InferThemeOptions, InferredFileTheme, compileExtractedTheme, inferThemeFromFile } from './infer/index.js';
|
|
9
9
|
export { B as BUILTIN_FORMAT_IDS, a as BuiltinFormatOptions, C as ConversionLimits, b as ConversionResult, c as ConvertOptions, d as ConvertSource, D as DEFAULT_CONVERSION_LIMITS, e as DbkFormatOptions, F as FormatDefinition, f as FormatId, g as FormatRegistry, M as MarkdownFormatOptions, N as NormalizedInput, P as PreparedConversion, h as PreparedExportOptions, r as resolveConversionLimits } from './types-CVv9eldp.js';
|
|
10
10
|
export { ConversionError, ConversionErrorCode, ConversionErrorOptions, convert, createRegistry, defaultFormats, defaultRegistry, prepareConversion } from './registry/index.js';
|
|
11
|
-
export { ImportedOutsideInDocument, OUTSIDE_IN_UPDATE_FROM_MARKDOWN_KEY, OutsideInMetadata, RenderOutsideInOptions, importOutsideInDocument, isOutsideInMarkdownEditingEnabled, readOutsideInMetadata, renderOutsideInDocument, withOutsideInMarkdownEditing, withOutsideInMetadata } from './outside-in/index.js';
|
|
11
|
+
export { ImportedOutsideInDocument, OUTSIDE_IN_UPDATE_FROM_MARKDOWN_KEY, OutsideInMetadata, RenderOutsideInOptions, importOutsideInDocument, isOutsideInMarkdownEditingEnabled, readOutsideInMetadata, readOutsideInOutputTemplate, renderOutsideInDocument, withOutsideInMarkdownEditing, withOutsideInMetadata } from './outside-in/index.js';
|
|
12
12
|
export { Z as ZipSafetyError, a as ZipSafetyErrorCode, b as ZipSafetyErrorOptions, c as ZipSafetyLimits } from './zipLimits-BOKCB7qk.js';
|
|
13
13
|
export { H as HtmlExportOptions, a as HtmlImportOptions, c as collectImagePaths, h as htmlToMarkdown, b as htmlToMarkdownDoc, d as htmlToMarkdownDocSync } from './import-ipP4Ghp8.js';
|
|
14
|
-
export { OUTSIDE_IN_FORMAT_IDS, OutsideInFormatId, OutsideInLayout, chooseOutsideInMarkdownPath, isOutsideInTargetPath, resolveOutsideInLayout } from './outside-in/layout.js';
|
|
14
|
+
export { OUTSIDE_IN_FORMAT_IDS, OUTSIDE_IN_OUTPUT_TEMPLATE_KEY, OutsideInFormatId, OutsideInLayout, chooseOutsideInMarkdownPath, isOutsideInTargetPath, resolveOutsideInLayout } from './outside-in/layout.js';
|
|
15
15
|
export { P as PptxExportOptions, a as PptxImportOptions, d as docToPptx, m as markdownDocToPptx, p as pptxToMarkdownDoc } from './import-Cn_V1_7e.js';
|
|
16
16
|
export { X as XlsxExportOptions, a as XlsxImportOptions, b as XlsxTable, c as XlsxTableColumn, d as XlsxTablesOptions, e as docToXlsx, g as gridToTables, m as markdownDocToXlsx, x as xlsxToMarkdownDoc, f as xlsxToTables } from './export-C4bBD3Mx.js';
|
|
17
17
|
import '@bendyline/squisq/schemas';
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
htmlToMarkdown,
|
|
6
6
|
htmlToMarkdownDoc,
|
|
7
7
|
htmlToMarkdownDocSync
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LK4KCVN6.js";
|
|
9
9
|
import {
|
|
10
10
|
docToEpub,
|
|
11
11
|
markdownDocToEpub
|
|
@@ -25,10 +25,11 @@ import {
|
|
|
25
25
|
importOutsideInDocument,
|
|
26
26
|
isOutsideInMarkdownEditingEnabled,
|
|
27
27
|
readOutsideInMetadata,
|
|
28
|
+
readOutsideInOutputTemplate,
|
|
28
29
|
renderOutsideInDocument,
|
|
29
30
|
withOutsideInMarkdownEditing,
|
|
30
31
|
withOutsideInMetadata
|
|
31
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-6ALSRHD7.js";
|
|
32
33
|
import {
|
|
33
34
|
DEFAULT_CONVERSION_LIMITS,
|
|
34
35
|
convert,
|
|
@@ -37,16 +38,17 @@ import {
|
|
|
37
38
|
defaultRegistry,
|
|
38
39
|
prepareConversion,
|
|
39
40
|
resolveConversionLimits
|
|
40
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-GDUZJ75A.js";
|
|
41
42
|
import {
|
|
42
43
|
ConversionError
|
|
43
44
|
} from "./chunk-KXOZMWBS.js";
|
|
44
45
|
import {
|
|
45
46
|
OUTSIDE_IN_FORMAT_IDS,
|
|
47
|
+
OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
|
|
46
48
|
chooseOutsideInMarkdownPath,
|
|
47
49
|
isOutsideInTargetPath,
|
|
48
50
|
resolveOutsideInLayout
|
|
49
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-WJTC5OD6.js";
|
|
50
52
|
import {
|
|
51
53
|
docToDocx,
|
|
52
54
|
docxToDoc,
|
|
@@ -105,6 +107,7 @@ export {
|
|
|
105
107
|
ConversionError,
|
|
106
108
|
DEFAULT_CONVERSION_LIMITS,
|
|
107
109
|
OUTSIDE_IN_FORMAT_IDS,
|
|
110
|
+
OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
|
|
108
111
|
OUTSIDE_IN_UPDATE_FROM_MARKDOWN_KEY,
|
|
109
112
|
ZipSafetyError,
|
|
110
113
|
chooseOutsideInMarkdownPath,
|
|
@@ -147,6 +150,7 @@ export {
|
|
|
147
150
|
pptxToMarkdownDoc,
|
|
148
151
|
prepareConversion,
|
|
149
152
|
readOutsideInMetadata,
|
|
153
|
+
readOutsideInOutputTemplate,
|
|
150
154
|
renderOutsideInDocument,
|
|
151
155
|
resolveConversionLimits,
|
|
152
156
|
resolveOutsideInLayout,
|
|
@@ -2,7 +2,7 @@ import { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
|
2
2
|
import { ContentContainer } from '@bendyline/squisq/storage';
|
|
3
3
|
import { c as ConvertOptions, b as ConversionResult } from '../types-CVv9eldp.js';
|
|
4
4
|
import { OutsideInLayout, OutsideInFormatId } from './layout.js';
|
|
5
|
-
export { OUTSIDE_IN_FORMAT_IDS, chooseOutsideInMarkdownPath, isOutsideInTargetPath, resolveOutsideInLayout } from './layout.js';
|
|
5
|
+
export { OUTSIDE_IN_FORMAT_IDS, OUTSIDE_IN_OUTPUT_TEMPLATE_KEY, chooseOutsideInMarkdownPath, isOutsideInTargetPath, resolveOutsideInLayout } from './layout.js';
|
|
6
6
|
import '@bendyline/squisq/schemas';
|
|
7
7
|
import '@bendyline/squisq/transform';
|
|
8
8
|
import '../docx/index.js';
|
|
@@ -65,6 +65,13 @@ declare function readOutsideInMetadata(source: string): OutsideInMetadata | null
|
|
|
65
65
|
declare function withOutsideInMetadata(source: string, layout: OutsideInLayout): string;
|
|
66
66
|
/** True only when the companion explicitly opts into rendered-file updates. */
|
|
67
67
|
declare function isOutsideInMarkdownEditingEnabled(source: string | MarkdownDocument): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The companion's page template choice: the authored path, `'none'` when it
|
|
70
|
+
* opts out of any host default, or null when it names none. Resolving the
|
|
71
|
+
* path against the companion is the host's job, since only the host knows
|
|
72
|
+
* where the container root is.
|
|
73
|
+
*/
|
|
74
|
+
declare function readOutsideInOutputTemplate(source: string | MarkdownDocument): string | null;
|
|
68
75
|
/**
|
|
69
76
|
* Opt a companion into or out of Markdown-driven regeneration while keeping
|
|
70
77
|
* the portable target relationship current.
|
|
@@ -82,4 +89,4 @@ declare function renderOutsideInDocument(source: {
|
|
|
82
89
|
container?: ContentContainer;
|
|
83
90
|
}, options?: RenderOutsideInOptions): Promise<ConversionResult>;
|
|
84
91
|
|
|
85
|
-
export { type ImportedOutsideInDocument, OUTSIDE_IN_UPDATE_FROM_MARKDOWN_KEY, OutsideInFormatId, OutsideInLayout, type OutsideInMetadata, type RenderOutsideInOptions, importOutsideInDocument, isOutsideInMarkdownEditingEnabled, readOutsideInMetadata, renderOutsideInDocument, withOutsideInMarkdownEditing, withOutsideInMetadata };
|
|
92
|
+
export { type ImportedOutsideInDocument, OUTSIDE_IN_UPDATE_FROM_MARKDOWN_KEY, OutsideInFormatId, OutsideInLayout, type OutsideInMetadata, type RenderOutsideInOptions, importOutsideInDocument, isOutsideInMarkdownEditingEnabled, readOutsideInMetadata, readOutsideInOutputTemplate, renderOutsideInDocument, withOutsideInMarkdownEditing, withOutsideInMetadata };
|
package/dist/outside-in/index.js
CHANGED
|
@@ -3,27 +3,31 @@ import {
|
|
|
3
3
|
importOutsideInDocument,
|
|
4
4
|
isOutsideInMarkdownEditingEnabled,
|
|
5
5
|
readOutsideInMetadata,
|
|
6
|
+
readOutsideInOutputTemplate,
|
|
6
7
|
renderOutsideInDocument,
|
|
7
8
|
withOutsideInMarkdownEditing,
|
|
8
9
|
withOutsideInMetadata
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
10
|
+
} from "../chunk-6ALSRHD7.js";
|
|
11
|
+
import "../chunk-GDUZJ75A.js";
|
|
11
12
|
import "../chunk-KXOZMWBS.js";
|
|
12
13
|
import {
|
|
13
14
|
OUTSIDE_IN_FORMAT_IDS,
|
|
15
|
+
OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
|
|
14
16
|
chooseOutsideInMarkdownPath,
|
|
15
17
|
isOutsideInTargetPath,
|
|
16
18
|
resolveOutsideInLayout
|
|
17
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-WJTC5OD6.js";
|
|
18
20
|
import "../chunk-7AWFHP5U.js";
|
|
19
21
|
export {
|
|
20
22
|
OUTSIDE_IN_FORMAT_IDS,
|
|
23
|
+
OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
|
|
21
24
|
OUTSIDE_IN_UPDATE_FROM_MARKDOWN_KEY,
|
|
22
25
|
chooseOutsideInMarkdownPath,
|
|
23
26
|
importOutsideInDocument,
|
|
24
27
|
isOutsideInMarkdownEditingEnabled,
|
|
25
28
|
isOutsideInTargetPath,
|
|
26
29
|
readOutsideInMetadata,
|
|
30
|
+
readOutsideInOutputTemplate,
|
|
27
31
|
renderOutsideInDocument,
|
|
28
32
|
resolveOutsideInLayout,
|
|
29
33
|
withOutsideInMarkdownEditing,
|
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
*/
|
|
20
20
|
declare const OUTSIDE_IN_FORMAT_IDS: readonly ["html", "docx", "pdf", "pptx", "xlsx", "csv"];
|
|
21
21
|
type OutsideInFormatId = (typeof OUTSIDE_IN_FORMAT_IDS)[number];
|
|
22
|
+
/**
|
|
23
|
+
* Frontmatter naming the page template a rendered output is generated
|
|
24
|
+
* through: a path relative to the companion Markdown, a `/`-prefixed path
|
|
25
|
+
* from the container root, or `none` to opt out of a host default.
|
|
26
|
+
*/
|
|
27
|
+
declare const OUTSIDE_IN_OUTPUT_TEMPLATE_KEY = "squisq-output-template";
|
|
22
28
|
interface OutsideInLayout {
|
|
23
29
|
/** User-facing rendered file, relative to the host's workspace root. */
|
|
24
30
|
targetPath: string;
|
|
@@ -55,4 +61,4 @@ declare function isOutsideInTargetPath(path: string): boolean;
|
|
|
55
61
|
*/
|
|
56
62
|
declare function chooseOutsideInMarkdownPath(layout: OutsideInLayout, paths: readonly string[]): string | null;
|
|
57
63
|
|
|
58
|
-
export { OUTSIDE_IN_FORMAT_IDS, type OutsideInFormatId, type OutsideInLayout, chooseOutsideInMarkdownPath, isOutsideInTargetPath, resolveOutsideInLayout };
|
|
64
|
+
export { OUTSIDE_IN_FORMAT_IDS, OUTSIDE_IN_OUTPUT_TEMPLATE_KEY, type OutsideInFormatId, type OutsideInLayout, chooseOutsideInMarkdownPath, isOutsideInTargetPath, resolveOutsideInLayout };
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
OUTSIDE_IN_FORMAT_IDS,
|
|
3
|
+
OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
|
|
3
4
|
chooseOutsideInMarkdownPath,
|
|
4
5
|
isOutsideInTargetPath,
|
|
5
6
|
resolveOutsideInLayout
|
|
6
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-WJTC5OD6.js";
|
|
7
8
|
export {
|
|
8
9
|
OUTSIDE_IN_FORMAT_IDS,
|
|
10
|
+
OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
|
|
9
11
|
chooseOutsideInMarkdownPath,
|
|
10
12
|
isOutsideInTargetPath,
|
|
11
13
|
resolveOutsideInLayout
|
package/dist/registry/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-formats",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.8",
|
|
4
4
|
"description": "Document format converters — DOCX, PDF, OOXML import/export",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"typecheck": "tsc --noEmit"
|
|
126
126
|
},
|
|
127
127
|
"dependencies": {
|
|
128
|
-
"@bendyline/squisq": "2.13.
|
|
128
|
+
"@bendyline/squisq": "2.13.7",
|
|
129
129
|
"@pdf-lib/fontkit": "1.1.1",
|
|
130
130
|
"@pdf-lib/upng": "1.0.1",
|
|
131
131
|
"@xmldom/xmldom": "0.9.12",
|
|
File without changes
|