@drawnagency/primitives 0.1.38 → 0.1.39
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-OUFUUBZ4.js → chunk-GQV2554Z.js} +1 -1
- package/dist/{chunk-C2MVDXD7.js → chunk-I6ZPOEK2.js} +27 -8
- package/dist/{chunk-V43WVSVS.js → chunk-LW5EGJFM.js} +5 -2
- package/dist/{chunk-VCZBZMXU.js → chunk-TNHX35TE.js} +28 -10
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/hooks/useBuildStatus.d.ts.map +1 -1
- package/dist/hooks/useEditorPersistence.d.ts.map +1 -1
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +4 -2
- package/dist/lib/loader.d.ts.map +1 -1
- package/dist/lib/nav.d.ts.map +1 -1
- package/dist/lib/sanitize.d.ts +10 -0
- package/dist/lib/sanitize.d.ts.map +1 -1
- package/dist/lib/text.d.ts.map +1 -1
- package/dist/media/index.js +1 -1
- package/dist/media/queue.d.ts.map +1 -1
- package/dist/media/videoPoster.d.ts.map +1 -1
- package/dist/schemas/index.js +2 -2
- package/dist/schemas/site-config.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/shell/EditorShell.tsx +2 -0
- package/src/components/shell/SiteSettingsModal.tsx +1 -1
- package/src/hooks/useBuildStatus.ts +13 -3
- package/src/hooks/useEditorPersistence.ts +19 -6
- package/src/hooks/useEditorPublish.ts +23 -6
- package/src/hooks/useMediaPipeline.ts +21 -6
- package/src/lib/index.ts +1 -1
- package/src/lib/loader.ts +3 -2
- package/src/lib/nav.ts +15 -3
- package/src/lib/sanitize.ts +22 -3
- package/src/lib/text.ts +5 -1
- package/src/media/queue.ts +3 -1
- package/src/media/videoPoster.ts +31 -10
- package/src/schemas/site-config.ts +6 -2
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
getAllSchemas,
|
|
4
4
|
getSection,
|
|
5
5
|
getSectionSchema
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-LW5EGJFM.js";
|
|
7
7
|
import {
|
|
8
8
|
safeNextPath
|
|
9
9
|
} from "./chunk-S2L3BPLS.js";
|
|
@@ -17,7 +17,13 @@ function cn(...inputs) {
|
|
|
17
17
|
|
|
18
18
|
// src/lib/nav.ts
|
|
19
19
|
function toSectionId(text) {
|
|
20
|
-
|
|
20
|
+
const slug = text.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/^-+|-+$/g, "");
|
|
21
|
+
if (slug) return slug;
|
|
22
|
+
let hash = 0;
|
|
23
|
+
for (let i = 0; i < text.length; i++) {
|
|
24
|
+
hash = (hash << 5) - hash + text.charCodeAt(i) | 0;
|
|
25
|
+
}
|
|
26
|
+
return `section-${Math.abs(hash).toString(36)}`;
|
|
21
27
|
}
|
|
22
28
|
function generateNavLinks(sections, registry) {
|
|
23
29
|
const nav = [];
|
|
@@ -33,6 +39,10 @@ function generateNavLinks(sections, registry) {
|
|
|
33
39
|
const role = lookupRole(section.type);
|
|
34
40
|
if (!role) continue;
|
|
35
41
|
if (role === "h1") {
|
|
42
|
+
if (content.excludeFromNav) {
|
|
43
|
+
currentParent = null;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
36
46
|
currentParent = {
|
|
37
47
|
href: `#${toSectionId(content.heading)}`,
|
|
38
48
|
label: content.heading,
|
|
@@ -77,15 +87,22 @@ function deriveContrast(hex) {
|
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
// src/lib/sanitize.ts
|
|
80
|
-
var
|
|
90
|
+
var purifierPromise = null;
|
|
91
|
+
var purifier = null;
|
|
81
92
|
if (typeof window !== "undefined") {
|
|
82
|
-
import("dompurify").then((
|
|
83
|
-
|
|
93
|
+
purifierPromise = import("dompurify").then((mod) => {
|
|
94
|
+
const DOMPurify = mod.default ?? mod;
|
|
95
|
+
purifier = (html) => DOMPurify.sanitize(html);
|
|
96
|
+
return mod;
|
|
84
97
|
});
|
|
85
98
|
}
|
|
86
99
|
function sanitizeHtml(html) {
|
|
87
100
|
if (!html) return "";
|
|
88
|
-
return purifier ? purifier
|
|
101
|
+
return purifier ? purifier(html) : html;
|
|
102
|
+
}
|
|
103
|
+
async function ensureSanitizer() {
|
|
104
|
+
if (typeof window === "undefined") return;
|
|
105
|
+
if (!purifier && purifierPromise) await purifierPromise;
|
|
89
106
|
}
|
|
90
107
|
|
|
91
108
|
// src/lib/grid.ts
|
|
@@ -212,14 +229,15 @@ var historySelectEvent = createEvent("history-select");
|
|
|
212
229
|
function mergeSiteContent(index, sectionFiles) {
|
|
213
230
|
const sections = [];
|
|
214
231
|
const canValidate = getAllSchemas().length >= 2;
|
|
232
|
+
const schema = canValidate ? getSectionSchema() : null;
|
|
215
233
|
for (const id of index.order) {
|
|
216
234
|
const raw = sectionFiles[id];
|
|
217
235
|
if (!raw) {
|
|
218
236
|
console.warn(`Section file missing for id: ${id}, skipping`);
|
|
219
237
|
continue;
|
|
220
238
|
}
|
|
221
|
-
if (canValidate) {
|
|
222
|
-
const result =
|
|
239
|
+
if (canValidate && schema) {
|
|
240
|
+
const result = schema.safeParse(raw);
|
|
223
241
|
if (!result.success) {
|
|
224
242
|
const type = raw.type ?? "unknown";
|
|
225
243
|
console.warn(`Skipping section "${id}" (type: ${type}): invalid schema`);
|
|
@@ -263,6 +281,7 @@ export {
|
|
|
263
281
|
generateNavLinks,
|
|
264
282
|
deriveContrast,
|
|
265
283
|
sanitizeHtml,
|
|
284
|
+
ensureSanitizer,
|
|
266
285
|
gridColsClass,
|
|
267
286
|
curatedIcons,
|
|
268
287
|
getIcon,
|
|
@@ -167,8 +167,11 @@ var IndexSchema = z3.object({
|
|
|
167
167
|
sections: z3.record(z3.string(), SectionMetaSchema),
|
|
168
168
|
lastModified: z3.string().nullable().optional()
|
|
169
169
|
}).refine(
|
|
170
|
-
(data) =>
|
|
171
|
-
|
|
170
|
+
(data) => {
|
|
171
|
+
const orderSet = new Set(data.order);
|
|
172
|
+
return data.order.every((id) => id in data.sections) && Object.keys(data.sections).every((key) => orderSet.has(key));
|
|
173
|
+
},
|
|
174
|
+
{ message: "Every id in order must have a sections entry and vice versa" }
|
|
172
175
|
);
|
|
173
176
|
var SiteConfigSchema = z3.object({
|
|
174
177
|
siteName: z3.string().default("Brand Portal"),
|
|
@@ -149,7 +149,9 @@ var ProcessingQueue = class {
|
|
|
149
149
|
this.activeWorkers.delete(id);
|
|
150
150
|
}
|
|
151
151
|
destroy() {
|
|
152
|
-
for (const [id, worker] of this.activeWorkers) {
|
|
152
|
+
for (const [id, worker] of Array.from(this.activeWorkers)) {
|
|
153
|
+
worker.onmessage = null;
|
|
154
|
+
worker.onerror = null;
|
|
153
155
|
worker.terminate();
|
|
154
156
|
this.activeWorkers.delete(id);
|
|
155
157
|
}
|
|
@@ -204,19 +206,38 @@ function generateVideoPoster(blob, quality) {
|
|
|
204
206
|
return new Promise((resolve, reject) => {
|
|
205
207
|
const video = document.createElement("video");
|
|
206
208
|
const url = URL.createObjectURL(blob);
|
|
207
|
-
|
|
208
|
-
video.preload = "auto";
|
|
209
|
-
video.src = url;
|
|
209
|
+
let settled = false;
|
|
210
210
|
const cleanup = () => {
|
|
211
|
+
clearTimeout(timeoutId);
|
|
211
212
|
URL.revokeObjectURL(url);
|
|
212
213
|
video.removeAttribute("src");
|
|
213
214
|
video.load();
|
|
214
215
|
};
|
|
216
|
+
const safeReject = (err) => {
|
|
217
|
+
if (settled) return;
|
|
218
|
+
settled = true;
|
|
219
|
+
cleanup();
|
|
220
|
+
reject(err);
|
|
221
|
+
};
|
|
222
|
+
const timeoutId = setTimeout(() => {
|
|
223
|
+
safeReject(new Error("Video poster generation timed out"));
|
|
224
|
+
}, 1e4);
|
|
225
|
+
video.addEventListener("error", () => {
|
|
226
|
+
safeReject(new Error("Failed to load video for poster generation"));
|
|
227
|
+
}, { once: true });
|
|
228
|
+
video.muted = true;
|
|
229
|
+
video.preload = "auto";
|
|
230
|
+
video.src = url;
|
|
215
231
|
video.addEventListener("loadeddata", () => {
|
|
216
232
|
video.addEventListener("seeked", () => {
|
|
233
|
+
if (settled) return;
|
|
217
234
|
try {
|
|
218
235
|
const w = video.videoWidth;
|
|
219
236
|
const h = video.videoHeight;
|
|
237
|
+
if (w === 0 || h === 0) {
|
|
238
|
+
safeReject(new Error("Video dimensions not available"));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
220
241
|
const canvas = document.createElement("canvas");
|
|
221
242
|
canvas.width = w;
|
|
222
243
|
canvas.height = h;
|
|
@@ -224,6 +245,8 @@ function generateVideoPoster(blob, quality) {
|
|
|
224
245
|
ctx.drawImage(video, 0, 0);
|
|
225
246
|
canvas.toBlob(
|
|
226
247
|
(posterBlob) => {
|
|
248
|
+
if (settled) return;
|
|
249
|
+
settled = true;
|
|
227
250
|
cleanup();
|
|
228
251
|
if (!posterBlob) {
|
|
229
252
|
reject(new Error("Failed to create poster blob"));
|
|
@@ -235,16 +258,11 @@ function generateVideoPoster(blob, quality) {
|
|
|
235
258
|
quality / 100
|
|
236
259
|
);
|
|
237
260
|
} catch (err) {
|
|
238
|
-
|
|
239
|
-
reject(err);
|
|
261
|
+
safeReject(err instanceof Error ? err : new Error(String(err)));
|
|
240
262
|
}
|
|
241
263
|
}, { once: true });
|
|
242
264
|
video.currentTime = isFinite(video.duration) ? Math.min(0.1, video.duration / 2) : 0;
|
|
243
265
|
}, { once: true });
|
|
244
|
-
video.addEventListener("error", () => {
|
|
245
|
-
cleanup();
|
|
246
|
-
reject(new Error("Failed to load video for poster generation"));
|
|
247
|
-
}, { once: true });
|
|
248
266
|
});
|
|
249
267
|
}
|
|
250
268
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAqDjD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;CACjE;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,GACZ,EAAE,KAAK,2CAqoBP"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuildStatus.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuildStatus.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AASrE,UAAU,iBAAiB;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAMD,wBAAgB,cAAc,IAAI,iBAAiB,
|
|
1
|
+
{"version":3,"file":"useBuildStatus.d.ts","sourceRoot":"","sources":["../../src/hooks/useBuildStatus.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AASrE,UAAU,iBAAiB;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAMD,wBAAgB,cAAc,IAAI,iBAAiB,CAgIlD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEditorPersistence.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPersistence.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AASpE,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"useEditorPersistence.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPersistence.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AASpE,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC;kCAoD7D,MAAM,WAAW,cAAc;;4BAYI,UAAU;;;;;EAwB5D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAe/D,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,EAAE,SAAS,EAAE,CAAC;IAC/B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gBAAgB,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtF,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAQD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,EAAE,WAAW;;;;;;
|
|
1
|
+
{"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAe/D,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,EAAE,SAAS,EAAE,CAAC;IAC/B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gBAAgB,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtF,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAQD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,EAAE,WAAW;;;;;;EA4Qb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMediaPipeline.d.ts","sourceRoot":"","sources":["../../src/hooks/useMediaPipeline.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,SAAS,EAAmB,MAAM,gBAAgB,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,gBAAgB,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AA0BxF,UAAU,qBAAqB;IAC7B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,aAAa,CAAC;IAC7B,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;IACtE,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACnE,oBAAoB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,kBAAkB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtE,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;CACxE;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+
|
|
1
|
+
{"version":3,"file":"useMediaPipeline.d.ts","sourceRoot":"","sources":["../../src/hooks/useMediaPipeline.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,SAAS,EAAmB,MAAM,gBAAgB,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,gBAAgB,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AA0BxF,UAAU,qBAAqB;IAC7B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,aAAa,CAAC;IAC7B,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;IACtE,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACnE,oBAAoB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,kBAAkB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtE,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;CACxE;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,EAAE,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BA6H8B,IAAI,EAAE;6BAyBR,MAAM,EAAE;+BA+CZ,MAAM,OAAO,MAAM;;;wBA+B1B,IAAI,eAAe,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;EAwEpF"}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
AudienceNameSchema,
|
|
4
4
|
MediaGridOptionsSchema,
|
|
5
5
|
slugifyAudienceName
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GQV2554Z.js";
|
|
7
7
|
import {
|
|
8
8
|
AudienceSchema,
|
|
9
9
|
RoleSchema,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
darkModeEvent,
|
|
19
19
|
deriveContrast,
|
|
20
20
|
editModeEvent,
|
|
21
|
+
ensureSanitizer,
|
|
21
22
|
formatTimestamp,
|
|
22
23
|
generateNavLinks,
|
|
23
24
|
getIcon,
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
safeRedirect,
|
|
30
31
|
sanitizeHtml,
|
|
31
32
|
toSectionId
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-I6ZPOEK2.js";
|
|
33
34
|
import {
|
|
34
35
|
ColorItemSchema,
|
|
35
36
|
ColorSpaceSchema,
|
|
@@ -50,7 +51,7 @@ import {
|
|
|
50
51
|
getSectionSchema,
|
|
51
52
|
registerSchema,
|
|
52
53
|
registerSection
|
|
53
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-LW5EGJFM.js";
|
|
54
55
|
import {
|
|
55
56
|
AUDIENCE_COOKIE,
|
|
56
57
|
LastOwnerError,
|
|
@@ -81,7 +82,7 @@ import {
|
|
|
81
82
|
resolveMedia,
|
|
82
83
|
sanitizeMediaName,
|
|
83
84
|
setMediaProvider
|
|
84
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-TNHX35TE.js";
|
|
85
86
|
import {
|
|
86
87
|
ImageManifestSchema,
|
|
87
88
|
MediaConfigSchema,
|
|
@@ -126,6 +127,7 @@ export {
|
|
|
126
127
|
deriveContrast,
|
|
127
128
|
displayFilenameExt,
|
|
128
129
|
editModeEvent,
|
|
130
|
+
ensureSanitizer,
|
|
129
131
|
env,
|
|
130
132
|
formatTimestamp,
|
|
131
133
|
generateNavLinks,
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { env } from "./env";
|
|
|
2
2
|
export { cn } from "./cn";
|
|
3
3
|
export { generateNavLinks, toSectionId, type NavItem } from "./nav";
|
|
4
4
|
export { deriveContrast } from "./contrast";
|
|
5
|
-
export { sanitizeHtml } from "./sanitize";
|
|
5
|
+
export { sanitizeHtml, ensureSanitizer } from "./sanitize";
|
|
6
6
|
export { gridColsClass } from "./grid";
|
|
7
7
|
export { getIcon, curatedIcons, type IconEntry } from "./icons";
|
|
8
8
|
export { buildGoogleFontsUrl } from "./google-fonts";
|
package/dist/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,KAAK,OAAO,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,KAAK,OAAO,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AACtG,OAAO,EACL,cAAc,EACd,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,EACV,SAAS,EACT,cAAc,EACd,aAAa,EACb,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,uBAAuB,GAC7B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,KAAK,aAAa,EAClB,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC"}
|
package/dist/lib/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
darkModeEvent,
|
|
7
7
|
deriveContrast,
|
|
8
8
|
editModeEvent,
|
|
9
|
+
ensureSanitizer,
|
|
9
10
|
formatTimestamp,
|
|
10
11
|
generateNavLinks,
|
|
11
12
|
getIcon,
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
safeRedirect,
|
|
18
19
|
sanitizeHtml,
|
|
19
20
|
toSectionId
|
|
20
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-I6ZPOEK2.js";
|
|
21
22
|
import {
|
|
22
23
|
clearRegistry,
|
|
23
24
|
createRegistry,
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
getSection,
|
|
29
30
|
registerSchema,
|
|
30
31
|
registerSection
|
|
31
|
-
} from "../chunk-
|
|
32
|
+
} from "../chunk-LW5EGJFM.js";
|
|
32
33
|
import "../chunk-S2L3BPLS.js";
|
|
33
34
|
import {
|
|
34
35
|
env
|
|
@@ -45,6 +46,7 @@ export {
|
|
|
45
46
|
defineSection,
|
|
46
47
|
deriveContrast,
|
|
47
48
|
editModeEvent,
|
|
49
|
+
ensureSanitizer,
|
|
48
50
|
env,
|
|
49
51
|
formatTimestamp,
|
|
50
52
|
generateNavLinks,
|
package/dist/lib/loader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/lib/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGvF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,KAAK,EAAE,SAAS,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACpC,WAAW,
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/lib/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGvF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,KAAK,EAAE,SAAS,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACpC,WAAW,CA4Bb;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,WAAW,CAQb;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAe9E"}
|
package/dist/lib/nav.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../../src/lib/nav.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../../src/lib/nav.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAchD;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,aAAa,EAAE,EACzB,QAAQ,CAAC,EAAE,eAAe,GACzB,OAAO,EAAE,CAqDX"}
|
package/dist/lib/sanitize.d.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronous sanitizer — returns sanitized HTML if DOMPurify has loaded,
|
|
3
|
+
* otherwise returns raw HTML. Call `ensureSanitizer()` during component mount
|
|
4
|
+
* to guarantee the purifier is ready before first render.
|
|
5
|
+
*/
|
|
1
6
|
export declare function sanitizeHtml(html: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Await this during component mount (e.g. useEffect) to guarantee
|
|
9
|
+
* DOMPurify is loaded before `sanitizeHtml` is called.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ensureSanitizer(): Promise<void>;
|
|
2
12
|
//# sourceMappingURL=sanitize.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD"}
|
package/dist/lib/text.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/lib/text.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/lib/text.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMzD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGhE"}
|
package/dist/media/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/media/queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,IAAI,CAAA;SAAE,EAAE,CAAC;QACxE,WAAW,EAAE,IAAI,CAAC;QAClB,UAAU,CAAC,EAAE,IAAI,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEvC,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,MAAM,CAAK;gBAEP,OAAO,EAAE,YAAY;IAIjC,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM;IAkB9B,SAAS,IAAI;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,EAAE,CAAA;KAAE;IAelF,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,eAAe;IA0DvB,OAAO,CAAC,aAAa;IAKrB,OAAO,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/media/queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,WAAW,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,IAAI,CAAA;SAAE,EAAE,CAAC;QACxE,WAAW,EAAE,IAAI,CAAC;QAClB,UAAU,CAAC,EAAE,IAAI,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEvC,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,MAAM,CAAK;gBAEP,OAAO,EAAE,YAAY;IAIjC,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM;IAkB9B,SAAS,IAAI;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,EAAE,CAAA;KAAE;IAelF,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,eAAe;IA0DvB,OAAO,CAAC,aAAa;IAKrB,OAAO,IAAI,IAAI;CAUhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"videoPoster.d.ts","sourceRoot":"","sources":["../../src/media/videoPoster.ts"],"names":[],"mappings":"AAAA,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"videoPoster.d.ts","sourceRoot":"","sources":["../../src/media/videoPoster.ts"],"names":[],"mappings":"AAAA,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAsE9D"}
|
package/dist/schemas/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
AudienceNameSchema,
|
|
4
4
|
MediaGridOptionsSchema,
|
|
5
5
|
slugifyAudienceName
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-GQV2554Z.js";
|
|
7
7
|
import {
|
|
8
8
|
AudienceSchema,
|
|
9
9
|
RoleSchema,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
TextLineSchema,
|
|
22
22
|
getSectionContentSchema,
|
|
23
23
|
getSectionSchema
|
|
24
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-LW5EGJFM.js";
|
|
25
25
|
import {
|
|
26
26
|
ImageManifestSchema,
|
|
27
27
|
MediaConfigSchema,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site-config.d.ts","sourceRoot":"","sources":["../../src/schemas/site-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,eAAO,MAAM,iBAAiB;;;;;;;;;iBAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,WAAW;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"site-config.d.ts","sourceRoot":"","sources":["../../src/schemas/site-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,eAAO,MAAM,iBAAiB;;;;;;;;;iBAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,WAAW;;;;;;;;;;;;;;iBAYvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEpD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;iBAY3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ import type { Audience } from "../../auth/types";
|
|
|
8
8
|
import type { MediaManifest } from "../../media/types";
|
|
9
9
|
import type { QueueItem } from "../../media/queue";
|
|
10
10
|
import { SiteConfigSchema } from "../../schemas/site-config";
|
|
11
|
+
import { ensureSanitizer } from "../../lib/sanitize";
|
|
11
12
|
import { EditorProvider, useEditorContext } from "./EditorContext";
|
|
12
13
|
import { EditorModalProvider, useEditorModal } from "./EditorModalContext";
|
|
13
14
|
import { EditorModal } from "./EditorModal";
|
|
@@ -112,6 +113,7 @@ export default function EditorShell({
|
|
|
112
113
|
const siteIndexRef = useRef<SiteIndex>({ siteId, order: [], sections: {} });
|
|
113
114
|
const fontLinkRef = useRef<HTMLLinkElement | null>(null);
|
|
114
115
|
useEffect(() => { siteIndexRef.current = siteIndex; }, [siteIndex]);
|
|
116
|
+
useEffect(() => { void ensureSanitizer(); }, []);
|
|
115
117
|
|
|
116
118
|
const persistence = useEditorPersistence(siteIndexRef);
|
|
117
119
|
|
|
@@ -29,7 +29,7 @@ type TabId = "users" | "viewer-access" | "display";
|
|
|
29
29
|
|
|
30
30
|
export function SiteSettingsModal({ isOpen, onClose, siteConfig, onSiteConfigChange, onAudiencesChange, capabilities, currentUser }: Props) {
|
|
31
31
|
const tabs: { id: TabId; label: string; show: boolean }[] = [
|
|
32
|
-
{ id: "users", label: "Users", show: capabilities.userManagement },
|
|
32
|
+
{ id: "users", label: "Users", show: capabilities.userManagement && currentUser?.role === "owner" },
|
|
33
33
|
{ id: "viewer-access", label: "Viewer Access", show: true },
|
|
34
34
|
{ id: "display", label: "Display", show: true },
|
|
35
35
|
];
|
|
@@ -25,8 +25,10 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
25
25
|
const [elapsedSeconds, setElapsedSeconds] = useState(0);
|
|
26
26
|
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
27
27
|
const clearRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
28
|
+
const fadeRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
28
29
|
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
29
30
|
const isPolling = useRef(false);
|
|
31
|
+
const mountedRef = useRef(true);
|
|
30
32
|
|
|
31
33
|
const stopTimer = useCallback(() => {
|
|
32
34
|
if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; }
|
|
@@ -61,6 +63,8 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
61
63
|
|
|
62
64
|
const handleStatusUpdate = useCallback(
|
|
63
65
|
(data: BuildStatusResponse | null, isInitialLoad: boolean) => {
|
|
66
|
+
if (!mountedRef.current) return;
|
|
67
|
+
|
|
64
68
|
if (!data) {
|
|
65
69
|
if (isInitialLoad) {
|
|
66
70
|
setState("idle");
|
|
@@ -80,13 +84,17 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
80
84
|
stopTimer();
|
|
81
85
|
if (data.state === "ready") {
|
|
82
86
|
clearRef.current = setTimeout(() => {
|
|
87
|
+
if (!mountedRef.current) return;
|
|
83
88
|
setState("fading");
|
|
84
|
-
|
|
89
|
+
fadeRef.current = setTimeout(() => {
|
|
90
|
+
if (!mountedRef.current) return;
|
|
91
|
+
setState("idle");
|
|
92
|
+
}, FADE_DURATION);
|
|
85
93
|
}, AUTO_CLEAR_DELAY);
|
|
86
94
|
}
|
|
87
95
|
}
|
|
88
96
|
},
|
|
89
|
-
[stopPolling],
|
|
97
|
+
[stopPolling, stopTimer],
|
|
90
98
|
);
|
|
91
99
|
|
|
92
100
|
const startPolling = useCallback(() => {
|
|
@@ -117,11 +125,13 @@ export function useBuildStatus(): BuildStatusResult {
|
|
|
117
125
|
|
|
118
126
|
return () => {
|
|
119
127
|
cancelled = true;
|
|
128
|
+
mountedRef.current = false;
|
|
120
129
|
stopPolling();
|
|
121
130
|
stopTimer();
|
|
122
131
|
if (clearRef.current) clearTimeout(clearRef.current);
|
|
132
|
+
if (fadeRef.current) clearTimeout(fadeRef.current);
|
|
123
133
|
};
|
|
124
|
-
}, [fetchStatus, handleStatusUpdate, startPolling, stopPolling]);
|
|
134
|
+
}, [fetchStatus, handleStatusUpdate, startPolling, stopPolling, stopTimer]);
|
|
125
135
|
|
|
126
136
|
const startTracking = useCallback(() => {
|
|
127
137
|
if (clearRef.current) { clearTimeout(clearRef.current); clearRef.current = null; }
|
|
@@ -25,15 +25,27 @@ export function useEditorPersistence(siteIndexRef: React.RefObject<SiteIndex>) {
|
|
|
25
25
|
sectionId,
|
|
26
26
|
content,
|
|
27
27
|
}));
|
|
28
|
+
const wasIndexDirty = s.indexDirty;
|
|
28
29
|
|
|
30
|
+
// Clear optimistically so new edits during the await go into a fresh set
|
|
29
31
|
s.pendingSections = new Map();
|
|
30
|
-
const wasIndexDirty = s.indexDirty;
|
|
31
32
|
s.indexDirty = false;
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
try {
|
|
35
|
+
await persistAll(
|
|
36
|
+
entries,
|
|
37
|
+
wasIndexDirty ? siteIndexRef.current : undefined,
|
|
38
|
+
);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
// Restore: merge back any entries that weren't re-dirtied during the await
|
|
41
|
+
for (const { sectionId, content } of entries) {
|
|
42
|
+
if (!s.pendingSections.has(sectionId)) {
|
|
43
|
+
s.pendingSections.set(sectionId, content);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (wasIndexDirty) s.indexDirty = true;
|
|
47
|
+
console.error("Failed to flush to Dexie:", err);
|
|
48
|
+
}
|
|
37
49
|
}, [siteIndexRef]);
|
|
38
50
|
|
|
39
51
|
const scheduleFlush = useCallback(() => {
|
|
@@ -44,8 +56,9 @@ export function useEditorPersistence(siteIndexRef: React.RefObject<SiteIndex>) {
|
|
|
44
56
|
useEffect(() => {
|
|
45
57
|
return () => {
|
|
46
58
|
if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
|
|
59
|
+
void flushToDexie();
|
|
47
60
|
};
|
|
48
|
-
}, []);
|
|
61
|
+
}, [flushToDexie]);
|
|
49
62
|
|
|
50
63
|
const markSectionDirty = useCallback(
|
|
51
64
|
(sectionId: string, content: SectionContent) => {
|
|
@@ -62,6 +62,7 @@ export function useEditorPublish({
|
|
|
62
62
|
const [publishAction, setPublishAction] = useState<PublishAction>("idle");
|
|
63
63
|
const [publishFeedback, setPublishFeedback] = useState<string | null>(null);
|
|
64
64
|
const feedbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
65
|
+
const inFlightRef = useRef(false);
|
|
65
66
|
|
|
66
67
|
useEffect(() => {
|
|
67
68
|
return () => {
|
|
@@ -184,11 +185,16 @@ export function useEditorPublish({
|
|
|
184
185
|
throw new Error(errorBody.error || "Save failed");
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
|
|
188
|
+
const responseData = await response.json().catch(() => null);
|
|
189
|
+
if (!responseData?.sha) {
|
|
190
|
+
throw new Error("Save response missing SHA");
|
|
191
|
+
}
|
|
192
|
+
return responseData;
|
|
188
193
|
}
|
|
189
194
|
|
|
190
195
|
const handleSave = useCallback(async () => {
|
|
191
|
-
if (!siteConfig) return;
|
|
196
|
+
if (!siteConfig || inFlightRef.current) return;
|
|
197
|
+
inFlightRef.current = true;
|
|
192
198
|
|
|
193
199
|
setPublishAction("saving");
|
|
194
200
|
setPublishFeedback(null);
|
|
@@ -222,9 +228,10 @@ export function useEditorPublish({
|
|
|
222
228
|
console.error("Save failed:", error);
|
|
223
229
|
showFeedback("Save failed", 5000);
|
|
224
230
|
} finally {
|
|
231
|
+
inFlightRef.current = false;
|
|
225
232
|
setPublishAction("idle");
|
|
226
233
|
}
|
|
227
|
-
}, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig,
|
|
234
|
+
}, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, deletedSectionIds, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback]);
|
|
228
235
|
|
|
229
236
|
const handlePublish = useCallback(async () => {
|
|
230
237
|
setPublishAction("publishing");
|
|
@@ -241,7 +248,11 @@ export function useEditorPublish({
|
|
|
241
248
|
throw new Error(errorBody.error || "Publish failed");
|
|
242
249
|
}
|
|
243
250
|
|
|
244
|
-
const
|
|
251
|
+
const responseData = await response.json().catch(() => null);
|
|
252
|
+
if (!responseData?.sha) {
|
|
253
|
+
throw new Error("Publish response missing SHA");
|
|
254
|
+
}
|
|
255
|
+
const { sha } = responseData;
|
|
245
256
|
|
|
246
257
|
onShasUpdated(null, sha);
|
|
247
258
|
onPublishComplete?.();
|
|
@@ -254,7 +265,8 @@ export function useEditorPublish({
|
|
|
254
265
|
}, [onShasUpdated, showFeedback, onPublishComplete]);
|
|
255
266
|
|
|
256
267
|
const handleSaveAndPublish = useCallback(async () => {
|
|
257
|
-
if (!siteConfig) return;
|
|
268
|
+
if (!siteConfig || inFlightRef.current) return;
|
|
269
|
+
inFlightRef.current = true;
|
|
258
270
|
|
|
259
271
|
setPublishAction("saving");
|
|
260
272
|
setPublishFeedback(null);
|
|
@@ -287,7 +299,11 @@ export function useEditorPublish({
|
|
|
287
299
|
throw new Error(errorBody.error || "Publish failed");
|
|
288
300
|
}
|
|
289
301
|
|
|
290
|
-
const
|
|
302
|
+
const publishData = await publishResponse.json().catch(() => null);
|
|
303
|
+
if (!publishData?.sha) {
|
|
304
|
+
throw new Error("Publish response missing SHA");
|
|
305
|
+
}
|
|
306
|
+
const { sha } = publishData;
|
|
291
307
|
|
|
292
308
|
if (hasLocalEdits) {
|
|
293
309
|
await discardLocalChanges();
|
|
@@ -304,6 +320,7 @@ export function useEditorPublish({
|
|
|
304
320
|
console.error("Publish failed:", error);
|
|
305
321
|
showFeedback("Publish failed", 5000);
|
|
306
322
|
} finally {
|
|
323
|
+
inFlightRef.current = false;
|
|
307
324
|
setPublishAction("idle");
|
|
308
325
|
}
|
|
309
326
|
}, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback, onPublishComplete]);
|
|
@@ -59,6 +59,7 @@ export function useMediaPipeline({
|
|
|
59
59
|
|
|
60
60
|
const queueRef = useRef<ProcessingQueue | null>(null);
|
|
61
61
|
const uploadCallbacksRef = useRef<Map<string, (imageId: string) => void>>(new Map());
|
|
62
|
+
const destroyedRef = useRef(false);
|
|
62
63
|
|
|
63
64
|
// --- Processing queue ---
|
|
64
65
|
|
|
@@ -68,6 +69,7 @@ export function useMediaPipeline({
|
|
|
68
69
|
|
|
69
70
|
useEffect(() => {
|
|
70
71
|
if (!siteConfig) return;
|
|
72
|
+
destroyedRef.current = false;
|
|
71
73
|
const mediaConfig = siteConfig.media;
|
|
72
74
|
const queue = new ProcessingQueue({
|
|
73
75
|
sizes: mediaConfig.sizes,
|
|
@@ -93,6 +95,7 @@ export function useMediaPipeline({
|
|
|
93
95
|
width: number,
|
|
94
96
|
height: number,
|
|
95
97
|
) => {
|
|
98
|
+
if (destroyedRef.current) return;
|
|
96
99
|
const item: LibraryMediaItem = {
|
|
97
100
|
id: event.item.hash,
|
|
98
101
|
hash: event.item.hash,
|
|
@@ -163,7 +166,11 @@ export function useMediaPipeline({
|
|
|
163
166
|
},
|
|
164
167
|
});
|
|
165
168
|
queueRef.current = queue;
|
|
166
|
-
return () =>
|
|
169
|
+
return () => {
|
|
170
|
+
destroyedRef.current = true;
|
|
171
|
+
queue.destroy();
|
|
172
|
+
uploadCallbacksRef.current = new Map();
|
|
173
|
+
};
|
|
167
174
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- rebuild only when processing params change, not on every siteConfig reference change
|
|
168
175
|
}, [mediaConfigKey, setLocalChangesExist]);
|
|
169
176
|
|
|
@@ -198,14 +205,20 @@ export function useMediaPipeline({
|
|
|
198
205
|
const idSet = new Set(ids);
|
|
199
206
|
const pendingIds = new Set(pendingMediaItems.filter((i) => idSet.has(i.id)).map((i) => i.id));
|
|
200
207
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
try {
|
|
209
|
+
for (const id of ids) {
|
|
210
|
+
if (pendingIds.has(id)) {
|
|
211
|
+
await removePendingMediaItem(id);
|
|
212
|
+
} else {
|
|
213
|
+
await markPendingMediaDeleted(id);
|
|
214
|
+
}
|
|
206
215
|
}
|
|
216
|
+
} catch (err) {
|
|
217
|
+
console.error("Failed to delete media:", err);
|
|
218
|
+
throw err;
|
|
207
219
|
}
|
|
208
220
|
|
|
221
|
+
// Only update React state after all Dexie writes succeed
|
|
209
222
|
setPendingMediaItems((prev) => prev.filter((i) => !idSet.has(i.id)));
|
|
210
223
|
setPendingLocalUrls((prev) => {
|
|
211
224
|
const next = { ...prev };
|
|
@@ -271,9 +284,11 @@ export function useMediaPipeline({
|
|
|
271
284
|
const mediaConfig = siteConfig.media;
|
|
272
285
|
if (file.size > mediaConfig.maxFileSize) return;
|
|
273
286
|
|
|
287
|
+
const currentQueue = queueRef.current;
|
|
274
288
|
(async () => {
|
|
275
289
|
try {
|
|
276
290
|
const buffer = await file.arrayBuffer();
|
|
291
|
+
if (queueRef.current !== currentQueue) return; // queue was rebuilt, skip
|
|
277
292
|
const hash = await hashFileBuffer(buffer);
|
|
278
293
|
|
|
279
294
|
const isDeleted = pendingDeletions.includes(hash);
|
package/src/lib/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { env } from "./env";
|
|
|
2
2
|
export { cn } from "./cn";
|
|
3
3
|
export { generateNavLinks, toSectionId, type NavItem } from "./nav";
|
|
4
4
|
export { deriveContrast } from "./contrast";
|
|
5
|
-
export { sanitizeHtml } from "./sanitize";
|
|
5
|
+
export { sanitizeHtml, ensureSanitizer } from "./sanitize";
|
|
6
6
|
export { gridColsClass } from "./grid";
|
|
7
7
|
export { getIcon, curatedIcons, type IconEntry } from "./icons";
|
|
8
8
|
export { buildGoogleFontsUrl } from "./google-fonts";
|
package/src/lib/loader.ts
CHANGED
|
@@ -24,6 +24,7 @@ export function mergeSiteContent(
|
|
|
24
24
|
const sections: LoadedSection[] = [];
|
|
25
25
|
|
|
26
26
|
const canValidate = getAllSchemas().length >= 2;
|
|
27
|
+
const schema = canValidate ? getSectionSchema() : null;
|
|
27
28
|
|
|
28
29
|
for (const id of index.order) {
|
|
29
30
|
const raw = sectionFiles[id];
|
|
@@ -31,8 +32,8 @@ export function mergeSiteContent(
|
|
|
31
32
|
console.warn(`Section file missing for id: ${id}, skipping`);
|
|
32
33
|
continue;
|
|
33
34
|
}
|
|
34
|
-
if (canValidate) {
|
|
35
|
-
const result =
|
|
35
|
+
if (canValidate && schema) {
|
|
36
|
+
const result = schema.safeParse(raw);
|
|
36
37
|
if (!result.success) {
|
|
37
38
|
const type = (raw as Record<string, unknown>).type ?? "unknown";
|
|
38
39
|
console.warn(`Skipping section "${id}" (type: ${type}): invalid schema`);
|
package/src/lib/nav.ts
CHANGED
|
@@ -9,11 +9,19 @@ export interface NavItem {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function toSectionId(text: string): string {
|
|
12
|
-
|
|
12
|
+
const slug = text
|
|
13
|
+
.normalize("NFKD")
|
|
14
|
+
.replace(/[̀-ͯ]/g, "")
|
|
13
15
|
.toLowerCase()
|
|
14
16
|
.replace(/[^\w\s-]/g, "")
|
|
15
|
-
.
|
|
16
|
-
.replace(
|
|
17
|
+
.replace(/\s+/g, "-")
|
|
18
|
+
.replace(/^-+|-+$/g, "");
|
|
19
|
+
if (slug) return slug;
|
|
20
|
+
let hash = 0;
|
|
21
|
+
for (let i = 0; i < text.length; i++) {
|
|
22
|
+
hash = ((hash << 5) - hash + text.charCodeAt(i)) | 0;
|
|
23
|
+
}
|
|
24
|
+
return `section-${Math.abs(hash).toString(36)}`;
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
export function generateNavLinks(
|
|
@@ -37,6 +45,10 @@ export function generateNavLinks(
|
|
|
37
45
|
if (!role) continue;
|
|
38
46
|
|
|
39
47
|
if (role === "h1") {
|
|
48
|
+
if (content.excludeFromNav) {
|
|
49
|
+
currentParent = null;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
40
52
|
currentParent = {
|
|
41
53
|
href: `#${toSectionId(content.heading)}`,
|
|
42
54
|
label: content.heading,
|
package/src/lib/sanitize.ts
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
|
-
let
|
|
1
|
+
let purifierPromise: Promise<typeof import("dompurify")> | null = null;
|
|
2
|
+
let purifier: ((html: string) => string) | null = null;
|
|
2
3
|
|
|
3
4
|
if (typeof window !== "undefined") {
|
|
4
|
-
import("dompurify").then((
|
|
5
|
+
purifierPromise = import("dompurify").then((mod) => {
|
|
6
|
+
const DOMPurify = mod.default ?? mod;
|
|
7
|
+
purifier = (html: string) => DOMPurify.sanitize(html);
|
|
8
|
+
return mod;
|
|
9
|
+
});
|
|
5
10
|
}
|
|
6
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Synchronous sanitizer — returns sanitized HTML if DOMPurify has loaded,
|
|
14
|
+
* otherwise returns raw HTML. Call `ensureSanitizer()` during component mount
|
|
15
|
+
* to guarantee the purifier is ready before first render.
|
|
16
|
+
*/
|
|
7
17
|
export function sanitizeHtml(html: string): string {
|
|
8
18
|
if (!html) return "";
|
|
9
|
-
return purifier ? purifier
|
|
19
|
+
return purifier ? purifier(html) : html;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Await this during component mount (e.g. useEffect) to guarantee
|
|
24
|
+
* DOMPurify is loaded before `sanitizeHtml` is called.
|
|
25
|
+
*/
|
|
26
|
+
export async function ensureSanitizer(): Promise<void> {
|
|
27
|
+
if (typeof window === "undefined") return;
|
|
28
|
+
if (!purifier && purifierPromise) await purifierPromise;
|
|
10
29
|
}
|
package/src/lib/text.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export function stripHtmlToPlainText(html: string): string {
|
|
2
|
-
return html
|
|
2
|
+
return html
|
|
3
|
+
.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, "")
|
|
4
|
+
.replace(/<[^>]+>/g, " ")
|
|
5
|
+
.replace(/\s+/g, " ")
|
|
6
|
+
.trim();
|
|
3
7
|
}
|
|
4
8
|
|
|
5
9
|
export function truncate(text: string, maxLength: number): string {
|
package/src/media/queue.ts
CHANGED
|
@@ -156,7 +156,9 @@ export class ProcessingQueue {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
destroy(): void {
|
|
159
|
-
for (const [id, worker] of this.activeWorkers) {
|
|
159
|
+
for (const [id, worker] of Array.from(this.activeWorkers)) {
|
|
160
|
+
worker.onmessage = null;
|
|
161
|
+
worker.onerror = null;
|
|
160
162
|
worker.terminate();
|
|
161
163
|
this.activeWorkers.delete(id);
|
|
162
164
|
}
|
package/src/media/videoPoster.ts
CHANGED
|
@@ -5,21 +5,46 @@ export function generateVideoPoster(
|
|
|
5
5
|
return new Promise((resolve, reject) => {
|
|
6
6
|
const video = document.createElement("video");
|
|
7
7
|
const url = URL.createObjectURL(blob);
|
|
8
|
-
|
|
9
|
-
video.preload = "auto";
|
|
10
|
-
video.src = url;
|
|
8
|
+
let settled = false;
|
|
11
9
|
|
|
12
10
|
const cleanup = () => {
|
|
11
|
+
clearTimeout(timeoutId);
|
|
13
12
|
URL.revokeObjectURL(url);
|
|
14
13
|
video.removeAttribute("src");
|
|
15
14
|
video.load();
|
|
16
15
|
};
|
|
17
16
|
|
|
17
|
+
const safeReject = (err: Error) => {
|
|
18
|
+
if (settled) return;
|
|
19
|
+
settled = true;
|
|
20
|
+
cleanup();
|
|
21
|
+
reject(err);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const timeoutId = setTimeout(() => {
|
|
25
|
+
safeReject(new Error("Video poster generation timed out"));
|
|
26
|
+
}, 10_000);
|
|
27
|
+
|
|
28
|
+
video.addEventListener("error", () => {
|
|
29
|
+
safeReject(new Error("Failed to load video for poster generation"));
|
|
30
|
+
}, { once: true });
|
|
31
|
+
|
|
32
|
+
video.muted = true;
|
|
33
|
+
video.preload = "auto";
|
|
34
|
+
video.src = url;
|
|
35
|
+
|
|
18
36
|
video.addEventListener("loadeddata", () => {
|
|
19
37
|
video.addEventListener("seeked", () => {
|
|
38
|
+
if (settled) return;
|
|
20
39
|
try {
|
|
21
40
|
const w = video.videoWidth;
|
|
22
41
|
const h = video.videoHeight;
|
|
42
|
+
|
|
43
|
+
if (w === 0 || h === 0) {
|
|
44
|
+
safeReject(new Error("Video dimensions not available"));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
23
48
|
const canvas = document.createElement("canvas");
|
|
24
49
|
canvas.width = w;
|
|
25
50
|
canvas.height = h;
|
|
@@ -27,6 +52,8 @@ export function generateVideoPoster(
|
|
|
27
52
|
ctx.drawImage(video, 0, 0);
|
|
28
53
|
canvas.toBlob(
|
|
29
54
|
(posterBlob) => {
|
|
55
|
+
if (settled) return;
|
|
56
|
+
settled = true;
|
|
30
57
|
cleanup();
|
|
31
58
|
if (!posterBlob) {
|
|
32
59
|
reject(new Error("Failed to create poster blob"));
|
|
@@ -38,16 +65,10 @@ export function generateVideoPoster(
|
|
|
38
65
|
quality / 100,
|
|
39
66
|
);
|
|
40
67
|
} catch (err) {
|
|
41
|
-
|
|
42
|
-
reject(err);
|
|
68
|
+
safeReject(err instanceof Error ? err : new Error(String(err)));
|
|
43
69
|
}
|
|
44
70
|
}, { once: true });
|
|
45
71
|
video.currentTime = isFinite(video.duration) ? Math.min(0.1, video.duration / 2) : 0;
|
|
46
72
|
}, { once: true });
|
|
47
|
-
|
|
48
|
-
video.addEventListener("error", () => {
|
|
49
|
-
cleanup();
|
|
50
|
-
reject(new Error("Failed to load video for poster generation"));
|
|
51
|
-
}, { once: true });
|
|
52
73
|
});
|
|
53
74
|
}
|
|
@@ -20,8 +20,12 @@ export const IndexSchema = z.object({
|
|
|
20
20
|
sections: z.record(z.string(), SectionMetaSchema),
|
|
21
21
|
lastModified: z.string().nullable().optional(),
|
|
22
22
|
}).refine(
|
|
23
|
-
(data) =>
|
|
24
|
-
|
|
23
|
+
(data) => {
|
|
24
|
+
const orderSet = new Set(data.order);
|
|
25
|
+
return data.order.every((id) => id in data.sections) &&
|
|
26
|
+
Object.keys(data.sections).every((key) => orderSet.has(key));
|
|
27
|
+
},
|
|
28
|
+
{ message: "Every id in order must have a sections entry and vice versa" }
|
|
25
29
|
);
|
|
26
30
|
|
|
27
31
|
export type SiteIndex = z.infer<typeof IndexSchema>;
|