@classic-homes/slides 0.7.0 → 0.8.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/README.md +21 -3
- package/dist/{chunk-UKRX7A2V.js → chunk-4LBQOMMI.js} +5 -3
- package/dist/chunk-4LBQOMMI.js.map +1 -0
- package/dist/chunk-4NSKDQ5V.js +7 -0
- package/dist/chunk-4NSKDQ5V.js.map +1 -0
- package/dist/chunk-6ARF37DO.js +38 -0
- package/dist/chunk-6ARF37DO.js.map +1 -0
- package/dist/{chunk-HI5ZCAHO.js → chunk-MCG5BFJN.js} +85 -84
- package/dist/chunk-MCG5BFJN.js.map +1 -0
- package/dist/{chunk-ORNJ4CTQ.js → chunk-QOKQWHHD.js} +2 -2
- package/dist/cli.js +7 -5
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +235 -0
- package/dist/core.js +143 -0
- package/dist/core.js.map +1 -0
- package/dist/{images-rkJZtnjl.d.ts → images-C90NCxYd.d.ts} +118 -1
- package/dist/index.d.ts +13 -320
- package/dist/index.js +13 -7
- package/dist/index.js.map +1 -1
- package/dist/{mcp-MG2E7J3I.js → mcp-ZBYNCCHN.js} +5 -4
- package/dist/{mcp-MG2E7J3I.js.map → mcp-ZBYNCCHN.js.map} +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +3 -2
- package/dist/render-default-B-QkReoS.d.ts +5 -0
- package/package.json +5 -1
- package/dist/chunk-HI5ZCAHO.js.map +0 -1
- package/dist/chunk-UKRX7A2V.js.map +0 -1
- /package/dist/{chunk-ORNJ4CTQ.js.map → chunk-QOKQWHHD.js.map} +0 -0
package/README.md
CHANGED
|
@@ -96,7 +96,8 @@ Every slide also accepts `track` (value/red, build/teal, neutral/navy),
|
|
|
96
96
|
|
|
97
97
|
Charts are rendered to static SVG with ECharts (server-side rendering, themed
|
|
98
98
|
from `@classic-homes/charts-core`), so they work offline and in print. ECharts
|
|
99
|
-
is loaded only when a deck has charts
|
|
99
|
+
is loaded only when a deck has charts, and `/core` leaves it out entirely (see
|
|
100
|
+
[As a library](#as-a-library)).
|
|
100
101
|
|
|
101
102
|
### Video and embeds
|
|
102
103
|
|
|
@@ -160,8 +161,24 @@ import { buildDeckFile, fileImageResolver } from '@classic-homes/slides/node';
|
|
|
160
161
|
const { html } = await renderDeck(deck, { resolveImage: (src) => lookUp(src) });
|
|
161
162
|
```
|
|
162
163
|
|
|
163
|
-
The
|
|
164
|
-
`/node` adds file-based image loading and `buildDeckFile`.
|
|
164
|
+
The main entry has no Node dependencies (it runs in Workers and browsers);
|
|
165
|
+
`/node` adds file-based image loading and `buildDeckFile`.
|
|
166
|
+
|
|
167
|
+
`/core` is the main entry without ECharts (about 650 KB minified), for bundles
|
|
168
|
+
that must stay small, such as a Cloudflare Worker. Its `renderDeck` draws charts
|
|
169
|
+
with the `renderCharts` option you pass: a `ChartSvgRenderer` that gets every
|
|
170
|
+
chart in the deck in one call (`{ spec, width, height, palette?, title? }`,
|
|
171
|
+
plain data that survives RPC) and returns their SVGs in the same order. Run
|
|
172
|
+
`renderChartSvgs` from the main entry wherever ECharts can live (another Worker,
|
|
173
|
+
a server) and return its output. A deck with charts and no renderer throws.
|
|
174
|
+
|
|
175
|
+
````ts
|
|
176
|
+
import { renderDeck } from '@classic-homes/slides/core';
|
|
177
|
+
|
|
178
|
+
const { html } = await renderDeck(deck, {
|
|
179
|
+
renderCharts: (charts) => env.CHARTS.renderSlideChartSvgs(charts),
|
|
180
|
+
});
|
|
181
|
+
``` For editors,
|
|
165
182
|
`layoutEditorInfo()` / `deckEditorFields()` describe every field as a widget
|
|
166
183
|
(with `hint`s for chart, grid, url, video-url and point pickers),
|
|
167
184
|
`blankSlide(layout)` gives a valid starting slide, and `renderDeck(deck,
|
|
@@ -199,3 +216,4 @@ Colours come from `@classic-homes/theme-tokens`. Fonts (Figtree, Spectral) are
|
|
|
199
216
|
embedded from `assets/fonts`. Styling lives in `src/styles.ts`; add a layout in
|
|
200
217
|
`src/layouts/` and register it in `src/layouts/index.ts` — its example is
|
|
201
218
|
tested automatically.
|
|
219
|
+
````
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
renderDeck
|
|
3
|
+
} from "./chunk-6ARF37DO.js";
|
|
1
4
|
import {
|
|
2
5
|
inlineDeckImages,
|
|
3
6
|
parseDeckSource,
|
|
4
|
-
renderDeck,
|
|
5
7
|
stringifyDeck
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MCG5BFJN.js";
|
|
7
9
|
|
|
8
10
|
// src/node/index.ts
|
|
9
11
|
import { readFile, stat, writeFile } from "fs/promises";
|
|
@@ -181,4 +183,4 @@ export {
|
|
|
181
183
|
buildDeckFile,
|
|
182
184
|
exportDeckFile
|
|
183
185
|
};
|
|
184
|
-
//# sourceMappingURL=chunk-
|
|
186
|
+
//# sourceMappingURL=chunk-4LBQOMMI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node/index.ts","../src/node/paths.ts"],"sourcesContent":["/** Node-only helpers: read deck files, inline local images, write HTML. */\nimport { readFile, stat, writeFile } from 'node:fs/promises';\nimport { dirname, extname, isAbsolute, join, resolve } from 'node:path';\nimport { confinedPath } from './paths.js';\nimport { inlineDeckImages, type InlineImagesResult } from '../images.js';\nimport { parseDeckSource, stringifyDeck } from '../parse.js';\nimport type { ImageResolver, RenderOptions, RenderResult } from '../render.js';\nimport { renderDeck } from '../render-default.js';\n\nconst MIME: Record<string, string> = {\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.png': 'image/png',\n '.webp': 'image/webp',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.avif': 'image/avif',\n};\n\n/** Largest single image we inline without warning. Big photos bloat the deck file. */\nexport const IMAGE_WARN_BYTES = 800 * 1024;\n\n/** Largest remote image we download. Anything bigger is refused, not truncated. */\nconst REMOTE_IMAGE_MAX_BYTES = 25 * 1024 * 1024;\n\n/** How long one remote image (including redirects) may take to download. */\nconst REMOTE_IMAGE_TIMEOUT_MS = 15_000;\n\nconst MAX_REDIRECTS = 5;\n\n/**\n * Downloads a remote image for inlining. Only https is fetched, redirects included, so a\n * deck (or an agent building one over MCP) can't reach plain-http hosts such as cloud\n * metadata endpoints; non-image responses, slow servers and oversized bodies are refused.\n */\nasync function fetchRemoteImage(src: string): Promise<{ type: string; buf: Buffer }> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REMOTE_IMAGE_TIMEOUT_MS);\n try {\n return await downloadImage(src, controller.signal);\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function downloadImage(src: string, signal: AbortSignal) {\n let url = src;\n let res: Response;\n for (let hop = 0; ; hop++) {\n if (new URL(url).protocol !== 'https:') throw new Error(`Image ${src} must use https.`);\n try {\n res = await fetch(url, { redirect: 'manual', signal });\n } catch (e) {\n if (signal.aborted)\n throw new Error(`Image ${src} took over ${REMOTE_IMAGE_TIMEOUT_MS / 1000}s to download.`);\n throw e;\n }\n const location = res.headers.get('location');\n if (res.status < 300 || res.status >= 400 || !location) break;\n if (hop === MAX_REDIRECTS) throw new Error(`Image ${src} redirected too many times.`);\n url = new URL(location, url).href;\n }\n if (!res.ok) throw new Error(`Image ${src} returned HTTP ${res.status}`);\n const type = (res.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase();\n if (!type.startsWith('image/'))\n throw new Error(`Image ${src} is not an image (content-type \"${type || 'none'}\").`);\n const tooBig = () =>\n new Error(\n `Image ${src} is over ${REMOTE_IMAGE_MAX_BYTES / 1024 / 1024} MB; use a smaller copy.`\n );\n if (Number(res.headers.get('content-length')) > REMOTE_IMAGE_MAX_BYTES) {\n await res.body?.cancel();\n throw tooBig();\n }\n const chunks: Uint8Array[] = [];\n let size = 0;\n if (res.body) {\n const reader = res.body.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > REMOTE_IMAGE_MAX_BYTES) {\n await reader.cancel();\n throw tooBig();\n }\n chunks.push(value);\n }\n } catch (e) {\n if (signal.aborted)\n throw new Error(`Image ${src} took over ${REMOTE_IMAGE_TIMEOUT_MS / 1000}s to download.`);\n throw e;\n }\n }\n return { type, buf: Buffer.concat(chunks) };\n}\n\nexport interface FileResolverOptions {\n /** Restrict local image reads and build paths to this root (used by the MCP server). */\n rootDir?: string;\n /**\n * Fetch https images and inline them so the deck works offline. Default true.\n * Plain-http images are never fetched; they stay remote links (with a warning).\n */\n inlineRemote?: boolean;\n onWarning?: (message: string) => void;\n}\n\n/** Resolves image paths relative to the deck file and inlines them as data URIs. */\nexport function fileImageResolver(\n baseDir: string,\n options: FileResolverOptions = {}\n): ImageResolver {\n const warn = options.onWarning ?? (() => {});\n return async (src) => {\n if (src.startsWith('data:')) return src;\n if (/^https?:/i.test(src)) {\n if (options.inlineRemote === false) return src;\n if (!/^https:/i.test(src)) {\n warn(`${src} is not https, so it was left as a remote link; use an https URL.`);\n return src;\n }\n const { type, buf } = await fetchRemoteImage(src);\n if (buf.length > IMAGE_WARN_BYTES)\n warn(`${src} is ${(buf.length / 1024).toFixed(0)} KB; consider a smaller copy.`);\n return `data:${type};base64,${buf.toString('base64')}`;\n }\n const candidate = isAbsolute(src) ? src : join(baseDir, src);\n const path = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n const type = MIME[extname(path).toLowerCase()];\n if (!type)\n throw new Error(`Unsupported image type for \"${src}\". Use jpg, png, webp, gif, svg or avif.`);\n let buf: Buffer;\n try {\n buf = await readFile(path);\n } catch {\n throw new Error(`Image not found: \"${src}\" (looked in ${path})`);\n }\n if (buf.length > IMAGE_WARN_BYTES)\n warn(`${src} is ${(buf.length / 1024).toFixed(0)} KB; consider resizing to ~2000px wide.`);\n return `data:${type};base64,${buf.toString('base64')}`;\n };\n}\n\nexport async function loadDeckFile(path: string): Promise<unknown> {\n return parseDeckSource(await readFile(path, 'utf8'));\n}\n\nexport async function saveDeckFile(path: string, deck: unknown): Promise<void> {\n const text = path.endsWith('.json') ? JSON.stringify(deck, null, 2) + '\\n' : stringifyDeck(deck);\n await writeFile(path, text);\n}\n\nexport interface BuildOptions extends Omit<RenderOptions, 'resolveImage'>, FileResolverOptions {\n out?: string;\n}\n\n/** Renders a deck file to a self-contained HTML file next to it (or at `out`). */\nexport async function buildDeckFile(\n path: string,\n options: BuildOptions = {}\n): Promise<RenderResult & { out: string; bytes: number }> {\n const abs = options.rootDir ? confinedPath(options.rootDir, resolve(path)) : resolve(path);\n const deck = await loadDeckFile(abs);\n const result = await renderDeck(deck, {\n ...options,\n resolveImage: fileImageResolver(dirname(abs), options),\n });\n const candidate = options.out\n ? resolve(options.out)\n : abs.replace(/\\.(ya?ml|json)$/i, '') + '.html';\n const out = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n await writeFile(out, result.html);\n return { ...result, out, bytes: (await stat(out)).size };\n}\n\nexport interface ExportOptions extends FileResolverOptions {\n out?: string;\n}\n\n/**\n * Writes a portable copy of a deck file: JSON with every image inlined as a\n * data URI, ready to import into a hosted deck editor. Defaults to\n * `<name>.deck.json` next to the source.\n */\nexport async function exportDeckFile(\n path: string,\n options: ExportOptions = {}\n): Promise<InlineImagesResult & { out: string; bytes: number }> {\n const abs = options.rootDir ? confinedPath(options.rootDir, resolve(path)) : resolve(path);\n const result = await inlineDeckImages(\n await loadDeckFile(abs),\n fileImageResolver(dirname(abs), options)\n );\n const candidate = options.out\n ? resolve(options.out)\n : abs.replace(/\\.(ya?ml|json)$/i, '').replace(/\\.deck$/i, '') + '.deck.json';\n const out = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n if (out === abs) throw new Error(`Refusing to overwrite the source deck ${abs}. Pass --out.`);\n await writeFile(out, JSON.stringify(result.deck) + '\\n');\n return { ...result, out, bytes: (await stat(out)).size };\n}\n\nexport { renderDeck };\n","/** Canonical containment for existing files and new output files. */\nimport { lstatSync, realpathSync } from 'node:fs';\nimport { dirname, relative, resolve, sep } from 'node:path';\n\nexport function confinedPath(root: string, path: string): string {\n const canonicalRoot = realpathSync(root);\n const absolute = resolve(path);\n let ancestor = absolute;\n for (;;) {\n try {\n const canonical = resolve(realpathSync(ancestor), relative(ancestor, absolute));\n if (canonical !== canonicalRoot && !canonical.startsWith(canonicalRoot + sep)) {\n throw new Error(`Path \"${path}\" is outside the deck root ${root}.`);\n }\n return canonical;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT' || ancestor === dirname(ancestor))\n throw error;\n if (lstatSync(ancestor, { throwIfNoEntry: false })?.isSymbolicLink()) {\n throw new Error(`Cannot follow dangling symlink \"${ancestor}\".`);\n }\n ancestor = dirname(ancestor);\n }\n }\n}\n"],"mappings":"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","names":["dirname","resolve","type","buf","resolve","dirname"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generated/runtime-hash.ts"],"sourcesContent":["// Generated by scripts/embed-fonts.ts. Do not edit.\n/** CSP source for the inline presenter runtime: script-src '<hash>'. */\nexport const RUNTIME_SCRIPT_HASH = 'sha256-gElvQAb4VPvKu4UeyJRVKuoHYNp8NGhXwx2EhL7NB88=';\n"],"mappings":";AAEO,IAAM,sBAAsB;","names":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chartOption,
|
|
3
|
+
finishChartSvg,
|
|
4
|
+
renderDeck
|
|
5
|
+
} from "./chunk-MCG5BFJN.js";
|
|
6
|
+
|
|
7
|
+
// src/chart-render.ts
|
|
8
|
+
var renderer;
|
|
9
|
+
function loadChartRenderer() {
|
|
10
|
+
renderer ??= import("./echarts-render-GDCT5QAF.js").then((m) => m.createRenderer());
|
|
11
|
+
return renderer;
|
|
12
|
+
}
|
|
13
|
+
async function renderChartSvg(spec, opts = {}) {
|
|
14
|
+
const width = opts.width ?? 960;
|
|
15
|
+
const height = opts.height ?? 520;
|
|
16
|
+
const r = await loadChartRenderer();
|
|
17
|
+
return finishChartSvg(r.render(chartOption(spec, opts.palette), width, height), spec, opts.title);
|
|
18
|
+
}
|
|
19
|
+
var renderChartSvgs = async (charts) => {
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const c of charts) out.push(await renderChartSvg(c.spec, c));
|
|
22
|
+
return out;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/render-default.ts
|
|
26
|
+
function renderDeck2(input, options = {}) {
|
|
27
|
+
return renderDeck(input, {
|
|
28
|
+
...options,
|
|
29
|
+
renderCharts: options.renderCharts ?? renderChartSvgs
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
renderChartSvg,
|
|
35
|
+
renderChartSvgs,
|
|
36
|
+
renderDeck2 as renderDeck
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=chunk-6ARF37DO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/chart-render.ts","../src/render-default.ts"],"sourcesContent":["/**\n * The built-in chart renderer: ECharts server-side SVG, in process. ECharts\n * is imported lazily (echarts-render.ts) so decks without charts never load\n * it. Only the main entry reaches this module; `/core` callers inject their\n * own `ChartSvgRenderer`.\n */\nimport type { BrandPalette } from './brand.js';\nimport {\n chartOption,\n finishChartSvg,\n type ChartRenderer,\n type ChartSpecValue,\n type ChartSvgRenderer,\n} from './charts.js';\n\nlet renderer: Promise<ChartRenderer> | undefined;\n\n/** Loads ECharts on first use. */\nexport function loadChartRenderer(): Promise<ChartRenderer> {\n renderer ??= import('./echarts-render.js').then((m) => m.createRenderer());\n return renderer;\n}\n\n/** Renders one chart spec to an accessible, responsive SVG string. */\nexport async function renderChartSvg(\n spec: ChartSpecValue,\n opts: { width?: number; height?: number; palette?: BrandPalette; title?: string } = {}\n): Promise<string> {\n const width = opts.width ?? 960;\n const height = opts.height ?? 520;\n const r = await loadChartRenderer();\n return finishChartSvg(r.render(chartOption(spec, opts.palette), width, height), spec, opts.title);\n}\n\n/** `ChartSvgRenderer` over `renderChartSvg`: the default for `renderDeck`. */\nexport const renderChartSvgs: ChartSvgRenderer = async (charts) => {\n const out: string[] = [];\n for (const c of charts) out.push(await renderChartSvg(c.spec, c));\n return out;\n};\n","/** `renderDeck` with the built-in ECharts chart renderer (main entry, CLI, node helpers). */\nimport { renderChartSvgs } from './chart-render.js';\nimport { renderDeck as renderDeckCore, type RenderOptions, type RenderResult } from './render.js';\n\nexport function renderDeck(input: unknown, options: RenderOptions = {}): Promise<RenderResult> {\n return renderDeckCore(input, {\n ...options,\n renderCharts: options.renderCharts ?? renderChartSvgs,\n });\n}\n"],"mappings":";;;;;;;AAeA,IAAI;AAGG,SAAS,oBAA4C;AAC1D,eAAa,OAAO,8BAAqB,EAAE,KAAK,CAAC,MAAM,EAAE,eAAe,CAAC;AACzE,SAAO;AACT;AAGA,eAAsB,eACpB,MACA,OAAoF,CAAC,GACpE;AACjB,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,IAAI,MAAM,kBAAkB;AAClC,SAAO,eAAe,EAAE,OAAO,YAAY,MAAM,KAAK,OAAO,GAAG,OAAO,MAAM,GAAG,MAAM,KAAK,KAAK;AAClG;AAGO,IAAM,kBAAoC,OAAO,WAAW;AACjE,QAAM,MAAgB,CAAC;AACvB,aAAW,KAAK,OAAQ,KAAI,KAAK,MAAM,eAAe,EAAE,MAAM,CAAC,CAAC;AAChE,SAAO;AACT;;;ACnCO,SAASA,YAAW,OAAgB,UAAyB,CAAC,GAA0B;AAC7F,SAAO,WAAe,OAAO;AAAA,IAC3B,GAAG;AAAA,IACH,cAAc,QAAQ,gBAAgB;AAAA,EACxC,CAAC;AACH;","names":["renderDeck"]}
|
|
@@ -147,6 +147,69 @@ function validateBrandProfile(input) {
|
|
|
147
147
|
return { ok: true, profile: parsed.data };
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// src/inline.ts
|
|
151
|
+
var ESCAPES = {
|
|
152
|
+
"&": "&",
|
|
153
|
+
"<": "<",
|
|
154
|
+
">": ">",
|
|
155
|
+
'"': """,
|
|
156
|
+
"'": "'"
|
|
157
|
+
};
|
|
158
|
+
function escapeHtml(value) {
|
|
159
|
+
return value.replace(/[&<>"']/g, (c) => ESCAPES[c]);
|
|
160
|
+
}
|
|
161
|
+
function escapeAttr(value) {
|
|
162
|
+
return escapeHtml(value);
|
|
163
|
+
}
|
|
164
|
+
function escapeCssString(value) {
|
|
165
|
+
return value.replace(/["'\\\n\r\f]/g, (c) => `\\${c.charCodeAt(0).toString(16)} `);
|
|
166
|
+
}
|
|
167
|
+
var SAFE_URL = /^(https?:|mailto:|tel:|#|\/)/i;
|
|
168
|
+
function isSafeLinkUrl(url) {
|
|
169
|
+
return SAFE_URL.test(url);
|
|
170
|
+
}
|
|
171
|
+
function inline(text) {
|
|
172
|
+
if (!text) return "";
|
|
173
|
+
text = text.replace(/[\uE000\uE001]/g, "");
|
|
174
|
+
if (!text) return "";
|
|
175
|
+
const codes = [];
|
|
176
|
+
let s = text.replace(/`([^`]+)`/g, (_, code) => {
|
|
177
|
+
codes.push(code);
|
|
178
|
+
return `\uE000${codes.length - 1}\uE000`;
|
|
179
|
+
});
|
|
180
|
+
const links = [];
|
|
181
|
+
s = s.replace(/\[([^[\]]+)\]\(([^)\s[]+)\)/g, (match, label, url) => {
|
|
182
|
+
if (!isSafeLinkUrl(url)) return label;
|
|
183
|
+
links.push(`<a href="${escapeAttr(url)}">${formatEmphasis(escapeHtml(label))}</a>`);
|
|
184
|
+
return `\uE001${links.length - 1}\uE001`;
|
|
185
|
+
});
|
|
186
|
+
s = formatEmphasis(escapeHtml(s));
|
|
187
|
+
s = s.replace(/\r?\n/g, "<br>");
|
|
188
|
+
s = s.replace(/\uE001(\d+)\uE001/g, (_, i) => links[Number(i)]);
|
|
189
|
+
s = s.replace(
|
|
190
|
+
/\uE000(\d+)\uE000/g,
|
|
191
|
+
(_, i) => `<code>${escapeHtml(codes[Number(i)])}</code>`
|
|
192
|
+
);
|
|
193
|
+
return s;
|
|
194
|
+
}
|
|
195
|
+
function formatEmphasis(escaped) {
|
|
196
|
+
return escaped.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>").replace(/(^|[^*])\*(?!\s)([^*\n]+?)(?<!\s)\*(?!\*)/g, '$1<em class="hl">$2</em>');
|
|
197
|
+
}
|
|
198
|
+
function prose(text) {
|
|
199
|
+
if (!text) return "";
|
|
200
|
+
return text.trim().split(/\n\s*\n/).map((para) => {
|
|
201
|
+
const lines = para.split(/\n/);
|
|
202
|
+
if (lines.every((l) => /^\s*[-*]\s+/.test(l))) {
|
|
203
|
+
return `<ul>${lines.map((l) => `<li>${inline(l.replace(/^\s*[-*]\s+/, ""))}</li>`).join("")}</ul>`;
|
|
204
|
+
}
|
|
205
|
+
return `<p>${inline(para)}</p>`;
|
|
206
|
+
}).join("");
|
|
207
|
+
}
|
|
208
|
+
function plain(text) {
|
|
209
|
+
if (!text) return "";
|
|
210
|
+
return text.replace(/`([^`]+)`/g, "$1").replace(/\[([^[\]]+)\]\([^)[]+\)/g, "$1").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1");
|
|
211
|
+
}
|
|
212
|
+
|
|
150
213
|
// src/theme.ts
|
|
151
214
|
import { colors as colors2 } from "@classic-homes/theme-tokens";
|
|
152
215
|
|
|
@@ -1062,17 +1125,8 @@ ${fmt(p.value)}` : p.name
|
|
|
1062
1125
|
})
|
|
1063
1126
|
};
|
|
1064
1127
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
renderer ??= import("./echarts-render-GDCT5QAF.js").then((m) => m.createRenderer());
|
|
1068
|
-
return renderer;
|
|
1069
|
-
}
|
|
1070
|
-
async function renderChartSvg(spec, opts = {}) {
|
|
1071
|
-
const width = opts.width ?? 960;
|
|
1072
|
-
const height = opts.height ?? 520;
|
|
1073
|
-
const r = await loadChartRenderer();
|
|
1074
|
-
const svg = r.render(chartOption(spec, opts.palette), width, height);
|
|
1075
|
-
const label = `${opts.title ? `${opts.title}: ` : ""}${describeChart(spec)}`.replace(
|
|
1128
|
+
function finishChartSvg(svg, spec, title2) {
|
|
1129
|
+
const label = `${title2 ? `${title2}: ` : ""}${describeChart(spec)}`.replace(
|
|
1076
1130
|
/[&<>"]/g,
|
|
1077
1131
|
(c) => `&#${c.charCodeAt(0)};`
|
|
1078
1132
|
);
|
|
@@ -1098,69 +1152,6 @@ function describeChart(spec) {
|
|
|
1098
1152
|
return `${kind} ${parts.join(". ")}.`;
|
|
1099
1153
|
}
|
|
1100
1154
|
|
|
1101
|
-
// src/inline.ts
|
|
1102
|
-
var ESCAPES = {
|
|
1103
|
-
"&": "&",
|
|
1104
|
-
"<": "<",
|
|
1105
|
-
">": ">",
|
|
1106
|
-
'"': """,
|
|
1107
|
-
"'": "'"
|
|
1108
|
-
};
|
|
1109
|
-
function escapeHtml(value) {
|
|
1110
|
-
return value.replace(/[&<>"']/g, (c) => ESCAPES[c]);
|
|
1111
|
-
}
|
|
1112
|
-
function escapeAttr(value) {
|
|
1113
|
-
return escapeHtml(value);
|
|
1114
|
-
}
|
|
1115
|
-
function escapeCssString(value) {
|
|
1116
|
-
return value.replace(/["'\\\n\r\f]/g, (c) => `\\${c.charCodeAt(0).toString(16)} `);
|
|
1117
|
-
}
|
|
1118
|
-
var SAFE_URL = /^(https?:|mailto:|tel:|#|\/)/i;
|
|
1119
|
-
function isSafeLinkUrl(url) {
|
|
1120
|
-
return SAFE_URL.test(url);
|
|
1121
|
-
}
|
|
1122
|
-
function inline(text) {
|
|
1123
|
-
if (!text) return "";
|
|
1124
|
-
text = text.replace(/[\uE000\uE001]/g, "");
|
|
1125
|
-
if (!text) return "";
|
|
1126
|
-
const codes = [];
|
|
1127
|
-
let s = text.replace(/`([^`]+)`/g, (_, code) => {
|
|
1128
|
-
codes.push(code);
|
|
1129
|
-
return `\uE000${codes.length - 1}\uE000`;
|
|
1130
|
-
});
|
|
1131
|
-
const links = [];
|
|
1132
|
-
s = s.replace(/\[([^[\]]+)\]\(([^)\s[]+)\)/g, (match, label, url) => {
|
|
1133
|
-
if (!isSafeLinkUrl(url)) return label;
|
|
1134
|
-
links.push(`<a href="${escapeAttr(url)}">${formatEmphasis(escapeHtml(label))}</a>`);
|
|
1135
|
-
return `\uE001${links.length - 1}\uE001`;
|
|
1136
|
-
});
|
|
1137
|
-
s = formatEmphasis(escapeHtml(s));
|
|
1138
|
-
s = s.replace(/\r?\n/g, "<br>");
|
|
1139
|
-
s = s.replace(/\uE001(\d+)\uE001/g, (_, i) => links[Number(i)]);
|
|
1140
|
-
s = s.replace(
|
|
1141
|
-
/\uE000(\d+)\uE000/g,
|
|
1142
|
-
(_, i) => `<code>${escapeHtml(codes[Number(i)])}</code>`
|
|
1143
|
-
);
|
|
1144
|
-
return s;
|
|
1145
|
-
}
|
|
1146
|
-
function formatEmphasis(escaped) {
|
|
1147
|
-
return escaped.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>").replace(/(^|[^*])\*(?!\s)([^*\n]+?)(?<!\s)\*(?!\*)/g, '$1<em class="hl">$2</em>');
|
|
1148
|
-
}
|
|
1149
|
-
function prose(text) {
|
|
1150
|
-
if (!text) return "";
|
|
1151
|
-
return text.trim().split(/\n\s*\n/).map((para) => {
|
|
1152
|
-
const lines = para.split(/\n/);
|
|
1153
|
-
if (lines.every((l) => /^\s*[-*]\s+/.test(l))) {
|
|
1154
|
-
return `<ul>${lines.map((l) => `<li>${inline(l.replace(/^\s*[-*]\s+/, ""))}</li>`).join("")}</ul>`;
|
|
1155
|
-
}
|
|
1156
|
-
return `<p>${inline(para)}</p>`;
|
|
1157
|
-
}).join("");
|
|
1158
|
-
}
|
|
1159
|
-
function plain(text) {
|
|
1160
|
-
if (!text) return "";
|
|
1161
|
-
return text.replace(/`([^`]+)`/g, "$1").replace(/\[([^[\]]+)\]\([^)[]+\)/g, "$1").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1");
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
1155
|
// src/video.ts
|
|
1165
1156
|
var YT = /^(?:https:\/\/)?(?:www\.|m\.)?(?:youtube\.com\/(?:watch\?(?:.*&)?v=|embed\/|shorts\/|live\/)|youtu\.be\/)([\w-]{11})/;
|
|
1166
1157
|
var VIMEO = /^(?:https:\/\/)?(?:www\.|player\.)?vimeo\.com\/(?:video\/)?(\d{6,12})/;
|
|
@@ -4045,15 +4036,25 @@ async function renderDeck(input, options = {}) {
|
|
|
4045
4036
|
})
|
|
4046
4037
|
);
|
|
4047
4038
|
const charts = /* @__PURE__ */ new Map();
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4039
|
+
if (chartReqs.size) {
|
|
4040
|
+
if (!options.renderCharts) {
|
|
4041
|
+
throw new Error(
|
|
4042
|
+
"This deck has charts but no chart renderer: pass options.renderCharts (the @classic-homes/slides/core entry has no built-in one)."
|
|
4043
|
+
);
|
|
4044
|
+
}
|
|
4045
|
+
const reqs = [...chartReqs.values()];
|
|
4046
|
+
const svgs = await options.renderCharts(
|
|
4047
|
+
reqs.map((req) => ({
|
|
4048
|
+
spec: req.spec,
|
|
4052
4049
|
width: req.width,
|
|
4053
4050
|
height: req.height,
|
|
4054
4051
|
palette: brand.stock ? void 0 : brand.palette
|
|
4055
|
-
})
|
|
4052
|
+
}))
|
|
4056
4053
|
);
|
|
4054
|
+
if (svgs.length !== reqs.length) {
|
|
4055
|
+
throw new Error(`renderCharts returned ${svgs.length} SVGs for ${reqs.length} charts.`);
|
|
4056
|
+
}
|
|
4057
|
+
reqs.forEach((req, i) => charts.set(req.key, svgs[i]));
|
|
4057
4058
|
}
|
|
4058
4059
|
const imageBudget = options.maxInlineImageBytes;
|
|
4059
4060
|
if (imageBudget !== void 0 && (!Number.isFinite(imageBudget) || imageBudget < 0)) {
|
|
@@ -4498,6 +4499,9 @@ export {
|
|
|
4498
4499
|
resolveBrand,
|
|
4499
4500
|
checkPalette,
|
|
4500
4501
|
validateBrandProfile,
|
|
4502
|
+
escapeHtml,
|
|
4503
|
+
inline,
|
|
4504
|
+
prose,
|
|
4501
4505
|
PALETTE,
|
|
4502
4506
|
LOGO_SVG,
|
|
4503
4507
|
logoLockup,
|
|
@@ -4506,11 +4510,8 @@ export {
|
|
|
4506
4510
|
ChartSpec,
|
|
4507
4511
|
formatValue,
|
|
4508
4512
|
chartOption,
|
|
4509
|
-
|
|
4513
|
+
finishChartSvg,
|
|
4510
4514
|
describeChart,
|
|
4511
|
-
escapeHtml,
|
|
4512
|
-
inline,
|
|
4513
|
-
prose,
|
|
4514
4515
|
parseVideoUrl,
|
|
4515
4516
|
videoEmbedUrl,
|
|
4516
4517
|
LAYOUTS,
|
|
@@ -4553,4 +4554,4 @@ export {
|
|
|
4553
4554
|
inlineDeckImages,
|
|
4554
4555
|
externalizeDeckImages
|
|
4555
4556
|
};
|
|
4556
|
-
//# sourceMappingURL=chunk-
|
|
4557
|
+
//# sourceMappingURL=chunk-MCG5BFJN.js.map
|