@drawnagency/primitives 0.1.110 → 0.1.112
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/{chunk-3GAG6ZXB.js → chunk-3SZNUCXI.js} +8 -4
- package/dist/chunk-4I5OO5UB.js +785 -0
- package/dist/{chunk-ALMEC3XN.js → chunk-5KNXTMUC.js} +22 -18
- package/dist/{chunk-OUYB2GCW.js → chunk-BR4MIPTM.js} +1 -57
- package/dist/chunk-CXZ5YJG4.js +85 -0
- package/dist/{chunk-RA5IOFPY.js → chunk-DNY72JZN.js} +16 -38
- package/dist/{chunk-7MI3HCOF.js → chunk-EPWBPIV6.js} +10 -6
- package/dist/chunk-FV7JQRCN.js +94 -0
- package/dist/{chunk-UVIYI4J6.js → chunk-HSXMOS5Q.js} +15 -2
- package/dist/chunk-IIDKW7AV.js +41 -0
- package/dist/chunk-IINIZBQB.js +204 -0
- package/dist/chunk-MM7KZS5C.js +30 -0
- package/dist/{chunk-VCCNKTFD.js → chunk-OGGZ5AXT.js} +3 -31
- package/dist/{chunk-XQMDWXD6.js → chunk-P3ZNCHW7.js} +1 -1
- package/dist/chunk-QQ3S3P5P.js +38 -0
- package/dist/{chunk-ELQ4C677.js → chunk-TALRVOGO.js} +9 -44
- package/dist/chunk-VAYZJKVR.js +209 -0
- package/dist/{chunk-OTDA6KWL.js → chunk-VLPELTPI.js} +1 -1
- package/dist/{chunk-7WQJ57XG.js → chunk-VT3PYGVW.js} +68 -260
- package/dist/chunk-WUJ3XNFI.js +62 -0
- package/dist/{chunk-NXGIHSL5.js → chunk-Z2LO4PSQ.js} +11 -91
- package/dist/collab/rebase.js +1 -1
- package/dist/components/chat/ChatComposer.d.ts +25 -0
- package/dist/components/chat/ChatComposer.d.ts.map +1 -0
- package/dist/components/chat/ChatPanel.d.ts +5 -4
- package/dist/components/chat/ChatPanel.d.ts.map +1 -1
- package/dist/components/chat/markdown.d.ts +14 -0
- package/dist/components/chat/markdown.d.ts.map +1 -0
- package/dist/components/editor/index.js +16 -11
- package/dist/components/primitives/EditablePlainText.d.ts.map +1 -1
- package/dist/components/primitives/index.js +15 -10
- package/dist/components/sections/brand-guide.js +11 -9
- package/dist/components/sections/register-schemas.js +19 -14
- package/dist/components/sections/register.js +19 -14
- package/dist/components/shell/AudienceChips.d.ts +10 -0
- package/dist/components/shell/AudienceChips.d.ts.map +1 -0
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsChatbot.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsDecks.d.ts +13 -0
- package/dist/components/shell/SiteSettingsDecks.d.ts.map +1 -0
- package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
- package/dist/deck/DeckComponentView.d.ts +13 -0
- package/dist/deck/DeckComponentView.d.ts.map +1 -0
- package/dist/deck/DeckPlayer.d.ts +26 -0
- package/dist/deck/DeckPlayer.d.ts.map +1 -0
- package/dist/deck/DeckSlideView.d.ts +17 -0
- package/dist/deck/DeckSlideView.d.ts.map +1 -0
- package/dist/deck/DeckStage.d.ts +16 -0
- package/dist/deck/DeckStage.d.ts.map +1 -0
- package/dist/deck/components/DeckImage.d.ts +14 -0
- package/dist/deck/components/DeckImage.d.ts.map +1 -0
- package/dist/deck/components/DeckImageGrid.d.ts +27 -0
- package/dist/deck/components/DeckImageGrid.d.ts.map +1 -0
- package/dist/deck/components/DeckRichText.d.ts +22 -0
- package/dist/deck/components/DeckRichText.d.ts.map +1 -0
- package/dist/deck/components/DeckVideo.d.ts +15 -0
- package/dist/deck/components/DeckVideo.d.ts.map +1 -0
- package/dist/deck/editing.d.ts +50 -0
- package/dist/deck/editing.d.ts.map +1 -0
- package/dist/deck/flip.d.ts +11 -0
- package/dist/deck/flip.d.ts.map +1 -0
- package/dist/deck/index.d.ts +24 -0
- package/dist/deck/index.d.ts.map +1 -0
- package/dist/deck/index.js +53 -0
- package/dist/deck/layouts/CenteredTextLayout.d.ts +12 -0
- package/dist/deck/layouts/CenteredTextLayout.d.ts.map +1 -0
- package/dist/deck/layouts/ColumnSplitLayout.d.ts +14 -0
- package/dist/deck/layouts/ColumnSplitLayout.d.ts.map +1 -0
- package/dist/deck/layouts/FullBleedImageLayout.d.ts +14 -0
- package/dist/deck/layouts/FullBleedImageLayout.d.ts.map +1 -0
- package/dist/deck/media.d.ts +33 -0
- package/dist/deck/media.d.ts.map +1 -0
- package/dist/deck/tokens.d.ts +22 -0
- package/dist/deck/tokens.d.ts.map +1 -0
- package/dist/deck/useStageScale.d.ts +10 -0
- package/dist/deck/useStageScale.d.ts.map +1 -0
- package/dist/deck-builder/DeckBuilderApp.d.ts +30 -0
- package/dist/deck-builder/DeckBuilderApp.d.ts.map +1 -0
- package/dist/deck-builder/DeckBuilderTopBar.d.ts +24 -0
- package/dist/deck-builder/DeckBuilderTopBar.d.ts.map +1 -0
- package/dist/deck-builder/DeckChatPanel.d.ts +45 -0
- package/dist/deck-builder/DeckChatPanel.d.ts.map +1 -0
- package/dist/deck-builder/DeckEditor.d.ts +37 -0
- package/dist/deck-builder/DeckEditor.d.ts.map +1 -0
- package/dist/deck-builder/DeckLibrary.d.ts +22 -0
- package/dist/deck-builder/DeckLibrary.d.ts.map +1 -0
- package/dist/deck-builder/DeckSlideSurface.d.ts +31 -0
- package/dist/deck-builder/DeckSlideSurface.d.ts.map +1 -0
- package/dist/deck-builder/deck-api.d.ts +76 -0
- package/dist/deck-builder/deck-api.d.ts.map +1 -0
- package/dist/deck-builder/deck-chat-client.d.ts +44 -0
- package/dist/deck-builder/deck-chat-client.d.ts.map +1 -0
- package/dist/deck-builder/deck-chat-history.d.ts +10 -0
- package/dist/deck-builder/deck-chat-history.d.ts.map +1 -0
- package/dist/deck-builder/index.d.ts +13 -0
- package/dist/deck-builder/index.d.ts.map +1 -0
- package/dist/deck-builder/index.js +1505 -0
- package/dist/deck-builder/starter-deck.d.ts +23 -0
- package/dist/deck-builder/starter-deck.d.ts.map +1 -0
- package/dist/deck-builder/types.d.ts +29 -0
- package/dist/deck-builder/types.d.ts.map +1 -0
- package/dist/deck-builder/useSortableSlide.d.ts +29 -0
- package/dist/deck-builder/useSortableSlide.d.ts.map +1 -0
- package/dist/index.js +70 -63
- package/dist/lib/deck-access.d.ts +86 -0
- package/dist/lib/deck-access.d.ts.map +1 -0
- package/dist/lib/deck-access.js +58 -0
- package/dist/lib/deck-dexie.d.ts +88 -0
- package/dist/lib/deck-dexie.d.ts.map +1 -0
- package/dist/lib/deck-dexie.js +22 -0
- package/dist/lib/deck-edit.d.ts +67 -0
- package/dist/lib/deck-edit.d.ts.map +1 -0
- package/dist/lib/deck-media-assets.d.ts +27 -0
- package/dist/lib/deck-media-assets.d.ts.map +1 -0
- package/dist/lib/deck-media-assets.js +25 -0
- package/dist/lib/deck-media-refs.d.ts +35 -0
- package/dist/lib/deck-media-refs.d.ts.map +1 -0
- package/dist/lib/deck-media-refs.js +36 -0
- package/dist/lib/deck-tool.d.ts +25 -0
- package/dist/lib/deck-tool.d.ts.map +1 -0
- package/dist/lib/deck-tool.js +58 -0
- package/dist/lib/index-ops.js +2 -2
- package/dist/lib/index.js +17 -12
- package/dist/lib/nav-progress.d.ts +14 -0
- package/dist/lib/nav-progress.d.ts.map +1 -0
- package/dist/lib/nav.d.ts.map +1 -1
- package/dist/media/index.js +3 -3
- package/dist/schemas/deck-meta.d.ts +85 -0
- package/dist/schemas/deck-meta.d.ts.map +1 -0
- package/dist/schemas/deck-meta.js +64 -0
- package/dist/schemas/deck.d.ts +954 -0
- package/dist/schemas/deck.d.ts.map +1 -0
- package/dist/schemas/deck.js +65 -0
- package/dist/schemas/index.js +39 -37
- package/dist/schemas/site-config.d.ts +13 -1
- package/dist/schemas/site-config.d.ts.map +1 -1
- package/package.json +37 -1
- package/src/components/chat/ChatComposer.tsx +100 -0
- package/src/components/chat/ChatPanel.tsx +20 -69
- package/src/components/chat/markdown.ts +32 -0
- package/src/components/primitives/EditablePlainText.tsx +11 -1
- package/src/components/shell/AudienceChips.tsx +30 -0
- package/src/components/shell/EditorShell.tsx +12 -0
- package/src/components/shell/SiteSettingsChatbot.tsx +1 -18
- package/src/components/shell/SiteSettingsDecks.tsx +41 -0
- package/src/components/shell/SiteSettingsModal.tsx +11 -0
- package/src/deck/DeckComponentView.tsx +28 -0
- package/src/deck/DeckPlayer.tsx +166 -0
- package/src/deck/DeckSlideView.tsx +38 -0
- package/src/deck/DeckStage.tsx +39 -0
- package/src/deck/components/DeckImage.tsx +38 -0
- package/src/deck/components/DeckImageGrid.tsx +254 -0
- package/src/deck/components/DeckRichText.tsx +111 -0
- package/src/deck/components/DeckVideo.tsx +81 -0
- package/src/deck/editing.tsx +201 -0
- package/src/deck/flip.ts +70 -0
- package/src/deck/index.ts +30 -0
- package/src/deck/layouts/CenteredTextLayout.tsx +29 -0
- package/src/deck/layouts/ColumnSplitLayout.tsx +69 -0
- package/src/deck/layouts/FullBleedImageLayout.tsx +58 -0
- package/src/deck/media.tsx +46 -0
- package/src/deck/tokens.ts +57 -0
- package/src/deck/useStageScale.ts +44 -0
- package/src/deck-builder/DeckBuilderApp.tsx +572 -0
- package/src/deck-builder/DeckBuilderTopBar.tsx +62 -0
- package/src/deck-builder/DeckChatPanel.tsx +334 -0
- package/src/deck-builder/DeckEditor.tsx +258 -0
- package/src/deck-builder/DeckLibrary.tsx +195 -0
- package/src/deck-builder/DeckSlideSurface.tsx +55 -0
- package/src/deck-builder/deck-api.ts +177 -0
- package/src/deck-builder/deck-chat-client.ts +129 -0
- package/src/deck-builder/deck-chat-history.ts +62 -0
- package/src/deck-builder/index.ts +12 -0
- package/src/deck-builder/starter-deck.ts +86 -0
- package/src/deck-builder/types.ts +24 -0
- package/src/deck-builder/useSortableSlide.ts +102 -0
- package/src/lib/deck-access.ts +132 -0
- package/src/lib/deck-dexie.ts +190 -0
- package/src/lib/deck-edit.ts +196 -0
- package/src/lib/deck-media-assets.ts +57 -0
- package/src/lib/deck-media-refs.ts +67 -0
- package/src/lib/deck-tool.ts +95 -0
- package/src/lib/nav-progress.ts +108 -0
- package/src/lib/nav.ts +21 -1
- package/src/schemas/deck-meta.ts +88 -0
- package/src/schemas/deck.ts +478 -0
- package/src/schemas/index.ts +4 -0
- package/src/schemas/site-config.ts +24 -1
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-7EOSDFS3.js";
|
|
4
|
+
|
|
5
|
+
// src/components/shared/Tooltip.tsx
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
var sideClasses = {
|
|
8
|
+
bottom: "left-1/2 top-full mt-1.5 -translate-x-1/2",
|
|
9
|
+
right: "left-full top-1/2 ml-1.5 -translate-y-1/2",
|
|
10
|
+
"bottom-clamp": "top-full mt-1.5 left-1/2 -translate-x-1/2 max-xl:left-auto max-xl:right-0 max-xl:translate-x-0"
|
|
11
|
+
};
|
|
12
|
+
function Tooltip({ content, children, className, side = "bottom" }) {
|
|
13
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("group/tooltip relative inline-flex", className), children: [
|
|
14
|
+
children,
|
|
15
|
+
/* @__PURE__ */ jsx(
|
|
16
|
+
"span",
|
|
17
|
+
{
|
|
18
|
+
className: cn(
|
|
19
|
+
"pointer-events-none absolute z-50 opacity-0 transition-opacity delay-300 group-hover/tooltip:opacity-100",
|
|
20
|
+
sideClasses[side]
|
|
21
|
+
),
|
|
22
|
+
role: "tooltip",
|
|
23
|
+
children: /* @__PURE__ */ jsx("span", { className: "block whitespace-nowrap rounded bg-tooltip px-2 py-1 text-center text-xs leading-relaxed text-tooltip-muted", children: content })
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
] });
|
|
27
|
+
}
|
|
28
|
+
function Kbd({ children }) {
|
|
29
|
+
return /* @__PURE__ */ jsx("strong", { className: "text-tooltip-contrast", children });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/components/shared/Popover.tsx
|
|
33
|
+
import { useEffect as useEffect2, useLayoutEffect, useRef, useState } from "react";
|
|
34
|
+
import { createPortal } from "react-dom";
|
|
35
|
+
|
|
36
|
+
// src/hooks/useFocusTrap.ts
|
|
37
|
+
import { useEffect } from "react";
|
|
38
|
+
var FOCUSABLE_SELECTOR = [
|
|
39
|
+
"a[href]",
|
|
40
|
+
"button:not([disabled])",
|
|
41
|
+
"input:not([disabled])",
|
|
42
|
+
"select:not([disabled])",
|
|
43
|
+
"textarea:not([disabled])",
|
|
44
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
45
|
+
].join(", ");
|
|
46
|
+
function useInitialFocus(ref, active) {
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!active || !ref.current) return;
|
|
49
|
+
const previouslyFocused = document.activeElement;
|
|
50
|
+
ref.current.querySelector(FOCUSABLE_SELECTOR)?.focus();
|
|
51
|
+
return () => {
|
|
52
|
+
if (previouslyFocused && document.body.contains(previouslyFocused)) {
|
|
53
|
+
previouslyFocused.focus();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}, [active, ref]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/components/shared/Popover.tsx
|
|
60
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
61
|
+
function Popover({
|
|
62
|
+
isOpen,
|
|
63
|
+
onClose,
|
|
64
|
+
anchorRef,
|
|
65
|
+
children,
|
|
66
|
+
align = "auto",
|
|
67
|
+
className,
|
|
68
|
+
portal = false,
|
|
69
|
+
placement = "bottom"
|
|
70
|
+
}) {
|
|
71
|
+
const panelRef = useRef(null);
|
|
72
|
+
const [resolvedAlign, setResolvedAlign] = useState("start");
|
|
73
|
+
const [fixedPos, setFixedPos] = useState(null);
|
|
74
|
+
useInitialFocus(panelRef, isOpen && (!portal || fixedPos !== null));
|
|
75
|
+
useLayoutEffect(() => {
|
|
76
|
+
if (!portal) return;
|
|
77
|
+
if (!isOpen) {
|
|
78
|
+
setFixedPos(null);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const anchor = anchorRef.current;
|
|
82
|
+
if (!anchor) return;
|
|
83
|
+
const update = () => {
|
|
84
|
+
const r = anchor.getBoundingClientRect();
|
|
85
|
+
const next = placement === "top" ? { left: r.left, bottom: window.innerHeight - r.top + 4 } : { left: r.left, top: r.bottom + 4 };
|
|
86
|
+
setFixedPos(
|
|
87
|
+
(prev) => prev && prev.left === next.left && prev.top === next.top && prev.bottom === next.bottom ? prev : next
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
update();
|
|
91
|
+
window.addEventListener("resize", update);
|
|
92
|
+
window.addEventListener("scroll", update, true);
|
|
93
|
+
return () => {
|
|
94
|
+
window.removeEventListener("resize", update);
|
|
95
|
+
window.removeEventListener("scroll", update, true);
|
|
96
|
+
};
|
|
97
|
+
}, [isOpen, portal, placement, anchorRef]);
|
|
98
|
+
useLayoutEffect(() => {
|
|
99
|
+
if (portal) return;
|
|
100
|
+
if (!isOpen || align !== "auto") {
|
|
101
|
+
if (!isOpen) setResolvedAlign("start");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const panel = panelRef.current;
|
|
105
|
+
if (!panel) return;
|
|
106
|
+
panel.style.left = "0";
|
|
107
|
+
panel.style.right = "auto";
|
|
108
|
+
const rect = panel.getBoundingClientRect();
|
|
109
|
+
if (rect.right > window.innerWidth) {
|
|
110
|
+
setResolvedAlign("end");
|
|
111
|
+
} else {
|
|
112
|
+
setResolvedAlign("start");
|
|
113
|
+
}
|
|
114
|
+
panel.style.left = "";
|
|
115
|
+
panel.style.right = "";
|
|
116
|
+
}, [isOpen, align]);
|
|
117
|
+
useEffect2(() => {
|
|
118
|
+
if (!isOpen) return;
|
|
119
|
+
function onKeyDown(e) {
|
|
120
|
+
if (e.key !== "Escape") return;
|
|
121
|
+
if (document.querySelector("[data-modal-layer]")) return;
|
|
122
|
+
onClose();
|
|
123
|
+
}
|
|
124
|
+
function onMouseDown(e) {
|
|
125
|
+
const target = e.target;
|
|
126
|
+
if (panelRef.current?.contains(target)) return;
|
|
127
|
+
if (anchorRef.current?.contains(target)) return;
|
|
128
|
+
if (target instanceof Element && target.closest("[data-modal-layer]")) return;
|
|
129
|
+
onClose();
|
|
130
|
+
}
|
|
131
|
+
document.addEventListener("keydown", onKeyDown);
|
|
132
|
+
document.addEventListener("mousedown", onMouseDown);
|
|
133
|
+
return () => {
|
|
134
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
135
|
+
document.removeEventListener("mousedown", onMouseDown);
|
|
136
|
+
};
|
|
137
|
+
}, [isOpen, onClose, anchorRef]);
|
|
138
|
+
if (!isOpen) return null;
|
|
139
|
+
const effectiveAlign = align === "auto" ? resolvedAlign : align;
|
|
140
|
+
if (portal) {
|
|
141
|
+
if (!fixedPos) return null;
|
|
142
|
+
return createPortal(
|
|
143
|
+
/* @__PURE__ */ jsx2(
|
|
144
|
+
"div",
|
|
145
|
+
{
|
|
146
|
+
ref: panelRef,
|
|
147
|
+
role: "dialog",
|
|
148
|
+
"aria-modal": "false",
|
|
149
|
+
style: {
|
|
150
|
+
position: "fixed",
|
|
151
|
+
left: `${fixedPos.left}px`,
|
|
152
|
+
...fixedPos.top !== void 0 ? { top: `${fixedPos.top}px` } : {},
|
|
153
|
+
...fixedPos.bottom !== void 0 ? { bottom: `${fixedPos.bottom}px` } : {}
|
|
154
|
+
},
|
|
155
|
+
className: cn("z-50 rounded-md border border-base-200 bg-base shadow-lg", className),
|
|
156
|
+
children
|
|
157
|
+
}
|
|
158
|
+
),
|
|
159
|
+
document.body
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return /* @__PURE__ */ jsx2(
|
|
163
|
+
"div",
|
|
164
|
+
{
|
|
165
|
+
ref: panelRef,
|
|
166
|
+
role: "dialog",
|
|
167
|
+
"aria-modal": "false",
|
|
168
|
+
className: cn(
|
|
169
|
+
"absolute top-full z-50 mt-1 rounded-md border border-base-200 bg-base shadow-lg",
|
|
170
|
+
effectiveAlign === "end" ? "right-0" : "left-0",
|
|
171
|
+
className
|
|
172
|
+
),
|
|
173
|
+
children
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/components/shared/PopoverItem.tsx
|
|
179
|
+
import { forwardRef } from "react";
|
|
180
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
181
|
+
var PopoverItem = forwardRef(
|
|
182
|
+
function PopoverItem2({ className, children, ...rest }, ref) {
|
|
183
|
+
return /* @__PURE__ */ jsx3(
|
|
184
|
+
"button",
|
|
185
|
+
{
|
|
186
|
+
ref,
|
|
187
|
+
type: "button",
|
|
188
|
+
className: cn(
|
|
189
|
+
"flex w-full cursor-pointer items-center gap-3 px-3 py-1.5 text-left text-xs hover:bg-base-accent",
|
|
190
|
+
className
|
|
191
|
+
),
|
|
192
|
+
...rest,
|
|
193
|
+
children
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
Tooltip,
|
|
201
|
+
Kbd,
|
|
202
|
+
Popover,
|
|
203
|
+
PopoverItem
|
|
204
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/lib/timestamp.ts
|
|
2
|
+
function formatTimestamp(iso) {
|
|
3
|
+
const date = new Date(iso);
|
|
4
|
+
const now = /* @__PURE__ */ new Date();
|
|
5
|
+
const diffMs = now.getTime() - date.getTime();
|
|
6
|
+
const diffMinutes = Math.floor(diffMs / 6e4);
|
|
7
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
8
|
+
const sameDay = date.getUTCFullYear() === now.getUTCFullYear() && date.getUTCMonth() === now.getUTCMonth() && date.getUTCDate() === now.getUTCDate();
|
|
9
|
+
if (diffMinutes < 1) return "just now";
|
|
10
|
+
if (diffMinutes < 60) {
|
|
11
|
+
return `${diffMinutes} ${diffMinutes === 1 ? "minute" : "minutes"} ago`;
|
|
12
|
+
}
|
|
13
|
+
if (sameDay) {
|
|
14
|
+
return `${diffHours} ${diffHours === 1 ? "hour" : "hours"} ago`;
|
|
15
|
+
}
|
|
16
|
+
return date.toLocaleDateString("en-US", {
|
|
17
|
+
month: "short",
|
|
18
|
+
day: "numeric",
|
|
19
|
+
timeZone: "UTC"
|
|
20
|
+
}) + " at " + date.toLocaleTimeString("en-US", {
|
|
21
|
+
hour: "numeric",
|
|
22
|
+
minute: "2-digit",
|
|
23
|
+
hour12: true,
|
|
24
|
+
timeZone: "UTC"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
formatTimestamp
|
|
30
|
+
};
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
getSectionSchemaForType
|
|
12
12
|
} from "./chunk-DMCGN5AS.js";
|
|
13
|
+
import {
|
|
14
|
+
IndexSchema
|
|
15
|
+
} from "./chunk-HSXMOS5Q.js";
|
|
13
16
|
import {
|
|
14
17
|
getAllSchemas
|
|
15
18
|
} from "./chunk-FTPETWXE.js";
|
|
16
|
-
import {
|
|
17
|
-
IndexSchema
|
|
18
|
-
} from "./chunk-UVIYI4J6.js";
|
|
19
19
|
import {
|
|
20
20
|
MAX_BLOCK_DEPTH,
|
|
21
21
|
blockDepth
|
|
@@ -115,33 +115,6 @@ function buildGoogleFontsUrl(headingFont, bodyFont) {
|
|
|
115
115
|
return `https://fonts.googleapis.com/css2?${params}&display=swap`;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
// src/lib/timestamp.ts
|
|
119
|
-
function formatTimestamp(iso) {
|
|
120
|
-
const date = new Date(iso);
|
|
121
|
-
const now = /* @__PURE__ */ new Date();
|
|
122
|
-
const diffMs = now.getTime() - date.getTime();
|
|
123
|
-
const diffMinutes = Math.floor(diffMs / 6e4);
|
|
124
|
-
const diffHours = Math.floor(diffMs / 36e5);
|
|
125
|
-
const sameDay = date.getUTCFullYear() === now.getUTCFullYear() && date.getUTCMonth() === now.getUTCMonth() && date.getUTCDate() === now.getUTCDate();
|
|
126
|
-
if (diffMinutes < 1) return "just now";
|
|
127
|
-
if (diffMinutes < 60) {
|
|
128
|
-
return `${diffMinutes} ${diffMinutes === 1 ? "minute" : "minutes"} ago`;
|
|
129
|
-
}
|
|
130
|
-
if (sameDay) {
|
|
131
|
-
return `${diffHours} ${diffHours === 1 ? "hour" : "hours"} ago`;
|
|
132
|
-
}
|
|
133
|
-
return date.toLocaleDateString("en-US", {
|
|
134
|
-
month: "short",
|
|
135
|
-
day: "numeric",
|
|
136
|
-
timeZone: "UTC"
|
|
137
|
-
}) + " at " + date.toLocaleTimeString("en-US", {
|
|
138
|
-
hour: "numeric",
|
|
139
|
-
minute: "2-digit",
|
|
140
|
-
hour12: true,
|
|
141
|
-
timeZone: "UTC"
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
118
|
// src/lib/safeRedirect.ts
|
|
146
119
|
function safeRedirect(url, fallback) {
|
|
147
120
|
if (!url) return fallback;
|
|
@@ -257,7 +230,6 @@ export {
|
|
|
257
230
|
onPrimaryVarValue,
|
|
258
231
|
gridColsClass,
|
|
259
232
|
buildGoogleFontsUrl,
|
|
260
|
-
formatTimestamp,
|
|
261
233
|
safeRedirect,
|
|
262
234
|
mergeSiteContent,
|
|
263
235
|
loadStaticSiteContent,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/lib/sanitize.ts
|
|
2
|
+
var purifierPromise = null;
|
|
3
|
+
var purifier = null;
|
|
4
|
+
if (typeof window !== "undefined") {
|
|
5
|
+
purifierPromise = import("dompurify").then((mod) => {
|
|
6
|
+
const DOMPurify = mod.default ?? mod;
|
|
7
|
+
purifier = (html) => DOMPurify.sanitize(html);
|
|
8
|
+
return mod;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
var sanitizeCache = /* @__PURE__ */ new Map();
|
|
12
|
+
var SANITIZE_CACHE_LIMIT = 500;
|
|
13
|
+
function sanitizeHtml(html) {
|
|
14
|
+
if (!html) return "";
|
|
15
|
+
if (!purifier) return html;
|
|
16
|
+
const cached = sanitizeCache.get(html);
|
|
17
|
+
if (cached !== void 0) return cached;
|
|
18
|
+
const clean = purifier(html);
|
|
19
|
+
if (sanitizeCache.size >= SANITIZE_CACHE_LIMIT) {
|
|
20
|
+
const oldest = sanitizeCache.keys().next().value;
|
|
21
|
+
if (oldest !== void 0) sanitizeCache.delete(oldest);
|
|
22
|
+
}
|
|
23
|
+
sanitizeCache.set(html, clean);
|
|
24
|
+
return clean;
|
|
25
|
+
}
|
|
26
|
+
function isSanitizerReady() {
|
|
27
|
+
return typeof window === "undefined" || purifier !== null;
|
|
28
|
+
}
|
|
29
|
+
async function ensureSanitizer() {
|
|
30
|
+
if (typeof window === "undefined") return;
|
|
31
|
+
if (!purifier && purifierPromise) await purifierPromise;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
sanitizeHtml,
|
|
36
|
+
isSanitizerReady,
|
|
37
|
+
ensureSanitizer
|
|
38
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getMediaProvider
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-P3ZNCHW7.js";
|
|
4
4
|
import {
|
|
5
5
|
cn
|
|
6
6
|
} from "./chunk-7EOSDFS3.js";
|
|
@@ -72,44 +72,10 @@ function SettingsIcon({ size = 14 }) {
|
|
|
72
72
|
return /* @__PURE__ */ jsx(Settings, { size });
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
// src/components/shared/IconButton.tsx
|
|
76
|
-
import { forwardRef } from "react";
|
|
77
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
78
|
-
var sizeClasses = {
|
|
79
|
-
sm: "h-chrome-sm w-chrome-sm",
|
|
80
|
-
md: "h-chrome-md w-chrome-md",
|
|
81
|
-
lg: "h-chrome-lg w-chrome-lg"
|
|
82
|
-
};
|
|
83
|
-
var intentClasses = {
|
|
84
|
-
default: "text-base-contrast-light hover:text-base-contrast",
|
|
85
|
-
destructive: "text-destructive hover:text-destructive-hover",
|
|
86
|
-
primary: "text-primary/60 hover:text-primary"
|
|
87
|
-
};
|
|
88
|
-
var IconButton = forwardRef(
|
|
89
|
-
function IconButton2({ icon, label, size = "md", intent = "default", className, ...rest }, ref) {
|
|
90
|
-
return /* @__PURE__ */ jsx2(
|
|
91
|
-
"button",
|
|
92
|
-
{
|
|
93
|
-
ref,
|
|
94
|
-
type: "button",
|
|
95
|
-
className: cn(
|
|
96
|
-
"cursor-pointer flex items-center justify-center rounded transition-colors",
|
|
97
|
-
sizeClasses[size],
|
|
98
|
-
intentClasses[intent],
|
|
99
|
-
className
|
|
100
|
-
),
|
|
101
|
-
"aria-label": label,
|
|
102
|
-
...rest,
|
|
103
|
-
children: icon
|
|
104
|
-
}
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
);
|
|
108
|
-
|
|
109
75
|
// src/components/shared/FormLabel.tsx
|
|
110
|
-
import { jsx as
|
|
76
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
111
77
|
function FormLabel({ htmlFor, children, className }) {
|
|
112
|
-
return /* @__PURE__ */
|
|
78
|
+
return /* @__PURE__ */ jsx2(
|
|
113
79
|
"label",
|
|
114
80
|
{
|
|
115
81
|
htmlFor,
|
|
@@ -120,14 +86,14 @@ function FormLabel({ htmlFor, children, className }) {
|
|
|
120
86
|
}
|
|
121
87
|
|
|
122
88
|
// src/components/shared/Input.tsx
|
|
123
|
-
import { forwardRef
|
|
124
|
-
import { jsx as
|
|
125
|
-
var Input =
|
|
89
|
+
import { forwardRef, useId } from "react";
|
|
90
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
91
|
+
var Input = forwardRef(function Input2({ label, value, onChange, type = "text", className, disabled, error, ...rest }, ref) {
|
|
126
92
|
const id = useId();
|
|
127
93
|
const errorId = `${id}-error`;
|
|
128
94
|
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
129
|
-
/* @__PURE__ */
|
|
130
|
-
/* @__PURE__ */
|
|
95
|
+
/* @__PURE__ */ jsx3(FormLabel, { htmlFor: id, children: label }),
|
|
96
|
+
/* @__PURE__ */ jsx3(
|
|
131
97
|
"input",
|
|
132
98
|
{
|
|
133
99
|
ref,
|
|
@@ -146,7 +112,7 @@ var Input = forwardRef2(function Input2({ label, value, onChange, type = "text",
|
|
|
146
112
|
...rest
|
|
147
113
|
}
|
|
148
114
|
),
|
|
149
|
-
error && /* @__PURE__ */
|
|
115
|
+
error && /* @__PURE__ */ jsx3("p", { id: errorId, className: "mt-1 text-xs text-red-600", children: error })
|
|
150
116
|
] });
|
|
151
117
|
});
|
|
152
118
|
|
|
@@ -157,7 +123,6 @@ export {
|
|
|
157
123
|
AddIcon,
|
|
158
124
|
DeleteIcon,
|
|
159
125
|
SettingsIcon,
|
|
160
|
-
IconButton,
|
|
161
126
|
FormLabel,
|
|
162
127
|
Input
|
|
163
128
|
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// src/schemas/deck.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var DECK_STAGE_WIDTH = 1600;
|
|
4
|
+
var DECK_STAGE_HEIGHT = 900;
|
|
5
|
+
var DECK_SCHEMA_VERSION = 1;
|
|
6
|
+
var DECK_MAX_SLIDES = 40;
|
|
7
|
+
var DeckIdSchema = z.string().max(64, "must be 64 characters or fewer \u2014 a deck id is also a directory name").regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase letters, digits and hyphens, starting with a letter or digit");
|
|
8
|
+
var DECK_LAYOUT_TYPES = ["centeredText", "fullBleedImage", "columnSplit"];
|
|
9
|
+
var DECK_COMPONENT_TYPES = ["richText", "image", "imageGrid", "video"];
|
|
10
|
+
var DECK_MEDIA_KINDS = ["portal"];
|
|
11
|
+
var DECK_SURFACES = ["base", "brand"];
|
|
12
|
+
var DECK_OVERLAY_PLACEMENTS = ["sideLeft", "sideRight", "bottomBand"];
|
|
13
|
+
var DECK_SPLIT_RATIOS = ["1-2", "2-1"];
|
|
14
|
+
function vocabularyError(kind, types, field = "type") {
|
|
15
|
+
return `unknown ${kind} ${field} \u2014 the deck runtime provides: ${types.map((t) => `"${t}"`).join(", ")}`;
|
|
16
|
+
}
|
|
17
|
+
var PortalMediaRefSchema = z.object({
|
|
18
|
+
kind: z.literal("portal"),
|
|
19
|
+
// Same charset as a deck id, for the same reason and one more: a media id
|
|
20
|
+
// becomes a key into a plain object at resolve time, and `"__proto__"` parses
|
|
21
|
+
// as a perfectly good string. Constrain it here rather than relying on every
|
|
22
|
+
// resolver to be careful.
|
|
23
|
+
id: z.string().max(120, "must be 120 characters or fewer").regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase letters, digits and hyphens, starting with a letter or digit").describe("Media id from the portal's media library \u2014 the manifest key, not a filename, path or URL.")
|
|
24
|
+
});
|
|
25
|
+
var DeckMediaRefSchema = z.discriminatedUnion("kind", [PortalMediaRefSchema], {
|
|
26
|
+
error: vocabularyError("media reference", DECK_MEDIA_KINDS, "kind")
|
|
27
|
+
});
|
|
28
|
+
var PARAGRAPH_MAX = 400;
|
|
29
|
+
var BULLET_MAX = 160;
|
|
30
|
+
var MAX_SPANS = 12;
|
|
31
|
+
var DeckTextSpanSchema = z.strictObject({
|
|
32
|
+
text: z.string().min(1).max(PARAGRAPH_MAX).describe("The span's literal text. Never HTML or markdown \u2014 the marks below are the only formatting there is."),
|
|
33
|
+
bold: z.boolean().optional().describe("Renders the span at full ink weight. For the one phrase in a line that carries it."),
|
|
34
|
+
italic: z.boolean().optional().describe("Renders the span italic. For titles of works and genuine emphasis, not decoration.")
|
|
35
|
+
});
|
|
36
|
+
function inlineText(maxChars) {
|
|
37
|
+
return z.union([z.string().min(1).max(maxChars), z.array(DeckTextSpanSchema).min(1).max(MAX_SPANS)], {
|
|
38
|
+
error: "must be plain text, or an array of { text, bold?, italic? } spans \u2014 deck rich text is structured, never HTML or markdown"
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function inlineLength(value) {
|
|
42
|
+
return typeof value === "string" ? value.length : value.reduce((total, span) => total + span.text.length, 0);
|
|
43
|
+
}
|
|
44
|
+
var DeckRichTextSchema = z.object({
|
|
45
|
+
type: z.literal("richText"),
|
|
46
|
+
kicker: z.string().min(1).max(60).optional().describe("Short label above the heading, e.g. a campaign or section name. Rendered small, tracked and in the brand accent."),
|
|
47
|
+
heading: z.string().min(1).max(120).optional().describe("The slide's primary line. Keep it to a phrase \u2014 it renders at display size on the 16:9 stage."),
|
|
48
|
+
subheading: z.string().min(1).max(200).optional().describe("Secondary line under the heading. Optional; omit rather than repeating the heading."),
|
|
49
|
+
paragraphs: z.array(inlineText(PARAGRAPH_MAX)).max(3).optional().describe(
|
|
50
|
+
`Body copy, one entry per paragraph. Each is either a plain string or an array of { text, bold?, italic? } spans \u2014 use spans only where a phrase genuinely needs a mark. Never HTML or markdown; ${PARAGRAPH_MAX} characters per paragraph.`
|
|
51
|
+
),
|
|
52
|
+
bullets: z.array(inlineText(BULLET_MAX)).max(6).optional().describe(
|
|
53
|
+
`Short bullet lines, plain strings or span arrays like paragraphs. Prefer six or fewer so they stay legible at presentation size; ${BULLET_MAX} characters per line.`
|
|
54
|
+
)
|
|
55
|
+
}).superRefine((value, ctx) => {
|
|
56
|
+
const hasContent = value.kicker !== void 0 || value.heading !== void 0 || value.subheading !== void 0 || (value.paragraphs?.length ?? 0) > 0 || (value.bullets?.length ?? 0) > 0;
|
|
57
|
+
if (!hasContent) {
|
|
58
|
+
ctx.addIssue({
|
|
59
|
+
code: "custom",
|
|
60
|
+
message: "a richText component needs at least one of kicker, heading, subheading, paragraphs or bullets"
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const capLines = (lines, max, field) => {
|
|
64
|
+
lines?.forEach((line, i) => {
|
|
65
|
+
const length = inlineLength(line);
|
|
66
|
+
if (length > max) {
|
|
67
|
+
ctx.addIssue({
|
|
68
|
+
code: "custom",
|
|
69
|
+
path: [field, i],
|
|
70
|
+
message: `spans total ${length} characters \u2014 keep a ${field === "bullets" ? "bullet" : "paragraph"} to ${max} or fewer`
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
capLines(value.paragraphs, PARAGRAPH_MAX, "paragraphs");
|
|
76
|
+
capLines(value.bullets, BULLET_MAX, "bullets");
|
|
77
|
+
});
|
|
78
|
+
var DeckImageSchema = z.object({
|
|
79
|
+
type: z.literal("image"),
|
|
80
|
+
media: DeckMediaRefSchema.describe("The image to show. It fills its slot and is cropped to fit."),
|
|
81
|
+
caption: z.string().min(1).max(120).optional().describe("Caption printed on a solid brand block along the image's bottom edge. Omit rather than restating the slide.")
|
|
82
|
+
});
|
|
83
|
+
var DeckImageGridItemSchema = z.object({
|
|
84
|
+
media: DeckMediaRefSchema.describe("The tile's image."),
|
|
85
|
+
caption: z.string().min(1).max(80).optional().describe("Shown on the tile on hover, and while the tile is expanded. A few words, not a sentence.")
|
|
86
|
+
});
|
|
87
|
+
var DeckImageGridSchema = z.object({
|
|
88
|
+
type: z.literal("imageGrid"),
|
|
89
|
+
items: z.array(DeckImageGridItemSchema).min(2, "an image grid needs at least two images \u2014 use the image component for one").max(12, "an image grid holds at most twelve images \u2014 past that a tile is too small to read at presentation size").describe(
|
|
90
|
+
"The tiles, in order of importance. Two to twelve. They pack into masonry columns at their own aspect ratios \u2014 nothing is cropped and nothing is resized to match its neighbours \u2014 and the columns clip at the bottom of the slot if the stack runs past it, so put the frames that must be seen first."
|
|
91
|
+
)
|
|
92
|
+
});
|
|
93
|
+
var DeckVideoSchema = z.object({
|
|
94
|
+
type: z.literal("video"),
|
|
95
|
+
media: DeckMediaRefSchema.describe("The video to play. Must reference portal-hosted media \u2014 a deck cannot embed an external player."),
|
|
96
|
+
title: z.string().min(1).max(120).optional().describe("Label on a brand block across the bottom of the poster, before playback starts.")
|
|
97
|
+
});
|
|
98
|
+
var DeckComponentSchema = z.discriminatedUnion(
|
|
99
|
+
"type",
|
|
100
|
+
[DeckRichTextSchema, DeckImageSchema, DeckImageGridSchema, DeckVideoSchema],
|
|
101
|
+
{ error: vocabularyError("deck component", DECK_COMPONENT_TYPES) }
|
|
102
|
+
);
|
|
103
|
+
var CenteredTextLayoutSchema = z.object({
|
|
104
|
+
type: z.literal("centeredText"),
|
|
105
|
+
background: z.enum(DECK_SURFACES).default("base").describe("`base` puts the slide on the site's surface color; `brand` fills it with the brand primary and flips the ink."),
|
|
106
|
+
content: DeckComponentSchema.describe("The single Deck Component this Layout centers.")
|
|
107
|
+
});
|
|
108
|
+
var DeckOverlayPanelSchema = z.object({
|
|
109
|
+
placement: z.enum(DECK_OVERLAY_PLACEMENTS).default("sideLeft").describe(
|
|
110
|
+
"`sideLeft`/`sideRight` put the panel in a full-height column over that edge of the image; `bottomBand` runs it full width across the bottom. Pick the side the image is emptiest on."
|
|
111
|
+
),
|
|
112
|
+
content: DeckRichTextSchema.describe("The panel's text. It sits on the brand color, so keep it short \u2014 a kicker and a line or two.")
|
|
113
|
+
});
|
|
114
|
+
var FullBleedImageLayoutSchema = z.object({
|
|
115
|
+
type: z.literal("fullBleedImage"),
|
|
116
|
+
media: DeckMediaRefSchema.describe("The image filling the whole 16:9 stage. It is cropped to fit, so choose one that survives a wide crop."),
|
|
117
|
+
overlay: DeckOverlayPanelSchema.optional().describe("Optional text panel over the image. Omit it for a slide that is purely the picture.")
|
|
118
|
+
});
|
|
119
|
+
var ColumnSplitLayoutSchema = z.object({
|
|
120
|
+
type: z.literal("columnSplit"),
|
|
121
|
+
ratio: z.enum(DECK_SPLIT_RATIOS).default("1-2").describe("`1-2` gives the left column a third of the stage and the right two thirds; `2-1` is the reverse. Give the media the larger side."),
|
|
122
|
+
left: DeckComponentSchema.describe("The left column's Deck Component."),
|
|
123
|
+
right: DeckComponentSchema.describe("The right column's Deck Component.")
|
|
124
|
+
});
|
|
125
|
+
var DeckLayoutSchema = z.discriminatedUnion(
|
|
126
|
+
"type",
|
|
127
|
+
[CenteredTextLayoutSchema, FullBleedImageLayoutSchema, ColumnSplitLayoutSchema],
|
|
128
|
+
{ error: vocabularyError("layout", DECK_LAYOUT_TYPES) }
|
|
129
|
+
);
|
|
130
|
+
var DeckSlideSchema = z.object({
|
|
131
|
+
id: DeckIdSchema.describe("Stable slide id, unique within the deck. Reused across edits so revisions stay diffable."),
|
|
132
|
+
title: z.string().min(1).max(80).optional().describe("Short label for the slide, used by the builder filmstrip and by slide-navigation labels. Not rendered on the stage."),
|
|
133
|
+
layout: DeckLayoutSchema.describe("The full-slide arrangement. A slide must use exactly one Layout.")
|
|
134
|
+
});
|
|
135
|
+
var DeckSchema = z.object({
|
|
136
|
+
schemaVersion: z.literal(DECK_SCHEMA_VERSION).describe(`Deck schema version. Always ${DECK_SCHEMA_VERSION}.`),
|
|
137
|
+
id: DeckIdSchema.describe("Stable deck id \u2014 also the directory name on the decks branch."),
|
|
138
|
+
title: z.string().min(1).max(120).describe("The deck's name, shown in the builder and in the deck's chrome."),
|
|
139
|
+
slides: z.array(DeckSlideSchema).min(1, "a deck needs at least one slide").max(
|
|
140
|
+
DECK_MAX_SLIDES,
|
|
141
|
+
`a deck is capped at ${DECK_MAX_SLIDES} slides \u2014 split a longer story into several decks`
|
|
142
|
+
).describe(
|
|
143
|
+
`Slides in presentation order, at most ${DECK_MAX_SLIDES}. The whole deck is re-emitted on every authoring turn and re-committed on every publish, so the cap bounds both the model's output and the size of a stored deck.`
|
|
144
|
+
)
|
|
145
|
+
}).superRefine((deck, ctx) => {
|
|
146
|
+
const seen = /* @__PURE__ */ new Set();
|
|
147
|
+
deck.slides.forEach((slide, i) => {
|
|
148
|
+
if (seen.has(slide.id)) {
|
|
149
|
+
ctx.addIssue({
|
|
150
|
+
code: "custom",
|
|
151
|
+
path: ["slides", i, "id"],
|
|
152
|
+
message: `duplicate slide id "${slide.id}" \u2014 slide ids must be unique within a deck`
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
seen.add(slide.id);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
function formatPath(path) {
|
|
159
|
+
return path.reduce((acc, key) => {
|
|
160
|
+
if (typeof key === "number") return `${acc}[${key}]`;
|
|
161
|
+
return acc === "" ? String(key) : `${acc}.${String(key)}`;
|
|
162
|
+
}, "");
|
|
163
|
+
}
|
|
164
|
+
function formatDeckIssues(error) {
|
|
165
|
+
return error.issues.map((issue) => {
|
|
166
|
+
const path = formatPath(issue.path);
|
|
167
|
+
return path === "" ? issue.message : `${path}: ${issue.message}`;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function parseDeck(input) {
|
|
171
|
+
const result = DeckSchema.safeParse(input);
|
|
172
|
+
return result.success ? { ok: true, deck: result.data } : { ok: false, errors: formatDeckIssues(result.error) };
|
|
173
|
+
}
|
|
174
|
+
function serializeDeck(deck) {
|
|
175
|
+
return JSON.stringify(deck, null, 2) + "\n";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export {
|
|
179
|
+
DECK_STAGE_WIDTH,
|
|
180
|
+
DECK_STAGE_HEIGHT,
|
|
181
|
+
DECK_SCHEMA_VERSION,
|
|
182
|
+
DECK_MAX_SLIDES,
|
|
183
|
+
DeckIdSchema,
|
|
184
|
+
DECK_LAYOUT_TYPES,
|
|
185
|
+
DECK_COMPONENT_TYPES,
|
|
186
|
+
DECK_MEDIA_KINDS,
|
|
187
|
+
DECK_SURFACES,
|
|
188
|
+
DECK_OVERLAY_PLACEMENTS,
|
|
189
|
+
DECK_SPLIT_RATIOS,
|
|
190
|
+
PortalMediaRefSchema,
|
|
191
|
+
DeckMediaRefSchema,
|
|
192
|
+
DeckTextSpanSchema,
|
|
193
|
+
DeckRichTextSchema,
|
|
194
|
+
DeckImageSchema,
|
|
195
|
+
DeckImageGridItemSchema,
|
|
196
|
+
DeckImageGridSchema,
|
|
197
|
+
DeckVideoSchema,
|
|
198
|
+
DeckComponentSchema,
|
|
199
|
+
CenteredTextLayoutSchema,
|
|
200
|
+
DeckOverlayPanelSchema,
|
|
201
|
+
FullBleedImageLayoutSchema,
|
|
202
|
+
ColumnSplitLayoutSchema,
|
|
203
|
+
DeckLayoutSchema,
|
|
204
|
+
DeckSlideSchema,
|
|
205
|
+
DeckSchema,
|
|
206
|
+
formatDeckIssues,
|
|
207
|
+
parseDeck,
|
|
208
|
+
serializeDeck
|
|
209
|
+
};
|