@bendyline/squisq-formats 2.0.1 → 2.2.0
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/LICENSE +21 -0
- package/NOTICE.md +20 -0
- package/README.md +1 -1
- package/dist/{chunk-CRAVSMPZ.js → chunk-26ISNJ7Y.js} +356 -114
- package/dist/{chunk-NKAJPJ4G.js → chunk-2JJ5RFDZ.js} +0 -1
- package/dist/{chunk-HTW2M27H.js → chunk-3NKXBZSR.js} +193 -42
- package/dist/{chunk-U32AG3G3.js → chunk-4V3KCHAP.js} +3 -4
- package/dist/{chunk-MLX2BOJC.js → chunk-6RQOV3B3.js} +1 -2
- package/dist/{chunk-QRVN6A6E.js → chunk-6S6GU3ZG.js} +5 -6
- package/dist/{chunk-FE6OJV6O.js → chunk-7AWFHP5U.js} +1 -1
- package/dist/{chunk-RFAPOKHJ.js → chunk-AD2WT564.js} +59 -9
- package/dist/{chunk-O3GVVND4.js → chunk-AONELFLA.js} +0 -1
- package/dist/{chunk-XKUMNGBW.js → chunk-EJTNGKEA.js} +5 -8
- package/dist/chunk-GX7RAUME.js +121 -0
- package/dist/{chunk-SSUPBUF5.js → chunk-IIQYS2YH.js} +0 -1
- package/dist/{chunk-ABVI556T.js → chunk-IPN56VLW.js} +83 -58
- package/dist/{chunk-LXYLOOST.js → chunk-JE6LSIHE.js} +83 -22
- package/dist/{chunk-U4MRIFKL.js → chunk-JU2RHXUB.js} +0 -1
- package/dist/{chunk-4VUWTSGM.js → chunk-K6XRMVPW.js} +64 -31
- package/dist/{chunk-ODL3SSPT.js → chunk-KXOZMWBS.js} +0 -1
- package/dist/chunk-OGS5VCGJ.js +446 -0
- package/dist/{chunk-GVS2XXV6.js → chunk-PJXJI2LY.js} +449 -57
- package/dist/{chunk-2KPARF2P.js → chunk-PU7REGWV.js} +5 -8
- package/dist/{chunk-PN52A5AA.js → chunk-SBUW7NHR.js} +0 -1
- package/dist/{chunk-VSYHZECT.js → chunk-TAAENIRB.js} +5 -8
- package/dist/{chunk-WC7WULGV.js → chunk-X2DEAXNK.js} +62 -2
- package/dist/container/index.js +1 -2
- package/dist/csv/index.d.ts +27 -2
- package/dist/csv/index.js +1 -2
- package/dist/docx/index.d.ts +5 -1
- package/dist/docx/index.js +9 -10
- package/dist/epub/index.d.ts +2 -0
- package/dist/epub/index.js +5 -6
- package/dist/{export-D2NkylDT.d.ts → export-D9msROJS.d.ts} +18 -6
- package/dist/extract-MN7LA3NL.js +13 -0
- package/dist/html/index.d.ts +11 -4
- package/dist/html/index.js +3 -4
- package/dist/images-ESPQKVTW.js +6 -0
- package/dist/{import-K8mfc0fz.d.ts → import-C3htUTss.d.ts} +5 -1
- package/dist/{import-DTkDxHmZ.d.ts → import-C8whCC7_.d.ts} +6 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +28 -26
- package/dist/infer/index.d.ts +3 -3
- package/dist/infer/index.js +7 -9
- package/dist/{layouts-BHrgZ5FS.d.ts → layouts-CTdPlB-u.d.ts} +1 -1
- package/dist/layouts-DRWZGSPD.js +10 -0
- package/dist/{mapTheme-IR27S6IV.js → mapTheme-4TWH25FT.js} +1 -2
- package/dist/ooxml/index.d.ts +3 -3
- package/dist/ooxml/index.js +14 -13
- package/dist/pdf/index.d.ts +18 -0
- package/dist/pdf/index.js +2 -3
- package/dist/pptx/index.d.ts +4 -4
- package/dist/pptx/index.js +11 -13
- package/dist/{reader-B9L8Ucbj.d.ts → reader-B_m1aKZC.d.ts} +30 -1
- package/dist/registry/index.d.ts +21 -5
- package/dist/registry/index.js +9 -6
- package/dist/{themeReader-DJKErl_j.d.ts → themeReader-DCtwC83Q.d.ts} +1 -1
- package/dist/xlsx/index.d.ts +3 -3
- package/dist/xlsx/index.js +6 -7
- package/package.json +6 -3
- package/dist/chunk-2KPARF2P.js.map +0 -1
- package/dist/chunk-4VUWTSGM.js.map +0 -1
- package/dist/chunk-6M7Z25LA.js +0 -46
- package/dist/chunk-6M7Z25LA.js.map +0 -1
- package/dist/chunk-ABVI556T.js.map +0 -1
- package/dist/chunk-CRAVSMPZ.js.map +0 -1
- package/dist/chunk-FE6OJV6O.js.map +0 -1
- package/dist/chunk-GVS2XXV6.js.map +0 -1
- package/dist/chunk-HTW2M27H.js.map +0 -1
- package/dist/chunk-LXYLOOST.js.map +0 -1
- package/dist/chunk-MLX2BOJC.js.map +0 -1
- package/dist/chunk-NKAJPJ4G.js.map +0 -1
- package/dist/chunk-O3GVVND4.js.map +0 -1
- package/dist/chunk-ODL3SSPT.js.map +0 -1
- package/dist/chunk-PN52A5AA.js.map +0 -1
- package/dist/chunk-QRVN6A6E.js.map +0 -1
- package/dist/chunk-RFAPOKHJ.js.map +0 -1
- package/dist/chunk-SSUPBUF5.js.map +0 -1
- package/dist/chunk-U32AG3G3.js.map +0 -1
- package/dist/chunk-U4MRIFKL.js.map +0 -1
- package/dist/chunk-VJJM2SSH.js +0 -275
- package/dist/chunk-VJJM2SSH.js.map +0 -1
- package/dist/chunk-VSYHZECT.js.map +0 -1
- package/dist/chunk-WC7WULGV.js.map +0 -1
- package/dist/chunk-XKUMNGBW.js.map +0 -1
- package/dist/chunk-YRT7GQ5Y.js +0 -28
- package/dist/chunk-YRT7GQ5Y.js.map +0 -1
- package/dist/container/index.js.map +0 -1
- package/dist/csv/index.js.map +0 -1
- package/dist/docx/index.js.map +0 -1
- package/dist/epub/index.js.map +0 -1
- package/dist/extract-H6RXJMHP.js +0 -15
- package/dist/extract-H6RXJMHP.js.map +0 -1
- package/dist/html/index.js.map +0 -1
- package/dist/images-7FBWPKE3.js +0 -7
- package/dist/images-7FBWPKE3.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/infer/index.js.map +0 -1
- package/dist/layouts-QVPK3ZCU.js +0 -12
- package/dist/layouts-QVPK3ZCU.js.map +0 -1
- package/dist/mapTheme-IR27S6IV.js.map +0 -1
- package/dist/ooxml/index.js.map +0 -1
- package/dist/pdf/index.js.map +0 -1
- package/dist/pptx/index.js.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/xlsx/index.js.map +0 -1
- package/src/__tests__/container.test.ts +0 -230
- package/src/__tests__/convert.test.ts +0 -495
- package/src/__tests__/csvImport.test.ts +0 -84
- package/src/__tests__/docxExport.test.ts +0 -457
- package/src/__tests__/docxImport.test.ts +0 -410
- package/src/__tests__/epub.test.ts +0 -649
- package/src/__tests__/exportThemeReconciliation.test.ts +0 -87
- package/src/__tests__/formatRegistry.test.ts +0 -174
- package/src/__tests__/html.test.ts +0 -435
- package/src/__tests__/htmlImport.test.ts +0 -57
- package/src/__tests__/inferTheme.test.ts +0 -135
- package/src/__tests__/lossyWarnings.test.ts +0 -146
- package/src/__tests__/ooxml.test.ts +0 -271
- package/src/__tests__/ooxmlCancellation.test.ts +0 -113
- package/src/__tests__/ooxmlThemeReader.test.ts +0 -92
- package/src/__tests__/pdfExport.test.ts +0 -322
- package/src/__tests__/pdfImport.test.ts +0 -384
- package/src/__tests__/plainHtml.test.ts +0 -417
- package/src/__tests__/plainHtmlBundle.test.ts +0 -253
- package/src/__tests__/pptxExport.test.ts +0 -138
- package/src/__tests__/pptxImport.test.ts +0 -145
- package/src/__tests__/pptxInferFixtures.ts +0 -314
- package/src/__tests__/pptxLayoutInfer.test.ts +0 -395
- package/src/__tests__/roundTrip.test.ts +0 -201
- package/src/__tests__/roundTripAssets.test.ts +0 -50
- package/src/__tests__/roundTripMatrix.fixtures.ts +0 -86
- package/src/__tests__/roundTripMatrix.helpers.ts +0 -154
- package/src/__tests__/roundTripMatrix.test.ts +0 -142
- package/src/__tests__/sharedContainer.test.ts +0 -41
- package/src/__tests__/sharedImages.test.ts +0 -61
- package/src/__tests__/xlsxExport.test.ts +0 -164
- package/src/__tests__/xlsxImport.test.ts +0 -80
- package/src/__tests__/zipSafety.test.ts +0 -317
- package/src/container/index.ts +0 -94
- package/src/csv/index.ts +0 -188
- package/src/docx/export.ts +0 -1267
- package/src/docx/import.ts +0 -995
- package/src/docx/index.ts +0 -26
- package/src/docx/styles.ts +0 -145
- package/src/epub/export.ts +0 -968
- package/src/epub/index.ts +0 -20
- package/src/html/docsHtmlBundle.ts +0 -373
- package/src/html/htmlTemplate.ts +0 -385
- package/src/html/imageUtils.ts +0 -61
- package/src/html/import.ts +0 -297
- package/src/html/index.ts +0 -212
- package/src/html/plainHtml.ts +0 -790
- package/src/html/plainHtmlBundle.ts +0 -421
- package/src/index.ts +0 -109
- package/src/infer/extract.ts +0 -127
- package/src/infer/index.ts +0 -199
- package/src/infer/mapTheme.ts +0 -176
- package/src/infer/types.ts +0 -27
- package/src/ooxml/index.ts +0 -111
- package/src/ooxml/namespaces.ts +0 -196
- package/src/ooxml/readUtils.ts +0 -44
- package/src/ooxml/reader.ts +0 -318
- package/src/ooxml/themeReader.ts +0 -197
- package/src/ooxml/types.ts +0 -103
- package/src/ooxml/writer.ts +0 -339
- package/src/ooxml/xmlUtils.ts +0 -123
- package/src/pdf/export.ts +0 -1084
- package/src/pdf/import.ts +0 -1164
- package/src/pdf/index.ts +0 -29
- package/src/pdf/styles.ts +0 -180
- package/src/pptx/export.ts +0 -1184
- package/src/pptx/import.ts +0 -455
- package/src/pptx/index.ts +0 -52
- package/src/pptx/layouts.ts +0 -1222
- package/src/pptx/styles.ts +0 -96
- package/src/pptx/templates.ts +0 -187
- package/src/registry/convert.ts +0 -433
- package/src/registry/defaultFormats.ts +0 -413
- package/src/registry/errors.ts +0 -46
- package/src/registry/index.ts +0 -43
- package/src/registry/registry.ts +0 -48
- package/src/registry/types.ts +0 -170
- package/src/shared/boundedZipArchive.ts +0 -383
- package/src/shared/container.ts +0 -28
- package/src/shared/fidelity.ts +0 -130
- package/src/shared/images.ts +0 -44
- package/src/shared/inlineRuns.ts +0 -99
- package/src/shared/text.ts +0 -41
- package/src/shared/zipEntryCount.ts +0 -151
- package/src/shared/zipLimits.ts +0 -296
- package/src/shared/zipSafety.ts +0 -19
- package/src/xlsx/export.ts +0 -253
- package/src/xlsx/import.ts +0 -160
- package/src/xlsx/index.ts +0 -35
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
arrayBufferToBase64DataUrl,
|
|
3
3
|
extractFilename,
|
|
4
4
|
inferMimeType
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6RQOV3B3.js";
|
|
6
6
|
|
|
7
7
|
// src/html/index.ts
|
|
8
8
|
import JSZip3 from "jszip";
|
|
@@ -119,7 +119,23 @@ function scanTemplateImageFields(obj, paths) {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
function escapeForScript(str) {
|
|
122
|
-
return str.replace(
|
|
122
|
+
return str.replace(/<(?=!--|\/?script)/gi, "\\u003c");
|
|
123
|
+
}
|
|
124
|
+
function buildInlineImageMaps(images) {
|
|
125
|
+
const imageMap = {};
|
|
126
|
+
const imageAliases = {};
|
|
127
|
+
if (!images) return { imageMap, imageAliases };
|
|
128
|
+
const canonicalKeyFor = /* @__PURE__ */ new Map();
|
|
129
|
+
for (const [path, buffer] of images.entries()) {
|
|
130
|
+
const canonical = canonicalKeyFor.get(buffer);
|
|
131
|
+
if (canonical !== void 0) {
|
|
132
|
+
imageAliases[path] = canonical;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
canonicalKeyFor.set(buffer, path);
|
|
136
|
+
imageMap[path] = arrayBufferToBase64DataUrl(buffer, inferMimeType(path));
|
|
137
|
+
}
|
|
138
|
+
return { imageMap, imageAliases };
|
|
123
139
|
}
|
|
124
140
|
function generateInlineHtml(doc, options) {
|
|
125
141
|
const {
|
|
@@ -132,15 +148,10 @@ function generateInlineHtml(doc, options) {
|
|
|
132
148
|
themeRegistry
|
|
133
149
|
} = options;
|
|
134
150
|
doc = applyThemeSelection(doc, themeId, themeRegistry);
|
|
135
|
-
const imageMap =
|
|
136
|
-
if (images) {
|
|
137
|
-
for (const [path, buffer] of images.entries()) {
|
|
138
|
-
const mimeType = inferMimeType(path);
|
|
139
|
-
imageMap[path] = arrayBufferToBase64DataUrl(buffer, mimeType);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
151
|
+
const { imageMap, imageAliases } = buildInlineImageMaps(images);
|
|
142
152
|
const docJson = escapeForScript(JSON.stringify(doc));
|
|
143
153
|
const imageMapJson = escapeForScript(JSON.stringify(imageMap));
|
|
154
|
+
const imageAliasJson = escapeForScript(JSON.stringify(imageAliases));
|
|
144
155
|
return `<!DOCTYPE html>
|
|
145
156
|
<html lang="en">
|
|
146
157
|
<head>
|
|
@@ -151,7 +162,7 @@ function generateInlineHtml(doc, options) {
|
|
|
151
162
|
*,*::before,*::after{box-sizing:border-box}
|
|
152
163
|
html,body{margin:0;padding:0;height:100%;background:#1a1a2e;color:#e0e0e0;font-family:system-ui,-apple-system,sans-serif}
|
|
153
164
|
#squisq-root{width:100%;height:100%;display:flex;align-items:center;justify-content:center}
|
|
154
|
-
${mode === "static" ? "#squisq-root{
|
|
165
|
+
${mode === "static" ? "#squisq-root{display:block}" : ""}
|
|
155
166
|
</style>
|
|
156
167
|
</head>
|
|
157
168
|
<body>
|
|
@@ -161,6 +172,14 @@ ${mode === "static" ? "#squisq-root{align-items:flex-start;overflow-y:auto;backg
|
|
|
161
172
|
(function(){
|
|
162
173
|
var doc = JSON.parse(${JSON.stringify(docJson)});
|
|
163
174
|
var images = JSON.parse(${JSON.stringify(imageMapJson)});
|
|
175
|
+
// Re-expand path/basename aliases that share a payload (see
|
|
176
|
+
// buildInlineImageMaps) so \`images\` has every key the resolver may ask for.
|
|
177
|
+
var aliases = JSON.parse(${JSON.stringify(imageAliasJson)});
|
|
178
|
+
for (var alias in aliases) {
|
|
179
|
+
if (Object.prototype.hasOwnProperty.call(aliases, alias)) {
|
|
180
|
+
images[alias] = images[aliases[alias]];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
164
183
|
SquisqPlayer.mount(document.getElementById("squisq-root"), doc, {
|
|
165
184
|
mode: ${JSON.stringify(mode)},
|
|
166
185
|
images: images,
|
|
@@ -197,7 +216,7 @@ function generateExternalHtml(doc, options) {
|
|
|
197
216
|
*,*::before,*::after{box-sizing:border-box}
|
|
198
217
|
html,body{margin:0;padding:0;height:100%;background:#1a1a2e;color:#e0e0e0;font-family:system-ui,-apple-system,sans-serif}
|
|
199
218
|
#squisq-root{width:100%;height:100%;display:flex;align-items:center;justify-content:center}
|
|
200
|
-
${mode === "static" ? "#squisq-root{
|
|
219
|
+
${mode === "static" ? "#squisq-root{display:block}" : ""}
|
|
201
220
|
</style>
|
|
202
221
|
</head>
|
|
203
222
|
<body>
|
|
@@ -248,21 +267,31 @@ import {
|
|
|
248
267
|
import { resolveFontFamily, buildGoogleFontsUrl } from "@bendyline/squisq/schemas";
|
|
249
268
|
import { resolveThemeForDoc } from "@bendyline/squisq/doc";
|
|
250
269
|
function markdownDocToPlainHtml(doc, options = {}) {
|
|
251
|
-
const {
|
|
270
|
+
const {
|
|
271
|
+
title = "Document",
|
|
272
|
+
images,
|
|
273
|
+
links,
|
|
274
|
+
themeId,
|
|
275
|
+
iconsCss,
|
|
276
|
+
htmlPolicy = "sanitize",
|
|
277
|
+
externalResources = "deny"
|
|
278
|
+
} = options;
|
|
252
279
|
const resolveId = themeId ?? readFrontmatterThemeId2(doc.frontmatter);
|
|
253
280
|
const theme = options.theme ?? (resolveId ? resolveThemeForDoc(doc, resolveId, options.themeRegistry) : void 0);
|
|
254
281
|
const ctx = { images, links, htmlPolicy };
|
|
255
282
|
const body = renderTopLevel(doc.children, ctx);
|
|
256
|
-
const fontsLink = theme ? renderFontsLink(theme) : "";
|
|
283
|
+
const fontsLink = theme && externalResources === "allow" ? renderFontsLink(theme) : "";
|
|
257
284
|
const usesIcons = docUsesIcons(doc);
|
|
258
285
|
let iconsLink = "";
|
|
259
286
|
if (usesIcons) {
|
|
260
287
|
iconsLink = iconsCss ? `<style data-fa-inline>
|
|
261
|
-
${iconsCss}
|
|
288
|
+
${escapeStyleText(iconsCss)}
|
|
262
289
|
</style>
|
|
263
|
-
` : FONT_AWESOME_LINK;
|
|
290
|
+
` : externalResources === "allow" ? FONT_AWESOME_LINK : "";
|
|
264
291
|
}
|
|
265
292
|
const themedCss = theme ? renderThemedCss(theme) : DEFAULT_CSS;
|
|
293
|
+
const safeCss = escapeStyleText(`${themedCss}
|
|
294
|
+
${FEATURE_CSS}`);
|
|
266
295
|
return `<!DOCTYPE html>
|
|
267
296
|
<html lang="en">
|
|
268
297
|
<head>
|
|
@@ -270,8 +299,7 @@ ${iconsCss}
|
|
|
270
299
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
271
300
|
<title>${escapeHtml2(title)}</title>
|
|
272
301
|
${fontsLink}${iconsLink}<style>
|
|
273
|
-
${
|
|
274
|
-
${FEATURE_CSS}
|
|
302
|
+
${safeCss}
|
|
275
303
|
</style>
|
|
276
304
|
</head>
|
|
277
305
|
<body>
|
|
@@ -279,7 +307,10 @@ ${body}
|
|
|
279
307
|
</body>
|
|
280
308
|
</html>`;
|
|
281
309
|
}
|
|
282
|
-
|
|
310
|
+
function escapeStyleText(css) {
|
|
311
|
+
return css.replace(/</g, "\\3c ");
|
|
312
|
+
}
|
|
313
|
+
var FONT_AWESOME_LINK = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.2.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
|
|
283
314
|
`;
|
|
284
315
|
function docUsesIcons(doc) {
|
|
285
316
|
function visit(node) {
|
|
@@ -1379,6 +1410,14 @@ function toHtmlString(data) {
|
|
|
1379
1410
|
return new TextDecoder("utf-8").decode(bytes);
|
|
1380
1411
|
}
|
|
1381
1412
|
function htmlToMarkdownDocSync(html, options = {}) {
|
|
1413
|
+
options.signal?.throwIfAborted();
|
|
1414
|
+
const maxInputChars = options.maxInputChars ?? 16 * 1024 * 1024;
|
|
1415
|
+
if (!Number.isSafeInteger(maxInputChars) || maxInputChars < 0) {
|
|
1416
|
+
throw new RangeError("maxInputChars must be a non-negative safe integer");
|
|
1417
|
+
}
|
|
1418
|
+
if (html.length > maxInputChars) {
|
|
1419
|
+
throw new RangeError(`HTML exceeds the ${maxInputChars}-character safety limit`);
|
|
1420
|
+
}
|
|
1382
1421
|
let nodes = parseHtmlToNodes(html);
|
|
1383
1422
|
if (options.sanitize !== false) nodes = sanitizeHtmlNodes2(nodes);
|
|
1384
1423
|
return { type: "document", children: blocksFromNodes(nodes) };
|
|
@@ -1387,7 +1426,7 @@ async function htmlToMarkdownDoc(data, options = {}) {
|
|
|
1387
1426
|
return htmlToMarkdownDocSync(toHtmlString(data), options);
|
|
1388
1427
|
}
|
|
1389
1428
|
function htmlToMarkdown(html, options = {}) {
|
|
1390
|
-
return stringifyMarkdown(htmlToMarkdownDocSync(html, options));
|
|
1429
|
+
return stringifyMarkdown(htmlToMarkdownDocSync(html, options), { signal: options.signal });
|
|
1391
1430
|
}
|
|
1392
1431
|
|
|
1393
1432
|
// src/html/index.ts
|
|
@@ -1397,10 +1436,21 @@ function sanitizeZipPath3(path) {
|
|
|
1397
1436
|
if (normalized.split("/").some((seg) => seg === "..")) return null;
|
|
1398
1437
|
return normalized;
|
|
1399
1438
|
}
|
|
1439
|
+
function uniqueFilename(filename, used) {
|
|
1440
|
+
if (!used.has(filename)) return filename;
|
|
1441
|
+
const dot = filename.lastIndexOf(".");
|
|
1442
|
+
const base = dot > 0 ? filename.slice(0, dot) : filename;
|
|
1443
|
+
const ext = dot > 0 ? filename.slice(dot) : "";
|
|
1444
|
+
let suffix = 2;
|
|
1445
|
+
while (used.has(`${base}-${suffix}${ext}`)) suffix++;
|
|
1446
|
+
return `${base}-${suffix}${ext}`;
|
|
1447
|
+
}
|
|
1400
1448
|
function docToHtml(doc, options) {
|
|
1449
|
+
options.signal?.throwIfAborted();
|
|
1401
1450
|
return generateInlineHtml(doc, options);
|
|
1402
1451
|
}
|
|
1403
1452
|
async function docToHtmlZip(doc, options) {
|
|
1453
|
+
options.signal?.throwIfAborted();
|
|
1404
1454
|
const {
|
|
1405
1455
|
playerScript,
|
|
1406
1456
|
images,
|
|
@@ -1415,18 +1465,30 @@ async function docToHtmlZip(doc, options) {
|
|
|
1415
1465
|
zip.file("squisq-player.js", playerScript);
|
|
1416
1466
|
const imagePathMap = {};
|
|
1417
1467
|
if (images) {
|
|
1468
|
+
let imageIndex = 0;
|
|
1418
1469
|
for (const [originalPath, buffer] of images.entries()) {
|
|
1470
|
+
if ((imageIndex++ & 63) === 0) options.signal?.throwIfAborted();
|
|
1419
1471
|
const zipPath = sanitizeZipPath3(originalPath);
|
|
1420
|
-
if (!zipPath)
|
|
1472
|
+
if (!zipPath) {
|
|
1473
|
+
console.warn(
|
|
1474
|
+
`[squisq] docToHtmlZip: skipped image with unsafe path "${originalPath}" \u2014 the exported document will have a broken reference to it.`
|
|
1475
|
+
);
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1421
1478
|
zip.file(zipPath, buffer);
|
|
1422
1479
|
imagePathMap[originalPath] = zipPath;
|
|
1423
1480
|
}
|
|
1424
1481
|
}
|
|
1425
1482
|
const audioPathMap = {};
|
|
1426
1483
|
if (audio) {
|
|
1484
|
+
let audioIndex = 0;
|
|
1485
|
+
const usedAudioNames = /* @__PURE__ */ new Set();
|
|
1427
1486
|
for (const [segmentKey, buffer] of audio.entries()) {
|
|
1487
|
+
if ((audioIndex++ & 63) === 0) options.signal?.throwIfAborted();
|
|
1428
1488
|
const filename = extractFilename(segmentKey);
|
|
1429
|
-
const
|
|
1489
|
+
const named = filename.includes(".") ? filename : `${filename}.mp3`;
|
|
1490
|
+
const finalName = uniqueFilename(named, usedAudioNames);
|
|
1491
|
+
usedAudioNames.add(finalName);
|
|
1430
1492
|
const zipPath = `audio/${finalName}`;
|
|
1431
1493
|
zip.file(zipPath, buffer);
|
|
1432
1494
|
audioPathMap[segmentKey] = zipPath;
|
|
@@ -1462,4 +1524,3 @@ export {
|
|
|
1462
1524
|
docToHtml,
|
|
1463
1525
|
docToHtmlZip
|
|
1464
1526
|
};
|
|
1465
|
-
//# sourceMappingURL=chunk-LXYLOOST.js.map
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
extractPlainText
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SBUW7NHR.js";
|
|
4
4
|
import {
|
|
5
5
|
escapeXml
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-JU2RHXUB.js";
|
|
7
7
|
import {
|
|
8
8
|
extractFilename,
|
|
9
9
|
inferMimeType
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-6RQOV3B3.js";
|
|
11
11
|
|
|
12
12
|
// src/epub/export.ts
|
|
13
13
|
import JSZip from "jszip";
|
|
@@ -15,6 +15,7 @@ import { resolveFontFamily } from "@bendyline/squisq/schemas";
|
|
|
15
15
|
import { resolveThemeForDoc } from "@bendyline/squisq/doc";
|
|
16
16
|
import { readFrontmatterThemeId, sanitizeUrl } from "@bendyline/squisq/markdown";
|
|
17
17
|
async function markdownDocToEpub(doc, options = {}) {
|
|
18
|
+
options.signal?.throwIfAborted();
|
|
18
19
|
const fmTitle = doc.frontmatter?.title;
|
|
19
20
|
const fmAuthor = doc.frontmatter?.author;
|
|
20
21
|
const title = options.title ?? (typeof fmTitle === "string" ? fmTitle : "Untitled");
|
|
@@ -28,7 +29,9 @@ async function markdownDocToEpub(doc, options = {}) {
|
|
|
28
29
|
const resolvedImages = /* @__PURE__ */ new Map();
|
|
29
30
|
const usedImageNames = /* @__PURE__ */ new Set();
|
|
30
31
|
if (options.images) {
|
|
32
|
+
let imageIndex = 0;
|
|
31
33
|
for (const src of imageEntries) {
|
|
34
|
+
if ((imageIndex++ & 63) === 0) options.signal?.throwIfAborted();
|
|
32
35
|
const data = options.images.get(src);
|
|
33
36
|
if (data) {
|
|
34
37
|
const filename = uniqueFilename(safeArchiveBasename(src, "image"), usedImageNames);
|
|
@@ -47,6 +50,7 @@ async function markdownDocToEpub(doc, options = {}) {
|
|
|
47
50
|
zip.file("META-INF/container.xml", CONTAINER_XML);
|
|
48
51
|
zip.file("OEBPS/styles.css", css);
|
|
49
52
|
for (const [, img] of resolvedImages) {
|
|
53
|
+
options.signal?.throwIfAborted();
|
|
50
54
|
zip.file(`OEBPS/images/${img.filename}`, img.data);
|
|
51
55
|
}
|
|
52
56
|
let coverFilename;
|
|
@@ -134,12 +138,17 @@ async function markdownDocToEpub(doc, options = {}) {
|
|
|
134
138
|
const id = `chapter-${num}`;
|
|
135
139
|
const filename = `${id}.xhtml`;
|
|
136
140
|
const audioInfo = chapterAudio[i] ?? null;
|
|
137
|
-
const xhtml = renderChapterXhtml(
|
|
141
|
+
const { xhtml, ids } = renderChapterXhtml(
|
|
142
|
+
chap.nodes,
|
|
143
|
+
title,
|
|
144
|
+
resolvedImages,
|
|
145
|
+
audioInfo !== null
|
|
146
|
+
);
|
|
138
147
|
zip.file(`OEBPS/chapters/${filename}`, xhtml);
|
|
139
148
|
let smilFilename;
|
|
140
149
|
if (audioInfo) {
|
|
141
150
|
smilFilename = `${id}.smil`;
|
|
142
|
-
const smil = generateSmil(filename, audioInfo,
|
|
151
|
+
const smil = generateSmil(filename, audioInfo, ids);
|
|
143
152
|
zip.file(`OEBPS/chapters/${smilFilename}`, smil);
|
|
144
153
|
}
|
|
145
154
|
chapterFiles.push({
|
|
@@ -248,10 +257,14 @@ function collectDocImages(nodes) {
|
|
|
248
257
|
return images;
|
|
249
258
|
}
|
|
250
259
|
function renderChapterXhtml(nodes, bookTitle, images, addIds = false) {
|
|
251
|
-
|
|
252
|
-
const nextId = () =>
|
|
260
|
+
const ids = [];
|
|
261
|
+
const nextId = () => {
|
|
262
|
+
const id = `p${ids.length + 1}`;
|
|
263
|
+
ids.push(id);
|
|
264
|
+
return id;
|
|
265
|
+
};
|
|
253
266
|
const body = nodes.map((n) => blockToXhtml(n, images, addIds ? nextId : void 0)).join("\n");
|
|
254
|
-
|
|
267
|
+
const xhtml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
255
268
|
<!DOCTYPE html>
|
|
256
269
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
|
|
257
270
|
<head>
|
|
@@ -263,40 +276,46 @@ function renderChapterXhtml(nodes, bookTitle, images, addIds = false) {
|
|
|
263
276
|
${body}
|
|
264
277
|
</body>
|
|
265
278
|
</html>`;
|
|
279
|
+
return { xhtml, ids };
|
|
266
280
|
}
|
|
267
281
|
function blockToXhtml(node, images, nextId) {
|
|
268
|
-
|
|
282
|
+
let cached;
|
|
283
|
+
const idAttr = () => {
|
|
284
|
+
if (!nextId) return "";
|
|
285
|
+
cached ?? (cached = ` id="${nextId()}"`);
|
|
286
|
+
return cached;
|
|
287
|
+
};
|
|
269
288
|
switch (node.type) {
|
|
270
289
|
case "heading": {
|
|
271
290
|
const tag = `h${node.depth}`;
|
|
272
|
-
return `<${tag}${idAttr}>${inlinesToXhtml(node.children, images)}</${tag}>`;
|
|
291
|
+
return `<${tag}${idAttr()}>${inlinesToXhtml(node.children, images)}</${tag}>`;
|
|
273
292
|
}
|
|
274
293
|
case "paragraph":
|
|
275
|
-
return `<p${idAttr}>${inlinesToXhtml(node.children, images)}</p>`;
|
|
294
|
+
return `<p${idAttr()}>${inlinesToXhtml(node.children, images)}</p>`;
|
|
276
295
|
case "blockquote":
|
|
277
|
-
return `<blockquote${idAttr}>
|
|
296
|
+
return `<blockquote${idAttr()}>
|
|
278
297
|
${node.children.map((c) => blockToXhtml(c, images, nextId)).join("\n")}
|
|
279
298
|
</blockquote>`;
|
|
280
299
|
case "list": {
|
|
281
300
|
const tag = node.ordered ? "ol" : "ul";
|
|
282
301
|
const startAttr = node.ordered && node.start && node.start !== 1 ? ` start="${node.start}"` : "";
|
|
283
302
|
const items = node.children.map((item) => listItemToXhtml(item, images)).join("\n");
|
|
284
|
-
return `<${tag}${idAttr}${startAttr}>
|
|
303
|
+
return `<${tag}${idAttr()}${startAttr}>
|
|
285
304
|
${items}
|
|
286
305
|
</${tag}>`;
|
|
287
306
|
}
|
|
288
307
|
case "code": {
|
|
289
308
|
const langAttr = node.lang ? ` class="language-${escapeXml(node.lang)}"` : "";
|
|
290
|
-
return `<pre${idAttr}><code${langAttr}>${escapeXml(node.value)}</code></pre>`;
|
|
309
|
+
return `<pre${idAttr()}><code${langAttr}>${escapeXml(node.value)}</code></pre>`;
|
|
291
310
|
}
|
|
292
311
|
case "thematicBreak":
|
|
293
|
-
return `<hr${idAttr}/>`;
|
|
312
|
+
return `<hr${idAttr()}/>`;
|
|
294
313
|
case "table":
|
|
295
|
-
return tableToXhtml(node, images, idAttr);
|
|
314
|
+
return tableToXhtml(node, images, idAttr());
|
|
296
315
|
case "htmlBlock":
|
|
297
|
-
return `<p${idAttr}>${escapeXml(node.rawHtml.replace(/<[^>]+>/g, ""))}</p>`;
|
|
316
|
+
return `<p${idAttr()}>${escapeXml(node.rawHtml.replace(/<[^>]+>/g, ""))}</p>`;
|
|
298
317
|
case "math":
|
|
299
|
-
return `<p${idAttr} class="math">${escapeXml(node.value)}</p>`;
|
|
318
|
+
return `<p${idAttr()} class="math">${escapeXml(node.value)}</p>`;
|
|
300
319
|
default:
|
|
301
320
|
return "";
|
|
302
321
|
}
|
|
@@ -386,6 +405,14 @@ function safeArchiveBasename(path, fallback) {
|
|
|
386
405
|
}).join("").trim();
|
|
387
406
|
return !safe || safe === "." || safe === ".." ? fallback : safe;
|
|
388
407
|
}
|
|
408
|
+
function uniqueManifestId(prefix, filename, used) {
|
|
409
|
+
const base = `${prefix}-${filename.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
|
410
|
+
let id = base;
|
|
411
|
+
let suffix = 2;
|
|
412
|
+
while (used.has(id)) id = `${base}-${suffix++}`;
|
|
413
|
+
used.add(id);
|
|
414
|
+
return id;
|
|
415
|
+
}
|
|
389
416
|
function uniqueFilename(filename, used) {
|
|
390
417
|
if (!used.has(filename)) return filename;
|
|
391
418
|
const dot = filename.lastIndexOf(".");
|
|
@@ -395,22 +422,24 @@ function uniqueFilename(filename, used) {
|
|
|
395
422
|
while (used.has(`${base}-${suffix}${ext}`)) suffix++;
|
|
396
423
|
return `${base}-${suffix}${ext}`;
|
|
397
424
|
}
|
|
398
|
-
function generateSmil(chapterFilename, audioInfo,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
425
|
+
function generateSmil(chapterFilename, audioInfo, elementIds) {
|
|
426
|
+
if (elementIds.length === 0) {
|
|
427
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
428
|
+
<smil xmlns="http://www.w3.org/ns/SMIL" xmlns:epub="http://www.idpf.org/2007/ops" version="3.0">
|
|
429
|
+
<body>
|
|
430
|
+
<seq id="seq-1" epub:textref="${chapterFilename}">
|
|
431
|
+
</seq>
|
|
432
|
+
</body>
|
|
433
|
+
</smil>`;
|
|
403
434
|
}
|
|
404
|
-
|
|
405
|
-
if (elementCount === 0) elementCount = 1;
|
|
406
|
-
const clipDuration = audioInfo.duration / elementCount;
|
|
435
|
+
const clipDuration = audioInfo.duration / elementIds.length;
|
|
407
436
|
const pars = [];
|
|
408
|
-
for (let i = 0; i <
|
|
437
|
+
for (let i = 0; i < elementIds.length; i++) {
|
|
409
438
|
const clipStart = formatTime(audioInfo.clipStart + i * clipDuration, true);
|
|
410
439
|
const clipEnd = formatTime(audioInfo.clipStart + (i + 1) * clipDuration, true);
|
|
411
440
|
pars.push(
|
|
412
441
|
` <par id="par-${i + 1}">
|
|
413
|
-
<text src="${chapterFilename}
|
|
442
|
+
<text src="${chapterFilename}#${escapeXml(elementIds[i])}"/>
|
|
414
443
|
<audio src="../audio/${escapeXml(audioInfo.audioFilename)}" clipBegin="${clipStart}" clipEnd="${clipEnd}"/>
|
|
415
444
|
</par>`
|
|
416
445
|
);
|
|
@@ -477,12 +506,17 @@ function generateContentOpf(params) {
|
|
|
477
506
|
);
|
|
478
507
|
}
|
|
479
508
|
}
|
|
509
|
+
const usedManifestIds = /* @__PURE__ */ new Set(["cover-image", "cover-page"]);
|
|
510
|
+
for (const chap of chapters) {
|
|
511
|
+
usedManifestIds.add(chap.id);
|
|
512
|
+
if (chap.smilFilename) usedManifestIds.add(`${chap.id}-overlay`);
|
|
513
|
+
}
|
|
480
514
|
if (audioFiles) {
|
|
481
515
|
const usedAudioNames = /* @__PURE__ */ new Set();
|
|
482
516
|
for (const af of audioFiles) {
|
|
483
517
|
if (usedAudioNames.has(af.filename)) continue;
|
|
484
518
|
usedAudioNames.add(af.filename);
|
|
485
|
-
const audioId =
|
|
519
|
+
const audioId = uniqueManifestId("audio", af.filename, usedManifestIds);
|
|
486
520
|
manifestItems.push(
|
|
487
521
|
` <item id="${audioId}" href="audio/${escapeXml(af.filename)}" media-type="${af.mime}"/>`
|
|
488
522
|
);
|
|
@@ -492,7 +526,7 @@ function generateContentOpf(params) {
|
|
|
492
526
|
for (const [, img] of images) {
|
|
493
527
|
if (usedFilenames.has(img.filename)) continue;
|
|
494
528
|
usedFilenames.add(img.filename);
|
|
495
|
-
const imgId =
|
|
529
|
+
const imgId = uniqueManifestId("img", img.filename, usedManifestIds);
|
|
496
530
|
manifestItems.push(
|
|
497
531
|
` <item id="${imgId}" href="images/${escapeXml(img.filename)}" media-type="${img.mime}"/>`
|
|
498
532
|
);
|
|
@@ -676,4 +710,3 @@ export {
|
|
|
676
710
|
markdownDocToEpub,
|
|
677
711
|
docToEpub
|
|
678
712
|
};
|
|
679
|
-
//# sourceMappingURL=chunk-4VUWTSGM.js.map
|