@classic-homes/slides 0.7.0 → 0.8.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.
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 core entry has no Node dependencies (it runs in Workers and browsers);
164
- `/node` adds file-based image loading and `buildDeckFile`. For editors,
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
+ ````
@@ -0,0 +1,7 @@
1
+ // src/generated/runtime-hash.ts
2
+ var RUNTIME_SCRIPT_HASH = "sha256-gElvQAb4VPvKu4UeyJRVKuoHYNp8NGhXwx2EhL7NB88=";
3
+
4
+ export {
5
+ RUNTIME_SCRIPT_HASH
6
+ };
7
+ //# sourceMappingURL=chunk-4NSKDQ5V.js.map
@@ -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":[]}
@@ -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
+ "&": "&amp;",
153
+ "<": "&lt;",
154
+ ">": "&gt;",
155
+ '"': "&quot;",
156
+ "'": "&#39;"
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
- var renderer;
1066
- function loadChartRenderer() {
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
- "&": "&amp;",
1104
- "<": "&lt;",
1105
- ">": "&gt;",
1106
- '"': "&quot;",
1107
- "'": "&#39;"
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
- for (const [key, req] of chartReqs) {
4049
- charts.set(
4050
- key,
4051
- await renderChartSvg(req.spec, {
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,19 +4499,21 @@ 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,
4504
4508
  CHART_TYPES,
4505
4509
  SINGLE_SERIES_CHARTS,
4506
4510
  ChartSpec,
4511
+ chartIssues,
4512
+ chartWarnings,
4507
4513
  formatValue,
4508
4514
  chartOption,
4509
- renderChartSvg,
4515
+ finishChartSvg,
4510
4516
  describeChart,
4511
- escapeHtml,
4512
- inline,
4513
- prose,
4514
4517
  parseVideoUrl,
4515
4518
  videoEmbedUrl,
4516
4519
  LAYOUTS,
@@ -4553,4 +4556,4 @@ export {
4553
4556
  inlineDeckImages,
4554
4557
  externalizeDeckImages
4555
4558
  };
4556
- //# sourceMappingURL=chunk-HI5ZCAHO.js.map
4559
+ //# sourceMappingURL=chunk-R5HURANW.js.map