@agent-native/core 0.178.0-nightly-20260909183543 → 0.178.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +16 -0
- package/corpus/templates/calendar/CHANGELOG.md +19 -0
- package/corpus/templates/clips/CHANGELOG.md +6 -0
- package/corpus/templates/content/CHANGELOG.md +5 -0
- package/corpus/templates/content/app/i18n-data.ts +3 -3
- package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +9 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +5 -0
- package/corpus/templates/design/CHANGELOG.md +6 -0
- package/corpus/templates/factory/CHANGELOG.md +18 -0
- package/corpus/templates/mail/CHANGELOG.md +20 -0
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +36 -152
- package/corpus/templates/slides/CHANGELOG.md +11 -0
- package/corpus/templates/slides/actions/add-slide.ts +1 -0
- package/corpus/templates/slides/actions/create-deck.ts +1 -1
- package/corpus/templates/slides/actions/export-html.ts +342 -12
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +69 -20
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +3 -2
- package/corpus/templates/slides/app/components/editor/SlideInlineEditor.tsx +49 -15
- package/corpus/templates/slides/app/global.css +27 -22
- package/corpus/templates/slides/app/hooks/use-deck-design-system.ts +19 -15
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +2 -2
- package/corpus/templates/slides/app/lib/normalize-slide-padding.ts +2 -2
- package/corpus/templates/slides/app/pages/Index.tsx +2 -2
- package/corpus/templates/slides/changelog/2026-09-09-slides-default-generation-style.md +6 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/shared/slide-background.ts +18 -2
- package/dist/cli/index.js +17 -0
- package/dist/client/org/OrgSwitcher.js +16 -21
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +3 -3
- package/dist/observability/routes.d.ts +2 -2
- package/dist/org/federation.d.ts +1 -1
- package/dist/org/federation.js +6 -4
- package/dist/org/index.d.ts +2 -1
- package/dist/org/index.js +2 -1
- package/dist/org/membership.js +1 -1
- package/dist/org/workspace-app-access.d.ts +1 -0
- package/dist/org/workspace-app-access.js +21 -7
- package/package.json +3 -3
|
@@ -2,20 +2,31 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
|
|
4
4
|
import { defineAction, fail } from "@agent-native/core/action";
|
|
5
|
+
import {
|
|
6
|
+
hydrateBuilderDesignSystemReference,
|
|
7
|
+
parseBuilderDesignSystemProxyReference,
|
|
8
|
+
} from "@agent-native/core/server";
|
|
5
9
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
6
10
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
7
11
|
import { z } from "zod";
|
|
8
12
|
|
|
9
13
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
14
|
+
import { sanitizeCssValue } from "../app/lib/sanitize-slide-html.js";
|
|
10
15
|
import {
|
|
11
16
|
safeGeneratedFilename,
|
|
12
17
|
tenantExportDir,
|
|
13
18
|
} from "../server/lib/tenant-files.js";
|
|
19
|
+
import type { DesignSystemData } from "../shared/api.js";
|
|
14
20
|
import {
|
|
15
21
|
type AspectRatio,
|
|
16
22
|
getAspectRatioDims,
|
|
17
23
|
ASPECT_RATIO_VALUES,
|
|
18
24
|
} from "../shared/aspect-ratios.js";
|
|
25
|
+
import {
|
|
26
|
+
backgroundCssValue,
|
|
27
|
+
DEFAULT_SLIDE_BACKGROUND,
|
|
28
|
+
resolveSlideBackground,
|
|
29
|
+
} from "../shared/slide-background.js";
|
|
19
30
|
|
|
20
31
|
/**
|
|
21
32
|
* Minimal server-side HTML sanitizer for exported slide content.
|
|
@@ -37,17 +48,254 @@ function sanitizeSlideContent(html: string): string {
|
|
|
37
48
|
.replace(/\s+srcdoc\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
|
|
38
49
|
}
|
|
39
50
|
|
|
51
|
+
function safeCssToken(
|
|
52
|
+
value: unknown,
|
|
53
|
+
fallback: string,
|
|
54
|
+
builderTokenValues?: Record<string, string>,
|
|
55
|
+
): string {
|
|
56
|
+
if (typeof value !== "string") return fallback;
|
|
57
|
+
const sanitized = sanitizeCssValue(value);
|
|
58
|
+
if (!sanitized) return fallback;
|
|
59
|
+
const resolved = sanitized
|
|
60
|
+
.replace(/[{}<>;]/g, "")
|
|
61
|
+
.replace(/var\(\s*(--[a-zA-Z][\w-]*)\s*\)/g, (_, name: string) => {
|
|
62
|
+
const replacement = builderTokenValues?.[name];
|
|
63
|
+
const safeReplacement = replacement
|
|
64
|
+
? sanitizeCssValue(replacement)
|
|
65
|
+
: null;
|
|
66
|
+
return safeReplacement && !/[{}<>;]/.test(safeReplacement)
|
|
67
|
+
? safeReplacement
|
|
68
|
+
: `var(${name})`;
|
|
69
|
+
})
|
|
70
|
+
.trim();
|
|
71
|
+
const finalValue = sanitizeCssValue(resolved)?.replace(/[{}<>;]/g, "");
|
|
72
|
+
return !finalValue || /var\(\s*--/i.test(finalValue)
|
|
73
|
+
? fallback
|
|
74
|
+
: finalValue.slice(0, 240) || fallback;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const STANDALONE_TAILWIND_BACKGROUNDS: Record<string, string> = {
|
|
78
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
79
|
+
"bg-black": "#000000",
|
|
80
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
81
|
+
"bg-white": "#FFFFFF",
|
|
82
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
83
|
+
"bg-slate-900": "#0F172A",
|
|
84
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
85
|
+
"bg-slate-950": "#020617",
|
|
86
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
87
|
+
"bg-gray-900": "#111827",
|
|
88
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
89
|
+
"bg-zinc-900": "#18181B",
|
|
90
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
91
|
+
"bg-neutral-900": "#171717",
|
|
92
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
93
|
+
"bg-stone-900": "#1C1917",
|
|
94
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
95
|
+
"bg-red-500": "#EF4444",
|
|
96
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
97
|
+
"bg-orange-500": "#F97316",
|
|
98
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
99
|
+
"bg-amber-500": "#F59E0B",
|
|
100
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
101
|
+
"bg-yellow-400": "#FACC15",
|
|
102
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
103
|
+
"bg-lime-500": "#84CC16",
|
|
104
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
105
|
+
"bg-green-500": "#22C55E",
|
|
106
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
107
|
+
"bg-emerald-500": "#10B981",
|
|
108
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
109
|
+
"bg-teal-500": "#14B8A6",
|
|
110
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
111
|
+
"bg-cyan-500": "#06B6D4",
|
|
112
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
113
|
+
"bg-sky-500": "#0EA5E9",
|
|
114
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
115
|
+
"bg-blue-500": "#3B82F6",
|
|
116
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
117
|
+
"bg-indigo-500": "#6366F1",
|
|
118
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
119
|
+
"bg-indigo-950": "#1E1B4B",
|
|
120
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
121
|
+
"bg-violet-500": "#8B5CF6",
|
|
122
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
123
|
+
"bg-purple-600": "#9333EA",
|
|
124
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
125
|
+
"bg-fuchsia-500": "#D946EF",
|
|
126
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
127
|
+
"bg-pink-500": "#EC4899",
|
|
128
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
129
|
+
"bg-rose-500": "#F43F5E",
|
|
130
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
131
|
+
"bg-slate-700": "#334155",
|
|
132
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
133
|
+
"bg-slate-800": "#1E293B",
|
|
134
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
135
|
+
"bg-zinc-50": "#FAFAFA",
|
|
136
|
+
// guard:allow-raw-color - standalone Tailwind background compatibility
|
|
137
|
+
"bg-gray-100": "#F3F4F6",
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const TAILWIND_GRADIENT_DIRECTIONS: Record<string, string> = {
|
|
141
|
+
"bg-gradient-to-t": "to top",
|
|
142
|
+
"bg-gradient-to-tr": "to top right",
|
|
143
|
+
"bg-gradient-to-r": "to right",
|
|
144
|
+
"bg-gradient-to-br": "to bottom right",
|
|
145
|
+
"bg-gradient-to-b": "to bottom",
|
|
146
|
+
"bg-gradient-to-bl": "to bottom left",
|
|
147
|
+
"bg-gradient-to-l": "to left",
|
|
148
|
+
"bg-gradient-to-tl": "to top left",
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
function standaloneBackgroundCssValue(value: string): string {
|
|
152
|
+
const cssValue = backgroundCssValue(value);
|
|
153
|
+
if (cssValue) return cssValue;
|
|
154
|
+
const classes = value.split(/\s+/);
|
|
155
|
+
const solidBackground = classes.find(
|
|
156
|
+
(className) => STANDALONE_TAILWIND_BACKGROUNDS[className],
|
|
157
|
+
);
|
|
158
|
+
if (solidBackground) return STANDALONE_TAILWIND_BACKGROUNDS[solidBackground];
|
|
159
|
+
const direction = classes.find(
|
|
160
|
+
(className) => TAILWIND_GRADIENT_DIRECTIONS[className],
|
|
161
|
+
);
|
|
162
|
+
if (direction) {
|
|
163
|
+
const stops = classes
|
|
164
|
+
.filter((className) => /^(?:from|via|to)-/.test(className))
|
|
165
|
+
.map((className) => {
|
|
166
|
+
const [, color, shade] =
|
|
167
|
+
className.match(/^(?:from|via|to)-([\w]+)-([\d]+)$/) ?? [];
|
|
168
|
+
return color && shade
|
|
169
|
+
? STANDALONE_TAILWIND_BACKGROUNDS[`bg-${color}-${shade}`]
|
|
170
|
+
: undefined;
|
|
171
|
+
})
|
|
172
|
+
.filter((stop): stop is string => Boolean(stop));
|
|
173
|
+
if (stops.length >= 2) {
|
|
174
|
+
return `linear-gradient(${TAILWIND_GRADIENT_DIRECTIONS[direction]}, ${stops.join(", ")})`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return STANDALONE_TAILWIND_BACKGROUNDS[value] ?? DEFAULT_SLIDE_BACKGROUND;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function isDarkStandaloneBackground(value: string): boolean {
|
|
181
|
+
const colors = [
|
|
182
|
+
...value.matchAll(/#([\da-f]{3,8})\b/gi),
|
|
183
|
+
...value.matchAll(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/gi),
|
|
184
|
+
];
|
|
185
|
+
if (colors.length === 0) return false;
|
|
186
|
+
return colors.every((match) => {
|
|
187
|
+
const channels = match[0].startsWith("#")
|
|
188
|
+
? match[1].length === 3 || match[1].length === 4
|
|
189
|
+
? match[1]
|
|
190
|
+
.slice(0, 3)
|
|
191
|
+
.split("")
|
|
192
|
+
.map((channel) => parseInt(channel + channel, 16))
|
|
193
|
+
: [
|
|
194
|
+
match[1].slice(0, 2),
|
|
195
|
+
match[1].slice(2, 4),
|
|
196
|
+
match[1].slice(4, 6),
|
|
197
|
+
].map((channel) => parseInt(channel, 16))
|
|
198
|
+
: match.slice(1, 4).map(Number);
|
|
199
|
+
return (
|
|
200
|
+
channels[0] * 0.299 + channels[1] * 0.587 + channels[2] * 0.114 < 128
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function googleFontHref(font: unknown): string | undefined {
|
|
206
|
+
if (typeof font !== "string") return undefined;
|
|
207
|
+
const family = font.split(",", 1)[0]?.replace(/["']/g, "").trim();
|
|
208
|
+
const supported = [
|
|
209
|
+
"DM Sans",
|
|
210
|
+
"Inter",
|
|
211
|
+
"Manrope",
|
|
212
|
+
"Montserrat",
|
|
213
|
+
"Open Sans",
|
|
214
|
+
"Poppins",
|
|
215
|
+
"Plus Jakarta Sans",
|
|
216
|
+
"Roboto",
|
|
217
|
+
"Space Grotesk",
|
|
218
|
+
"Work Sans",
|
|
219
|
+
];
|
|
220
|
+
if (
|
|
221
|
+
!family ||
|
|
222
|
+
!supported.some(
|
|
223
|
+
(candidate) => candidate.toLowerCase() === family.toLowerCase(),
|
|
224
|
+
)
|
|
225
|
+
) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
return `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family).replace(/%20/g, "+")}:wght@400;700&display=swap`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function standaloneDesignSystemVars(
|
|
232
|
+
designSystem: DesignSystemData | undefined,
|
|
233
|
+
slideBackground: string,
|
|
234
|
+
builderTokenValues?: Record<string, string>,
|
|
235
|
+
): string {
|
|
236
|
+
const colors = designSystem?.colors;
|
|
237
|
+
const typography = designSystem?.typography;
|
|
238
|
+
const borders = designSystem?.borders;
|
|
239
|
+
const standaloneBackground = standaloneBackgroundCssValue(slideBackground);
|
|
240
|
+
const safeBackground = safeCssToken(
|
|
241
|
+
standaloneBackground,
|
|
242
|
+
DEFAULT_SLIDE_BACKGROUND,
|
|
243
|
+
builderTokenValues,
|
|
244
|
+
);
|
|
245
|
+
const darkBackground = isDarkStandaloneBackground(safeBackground);
|
|
246
|
+
return [
|
|
247
|
+
`--ds-bg: ${safeBackground}`,
|
|
248
|
+
// guard:allow-raw-color - standalone export fallback palette
|
|
249
|
+
// guard:allow-raw-color - standalone export dark-background fallback
|
|
250
|
+
`--ds-text: ${safeCssToken(colors?.text, darkBackground ? "#FFFFFF" : "#1F2933", builderTokenValues)}`,
|
|
251
|
+
// guard:allow-raw-color - standalone export fallback palette
|
|
252
|
+
// guard:allow-raw-color - standalone export dark-background fallback
|
|
253
|
+
`--ds-text-muted: ${safeCssToken(colors?.textMuted, darkBackground ? "rgba(255, 255, 255, 0.72)" : "#667085", builderTokenValues)}`,
|
|
254
|
+
// guard:allow-raw-color - standalone export fallback palette
|
|
255
|
+
`--ds-accent: ${safeCssToken(colors?.accent, "#2457D6", builderTokenValues)}`,
|
|
256
|
+
// guard:allow-raw-color - standalone export fallback palette
|
|
257
|
+
`--ds-primary: ${safeCssToken(colors?.primary, "#2457D6", builderTokenValues)}`,
|
|
258
|
+
// guard:allow-raw-color - standalone export fallback palette
|
|
259
|
+
`--ds-secondary: ${safeCssToken(colors?.secondary, "#C85C3A", builderTokenValues)}`,
|
|
260
|
+
// guard:allow-raw-color - standalone export fallback palette
|
|
261
|
+
`--ds-surface: ${safeCssToken(colors?.surface, "#FFFFFF", builderTokenValues)}`,
|
|
262
|
+
`--ds-heading-font: ${safeCssToken(typography?.headingFont, "Inter, sans-serif", builderTokenValues)}`,
|
|
263
|
+
`--ds-body-font: ${safeCssToken(typography?.bodyFont, "Inter, sans-serif", builderTokenValues)}`,
|
|
264
|
+
`--ds-radius: ${safeCssToken(borders?.radius, "14px", builderTokenValues)}`,
|
|
265
|
+
...Object.entries(builderTokenValues ?? {})
|
|
266
|
+
.filter(
|
|
267
|
+
([name, value]) =>
|
|
268
|
+
/^--[a-zA-Z][\w-]*$/.test(name) && typeof value === "string",
|
|
269
|
+
)
|
|
270
|
+
.map(
|
|
271
|
+
([name, value]) =>
|
|
272
|
+
`${name}: ${safeCssToken(value, "initial", builderTokenValues)}`,
|
|
273
|
+
),
|
|
274
|
+
].join("; ");
|
|
275
|
+
}
|
|
276
|
+
|
|
40
277
|
function buildStandaloneHtml(
|
|
41
278
|
title: string,
|
|
42
|
-
slides: Array<{
|
|
279
|
+
slides: Array<{
|
|
280
|
+
id: string;
|
|
281
|
+
content: string;
|
|
282
|
+
notes?: string;
|
|
283
|
+
background?: string;
|
|
284
|
+
}>,
|
|
43
285
|
aspectRatio?: AspectRatio,
|
|
286
|
+
designSystem?: DesignSystemData,
|
|
287
|
+
builderTokenValues?: Record<string, string>,
|
|
44
288
|
): string {
|
|
45
289
|
const dims = getAspectRatioDims(aspectRatio);
|
|
46
290
|
const slideHtmlSections = slides
|
|
47
|
-
.map(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
291
|
+
.map((slide, i) => {
|
|
292
|
+
const slideBackground = resolveSlideBackground(
|
|
293
|
+
slide.background,
|
|
294
|
+
designSystem,
|
|
295
|
+
);
|
|
296
|
+
const style = `display: ${i === 0 ? "flex" : "none"}; background: ${safeCssToken(standaloneBackgroundCssValue(slideBackground), DEFAULT_SLIDE_BACKGROUND, builderTokenValues)}; ${standaloneDesignSystemVars(designSystem, slideBackground, builderTokenValues)}`;
|
|
297
|
+
return `<section class="slide" data-index="${i}" style="${escapeHtml(style)}">${sanitizeSlideContent(slide.content)}</section>`;
|
|
298
|
+
})
|
|
51
299
|
.join("\n");
|
|
52
300
|
|
|
53
301
|
return `<!DOCTYPE html>
|
|
@@ -56,9 +304,16 @@ function buildStandaloneHtml(
|
|
|
56
304
|
<meta charset="UTF-8">
|
|
57
305
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
58
306
|
<title>${escapeHtml(title)}</title>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
307
|
+
${[
|
|
308
|
+
googleFontHref(designSystem?.typography?.headingFont),
|
|
309
|
+
googleFontHref(designSystem?.typography?.bodyFont),
|
|
310
|
+
]
|
|
311
|
+
.filter(
|
|
312
|
+
(href, index, all): href is string =>
|
|
313
|
+
Boolean(href) && all.indexOf(href) === index,
|
|
314
|
+
)
|
|
315
|
+
.map((href) => `<link rel="stylesheet" href="${escapeHtml(href)}">`)
|
|
316
|
+
.join("\n ")}
|
|
62
317
|
<style>
|
|
63
318
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
64
319
|
|
|
@@ -66,7 +321,7 @@ function buildStandaloneHtml(
|
|
|
66
321
|
width: 100%; height: 100%;
|
|
67
322
|
background: #111;
|
|
68
323
|
overflow: hidden;
|
|
69
|
-
font-family: '
|
|
324
|
+
font-family: 'Inter', sans-serif;
|
|
70
325
|
}
|
|
71
326
|
|
|
72
327
|
.viewport {
|
|
@@ -88,7 +343,9 @@ function buildStandaloneHtml(
|
|
|
88
343
|
.slide {
|
|
89
344
|
width: ${dims.width}px;
|
|
90
345
|
height: ${dims.height}px;
|
|
91
|
-
background:
|
|
346
|
+
background: var(--ds-bg);
|
|
347
|
+
color: var(--ds-text);
|
|
348
|
+
font-family: var(--ds-body-font);
|
|
92
349
|
overflow: hidden;
|
|
93
350
|
position: absolute;
|
|
94
351
|
top: 0;
|
|
@@ -106,6 +363,31 @@ function buildStandaloneHtml(
|
|
|
106
363
|
width: 100%;
|
|
107
364
|
height: 100%;
|
|
108
365
|
box-sizing: border-box;
|
|
366
|
+
padding: 64px 80px;
|
|
367
|
+
display: flex;
|
|
368
|
+
flex-direction: column;
|
|
369
|
+
color: var(--ds-text);
|
|
370
|
+
background: var(--ds-bg);
|
|
371
|
+
font-family: var(--ds-body-font);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.fmd-slide[data-imported-pptx="true"], .fmd-slide[data-imported-pdf="true"] { padding: 0; }
|
|
375
|
+
|
|
376
|
+
.fmd-slide h1, .fmd-slide h2, .fmd-slide h3 {
|
|
377
|
+
color: var(--ds-text);
|
|
378
|
+
font-family: var(--ds-heading-font);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.fmd-slide h1 { font-size: 56px; line-height: 1.05; }
|
|
382
|
+
.fmd-slide h2 { font-size: 34px; line-height: 1.12; }
|
|
383
|
+
.fmd-slide h3 { font-size: 24px; line-height: 1.2; }
|
|
384
|
+
.fmd-slide p, .fmd-slide li { color: var(--ds-text-muted); }
|
|
385
|
+
.fmd-slide strong { color: var(--ds-text); }
|
|
386
|
+
.fmd-slide hr { border-color: var(--ds-accent); }
|
|
387
|
+
|
|
388
|
+
.fmd-slide .fmd-img-placeholder {
|
|
389
|
+
border-radius: var(--ds-radius);
|
|
390
|
+
background: var(--ds-surface);
|
|
109
391
|
}
|
|
110
392
|
|
|
111
393
|
.bottom-bar {
|
|
@@ -120,7 +402,7 @@ function buildStandaloneHtml(
|
|
|
120
402
|
align-items: center;
|
|
121
403
|
justify-content: space-between;
|
|
122
404
|
padding: 0 20px;
|
|
123
|
-
font-family: '
|
|
405
|
+
font-family: 'Inter', sans-serif;
|
|
124
406
|
font-size: 13px;
|
|
125
407
|
color: rgba(255, 255, 255, 0.5);
|
|
126
408
|
z-index: 100;
|
|
@@ -258,6 +540,20 @@ function escapeHtml(str: string): string {
|
|
|
258
540
|
.replace(/"/g, """);
|
|
259
541
|
}
|
|
260
542
|
|
|
543
|
+
function parseStoredDesignSystem(
|
|
544
|
+
rawData: string,
|
|
545
|
+
): DesignSystemData | undefined {
|
|
546
|
+
try {
|
|
547
|
+
const parsed = JSON.parse(rawData);
|
|
548
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
549
|
+
? (parsed as DesignSystemData)
|
|
550
|
+
: undefined;
|
|
551
|
+
} catch (error) {
|
|
552
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
553
|
+
return undefined;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
261
557
|
export default defineAction({
|
|
262
558
|
description:
|
|
263
559
|
"Export a deck as a standalone HTML file with built-in keyboard navigation. Returns a download URL for the generated file.",
|
|
@@ -296,7 +592,41 @@ export default defineAction({
|
|
|
296
592
|
});
|
|
297
593
|
}
|
|
298
594
|
|
|
299
|
-
const
|
|
595
|
+
const designSystemId = row.designSystemId ?? deckData.designSystemId;
|
|
596
|
+
let designSystem: DesignSystemData | undefined;
|
|
597
|
+
let builderTokenValues: Record<string, string> | undefined;
|
|
598
|
+
if (typeof designSystemId === "string" && designSystemId.trim()) {
|
|
599
|
+
const designSystemAccess = await resolveAccess(
|
|
600
|
+
"design-system",
|
|
601
|
+
designSystemId,
|
|
602
|
+
);
|
|
603
|
+
const rawData = designSystemAccess?.resource?.data;
|
|
604
|
+
if (typeof rawData === "string") {
|
|
605
|
+
designSystem = parseStoredDesignSystem(rawData);
|
|
606
|
+
const builderReference =
|
|
607
|
+
parseBuilderDesignSystemProxyReference(rawData);
|
|
608
|
+
if (builderReference) {
|
|
609
|
+
try {
|
|
610
|
+
builderTokenValues = (
|
|
611
|
+
await hydrateBuilderDesignSystemReference(builderReference)
|
|
612
|
+
).tokenValues;
|
|
613
|
+
} catch (error) {
|
|
614
|
+
console.warn(
|
|
615
|
+
"Builder design-system export hydration failed",
|
|
616
|
+
error,
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const html = buildStandaloneHtml(
|
|
624
|
+
row.title,
|
|
625
|
+
slides,
|
|
626
|
+
aspectRatio,
|
|
627
|
+
designSystem,
|
|
628
|
+
builderTokenValues,
|
|
629
|
+
);
|
|
300
630
|
const filename = safeGeneratedFilename(row.title, ".html");
|
|
301
631
|
|
|
302
632
|
// Disk write is only useful when the same process can later serve the
|
|
@@ -23,6 +23,10 @@ import {
|
|
|
23
23
|
} from "@/lib/sanitize-slide-html";
|
|
24
24
|
|
|
25
25
|
import type { DesignSystemData } from "../../../shared/api";
|
|
26
|
+
import {
|
|
27
|
+
backgroundCssValue,
|
|
28
|
+
resolveSlideBackground,
|
|
29
|
+
} from "../../../shared/slide-background";
|
|
26
30
|
import { ExcalidrawThumbnail, parseExcalidrawData } from "./ExcalidrawSlide";
|
|
27
31
|
import { MermaidRenderer } from "./MermaidRenderer";
|
|
28
32
|
|
|
@@ -54,6 +58,29 @@ export const layoutClasses: Record<string, string> = {
|
|
|
54
58
|
blank: "flex flex-col",
|
|
55
59
|
};
|
|
56
60
|
|
|
61
|
+
function isDarkSlideBackground(value: string): boolean {
|
|
62
|
+
if (
|
|
63
|
+
/^(?:bg-black|bg-(?:slate|gray|zinc|neutral|stone)-(?:900|950))$/i.test(
|
|
64
|
+
value,
|
|
65
|
+
)
|
|
66
|
+
) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
const match = value.match(/^#([\da-f]{3,8})$/i);
|
|
70
|
+
if (!match) return /(?:^|-)black(?:$|\s)/i.test(value);
|
|
71
|
+
const hex = match[1];
|
|
72
|
+
const channels =
|
|
73
|
+
hex.length === 3 || hex.length === 4
|
|
74
|
+
? hex
|
|
75
|
+
.slice(0, 3)
|
|
76
|
+
.split("")
|
|
77
|
+
.map((channel) => parseInt(channel + channel, 16))
|
|
78
|
+
: [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)].map((channel) =>
|
|
79
|
+
parseInt(channel, 16),
|
|
80
|
+
);
|
|
81
|
+
return channels[0] * 0.299 + channels[1] * 0.587 + channels[2] * 0.114 < 128;
|
|
82
|
+
}
|
|
83
|
+
|
|
57
84
|
/** Custom image component that shows skeleton while loading */
|
|
58
85
|
function LazyImage({
|
|
59
86
|
src,
|
|
@@ -878,7 +905,9 @@ function BlankSlideContent({ content }: { content: string }) {
|
|
|
878
905
|
if (mermaidBlocks.length > 0) {
|
|
879
906
|
return (
|
|
880
907
|
<div
|
|
881
|
-
className="slide-content
|
|
908
|
+
className="slide-content w-full block h-full"
|
|
909
|
+
// guard:allow-raw-color - design-system text fallback for raw HTML
|
|
910
|
+
style={{ color: "var(--ds-text, #1f2933)" }}
|
|
882
911
|
data-slide-content-scope={scopeId}
|
|
883
912
|
>
|
|
884
913
|
<MermaidHtmlContent
|
|
@@ -891,7 +920,9 @@ function BlankSlideContent({ content }: { content: string }) {
|
|
|
891
920
|
|
|
892
921
|
return (
|
|
893
922
|
<div
|
|
894
|
-
className="slide-content
|
|
923
|
+
className="slide-content w-full block h-full"
|
|
924
|
+
// guard:allow-raw-color - design-system text fallback for raw HTML
|
|
925
|
+
style={{ color: "var(--ds-text, #1f2933)" }}
|
|
895
926
|
data-slide-content-scope={scopeId}
|
|
896
927
|
dangerouslySetInnerHTML={dangerousHtml}
|
|
897
928
|
/>
|
|
@@ -962,25 +993,43 @@ export function SlideInner({
|
|
|
962
993
|
height: dims.height,
|
|
963
994
|
};
|
|
964
995
|
|
|
965
|
-
const bg = slide.background
|
|
996
|
+
const bg = resolveSlideBackground(slide.background, designSystem);
|
|
966
997
|
const isGradientClass = bg.startsWith("bg-");
|
|
967
|
-
const
|
|
998
|
+
const cssBackground = isGradientClass ? backgroundCssValue(bg) : bg;
|
|
999
|
+
const safeBackground = cssBackground ? sanitizeCssValue(cssBackground) : null;
|
|
968
1000
|
const bgStyle = safeBackground ? { background: safeBackground } : undefined;
|
|
969
1001
|
const bgClass = isGradientClass ? bg : "";
|
|
970
1002
|
const isCentered = slide.layout === "title";
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1003
|
+
const darkSlide = isDarkSlideBackground(safeBackground ?? bg);
|
|
1004
|
+
|
|
1005
|
+
const dsStyle = {
|
|
1006
|
+
"--ds-bg": safeBackground ?? "transparent",
|
|
1007
|
+
...(designSystem
|
|
1008
|
+
? {
|
|
1009
|
+
"--ds-accent": designSystem.colors.accent,
|
|
1010
|
+
"--ds-text": designSystem.colors.text,
|
|
1011
|
+
"--ds-text-muted": designSystem.colors.textMuted,
|
|
1012
|
+
"--ds-heading-font": designSystem.typography.headingFont,
|
|
1013
|
+
"--ds-body-font": designSystem.typography.bodyFont,
|
|
1014
|
+
"--ds-primary": designSystem.colors.primary,
|
|
1015
|
+
"--ds-secondary": designSystem.colors.secondary,
|
|
1016
|
+
// guard:allow-raw-color - safe placeholder surface fallback
|
|
1017
|
+
"--ds-surface":
|
|
1018
|
+
// guard:allow-raw-color - safe placeholder surface fallback
|
|
1019
|
+
sanitizeCssValue(designSystem.colors.surface) ?? "#FFFFFF",
|
|
1020
|
+
"--ds-radius": designSystem.borders.radius,
|
|
1021
|
+
}
|
|
1022
|
+
: {}),
|
|
1023
|
+
} as React.CSSProperties & Record<string, string>;
|
|
1024
|
+
if (
|
|
1025
|
+
darkSlide &&
|
|
1026
|
+
(!designSystem || isDarkSlideBackground(designSystem.colors.text))
|
|
1027
|
+
) {
|
|
1028
|
+
// guard:allow-raw-color - readable Markdown defaults on explicit dark slides
|
|
1029
|
+
dsStyle["--ds-text"] = "#FFFFFF";
|
|
1030
|
+
// guard:allow-raw-color - readable Markdown defaults on explicit dark slides
|
|
1031
|
+
dsStyle["--ds-text-muted"] = "rgba(255, 255, 255, 0.72)";
|
|
1032
|
+
}
|
|
984
1033
|
|
|
985
1034
|
const overflowByTargetRef = useRef(new Map<string, SlideOverflowInfo>());
|
|
986
1035
|
const reportTargetOverflow = useCallback(
|
|
@@ -1119,7 +1168,7 @@ export function SlideInner({
|
|
|
1119
1168
|
canvasWidth={dims.width}
|
|
1120
1169
|
canvasHeight={dims.height}
|
|
1121
1170
|
fitKey={`${slide.layoutFitRevision ?? ""}:${left}`}
|
|
1122
|
-
className="slide-content
|
|
1171
|
+
className="slide-content"
|
|
1123
1172
|
{...(slideDeclaresTextColor(left)
|
|
1124
1173
|
? { contentScope: AUTHORED_COLOR_SCOPE }
|
|
1125
1174
|
: {})}
|
|
@@ -1137,7 +1186,7 @@ export function SlideInner({
|
|
|
1137
1186
|
canvasWidth={dims.width}
|
|
1138
1187
|
canvasHeight={dims.height}
|
|
1139
1188
|
fitKey={`${slide.layoutFitRevision ?? ""}:${right}`}
|
|
1140
|
-
className="slide-content
|
|
1189
|
+
className="slide-content"
|
|
1141
1190
|
{...(slideDeclaresTextColor(right)
|
|
1142
1191
|
? { contentScope: AUTHORED_COLOR_SCOPE }
|
|
1143
1192
|
: {})}
|
|
@@ -1192,7 +1241,7 @@ export function SlideInner({
|
|
|
1192
1241
|
canvasWidth={dims.width}
|
|
1193
1242
|
canvasHeight={dims.height}
|
|
1194
1243
|
fitKey={`${slide.layoutFitRevision ?? ""}:${content}`}
|
|
1195
|
-
className="slide-content
|
|
1244
|
+
className="slide-content w-full"
|
|
1196
1245
|
{...(slideDeclaresTextColor(content)
|
|
1197
1246
|
? { contentScope: AUTHORED_COLOR_SCOPE }
|
|
1198
1247
|
: {})}
|
|
@@ -4492,7 +4492,8 @@ export default function SlideEditor({
|
|
|
4492
4492
|
if (dragSized) box.style.height = `${geometry.height}px`;
|
|
4493
4493
|
box.style.fontSize = "24px";
|
|
4494
4494
|
box.style.color = getSlideTextBoxDefaultColor(target, positioningLayer);
|
|
4495
|
-
box.style.fontFamily =
|
|
4495
|
+
box.style.fontFamily =
|
|
4496
|
+
designSystem?.typography.bodyFont ?? "Inter, sans-serif";
|
|
4496
4497
|
box.style.lineHeight = "1.3";
|
|
4497
4498
|
if (text !== ZERO_WIDTH_SPACE) {
|
|
4498
4499
|
box.style.whiteSpace = "pre-wrap";
|
|
@@ -4505,7 +4506,7 @@ export default function SlideEditor({
|
|
|
4505
4506
|
enterInlineEdit(box);
|
|
4506
4507
|
return box;
|
|
4507
4508
|
},
|
|
4508
|
-
[enterInlineEdit],
|
|
4509
|
+
[designSystem?.typography.bodyFont, enterInlineEdit],
|
|
4509
4510
|
);
|
|
4510
4511
|
|
|
4511
4512
|
const pastePlainTextAsTextBox = useCallback(
|