hyperbook 0.78.0 → 0.79.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/dist/assets/directive-typst/client.js +151 -9
- package/dist/index.js +4 -2
- package/package.json +3 -3
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
hyperbook.typst = (function () {
|
|
2
|
+
// Debounce utility function
|
|
3
|
+
const debounce = (func, wait) => {
|
|
4
|
+
let timeout;
|
|
5
|
+
return function executedFunction(...args) {
|
|
6
|
+
const later = () => {
|
|
7
|
+
clearTimeout(timeout);
|
|
8
|
+
func(...args);
|
|
9
|
+
};
|
|
10
|
+
clearTimeout(timeout);
|
|
11
|
+
timeout = setTimeout(later, wait);
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
2
15
|
// Register code-input template for typst syntax highlighting
|
|
3
16
|
window.codeInput?.registerTemplate(
|
|
4
17
|
"typst-highlighted",
|
|
@@ -67,6 +80,85 @@ hyperbook.typst = (function () {
|
|
|
67
80
|
return typstLoadPromise;
|
|
68
81
|
};
|
|
69
82
|
|
|
83
|
+
// Asset cache for server-loaded images
|
|
84
|
+
const assetCache = new Map(); // filepath -> Uint8Array
|
|
85
|
+
|
|
86
|
+
// Extract relative image paths from typst source
|
|
87
|
+
const extractRelImagePaths = (src) => {
|
|
88
|
+
const paths = new Set();
|
|
89
|
+
const re = /image\s*\(\s*(['"])([^'"]+)\1/gi;
|
|
90
|
+
let m;
|
|
91
|
+
while ((m = re.exec(src))) {
|
|
92
|
+
const p = m[2];
|
|
93
|
+
// Skip absolute URLs, data URLs, blob URLs, and paths starting with "/"
|
|
94
|
+
if (/^(https?:|data:|blob:|\/)/i.test(p)) continue;
|
|
95
|
+
paths.add(p);
|
|
96
|
+
}
|
|
97
|
+
return [...paths];
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Fetch assets from server using base path
|
|
101
|
+
const fetchAssets = async (paths, basePath) => {
|
|
102
|
+
const misses = paths.filter(p => !assetCache.has(p));
|
|
103
|
+
await Promise.all(misses.map(async (p) => {
|
|
104
|
+
try {
|
|
105
|
+
// Construct URL using base path
|
|
106
|
+
const url = basePath ? `${basePath}/${p}`.replace(/\/+/g, '/') : p;
|
|
107
|
+
const res = await fetch(url);
|
|
108
|
+
if (!res.ok) {
|
|
109
|
+
console.warn(`Image not found: ${p} at ${url} (HTTP ${res.status})`);
|
|
110
|
+
assetCache.set(p, null); // Mark as failed
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const buf = await res.arrayBuffer();
|
|
114
|
+
assetCache.set(p, new Uint8Array(buf));
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.warn(`Error loading image ${p}:`, error);
|
|
117
|
+
assetCache.set(p, null); // Mark as failed
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Build typst preamble with inlined assets
|
|
123
|
+
const buildAssetsPreamble = () => {
|
|
124
|
+
if (assetCache.size === 0) return '';
|
|
125
|
+
const entries = [...assetCache.entries()]
|
|
126
|
+
.filter(([name, u8]) => u8 !== null) // Skip failed images
|
|
127
|
+
.map(([name, u8]) => {
|
|
128
|
+
const nums = Array.from(u8).join(',');
|
|
129
|
+
return ` "${name}": bytes((${nums}))`;
|
|
130
|
+
}).join(',\n');
|
|
131
|
+
if (!entries) return '';
|
|
132
|
+
return `#let __assets = (\n${entries}\n)\n\n`;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Rewrite image() calls to use inlined assets
|
|
136
|
+
const rewriteImageCalls = (src) => {
|
|
137
|
+
if (assetCache.size === 0) return src;
|
|
138
|
+
return src.replace(/image\s*\(\s*(['"])([^'"]+)\1/g, (m, q, fname) => {
|
|
139
|
+
if (assetCache.has(fname)) {
|
|
140
|
+
const asset = assetCache.get(fname);
|
|
141
|
+
if (asset === null) {
|
|
142
|
+
// Image not found – replace with error text
|
|
143
|
+
return `[Image not found: _${fname}_]`;
|
|
144
|
+
}
|
|
145
|
+
return `image(__assets.at("${fname}")`;
|
|
146
|
+
}
|
|
147
|
+
return m;
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Prepare typst source with server-loaded assets
|
|
152
|
+
const prepareTypstSourceWithAssets = async (src, basePath) => {
|
|
153
|
+
const relPaths = extractRelImagePaths(src);
|
|
154
|
+
if (relPaths.length > 0) {
|
|
155
|
+
await fetchAssets(relPaths, basePath);
|
|
156
|
+
const preamble = buildAssetsPreamble();
|
|
157
|
+
return preamble + rewriteImageCalls(src);
|
|
158
|
+
}
|
|
159
|
+
return src;
|
|
160
|
+
};
|
|
161
|
+
|
|
70
162
|
// Parse error message from SourceDiagnostic format
|
|
71
163
|
const parseTypstError = (errorMessage) => {
|
|
72
164
|
try {
|
|
@@ -82,7 +174,7 @@ hyperbook.typst = (function () {
|
|
|
82
174
|
};
|
|
83
175
|
|
|
84
176
|
// Render typst code to SVG
|
|
85
|
-
const renderTypst = async (code, container, loadingIndicator, sourceFiles, binaryFiles, id, previewContainer) => {
|
|
177
|
+
const renderTypst = async (code, container, loadingIndicator, sourceFiles, binaryFiles, id, previewContainer, basePath) => {
|
|
86
178
|
// Queue this render to ensure only one compilation runs at a time
|
|
87
179
|
return queueRender(async () => {
|
|
88
180
|
// Show loading indicator
|
|
@@ -96,6 +188,9 @@ hyperbook.typst = (function () {
|
|
|
96
188
|
// Reset shadow files for this render
|
|
97
189
|
$typst.resetShadow();
|
|
98
190
|
|
|
191
|
+
// Prepare code with server-loaded assets
|
|
192
|
+
const preparedCode = await prepareTypstSourceWithAssets(code, basePath);
|
|
193
|
+
|
|
99
194
|
// Add source files
|
|
100
195
|
for (const { filename, content } of sourceFiles) {
|
|
101
196
|
const path = filename.startsWith('/') ? filename.substring(1) : filename;
|
|
@@ -128,7 +223,7 @@ hyperbook.typst = (function () {
|
|
|
128
223
|
}
|
|
129
224
|
}
|
|
130
225
|
|
|
131
|
-
const svg = await $typst.svg({ mainContent:
|
|
226
|
+
const svg = await $typst.svg({ mainContent: preparedCode });
|
|
132
227
|
|
|
133
228
|
// Remove any existing error overlay from preview-container
|
|
134
229
|
if (previewContainer) {
|
|
@@ -204,7 +299,7 @@ hyperbook.typst = (function () {
|
|
|
204
299
|
};
|
|
205
300
|
|
|
206
301
|
// Export to PDF
|
|
207
|
-
const exportPdf = async (code, id, sourceFiles, binaryFiles) => {
|
|
302
|
+
const exportPdf = async (code, id, sourceFiles, binaryFiles, basePath) => {
|
|
208
303
|
// Queue this export to ensure only one compilation runs at a time
|
|
209
304
|
return queueRender(async () => {
|
|
210
305
|
await loadTypst();
|
|
@@ -213,6 +308,9 @@ hyperbook.typst = (function () {
|
|
|
213
308
|
// Reset shadow files for this export
|
|
214
309
|
$typst.resetShadow();
|
|
215
310
|
|
|
311
|
+
// Prepare code with server-loaded assets
|
|
312
|
+
const preparedCode = await prepareTypstSourceWithAssets(code, basePath);
|
|
313
|
+
|
|
216
314
|
// Add source files
|
|
217
315
|
for (const { filename, content } of sourceFiles) {
|
|
218
316
|
const path = filename.startsWith('/') ? filename.substring(1) : filename;
|
|
@@ -244,7 +342,7 @@ hyperbook.typst = (function () {
|
|
|
244
342
|
}
|
|
245
343
|
}
|
|
246
344
|
|
|
247
|
-
const pdfData = await $typst.pdf({ mainContent:
|
|
345
|
+
const pdfData = await $typst.pdf({ mainContent: preparedCode });
|
|
248
346
|
const pdfFile = new Blob([pdfData], { type: "application/pdf" });
|
|
249
347
|
const link = document.createElement("a");
|
|
250
348
|
link.href = URL.createObjectURL(pdfFile);
|
|
@@ -276,6 +374,12 @@ hyperbook.typst = (function () {
|
|
|
276
374
|
// Parse source files and binary files from data attributes
|
|
277
375
|
const sourceFilesData = elem.getAttribute("data-source-files");
|
|
278
376
|
const binaryFilesData = elem.getAttribute("data-binary-files");
|
|
377
|
+
let basePath = elem.getAttribute("data-base-path") || "";
|
|
378
|
+
|
|
379
|
+
// Ensure basePath starts with / for absolute paths
|
|
380
|
+
if (basePath && !basePath.startsWith('/')) {
|
|
381
|
+
basePath = '/' + basePath;
|
|
382
|
+
}
|
|
279
383
|
|
|
280
384
|
let sourceFiles = sourceFilesData
|
|
281
385
|
? JSON.parse(atob(sourceFilesData))
|
|
@@ -440,10 +544,13 @@ hyperbook.typst = (function () {
|
|
|
440
544
|
|
|
441
545
|
const mainFile = sourceFiles.find(f => f.filename === "main.typ" || f.filename === "main.typst");
|
|
442
546
|
const mainCode = mainFile ? mainFile.content : "";
|
|
443
|
-
renderTypst(mainCode, preview, loadingIndicator, sourceFiles, binaryFiles, id, previewContainer);
|
|
547
|
+
renderTypst(mainCode, preview, loadingIndicator, sourceFiles, binaryFiles, id, previewContainer, basePath);
|
|
444
548
|
}
|
|
445
549
|
};
|
|
446
550
|
|
|
551
|
+
// Create debounced version of rerenderTypst for input events (500ms delay)
|
|
552
|
+
const debouncedRerenderTypst = debounce(rerenderTypst, 500);
|
|
553
|
+
|
|
447
554
|
// Add source file button
|
|
448
555
|
addSourceFileBtn?.addEventListener("click", () => {
|
|
449
556
|
const filename = prompt(i18n.get("typst-filename-prompt") || "Enter filename (e.g., helper.typ):");
|
|
@@ -548,14 +655,14 @@ hyperbook.typst = (function () {
|
|
|
548
655
|
// Listen for input changes
|
|
549
656
|
editor.addEventListener("input", () => {
|
|
550
657
|
saveState();
|
|
551
|
-
|
|
658
|
+
debouncedRerenderTypst();
|
|
552
659
|
});
|
|
553
660
|
});
|
|
554
661
|
} else if (sourceTextarea) {
|
|
555
662
|
// Preview mode - code is in hidden textarea
|
|
556
663
|
initialCode = sourceTextarea.value;
|
|
557
664
|
loadTypst().then(() => {
|
|
558
|
-
renderTypst(initialCode, preview, loadingIndicator, sourceFiles, binaryFiles, id, previewContainer);
|
|
665
|
+
renderTypst(initialCode, preview, loadingIndicator, sourceFiles, binaryFiles, id, previewContainer, basePath);
|
|
559
666
|
});
|
|
560
667
|
}
|
|
561
668
|
|
|
@@ -564,7 +671,7 @@ hyperbook.typst = (function () {
|
|
|
564
671
|
// Get the main file content
|
|
565
672
|
const mainFile = sourceFiles.find(f => f.filename === "main.typ" || f.filename === "main.typst");
|
|
566
673
|
const code = mainFile ? mainFile.content : (editor ? editor.value : initialCode);
|
|
567
|
-
await exportPdf(code, id, sourceFiles, binaryFiles);
|
|
674
|
+
await exportPdf(code, id, sourceFiles, binaryFiles, basePath);
|
|
568
675
|
});
|
|
569
676
|
|
|
570
677
|
// Download Project button (ZIP with all files)
|
|
@@ -590,7 +697,7 @@ hyperbook.typst = (function () {
|
|
|
590
697
|
if (url.startsWith('data:')) {
|
|
591
698
|
const response = await fetch(url);
|
|
592
699
|
arrayBuffer = await response.arrayBuffer();
|
|
593
|
-
} else {
|
|
700
|
+
} else if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
594
701
|
// External URL
|
|
595
702
|
const response = await fetch(url);
|
|
596
703
|
if (response.ok) {
|
|
@@ -599,6 +706,16 @@ hyperbook.typst = (function () {
|
|
|
599
706
|
console.warn(`Failed to load binary file: ${url}`);
|
|
600
707
|
continue;
|
|
601
708
|
}
|
|
709
|
+
} else {
|
|
710
|
+
// Relative URL - use basePath to construct full URL
|
|
711
|
+
const fullUrl = basePath ? `${basePath}/${url}`.replace(/\/+/g, '/') : url;
|
|
712
|
+
const response = await fetch(fullUrl);
|
|
713
|
+
if (response.ok) {
|
|
714
|
+
arrayBuffer = await response.arrayBuffer();
|
|
715
|
+
} else {
|
|
716
|
+
console.warn(`Failed to load binary file: ${url} at ${fullUrl}`);
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
602
719
|
}
|
|
603
720
|
|
|
604
721
|
const path = dest.startsWith('/') ? dest.substring(1) : dest;
|
|
@@ -608,6 +725,31 @@ hyperbook.typst = (function () {
|
|
|
608
725
|
}
|
|
609
726
|
}
|
|
610
727
|
|
|
728
|
+
// Also include assets loaded from image() calls in the code
|
|
729
|
+
const relImagePaths = extractRelImagePaths(code);
|
|
730
|
+
for (const imagePath of relImagePaths) {
|
|
731
|
+
// Skip if already in zipFiles or already handled as binary file
|
|
732
|
+
const normalizedPath = imagePath.startsWith('/') ? imagePath.substring(1) : imagePath;
|
|
733
|
+
if (zipFiles[normalizedPath]) continue;
|
|
734
|
+
|
|
735
|
+
// Skip absolute URLs, data URLs, and blob URLs
|
|
736
|
+
if (/^(https?:|data:|blob:)/i.test(imagePath)) continue;
|
|
737
|
+
|
|
738
|
+
try {
|
|
739
|
+
// Construct URL using basePath
|
|
740
|
+
const url = basePath ? `${basePath}/${imagePath}`.replace(/\/+/g, '/') : imagePath;
|
|
741
|
+
const response = await fetch(url);
|
|
742
|
+
if (response.ok) {
|
|
743
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
744
|
+
zipFiles[normalizedPath] = new Uint8Array(arrayBuffer);
|
|
745
|
+
} else {
|
|
746
|
+
console.warn(`Failed to load image asset: ${imagePath} at ${url}`);
|
|
747
|
+
}
|
|
748
|
+
} catch (error) {
|
|
749
|
+
console.warn(`Error loading image asset ${imagePath}:`, error);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
611
753
|
// Create ZIP using UZIP
|
|
612
754
|
const zipData = UZIP.encode(zipFiles);
|
|
613
755
|
const zipBlob = new Blob([zipData], { type: "application/zip" });
|
package/dist/index.js
CHANGED
|
@@ -202070,6 +202070,7 @@ var remarkDirectiveTypst_default = (ctx) => () => {
|
|
|
202070
202070
|
}
|
|
202071
202071
|
const isEditMode = mode === "edit";
|
|
202072
202072
|
const isPreviewMode = mode === "preview" || !isEditMode;
|
|
202073
|
+
const basePath = ctx.navigation.current?.path?.directory || "";
|
|
202073
202074
|
data.hName = "div";
|
|
202074
202075
|
data.hProperties = {
|
|
202075
202076
|
class: ["directive-typst", isPreviewMode ? "preview-only" : ""].join(" ").trim(),
|
|
@@ -202079,7 +202080,8 @@ var remarkDirectiveTypst_default = (ctx) => () => {
|
|
|
202079
202080
|
).toString("base64"),
|
|
202080
202081
|
"data-binary-files": Buffer.from(
|
|
202081
202082
|
JSON.stringify(binaryFiles)
|
|
202082
|
-
).toString("base64")
|
|
202083
|
+
).toString("base64"),
|
|
202084
|
+
"data-base-path": basePath
|
|
202083
202085
|
};
|
|
202084
202086
|
const previewContainer = {
|
|
202085
202087
|
type: "element",
|
|
@@ -202500,7 +202502,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"application/1d-interleaved-parityfec
|
|
|
202500
202502
|
/***/ ((module) => {
|
|
202501
202503
|
|
|
202502
202504
|
"use strict";
|
|
202503
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"hyperbook","version":"0.
|
|
202505
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"hyperbook","version":"0.79.0","author":"Mike Barkmin","homepage":"https://github.com/openpatch/hyperbook#readme","license":"MIT","bin":{"hyperbook":"./dist/index.js"},"files":["dist"],"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/openpatch/hyperbook.git","directory":"packages/hyperbook"},"bugs":{"url":"https://github.com/openpatch/hyperbook/issues"},"engines":{"node":">=18"},"scripts":{"version":"pnpm build","lint":"tsc --noEmit","dev":"ncc build ./index.ts -w -o dist/","build":"rimraf dist && ncc build ./index.ts -o ./dist/ --no-cache --no-source-map-register --external favicons --external sharp && node postbuild.mjs"},"dependencies":{"favicons":"^7.2.0"},"devDependencies":{"create-hyperbook":"workspace:*","@hyperbook/fs":"workspace:*","@hyperbook/markdown":"workspace:*","@hyperbook/types":"workspace:*","@pnpm/exportable-manifest":"1000.0.6","@types/archiver":"6.0.3","@types/async-retry":"1.4.9","@types/cross-spawn":"6.0.6","@types/lunr":"^2.3.7","@types/prompts":"2.4.9","@types/tar":"6.1.13","@types/ws":"^8.5.14","@vercel/ncc":"0.38.3","archiver":"7.0.1","async-retry":"1.3.3","chalk":"5.4.1","chokidar":"4.0.3","commander":"12.1.0","cpy":"11.1.0","cross-spawn":"7.0.6","domutils":"^3.2.2","extract-zip":"^2.0.1","got":"12.6.0","htmlparser2":"^10.0.0","lunr":"^2.3.9","lunr-languages":"^1.14.0","mime":"^4.0.6","prompts":"2.4.2","rimraf":"6.0.1","tar":"7.4.3","update-check":"1.5.4","ws":"^8.18.0"}}');
|
|
202504
202506
|
|
|
202505
202507
|
/***/ })
|
|
202506
202508
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hyperbook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.79.0",
|
|
4
4
|
"author": "Mike Barkmin",
|
|
5
5
|
"homepage": "https://github.com/openpatch/hyperbook#readme",
|
|
6
6
|
"license": "MIT",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"ws": "^8.18.0",
|
|
59
59
|
"create-hyperbook": "0.3.2",
|
|
60
60
|
"@hyperbook/fs": "0.24.2",
|
|
61
|
-
"@hyperbook/
|
|
62
|
-
"@hyperbook/
|
|
61
|
+
"@hyperbook/markdown": "0.50.0",
|
|
62
|
+
"@hyperbook/types": "0.20.0"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"version": "pnpm build",
|