mr-md 2.0.0-beta → 2.1.1-beta
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 +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +72 -33
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/dev.js +16 -8
- package/dist/cli/generate.js +1 -1
- package/dist/client/app.js +55 -26
- package/dist/renderer/blocks.d.ts.map +1 -1
- package/dist/renderer/blocks.js +21 -12
- package/dist/renderer/html.d.ts +1 -0
- package/dist/renderer/html.d.ts.map +1 -1
- package/dist/renderer/html.js +25 -20
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +5 -91
- package/dist/renderer/markdown.d.ts.map +1 -1
- package/dist/renderer/markdown.js +49 -12
- package/dist/renderer/utils.d.ts.map +1 -1
- package/dist/renderer/utils.js +22 -7
- package/dist/styles/theme.css +239 -78
- package/dist/types.d.ts +4 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/builder.ts +78 -36
- package/src/cli/dev.ts +17 -8
- package/src/cli/generate.ts +1 -1
- package/src/client/app.js +55 -26
- package/src/renderer/blocks.ts +21 -12
- package/src/renderer/html.ts +46 -23
- package/src/renderer/index.ts +13 -91
- package/src/renderer/markdown.ts +54 -14
- package/src/renderer/utils.ts +25 -7
- package/src/styles/theme.css +239 -78
- package/src/types.ts +4 -1
package/src/renderer/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BuildOptions, Chapter, Lesson } from "../types.js";
|
|
2
2
|
import { escAttr, escHtml, renderBlock } from "./blocks.js";
|
|
3
|
-
import { clientScript, pageCSS, renderPage } from "./html.js"; // Used in renderChapter
|
|
3
|
+
import { clientScript, pageCSS, renderPage, renderLayout } from "./html.js"; // Used in renderChapter
|
|
4
4
|
import type { NavItem } from "./utils.js";
|
|
5
5
|
|
|
6
6
|
// ─── Main render function ─────────────────────────────────────────────────────
|
|
@@ -316,89 +316,7 @@ export function renderChapter(
|
|
|
316
316
|
}
|
|
317
317
|
`;
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
<html lang="en" data-palette="${palette}" data-ui="${ui}" ${schemeAttr}>
|
|
321
|
-
<head>
|
|
322
|
-
<meta charset="UTF-8">
|
|
323
|
-
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,650;9..144,760&family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&family=Archivo:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Syne:wght@600;700;800&family=Playfair+Display:ital,wght@0,400..700;1,400..700&family=Lora:ital,wght@0,400..700;1,400..700&display=swap" rel="stylesheet">
|
|
324
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
325
|
-
<title>${escHtml(chapter.meta.title)}</title>
|
|
326
|
-
${chapter.meta.description ? `<meta name="description" content="${escHtml(chapter.meta.description)}">` : ""}
|
|
327
|
-
${opts.head ?? ""}
|
|
328
|
-
<style>
|
|
329
|
-
${opts.font ? `:root { --font-sans: ${opts.font}, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }` : ""}
|
|
330
|
-
${pageCSS()}
|
|
331
|
-
${chapterStyles}
|
|
332
|
-
</style>
|
|
333
|
-
</head>
|
|
334
|
-
<body class="bk-layout-${layout} bk-density-${density} bk-tone-${tone}">
|
|
335
|
-
<div class="bk-shell">
|
|
336
|
-
<aside class="bk-sidebar">
|
|
337
|
-
<div class="bk-sidebar-inner">
|
|
338
|
-
<div class="bk-sidebar-header">
|
|
339
|
-
<div style="margin-top: 8px;"></div>
|
|
340
|
-
<div class="bk-sidebar-title">${escHtml(chapter.meta.title)}</div>
|
|
341
|
-
</div>
|
|
342
|
-
<nav class="bk-nav">${navHtml}</nav>
|
|
343
|
-
<div class="bk-sidebar-footer">
|
|
344
|
-
<button class="bk-icon-btn bk-settings-button" id="bk-settings-button" type="button" aria-expanded="false" aria-controls="bk-theme-panel" title="Display settings">
|
|
345
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
346
|
-
<span class="bk-sr-only">Display settings</span>
|
|
347
|
-
</button>
|
|
348
|
-
<div class="bk-theme-panel" id="bk-theme-panel" aria-label="Display settings" hidden>
|
|
349
|
-
<div class="bk-theme-row">
|
|
350
|
-
<span>Theme</span>
|
|
351
|
-
<div class="bk-segmented-control" id="bk-theme-icons">
|
|
352
|
-
<button type="button" class="bk-segment-btn ${theme === "light" ? "active" : ""}" data-theme="light" title="Light" aria-label="Light theme">
|
|
353
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
|
|
354
|
-
</button>
|
|
355
|
-
<button type="button" class="bk-segment-btn ${theme === "auto" ? "active" : (!theme ? "active" : "")}" data-theme="auto" title="System" aria-label="System theme">
|
|
356
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
|
|
357
|
-
</button>
|
|
358
|
-
<button type="button" class="bk-segment-btn ${theme === "dark" ? "active" : ""}" data-theme="dark" title="Dark" aria-label="Dark theme">
|
|
359
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
|
|
360
|
-
</button>
|
|
361
|
-
</div>
|
|
362
|
-
</div>
|
|
363
|
-
<div class="bk-theme-row">
|
|
364
|
-
<span>Palette</span>
|
|
365
|
-
<div class="bk-segmented-control" id="bk-palette-icons">
|
|
366
|
-
<button type="button" class="bk-segment-btn ${palette === "ink" ? "active" : ""}" data-palette="ink" title="Ink" aria-label="Ink palette">
|
|
367
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"></path></svg>
|
|
368
|
-
</button>
|
|
369
|
-
<button type="button" class="bk-segment-btn ${palette === "field" ? "active" : ""}" data-palette="field" title="Field" aria-label="Field palette">
|
|
370
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"></path><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 12"></path></svg>
|
|
371
|
-
</button>
|
|
372
|
-
<button type="button" class="bk-segment-btn ${palette === "ember" ? "active" : ""}" data-palette="ember" title="Ember" aria-label="Ember palette">
|
|
373
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8.5 14.5A2.5 2.5 0 0011 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 11-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 002.5 2.5z"></path></svg>
|
|
374
|
-
</button>
|
|
375
|
-
</div>
|
|
376
|
-
</div>
|
|
377
|
-
<div class="bk-theme-row">
|
|
378
|
-
<span>UI</span>
|
|
379
|
-
<div class="bk-segmented-control" id="bk-ui-icons">
|
|
380
|
-
<button type="button" class="bk-segment-btn ${ui === 'standard' ? 'active' : ''}" data-ui="standard" title="Standard" aria-label="Standard UI">
|
|
381
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="4" ry="4"></rect><line x1="9" y1="3" x2="9" y2="21"></line></svg>
|
|
382
|
-
</button>
|
|
383
|
-
<button type="button" class="bk-segment-btn ${ui === 'neo' ? 'active' : ''}" data-ui="neo" title="Neo Brutalist" aria-label="Neo UI">
|
|
384
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><rect x="3" y="3" width="18" height="18"></rect><path d="M3 10h18"></path><path d="M10 10v11"></path></svg>
|
|
385
|
-
</button>
|
|
386
|
-
<button type="button" class="bk-segment-btn ${ui === 'playful' ? 'active' : ''}" data-ui="playful" title="Playful" aria-label="Playful UI">
|
|
387
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="6" ry="6"></rect><circle cx="8.5" cy="8.5" r="1.5" fill="currentColor"></circle><circle cx="15.5" cy="15.5" r="1.5" fill="currentColor"></circle></svg>
|
|
388
|
-
</button>
|
|
389
|
-
</div>
|
|
390
|
-
</div>
|
|
391
|
-
</div>
|
|
392
|
-
</div>
|
|
393
|
-
</div>
|
|
394
|
-
</aside>
|
|
395
|
-
<button class="bk-sidebar-collapse-floating" id="bk-sidebar-collapse" aria-label="Collapse sidebar">
|
|
396
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
|
|
397
|
-
</button>
|
|
398
|
-
<main class="bk-main">
|
|
399
|
-
<button class="bk-sidebar-expand" id="bk-sidebar-expand" type="button" aria-label="Expand sidebar">
|
|
400
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
|
|
401
|
-
</button>
|
|
319
|
+
const contentHtml = `
|
|
402
320
|
<article class="bk-content" style="max-width: 1000px; margin: 0 auto;">
|
|
403
321
|
<header class="bk-hero" style="border-bottom: none;">
|
|
404
322
|
<p class="bk-eyebrow">Chapter</p>
|
|
@@ -407,11 +325,15 @@ ${chapterStyles}
|
|
|
407
325
|
</header>
|
|
408
326
|
${timelineHtml}
|
|
409
327
|
</article>
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
328
|
+
`;
|
|
329
|
+
|
|
330
|
+
const headHtml = `<style>${chapterStyles}</style>`;
|
|
331
|
+
|
|
332
|
+
return renderLayout(
|
|
333
|
+
chapter.meta.title,
|
|
334
|
+
chapter.meta.description,
|
|
335
|
+
navHtml,
|
|
336
|
+
contentHtml,
|
|
337
|
+
{ ...opts, head: (opts.head ?? "") + headHtml }
|
|
338
|
+
);
|
|
417
339
|
}
|
package/src/renderer/markdown.ts
CHANGED
|
@@ -52,15 +52,16 @@ function mdToHtml(md: string): { html: string; title: string; headings: { id: st
|
|
|
52
52
|
|
|
53
53
|
// Restore code blocks
|
|
54
54
|
codeBlocks.forEach((match, id) => {
|
|
55
|
-
processedMd = processedMd.
|
|
55
|
+
processedMd = processedMd.replaceAll(`@@BK_CODE_${id}@@`, () => match);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
const headings: { id: string; text: string; level: number }[] = [];
|
|
59
|
+
const idPrefix = Math.random().toString(36).substring(2, 6);
|
|
59
60
|
let headingIdCounter = 0;
|
|
60
61
|
|
|
61
62
|
const renderer = new marked.Renderer();
|
|
62
63
|
renderer.heading = ({ tokens, depth, text }) => {
|
|
63
|
-
const id = `bk-heading-${headingIdCounter++}`;
|
|
64
|
+
const id = `bk-heading-${idPrefix}-${headingIdCounter++}`;
|
|
64
65
|
if (depth === 1 || depth === 2) {
|
|
65
66
|
const plainText = text.replace(/<[^>]+>/g, "");
|
|
66
67
|
headings.push({ id, text: plainText, level: depth });
|
|
@@ -77,12 +78,12 @@ function mdToHtml(md: string): { html: string; title: string; headings: { id: st
|
|
|
77
78
|
displayMode: true,
|
|
78
79
|
});
|
|
79
80
|
// marked might wrap block placeholders in <p>
|
|
80
|
-
html = html.
|
|
81
|
+
html = html.replaceAll(
|
|
81
82
|
`<p>@@BK_MATH_BLOCK_${id}@@</p>`,
|
|
82
83
|
() => `<div class="bk-math-block">${rendered}</div>`,
|
|
83
84
|
);
|
|
84
85
|
// Fallback if not wrapped in <p>
|
|
85
|
-
html = html.
|
|
86
|
+
html = html.replaceAll(
|
|
86
87
|
`@@BK_MATH_BLOCK_${id}@@`,
|
|
87
88
|
() => `<div class="bk-math-block">${rendered}</div>`,
|
|
88
89
|
);
|
|
@@ -93,7 +94,7 @@ function mdToHtml(md: string): { html: string; title: string; headings: { id: st
|
|
|
93
94
|
throwOnError: false,
|
|
94
95
|
displayMode: false,
|
|
95
96
|
});
|
|
96
|
-
html = html.
|
|
97
|
+
html = html.replaceAll(`@@BK_MATH_INLINE_${id}@@`, () => rendered);
|
|
97
98
|
});
|
|
98
99
|
|
|
99
100
|
return { html, title, headings };
|
|
@@ -149,7 +150,7 @@ function mdInline(text: string): string {
|
|
|
149
150
|
throwOnError: false,
|
|
150
151
|
displayMode: false,
|
|
151
152
|
});
|
|
152
|
-
html = html.
|
|
153
|
+
html = html.replaceAll(`@@BK_MATH_INLINE_${id}@@`, () => rendered);
|
|
153
154
|
});
|
|
154
155
|
|
|
155
156
|
return html;
|
|
@@ -161,24 +162,63 @@ function renderSimulationControls(
|
|
|
161
162
|
const props = block.props ?? {};
|
|
162
163
|
const keys = Object.keys(block.tunables ?? props).filter((key) => {
|
|
163
164
|
const value = props[key];
|
|
164
|
-
return typeof value === "number" || typeof value === "boolean";
|
|
165
|
+
return typeof value === "number" || typeof value === "boolean" || typeof value === "string";
|
|
165
166
|
});
|
|
166
167
|
|
|
167
168
|
if (!keys.length || block.controls === "observe") return "";
|
|
168
169
|
|
|
170
|
+
const controls = keys.map((key) => {
|
|
171
|
+
const value = props[key];
|
|
172
|
+
const control = block.tunables?.[key] ?? {};
|
|
173
|
+
let type = control.type;
|
|
174
|
+
if (!type) {
|
|
175
|
+
if (typeof value === "boolean") type = "boolean";
|
|
176
|
+
else if (typeof value === "string") type = "text";
|
|
177
|
+
else type = "range";
|
|
178
|
+
}
|
|
179
|
+
const label = escHtml(control.label ?? key.replace(/([A-Z])/g, " $1"));
|
|
180
|
+
return { key, value, control, type, label };
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const ranges = controls.filter((c) => c.type === "range");
|
|
184
|
+
const booleans = controls.filter((c) => c.type === "boolean");
|
|
185
|
+
const others = controls.filter((c) => c.type === "text" || c.type === "number");
|
|
186
|
+
|
|
187
|
+
const sortedControls = [...ranges, ...others, ...booleans];
|
|
188
|
+
|
|
189
|
+
let firstBooleanRendered = false;
|
|
190
|
+
|
|
169
191
|
return `<div class="bk-sim-controls" aria-label="Simulation controls">
|
|
170
|
-
${
|
|
171
|
-
.map((key) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return `<label class="bk-sim-toggle">
|
|
192
|
+
${sortedControls
|
|
193
|
+
.map(({ key, value, control, type, label }) => {
|
|
194
|
+
if (type === "boolean") {
|
|
195
|
+
const isFirst = !firstBooleanRendered;
|
|
196
|
+
firstBooleanRendered = true;
|
|
197
|
+
const extraClass = isFirst ? " bk-sim-toggle--first" : "";
|
|
198
|
+
return `<label class="bk-sim-toggle${extraClass}">
|
|
177
199
|
<input type="checkbox" data-bk-prop="${escAttr(key)}" ${value ? "checked" : ""}>
|
|
178
200
|
<span>${label}</span>
|
|
179
201
|
</label>`;
|
|
180
202
|
}
|
|
181
203
|
|
|
204
|
+
if (type === "text") {
|
|
205
|
+
return `<label class="bk-sim-text">
|
|
206
|
+
<span>${label}</span>
|
|
207
|
+
<input type="text" data-bk-prop="${escAttr(key)}" value="${escAttr(String(value))}">
|
|
208
|
+
</label>`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (type === "number") {
|
|
212
|
+
const min = control.min ?? "";
|
|
213
|
+
const max = control.max ?? "";
|
|
214
|
+
const step = control.step ?? "any";
|
|
215
|
+
return `<label class="bk-sim-number">
|
|
216
|
+
<span>${label}</span>
|
|
217
|
+
<input type="number" data-bk-prop="${escAttr(key)}" min="${min}" max="${max}" step="${step}" value="${value}">
|
|
218
|
+
</label>`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// type === "range"
|
|
182
222
|
const min = control.min ?? Math.min(0, Number(value));
|
|
183
223
|
const max = control.max ?? Math.max(10, Number(value) * 2);
|
|
184
224
|
const step = control.step ?? 1;
|
package/src/renderer/utils.ts
CHANGED
|
@@ -45,6 +45,11 @@ function resolveContent(
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
const baseDir = path.resolve(options.contentBase ?? ".");
|
|
49
|
+
if (!filePath.startsWith(baseDir) && options.strict !== false) {
|
|
50
|
+
throw new Error(`Security Error: Path traversal attempt outside contentBase: ${filePath}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
if (fs.existsSync(filePath)) {
|
|
49
54
|
const stat = fs.statSync(filePath);
|
|
50
55
|
if (stat.isFile()) {
|
|
@@ -74,14 +79,27 @@ function resolveContent(
|
|
|
74
79
|
function resolveAssetSrc(src: string, options: BuildOptions): string {
|
|
75
80
|
if (/^(https?:|data:)/.test(src)) return src;
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
const hashIndex = src.indexOf("#");
|
|
83
|
+
const queryIndex = src.indexOf("?");
|
|
84
|
+
const breakIndex = hashIndex !== -1 && queryIndex !== -1
|
|
85
|
+
? Math.min(hashIndex, queryIndex)
|
|
86
|
+
: Math.max(hashIndex, queryIndex);
|
|
87
|
+
|
|
88
|
+
let cleanSrc = src;
|
|
89
|
+
let suffix = "";
|
|
90
|
+
if (breakIndex !== -1) {
|
|
91
|
+
cleanSrc = src.substring(0, breakIndex);
|
|
92
|
+
suffix = src.substring(breakIndex);
|
|
93
|
+
}
|
|
78
94
|
|
|
79
|
-
let
|
|
80
|
-
? src
|
|
81
|
-
: path.resolve(options.contentBase ?? ".", src);
|
|
95
|
+
let isWebAbsolute = cleanSrc.startsWith("/") && !fs.existsSync(cleanSrc);
|
|
82
96
|
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
let filePath = path.isAbsolute(cleanSrc)
|
|
98
|
+
? cleanSrc
|
|
99
|
+
: path.resolve(options.contentBase ?? ".", cleanSrc);
|
|
100
|
+
|
|
101
|
+
if (cleanSrc.startsWith("/") && !fs.existsSync(filePath)) {
|
|
102
|
+
const fallbackPath = path.resolve(options.contentBase ?? ".", cleanSrc.slice(1));
|
|
85
103
|
if (fs.existsSync(fallbackPath)) {
|
|
86
104
|
filePath = fallbackPath;
|
|
87
105
|
isWebAbsolute = false; // We found it locally, so don't treat it as a web URL
|
|
@@ -112,7 +130,7 @@ function resolveAssetSrc(src: string, options: BuildOptions): string {
|
|
|
112
130
|
fs.copyFileSync(filePath, outPath);
|
|
113
131
|
}
|
|
114
132
|
|
|
115
|
-
return `assets/${filename}`;
|
|
133
|
+
return `assets/${filename}${suffix}`;
|
|
116
134
|
}
|
|
117
135
|
|
|
118
136
|
export { resolveAssetSrc, resolveContent };
|