@hyperbook/markdown 0.52.0 → 0.52.1
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.
|
@@ -15,7 +15,7 @@ hyperbook.typst = (function () {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const REGEX_PATTERNS = {
|
|
18
|
-
READ:
|
|
18
|
+
READ: /read\s*\(\s*(['"])([^'"]+)\1[^)]*\)/gi,
|
|
19
19
|
CSV: /csv\s*\(\s*(['"])([^'"]+)\1[^)]*\)/gi,
|
|
20
20
|
JSON: /json\s*\(\s*(['"])([^'"]+)\1[^)]*\)/gi,
|
|
21
21
|
YAML: /yaml\s*\(\s*(['"])([^'"]+)\1[^)]*\)/gi,
|
|
@@ -25,6 +25,9 @@ hyperbook.typst = (function () {
|
|
|
25
25
|
ERROR_MESSAGE: /message:\s*"([^"]+)"/,
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
// Text file patterns that need UTF-8 encoding
|
|
29
|
+
const TEXT_PATTERNS = ['READ', 'CSV', 'JSON', 'YAML', 'XML'];
|
|
30
|
+
|
|
28
31
|
// ============================================================================
|
|
29
32
|
// UTILITY FUNCTIONS
|
|
30
33
|
// ============================================================================
|
|
@@ -229,15 +232,15 @@ hyperbook.typst = (function () {
|
|
|
229
232
|
/**
|
|
230
233
|
* Extract relative file paths from Typst source code
|
|
231
234
|
* @param {string} src - Typst source code
|
|
232
|
-
* @returns {Array<string>} Array of file paths
|
|
235
|
+
* @returns {Array<{path: string, isText: boolean}>} Array of file paths with type info
|
|
233
236
|
*/
|
|
234
237
|
extractFilePaths(src) {
|
|
235
|
-
const paths = new
|
|
236
|
-
const patterns = Object.values(REGEX_PATTERNS).filter(
|
|
237
|
-
p => p !== REGEX_PATTERNS.ABSOLUTE_URL && p !== REGEX_PATTERNS.ERROR_MESSAGE
|
|
238
|
-
);
|
|
238
|
+
const paths = new Map(); // path -> isText
|
|
239
239
|
|
|
240
|
-
for (const pattern of
|
|
240
|
+
for (const [name, pattern] of Object.entries(REGEX_PATTERNS)) {
|
|
241
|
+
if (name === 'ABSOLUTE_URL' || name === 'ERROR_MESSAGE') continue;
|
|
242
|
+
|
|
243
|
+
const isText = TEXT_PATTERNS.includes(name);
|
|
241
244
|
let match;
|
|
242
245
|
// Reset regex lastIndex
|
|
243
246
|
pattern.lastIndex = 0;
|
|
@@ -246,11 +249,11 @@ hyperbook.typst = (function () {
|
|
|
246
249
|
const path = match[2];
|
|
247
250
|
// Skip absolute URLs, data URLs, blob URLs
|
|
248
251
|
if (REGEX_PATTERNS.ABSOLUTE_URL.test(path)) continue;
|
|
249
|
-
paths.
|
|
252
|
+
paths.set(path, isText);
|
|
250
253
|
}
|
|
251
254
|
}
|
|
252
255
|
|
|
253
|
-
return Array.from(paths);
|
|
256
|
+
return Array.from(paths.entries()).map(([path, isText]) => ({ path, isText }));
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
/**
|
|
@@ -258,9 +261,10 @@ hyperbook.typst = (function () {
|
|
|
258
261
|
* @param {string} path - Asset path
|
|
259
262
|
* @param {string} basePath - Base path
|
|
260
263
|
* @param {string} pagePath - Page path
|
|
264
|
+
* @param {boolean} isText - Whether this is a text file
|
|
261
265
|
* @returns {Promise<Uint8Array|null>}
|
|
262
266
|
*/
|
|
263
|
-
async fetchAsset(path, basePath, pagePath) {
|
|
267
|
+
async fetchAsset(path, basePath, pagePath, isText = false) {
|
|
264
268
|
try {
|
|
265
269
|
const url = constructUrl(path, basePath, pagePath);
|
|
266
270
|
const response = await fetch(url);
|
|
@@ -270,8 +274,15 @@ hyperbook.typst = (function () {
|
|
|
270
274
|
return null;
|
|
271
275
|
}
|
|
272
276
|
|
|
273
|
-
|
|
274
|
-
|
|
277
|
+
if (isText) {
|
|
278
|
+
// For text files, decode as text and re-encode as UTF-8
|
|
279
|
+
const text = await response.text();
|
|
280
|
+
return new TextEncoder().encode(text);
|
|
281
|
+
} else {
|
|
282
|
+
// For binary files, use arrayBuffer directly
|
|
283
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
284
|
+
return new Uint8Array(arrayBuffer);
|
|
285
|
+
}
|
|
275
286
|
} catch (error) {
|
|
276
287
|
console.warn(`Error loading asset ${path}:`, error);
|
|
277
288
|
return null;
|
|
@@ -280,36 +291,124 @@ hyperbook.typst = (function () {
|
|
|
280
291
|
|
|
281
292
|
/**
|
|
282
293
|
* Fetch multiple assets and cache them
|
|
283
|
-
* @param {Array<string>}
|
|
294
|
+
* @param {Array<{path: string, isText: boolean}>} pathInfos - Array of path info objects
|
|
284
295
|
* @param {string} basePath - Base path
|
|
285
296
|
* @param {string} pagePath - Page path
|
|
286
297
|
* @returns {Promise<void>}
|
|
287
298
|
*/
|
|
288
|
-
async fetchAssets(
|
|
289
|
-
const missingPaths =
|
|
299
|
+
async fetchAssets(pathInfos, basePath, pagePath) {
|
|
300
|
+
const missingPaths = pathInfos.filter(({ path }) => !this.cache.has(path));
|
|
290
301
|
|
|
291
302
|
await Promise.all(
|
|
292
|
-
missingPaths.map(async (path) => {
|
|
293
|
-
const data = await this.fetchAsset(path, basePath, pagePath);
|
|
303
|
+
missingPaths.map(async ({ path, isText }) => {
|
|
304
|
+
const data = await this.fetchAsset(path, basePath, pagePath, isText);
|
|
294
305
|
this.cache.set(path, data);
|
|
295
306
|
})
|
|
296
307
|
);
|
|
297
308
|
}
|
|
298
309
|
|
|
299
310
|
/**
|
|
300
|
-
*
|
|
311
|
+
* Build Typst preamble with inlined assets as bytes
|
|
312
|
+
* @returns {string} Typst preamble code
|
|
301
313
|
*/
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
314
|
+
buildAssetsPreamble() {
|
|
315
|
+
if (this.cache.size === 0) return "";
|
|
316
|
+
const entries = [...this.cache.entries()]
|
|
317
|
+
.filter(([name, u8]) => u8 !== null)
|
|
318
|
+
.map(([name, u8]) => {
|
|
319
|
+
const nums = Array.from(u8).join(",");
|
|
320
|
+
return ` "${name}": bytes((${nums}))`;
|
|
321
|
+
})
|
|
322
|
+
.join(",\n");
|
|
323
|
+
if (!entries) return "";
|
|
324
|
+
return `#let __assets = (\n${entries}\n)\n\n`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Rewrite file calls (image, read, csv, json, yaml, xml) to use inlined assets
|
|
329
|
+
* @param {string} src - Typst source code
|
|
330
|
+
* @returns {string} Rewritten source code
|
|
331
|
+
*/
|
|
332
|
+
rewriteAssetCalls(src) {
|
|
333
|
+
if (this.cache.size === 0) return src;
|
|
334
|
+
|
|
335
|
+
// Rewrite image() calls
|
|
336
|
+
src = src.replace(/image\s*\(\s*(['"])([^'"]+)\1/g, (m, q, fname) => {
|
|
337
|
+
if (this.cache.has(fname)) {
|
|
338
|
+
const asset = this.cache.get(fname);
|
|
339
|
+
if (asset === null) {
|
|
340
|
+
return `[File not found: _${fname}_]`;
|
|
341
|
+
}
|
|
342
|
+
return `image(__assets.at("${fname}")`;
|
|
307
343
|
}
|
|
308
|
-
|
|
344
|
+
return m;
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Rewrite read() calls
|
|
348
|
+
src = src.replace(/read\s*\(\s*(['"])([^'"]+)\1/g, (m, q, fname) => {
|
|
349
|
+
if (this.cache.has(fname)) {
|
|
350
|
+
const asset = this.cache.get(fname);
|
|
351
|
+
if (asset === null) {
|
|
352
|
+
return `[File not found: _${fname}_]`;
|
|
353
|
+
}
|
|
354
|
+
return `read(__assets.at("${fname}")`;
|
|
355
|
+
}
|
|
356
|
+
return m;
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Rewrite csv() calls
|
|
360
|
+
src = src.replace(/csv\s*\(\s*(['"])([^'"]+)\1/g, (m, q, fname) => {
|
|
361
|
+
if (this.cache.has(fname)) {
|
|
362
|
+
const asset = this.cache.get(fname);
|
|
363
|
+
if (asset === null) {
|
|
364
|
+
return `[File not found: _${fname}_]`;
|
|
365
|
+
}
|
|
366
|
+
return `csv(__assets.at("${fname}")`;
|
|
367
|
+
}
|
|
368
|
+
return m;
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Rewrite json() calls
|
|
372
|
+
src = src.replace(/json\s*\(\s*(['"])([^'"]+)\1/g, (m, q, fname) => {
|
|
373
|
+
if (this.cache.has(fname)) {
|
|
374
|
+
const asset = this.cache.get(fname);
|
|
375
|
+
if (asset === null) {
|
|
376
|
+
return `[File not found: _${fname}_]`;
|
|
377
|
+
}
|
|
378
|
+
return `json(__assets.at("${fname}")`;
|
|
379
|
+
}
|
|
380
|
+
return m;
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Rewrite yaml() calls
|
|
384
|
+
src = src.replace(/yaml\s*\(\s*(['"])([^'"]+)\1/g, (m, q, fname) => {
|
|
385
|
+
if (this.cache.has(fname)) {
|
|
386
|
+
const asset = this.cache.get(fname);
|
|
387
|
+
if (asset === null) {
|
|
388
|
+
return `[File not found: _${fname}_]`;
|
|
389
|
+
}
|
|
390
|
+
return `yaml(__assets.at("${fname}")`;
|
|
391
|
+
}
|
|
392
|
+
return m;
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Rewrite xml() calls
|
|
396
|
+
src = src.replace(/xml\s*\(\s*(['"])([^'"]+)\1/g, (m, q, fname) => {
|
|
397
|
+
if (this.cache.has(fname)) {
|
|
398
|
+
const asset = this.cache.get(fname);
|
|
399
|
+
if (asset === null) {
|
|
400
|
+
return `[File not found: _${fname}_]`;
|
|
401
|
+
}
|
|
402
|
+
return `xml(__assets.at("${fname}")`;
|
|
403
|
+
}
|
|
404
|
+
return m;
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
return src;
|
|
309
408
|
}
|
|
310
409
|
|
|
311
410
|
/**
|
|
312
|
-
* Prepare assets for rendering (extract
|
|
411
|
+
* Prepare assets for rendering (extract and fetch)
|
|
313
412
|
* @param {string} mainSrc - Main Typst source
|
|
314
413
|
* @param {Array} sourceFiles - Source file objects
|
|
315
414
|
* @param {string} basePath - Base path
|
|
@@ -317,21 +416,24 @@ hyperbook.typst = (function () {
|
|
|
317
416
|
* @returns {Promise<void>}
|
|
318
417
|
*/
|
|
319
418
|
async prepare(mainSrc, sourceFiles, basePath, pagePath) {
|
|
320
|
-
const allPaths = new
|
|
419
|
+
const allPaths = new Map(); // path -> isText
|
|
321
420
|
|
|
322
421
|
// Extract from main source
|
|
323
|
-
this.extractFilePaths(mainSrc)
|
|
422
|
+
for (const { path, isText } of this.extractFilePaths(mainSrc)) {
|
|
423
|
+
allPaths.set(path, isText);
|
|
424
|
+
}
|
|
324
425
|
|
|
325
426
|
// Extract from all source files
|
|
326
427
|
for (const { content } of sourceFiles) {
|
|
327
|
-
this.extractFilePaths(content)
|
|
428
|
+
for (const { path, isText } of this.extractFilePaths(content)) {
|
|
429
|
+
allPaths.set(path, isText);
|
|
430
|
+
}
|
|
328
431
|
}
|
|
329
432
|
|
|
330
|
-
const
|
|
433
|
+
const pathInfos = Array.from(allPaths.entries()).map(([path, isText]) => ({ path, isText }));
|
|
331
434
|
|
|
332
|
-
if (
|
|
333
|
-
await this.fetchAssets(
|
|
334
|
-
this.mapToShadow();
|
|
435
|
+
if (pathInfos.length > 0) {
|
|
436
|
+
await this.fetchAssets(pathInfos, basePath, pagePath);
|
|
335
437
|
}
|
|
336
438
|
}
|
|
337
439
|
}
|
|
@@ -516,14 +618,23 @@ hyperbook.typst = (function () {
|
|
|
516
618
|
// Prepare assets
|
|
517
619
|
await this.assetManager.prepare(code, sourceFiles, basePath, pagePath);
|
|
518
620
|
|
|
519
|
-
//
|
|
520
|
-
|
|
621
|
+
// Build assets preamble and rewrite source files
|
|
622
|
+
const assetsPreamble = this.assetManager.buildAssetsPreamble();
|
|
623
|
+
const rewrittenCode = this.assetManager.rewriteAssetCalls(code);
|
|
624
|
+
const rewrittenSourceFiles = sourceFiles.map(({ filename, content }) => ({
|
|
625
|
+
filename,
|
|
626
|
+
content: assetsPreamble + this.assetManager.rewriteAssetCalls(content),
|
|
627
|
+
}));
|
|
628
|
+
|
|
629
|
+
// Add source files with rewritten content (includes preamble)
|
|
630
|
+
await this.addSourceFiles(rewrittenSourceFiles);
|
|
521
631
|
|
|
522
632
|
// Add binary files
|
|
523
633
|
await BinaryFileHandler.addToShadow(binaryFiles);
|
|
524
634
|
|
|
525
|
-
// Render to SVG
|
|
526
|
-
const
|
|
635
|
+
// Render to SVG with preamble prepended
|
|
636
|
+
const mainContent = assetsPreamble + rewrittenCode;
|
|
637
|
+
const svg = await window.$typst.svg({ mainContent });
|
|
527
638
|
|
|
528
639
|
// Clear any existing errors
|
|
529
640
|
if (previewContainer) {
|
|
@@ -584,14 +695,23 @@ hyperbook.typst = (function () {
|
|
|
584
695
|
// Prepare assets
|
|
585
696
|
await this.assetManager.prepare(code, sourceFiles, basePath, pagePath);
|
|
586
697
|
|
|
587
|
-
//
|
|
588
|
-
|
|
698
|
+
// Build assets preamble and rewrite source files
|
|
699
|
+
const assetsPreamble = this.assetManager.buildAssetsPreamble();
|
|
700
|
+
const rewrittenCode = this.assetManager.rewriteAssetCalls(code);
|
|
701
|
+
const rewrittenSourceFiles = sourceFiles.map(({ filename, content }) => ({
|
|
702
|
+
filename,
|
|
703
|
+
content: assetsPreamble + this.assetManager.rewriteAssetCalls(content),
|
|
704
|
+
}));
|
|
705
|
+
|
|
706
|
+
// Add source files with rewritten content (includes preamble)
|
|
707
|
+
await this.addSourceFiles(rewrittenSourceFiles);
|
|
589
708
|
|
|
590
709
|
// Add binary files
|
|
591
710
|
await BinaryFileHandler.addToShadow(binaryFiles);
|
|
592
711
|
|
|
593
|
-
// Generate PDF
|
|
594
|
-
const
|
|
712
|
+
// Generate PDF with preamble prepended
|
|
713
|
+
const mainContent = assetsPreamble + rewrittenCode;
|
|
714
|
+
const pdfData = await window.$typst.pdf({ mainContent });
|
|
595
715
|
const pdfBlob = new Blob([pdfData], { type: 'application/pdf' });
|
|
596
716
|
|
|
597
717
|
// Download PDF
|
|
@@ -803,9 +923,9 @@ hyperbook.typst = (function () {
|
|
|
803
923
|
* @returns {Promise<void>}
|
|
804
924
|
*/
|
|
805
925
|
async addAssets(zipFiles, code, basePath, pagePath) {
|
|
806
|
-
const
|
|
926
|
+
const pathInfos = this.assetManager.extractFilePaths(code);
|
|
807
927
|
|
|
808
|
-
for (const relPath of
|
|
928
|
+
for (const { path: relPath, isText } of pathInfos) {
|
|
809
929
|
const normalizedPath = relPath.startsWith('/')
|
|
810
930
|
? relPath.substring(1)
|
|
811
931
|
: relPath;
|
|
@@ -821,8 +941,13 @@ hyperbook.typst = (function () {
|
|
|
821
941
|
const response = await fetch(url);
|
|
822
942
|
|
|
823
943
|
if (response.ok) {
|
|
824
|
-
|
|
825
|
-
|
|
944
|
+
if (isText) {
|
|
945
|
+
const text = await response.text();
|
|
946
|
+
zipFiles[normalizedPath] = new TextEncoder().encode(text);
|
|
947
|
+
} else {
|
|
948
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
949
|
+
zipFiles[normalizedPath] = new Uint8Array(arrayBuffer);
|
|
950
|
+
}
|
|
826
951
|
} else {
|
|
827
952
|
console.warn(`Failed to load asset: ${relPath} at ${url}`);
|
|
828
953
|
}
|
|
@@ -1189,7 +1314,9 @@ hyperbook.typst = (function () {
|
|
|
1189
1314
|
this.fileManager.updateCurrentContent(this.editor.value);
|
|
1190
1315
|
|
|
1191
1316
|
const mainFile = this.fileManager.findMainFile();
|
|
1192
|
-
const mainCode = mainFile
|
|
1317
|
+
const mainCode = mainFile
|
|
1318
|
+
? this.fileManager.contents.get(mainFile.filename) || mainFile.content
|
|
1319
|
+
: '';
|
|
1193
1320
|
|
|
1194
1321
|
this.renderer.render({
|
|
1195
1322
|
code: mainCode,
|
|
@@ -1388,9 +1515,16 @@ hyperbook.typst = (function () {
|
|
|
1388
1515
|
const basePath = elem.getAttribute('data-base-path') || '';
|
|
1389
1516
|
const pagePath = elem.getAttribute('data-page-path') || '';
|
|
1390
1517
|
|
|
1391
|
-
|
|
1392
|
-
const
|
|
1393
|
-
|
|
1518
|
+
// Decode base64 with proper UTF-8 handling
|
|
1519
|
+
const decodeBase64 = (str) => {
|
|
1520
|
+
const binaryStr = atob(str);
|
|
1521
|
+
const bytes = Uint8Array.from(binaryStr, (c) => c.charCodeAt(0));
|
|
1522
|
+
return new TextDecoder('utf-8').decode(bytes);
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1525
|
+
const sourceFiles = sourceFilesData ? JSON.parse(decodeBase64(sourceFilesData)) : [];
|
|
1526
|
+
const binaryFiles = binaryFilesData ? JSON.parse(decodeBase64(binaryFilesData)) : [];
|
|
1527
|
+
const fontFiles = fontFilesData ? JSON.parse(decodeBase64(fontFilesData)) : [];
|
|
1394
1528
|
|
|
1395
1529
|
new TypstEditor({
|
|
1396
1530
|
elem,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperbook/markdown",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.1",
|
|
4
4
|
"author": "Mike Barkmin",
|
|
5
5
|
"homepage": "https://github.com/openpatch/hyperbook#readme",
|
|
6
6
|
"license": "MIT",
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
"vfile": "^6.0.3",
|
|
93
93
|
"vitest": "^4.0.18",
|
|
94
94
|
"wavesurfer.js": "^7.12.1",
|
|
95
|
-
"@hyperbook/types": "0.20.0",
|
|
96
95
|
"@hyperbook/fs": "0.24.2",
|
|
96
|
+
"@hyperbook/types": "0.20.0",
|
|
97
97
|
"@hyperbook/web-component-excalidraw": "0.3.2"
|
|
98
98
|
},
|
|
99
99
|
"scripts": {
|