@drawnagency/primitives 0.1.91 → 0.1.92
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-ZLG7S54V.js → chunk-7QEJDYTG.js} +6 -6
- package/dist/{chunk-XUIJHYKI.js → chunk-A2V7BZPD.js} +3 -3
- package/dist/{chunk-NV4R6VHN.js → chunk-CI624HRS.js} +1 -1
- package/dist/{chunk-SIYQ37QY.js → chunk-IZLXF3CR.js} +1 -1
- package/dist/{chunk-XKRQMTE4.js → chunk-LQQY2RW6.js} +2 -2
- package/dist/{chunk-KMQZYORS.js → chunk-RUKO2N3F.js} +5 -1
- package/dist/{chunk-CZ3YXGK5.js → chunk-SOILPZ7U.js} +27 -3
- package/dist/{chunk-AGWE2LJG.js → chunk-WOYGQK2D.js} +1 -1
- package/dist/{chunk-KBQNJZUX.js → chunk-YI2HUFE3.js} +1 -1
- package/dist/{chunk-MK7YB2A4.js → chunk-Z66TBL4Q.js} +1 -1
- package/dist/components/editor/index.js +4 -4
- package/dist/components/primitives/index.js +5 -5
- package/dist/components/sections/brand-guide.js +5 -5
- package/dist/components/sections/register-schemas.js +8 -8
- package/dist/components/sections/register.js +8 -8
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/ViewerLoginForm.d.ts +1 -0
- package/dist/components/shell/ViewerLoginForm.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/lib/index.js +2 -2
- package/dist/media/compose-preview.d.ts +9 -0
- package/dist/media/compose-preview.d.ts.map +1 -1
- package/dist/media/index.js +2 -2
- package/dist/media/preview-inputs.d.ts.map +1 -1
- package/dist/schemas/index.js +2 -2
- package/dist/schemas/site-config.d.ts +1 -0
- package/dist/schemas/site-config.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/shell/EditorShell.tsx +17 -6
- package/src/components/shell/ViewerLoginForm.tsx +19 -8
- package/src/media/compose-preview.ts +56 -4
- package/src/media/preview-inputs.ts +3 -1
- package/src/schemas/site-config.ts +4 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
pluralize,
|
|
4
4
|
stripHtmlToPlainText,
|
|
5
5
|
truncate
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-A2V7BZPD.js";
|
|
7
7
|
import {
|
|
8
8
|
DeleteButton,
|
|
9
9
|
DragHandle,
|
|
@@ -17,11 +17,11 @@ import {
|
|
|
17
17
|
buildBlockDropData,
|
|
18
18
|
canDropBlock,
|
|
19
19
|
isBlockDragData
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-IZLXF3CR.js";
|
|
21
21
|
import {
|
|
22
22
|
EditableRichText,
|
|
23
23
|
defineHeadingSection
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-YI2HUFE3.js";
|
|
25
25
|
import {
|
|
26
26
|
EditablePlainText,
|
|
27
27
|
ImageDropZone,
|
|
@@ -31,11 +31,11 @@ import {
|
|
|
31
31
|
containerGridClass,
|
|
32
32
|
masonryColumnsClass,
|
|
33
33
|
spacerHiddenClass
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-LQQY2RW6.js";
|
|
35
35
|
import {
|
|
36
36
|
useMediaLibrary,
|
|
37
37
|
useResolvedMedia
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-CI624HRS.js";
|
|
39
39
|
import {
|
|
40
40
|
embedUrl
|
|
41
41
|
} from "./chunk-Y4JQGO6G.js";
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
} from "./chunk-TZQPOR5A.js";
|
|
55
55
|
import {
|
|
56
56
|
SingleMediaReferenceSchema
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-RUKO2N3F.js";
|
|
58
58
|
import {
|
|
59
59
|
defineSection,
|
|
60
60
|
getSection
|
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
MediaBlock,
|
|
10
10
|
containerGridClass,
|
|
11
11
|
useEditableCollection
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LQQY2RW6.js";
|
|
13
13
|
import {
|
|
14
14
|
AddIcon,
|
|
15
15
|
DeleteIcon,
|
|
16
16
|
DragHandle,
|
|
17
17
|
IconButton
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-CI624HRS.js";
|
|
19
19
|
import {
|
|
20
20
|
DEFAULT_LINK,
|
|
21
21
|
LinkValueSchema
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
import {
|
|
31
31
|
HexColorSchema,
|
|
32
32
|
SingleMediaReferenceSchema
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-RUKO2N3F.js";
|
|
34
34
|
import {
|
|
35
35
|
defineSection
|
|
36
36
|
} from "./chunk-VKAGMEKE.js";
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
SettingsIcon,
|
|
7
7
|
useMediaLibrary,
|
|
8
8
|
useResolvedMedia
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-CI624HRS.js";
|
|
10
10
|
import {
|
|
11
11
|
rebindImageRef
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SOILPZ7U.js";
|
|
13
13
|
import {
|
|
14
14
|
cn
|
|
15
15
|
} from "./chunk-7EOSDFS3.js";
|
|
@@ -309,7 +309,11 @@ var SiteConfigSchema = z3.object({
|
|
|
309
309
|
previewImage: z3.object({
|
|
310
310
|
imageId: z3.string(),
|
|
311
311
|
width: z3.number().int().positive(),
|
|
312
|
-
height: z3.number().int().positive()
|
|
312
|
+
height: z3.number().int().positive(),
|
|
313
|
+
// Compose-logic version that produced this raster. Pre-existing rasters
|
|
314
|
+
// (and manifests written before this field) default to 0, which is below
|
|
315
|
+
// any current PREVIEW_COMPOSE_VERSION, so the editor regenerates them.
|
|
316
|
+
version: z3.number().int().nonnegative().default(0)
|
|
313
317
|
}).nullable().default(null),
|
|
314
318
|
// Optional site description → og:description + <meta name="description">.
|
|
315
319
|
description: z3.string().max(300).nullable().default(null),
|
|
@@ -311,6 +311,31 @@ function generateVideoPoster(blob, quality) {
|
|
|
311
311
|
// src/media/compose-preview.ts
|
|
312
312
|
var WIDTH = 1200;
|
|
313
313
|
var HEIGHT = 630;
|
|
314
|
+
var HEADING_WEIGHT = 700;
|
|
315
|
+
async function loadHeadingFont(family, googleFontsUrl) {
|
|
316
|
+
if (!family || family === "system-ui" || !googleFontsUrl || typeof FontFace === "undefined") return;
|
|
317
|
+
const spec = `${HEADING_WEIGHT} 84px "${family}"`;
|
|
318
|
+
try {
|
|
319
|
+
const loaded = await document.fonts.load(spec);
|
|
320
|
+
if (loaded.length > 0) return;
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
const css = await (await fetch(googleFontsUrl)).text();
|
|
325
|
+
const blocks = css.match(/@font-face\s*\{[^}]*\}/g) ?? [];
|
|
326
|
+
const wanted = blocks.filter((b) => new RegExp(`font-weight:\\s*${HEADING_WEIGHT}\\b`).test(b));
|
|
327
|
+
await Promise.all(
|
|
328
|
+
(wanted.length ? wanted : blocks).map(async (block) => {
|
|
329
|
+
const url = block.match(/url\(([^)]+)\)/)?.[1];
|
|
330
|
+
if (!url) return;
|
|
331
|
+
const face = new FontFace(family, `url(${url})`, { weight: String(HEADING_WEIGHT) });
|
|
332
|
+
await face.load();
|
|
333
|
+
document.fonts.add(face);
|
|
334
|
+
})
|
|
335
|
+
);
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
}
|
|
314
339
|
async function composePreviewImage(inputs) {
|
|
315
340
|
const canvas = document.createElement("canvas");
|
|
316
341
|
canvas.width = WIDTH;
|
|
@@ -340,12 +365,11 @@ async function composePreviewImage(inputs) {
|
|
|
340
365
|
}
|
|
341
366
|
if (!drewLogo) {
|
|
342
367
|
if (document.fonts?.ready) await document.fonts.ready;
|
|
343
|
-
await
|
|
344
|
-
});
|
|
368
|
+
await loadHeadingFont(inputs.headingFont, inputs.googleFontsUrl);
|
|
345
369
|
ctx.fillStyle = inputs.textColor;
|
|
346
370
|
ctx.textAlign = "center";
|
|
347
371
|
ctx.textBaseline = "middle";
|
|
348
|
-
ctx.font =
|
|
372
|
+
ctx.font = `${HEADING_WEIGHT} 84px "${inputs.headingFont}", system-ui, sans-serif`;
|
|
349
373
|
ctx.fillText(inputs.siteName, WIDTH / 2, HEIGHT / 2, WIDTH * 0.86);
|
|
350
374
|
}
|
|
351
375
|
return await new Promise((resolve, reject) => {
|
|
@@ -11,20 +11,20 @@ import {
|
|
|
11
11
|
buildBlockDropData,
|
|
12
12
|
canDropBlock,
|
|
13
13
|
isBlockDragData
|
|
14
|
-
} from "../../chunk-
|
|
14
|
+
} from "../../chunk-IZLXF3CR.js";
|
|
15
15
|
import "../../chunk-5THS4K5D.js";
|
|
16
16
|
import "../../chunk-ZSPCE6MZ.js";
|
|
17
17
|
import {
|
|
18
18
|
AddIcon,
|
|
19
19
|
IconButton
|
|
20
|
-
} from "../../chunk-
|
|
21
|
-
import "../../chunk-
|
|
20
|
+
} from "../../chunk-CI624HRS.js";
|
|
21
|
+
import "../../chunk-SOILPZ7U.js";
|
|
22
22
|
import "../../chunk-677NTHAZ.js";
|
|
23
23
|
import {
|
|
24
24
|
cn
|
|
25
25
|
} from "../../chunk-7EOSDFS3.js";
|
|
26
26
|
import "../../chunk-YBVK254P.js";
|
|
27
|
-
import "../../chunk-
|
|
27
|
+
import "../../chunk-RUKO2N3F.js";
|
|
28
28
|
import "../../chunk-FM4M4Q2W.js";
|
|
29
29
|
import "../../chunk-JSBRDJBE.js";
|
|
30
30
|
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
EditableRichText,
|
|
3
3
|
LinkPopover,
|
|
4
4
|
defineHeadingSection
|
|
5
|
-
} from "../../chunk-
|
|
5
|
+
} from "../../chunk-YI2HUFE3.js";
|
|
6
6
|
import {
|
|
7
7
|
EditableGrid,
|
|
8
8
|
EditablePlainText,
|
|
@@ -10,20 +10,20 @@ import {
|
|
|
10
10
|
MediaBlock,
|
|
11
11
|
ResolvedMedia,
|
|
12
12
|
useEditableCollection
|
|
13
|
-
} from "../../chunk-
|
|
13
|
+
} from "../../chunk-LQQY2RW6.js";
|
|
14
14
|
import "../../chunk-ZSPCE6MZ.js";
|
|
15
15
|
import {
|
|
16
16
|
DeleteIcon,
|
|
17
17
|
DragHandle,
|
|
18
18
|
IconButton
|
|
19
|
-
} from "../../chunk-
|
|
20
|
-
import "../../chunk-
|
|
19
|
+
} from "../../chunk-CI624HRS.js";
|
|
20
|
+
import "../../chunk-SOILPZ7U.js";
|
|
21
21
|
import "../../chunk-53DKVAB6.js";
|
|
22
22
|
import {
|
|
23
23
|
cn
|
|
24
24
|
} from "../../chunk-7EOSDFS3.js";
|
|
25
25
|
import "../../chunk-YBVK254P.js";
|
|
26
|
-
import "../../chunk-
|
|
26
|
+
import "../../chunk-RUKO2N3F.js";
|
|
27
27
|
import "../../chunk-VKAGMEKE.js";
|
|
28
28
|
import "../../chunk-FM4M4Q2W.js";
|
|
29
29
|
import "../../chunk-JSBRDJBE.js";
|
|
@@ -2,16 +2,16 @@ import {
|
|
|
2
2
|
ColorItemSchema,
|
|
3
3
|
ColorSpaceSchema,
|
|
4
4
|
brandGuideSectionDefs
|
|
5
|
-
} from "../../chunk-
|
|
5
|
+
} from "../../chunk-A2V7BZPD.js";
|
|
6
6
|
import "../../chunk-5THS4K5D.js";
|
|
7
|
-
import "../../chunk-
|
|
8
|
-
import "../../chunk-
|
|
9
|
-
import "../../chunk-
|
|
7
|
+
import "../../chunk-LQQY2RW6.js";
|
|
8
|
+
import "../../chunk-CI624HRS.js";
|
|
9
|
+
import "../../chunk-SOILPZ7U.js";
|
|
10
10
|
import "../../chunk-PZJL2S3Z.js";
|
|
11
11
|
import "../../chunk-WDSVHKAH.js";
|
|
12
12
|
import "../../chunk-7EOSDFS3.js";
|
|
13
13
|
import "../../chunk-YBVK254P.js";
|
|
14
|
-
import "../../chunk-
|
|
14
|
+
import "../../chunk-RUKO2N3F.js";
|
|
15
15
|
import "../../chunk-VKAGMEKE.js";
|
|
16
16
|
import "../../chunk-FM4M4Q2W.js";
|
|
17
17
|
import "../../chunk-JSBRDJBE.js";
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allSectionDefs
|
|
3
|
-
} from "../../chunk-
|
|
4
|
-
import "../../chunk-
|
|
5
|
-
import "../../chunk-
|
|
3
|
+
} from "../../chunk-7QEJDYTG.js";
|
|
4
|
+
import "../../chunk-A2V7BZPD.js";
|
|
5
|
+
import "../../chunk-IZLXF3CR.js";
|
|
6
6
|
import "../../chunk-5THS4K5D.js";
|
|
7
|
-
import "../../chunk-
|
|
8
|
-
import "../../chunk-
|
|
7
|
+
import "../../chunk-YI2HUFE3.js";
|
|
8
|
+
import "../../chunk-LQQY2RW6.js";
|
|
9
9
|
import "../../chunk-ZSPCE6MZ.js";
|
|
10
|
-
import "../../chunk-
|
|
10
|
+
import "../../chunk-CI624HRS.js";
|
|
11
11
|
import "../../chunk-Y4JQGO6G.js";
|
|
12
|
-
import "../../chunk-
|
|
12
|
+
import "../../chunk-SOILPZ7U.js";
|
|
13
13
|
import "../../chunk-PZJL2S3Z.js";
|
|
14
14
|
import "../../chunk-WDSVHKAH.js";
|
|
15
15
|
import "../../chunk-53DKVAB6.js";
|
|
16
16
|
import "../../chunk-7EOSDFS3.js";
|
|
17
17
|
import "../../chunk-YBVK254P.js";
|
|
18
18
|
import "../../chunk-TZQPOR5A.js";
|
|
19
|
-
import "../../chunk-
|
|
19
|
+
import "../../chunk-RUKO2N3F.js";
|
|
20
20
|
import {
|
|
21
21
|
registerRichText,
|
|
22
22
|
registerSchema
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allSectionDefs
|
|
3
|
-
} from "../../chunk-
|
|
4
|
-
import "../../chunk-
|
|
5
|
-
import "../../chunk-
|
|
3
|
+
} from "../../chunk-7QEJDYTG.js";
|
|
4
|
+
import "../../chunk-A2V7BZPD.js";
|
|
5
|
+
import "../../chunk-IZLXF3CR.js";
|
|
6
6
|
import "../../chunk-5THS4K5D.js";
|
|
7
|
-
import "../../chunk-
|
|
8
|
-
import "../../chunk-
|
|
7
|
+
import "../../chunk-YI2HUFE3.js";
|
|
8
|
+
import "../../chunk-LQQY2RW6.js";
|
|
9
9
|
import "../../chunk-ZSPCE6MZ.js";
|
|
10
|
-
import "../../chunk-
|
|
10
|
+
import "../../chunk-CI624HRS.js";
|
|
11
11
|
import "../../chunk-Y4JQGO6G.js";
|
|
12
|
-
import "../../chunk-
|
|
12
|
+
import "../../chunk-SOILPZ7U.js";
|
|
13
13
|
import "../../chunk-PZJL2S3Z.js";
|
|
14
14
|
import "../../chunk-WDSVHKAH.js";
|
|
15
15
|
import "../../chunk-53DKVAB6.js";
|
|
16
16
|
import "../../chunk-7EOSDFS3.js";
|
|
17
17
|
import "../../chunk-YBVK254P.js";
|
|
18
18
|
import "../../chunk-TZQPOR5A.js";
|
|
19
|
-
import "../../chunk-
|
|
19
|
+
import "../../chunk-RUKO2N3F.js";
|
|
20
20
|
import {
|
|
21
21
|
registerSection
|
|
22
22
|
} from "../../chunk-VKAGMEKE.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAcjD,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAuDtF,OAAO,KAAK,EAAE,eAAe,EAA2D,MAAM,oBAAoB,CAAC;AAwCnH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAoBxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAIhB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,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;IAIhE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAKjC,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,YAAY,EACZ,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,EACX,sBAA8B,EAC9B,eAAe,GAChB,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAcjD,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAuDtF,OAAO,KAAK,EAAE,eAAe,EAA2D,MAAM,oBAAoB,CAAC;AAwCnH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAoBxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAIhB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,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;IAIhE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAKjC,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,YAAY,EACZ,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,EACX,sBAA8B,EAC9B,eAAe,GAChB,EAAE,KAAK,2CAunDP;AAuED;6EAC6E;AAC7E,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,WAAW,CAAC,YAAY,CAAC,EACvC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,eAAe,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAK/B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ViewerLoginForm.d.ts","sourceRoot":"","sources":["../../../src/components/shell/ViewerLoginForm.tsx"],"names":[],"mappings":"AAKA,UAAU,KAAK;IACb,SAAS,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ViewerLoginForm.d.ts","sourceRoot":"","sources":["../../../src/components/shell/ViewerLoginForm.tsx"],"names":[],"mappings":"AAKA,UAAU,KAAK;IACb,SAAS,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC/E,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,KAAK,2CA+FzD"}
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
resolveMedia,
|
|
33
33
|
sanitizeMediaName,
|
|
34
34
|
setMediaProvider
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-SOILPZ7U.js";
|
|
36
36
|
import {
|
|
37
37
|
CollabPeerSchema,
|
|
38
38
|
HEARTBEAT_INTERVAL_MS,
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
AudienceNameSchema,
|
|
52
52
|
MediaGridOptionsSchema,
|
|
53
53
|
slugifyAudienceName
|
|
54
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-Z66TBL4Q.js";
|
|
55
55
|
import {
|
|
56
56
|
AudienceSchema,
|
|
57
57
|
RoleSchema,
|
|
@@ -77,7 +77,7 @@ import {
|
|
|
77
77
|
resolveChatbotName,
|
|
78
78
|
resolveThinkingPhrases,
|
|
79
79
|
safeRedirect
|
|
80
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-WOYGQK2D.js";
|
|
81
81
|
import {
|
|
82
82
|
curatedIcons,
|
|
83
83
|
getIcon
|
|
@@ -131,7 +131,7 @@ import {
|
|
|
131
131
|
TextLineSchema,
|
|
132
132
|
VariantSchema,
|
|
133
133
|
normalizeSiteIndex
|
|
134
|
-
} from "./chunk-
|
|
134
|
+
} from "./chunk-RUKO2N3F.js";
|
|
135
135
|
import {
|
|
136
136
|
clearRegistry,
|
|
137
137
|
createRegistry,
|
package/dist/lib/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resolveChatbotName,
|
|
12
12
|
resolveThinkingPhrases,
|
|
13
13
|
safeRedirect
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-WOYGQK2D.js";
|
|
15
15
|
import {
|
|
16
16
|
curatedIcons,
|
|
17
17
|
getIcon
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
} from "../chunk-YBVK254P.js";
|
|
38
38
|
import "../chunk-5BM2Y3NJ.js";
|
|
39
39
|
import "../chunk-TZQPOR5A.js";
|
|
40
|
-
import "../chunk-
|
|
40
|
+
import "../chunk-RUKO2N3F.js";
|
|
41
41
|
import {
|
|
42
42
|
clearRegistry,
|
|
43
43
|
createRegistry,
|
|
@@ -10,9 +10,18 @@ export interface PreviewImageInputs {
|
|
|
10
10
|
invertLogo: boolean;
|
|
11
11
|
siteName: string;
|
|
12
12
|
headingFont: string;
|
|
13
|
+
/** The site's Google Fonts CSS URL (or null for system-ui), used to load the
|
|
14
|
+
* heading font before drawing so text mode never falls back to system-ui. */
|
|
15
|
+
googleFontsUrl: string | null;
|
|
13
16
|
/** Computed contrasting text color (text mode). */
|
|
14
17
|
textColor: string;
|
|
15
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Bump when the composition logic changes so already-composed rasters (stamped
|
|
21
|
+
* with the old version, or 0 for pre-versioning ones) regenerate on next editor
|
|
22
|
+
* open. v2 = load the Google heading font before drawing + draw at weight 700.
|
|
23
|
+
*/
|
|
24
|
+
export declare const PREVIEW_COMPOSE_VERSION = 2;
|
|
16
25
|
/**
|
|
17
26
|
* Composes the 1200×630 social-preview raster in the browser (Canvas API only,
|
|
18
27
|
* no dependency). Returns a WebP blob. Must run client-side (uses document/Image
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose-preview.d.ts","sourceRoot":"","sources":["../../src/media/compose-preview.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC;mFAC+E;IAC/E,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,uFAAuF;IACvF,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"compose-preview.d.ts","sourceRoot":"","sources":["../../src/media/compose-preview.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC;mFAC+E;IAC/E,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,uFAAuF;IACvF,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB;kFAC8E;IAC9E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;CACnB;AAKD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AA0CzC;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwDnF"}
|
package/dist/media/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
resolveMedia,
|
|
19
19
|
sanitizeMediaName,
|
|
20
20
|
setMediaProvider
|
|
21
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-SOILPZ7U.js";
|
|
22
22
|
import "../chunk-YBVK254P.js";
|
|
23
23
|
import {
|
|
24
24
|
ImageManifestSchema,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
MediaFolderSchema,
|
|
27
27
|
MediaItemSchema,
|
|
28
28
|
VariantSchema
|
|
29
|
-
} from "../chunk-
|
|
29
|
+
} from "../chunk-RUKO2N3F.js";
|
|
30
30
|
import "../chunk-FM4M4Q2W.js";
|
|
31
31
|
import "../chunk-JSBRDJBE.js";
|
|
32
32
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview-inputs.d.ts","sourceRoot":"","sources":["../../src/media/preview-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GACjD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"preview-inputs.d.ts","sourceRoot":"","sources":["../../src/media/preview-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GACjD;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAyCnD;AAKD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAKpF"}
|
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-Z66TBL4Q.js";
|
|
7
7
|
import {
|
|
8
8
|
AudienceSchema,
|
|
9
9
|
RoleSchema,
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
TextLineSchema,
|
|
42
42
|
VariantSchema,
|
|
43
43
|
normalizeSiteIndex
|
|
44
|
-
} from "../chunk-
|
|
44
|
+
} from "../chunk-RUKO2N3F.js";
|
|
45
45
|
import "../chunk-VKAGMEKE.js";
|
|
46
46
|
import "../chunk-FM4M4Q2W.js";
|
|
47
47
|
import "../chunk-NSCT3AMV.js";
|
|
@@ -485,6 +485,7 @@ export declare const SiteConfigSchema: z.ZodObject<{
|
|
|
485
485
|
imageId: z.ZodString;
|
|
486
486
|
width: z.ZodNumber;
|
|
487
487
|
height: z.ZodNumber;
|
|
488
|
+
version: z.ZodDefault<z.ZodNumber>;
|
|
488
489
|
}, z.core.$strip>>>;
|
|
489
490
|
description: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
490
491
|
chatbot: z.ZodDefault<z.ZodObject<{
|
|
@@ -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;AASxB,eAAO,MAAM,YAAY;;;;;+FAExB,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;iBAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAQ5D,eAAO,MAAM,cAAc,8EAA+E,CAAC;AAW3G,eAAO,MAAM,gBAAgB;;;EAA+B,CAAC;AAE7D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAUrB,CAAC;AAEH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AA0C9C,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6CtB,CAAC;AAEL,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAyE,CAAC;AAElG,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAE7D,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,CAE1D;AAMD,eAAO,MAAM,iBAAiB;;;;;iBAM5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;iBAiC5B,CAAC;AACL,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,gBAAgB
|
|
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;AASxB,eAAO,MAAM,YAAY;;;;;+FAExB,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;iBAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAQ5D,eAAO,MAAM,cAAc,8EAA+E,CAAC;AAW3G,eAAO,MAAM,gBAAgB;;;EAA+B,CAAC;AAE7D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAUrB,CAAC;AAEH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AA0C9C,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6CtB,CAAC;AAEL,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAyE,CAAC;AAElG,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAE7D,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,CAE1D;AAMD,eAAO,MAAM,iBAAiB;;;;;iBAM5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;iBAiC5B,CAAC;AACL,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6D3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@ import type { SiteIndex, SiteConfig, Page } from "../../schemas/site-config";
|
|
|
10
10
|
import type { Audience } from "../../auth/types";
|
|
11
11
|
import type { MediaManifest } from "../../media/types";
|
|
12
12
|
import type { QueueItem } from "../../media/queue";
|
|
13
|
-
import { composePreviewImage } from "../../media/compose-preview";
|
|
13
|
+
import { composePreviewImage, PREVIEW_COMPOSE_VERSION } from "../../media/compose-preview";
|
|
14
14
|
import { derivePreviewInputs, servedPreviewSize } from "../../media/preview-inputs";
|
|
15
15
|
import { resolveMedia } from "../../media/resolve";
|
|
16
16
|
import { getMediaProvider } from "../../media";
|
|
@@ -264,6 +264,9 @@ export default function EditorShell({
|
|
|
264
264
|
// dedupes concurrent runs against the same composition signature.
|
|
265
265
|
const previewInitializedRef = useRef(false);
|
|
266
266
|
const lastPreviewSigRef = useRef<string | null>(null);
|
|
267
|
+
// Increments per compose; the async upload callback applies its result only if
|
|
268
|
+
// still current, so a slower earlier upload can't overwrite a newer preview.
|
|
269
|
+
const previewGenRef = useRef(0);
|
|
267
270
|
|
|
268
271
|
// --- Collaboration refs ---
|
|
269
272
|
// Freshest dirty-section set for the inbound remote-section handler (never
|
|
@@ -1376,12 +1379,14 @@ export default function EditorShell({
|
|
|
1376
1379
|
};
|
|
1377
1380
|
const { inputs, signature } = derivePreviewInputs(siteConfig, resolveLogoUrl);
|
|
1378
1381
|
|
|
1379
|
-
// On first run: if a preview already exists
|
|
1380
|
-
// (don't recompose
|
|
1381
|
-
//
|
|
1382
|
+
// On first run: if a preview already exists AND was composed by the current
|
|
1383
|
+
// logic version, seed the signature and skip (don't recompose on every open).
|
|
1384
|
+
// A missing preview, or one stamped with an older version (or 0 for
|
|
1385
|
+
// pre-versioning rasters), falls through to recompose so stale rasters
|
|
1386
|
+
// self-heal. Subsequent runs recompose only on change.
|
|
1382
1387
|
if (!previewInitializedRef.current) {
|
|
1383
1388
|
previewInitializedRef.current = true;
|
|
1384
|
-
if (siteConfig.previewImage) {
|
|
1389
|
+
if (siteConfig.previewImage && siteConfig.previewImage.version === PREVIEW_COMPOSE_VERSION) {
|
|
1385
1390
|
lastPreviewSigRef.current = signature;
|
|
1386
1391
|
return;
|
|
1387
1392
|
}
|
|
@@ -1390,13 +1395,19 @@ export default function EditorShell({
|
|
|
1390
1395
|
|
|
1391
1396
|
const timer = setTimeout(() => {
|
|
1392
1397
|
lastPreviewSigRef.current = signature; // guard against duplicate concurrent runs
|
|
1398
|
+
const gen = ++previewGenRef.current;
|
|
1393
1399
|
(async () => {
|
|
1394
1400
|
try {
|
|
1395
1401
|
const blob = await composePreviewImage(inputs);
|
|
1396
1402
|
const file = new File([blob], "site-preview.webp", { type: "image/webp" });
|
|
1397
1403
|
const { width, height } = servedPreviewSize(siteConfigRef.current!.media.sizes);
|
|
1398
1404
|
mediaPipeline.contextValue.uploadFileWithCallback(file, (imageId) => {
|
|
1399
|
-
|
|
1405
|
+
// Ignore a stale upload: a newer compose has superseded this one.
|
|
1406
|
+
if (gen !== previewGenRef.current) return;
|
|
1407
|
+
handleSiteConfigChange({
|
|
1408
|
+
...siteConfigRef.current!,
|
|
1409
|
+
previewImage: { imageId, width, height, version: PREVIEW_COMPOSE_VERSION },
|
|
1410
|
+
});
|
|
1400
1411
|
}, { hidden: true });
|
|
1401
1412
|
} catch (err) {
|
|
1402
1413
|
console.error("[EditorShell] preview-image composition failed:", err);
|
|
@@ -4,7 +4,7 @@ import { Button } from "../shared/Button";
|
|
|
4
4
|
import { safeRedirect } from "../../lib/safeRedirect";
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
|
-
audiences: Array<{ name: string; displayName: string }>;
|
|
7
|
+
audiences: Array<{ name: string; displayName: string; hasPassword?: boolean }>;
|
|
8
8
|
next?: string | null;
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -14,12 +14,21 @@ export function ViewerLoginForm({ audiences, next }: Props) {
|
|
|
14
14
|
const [error, setError] = useState<string | null>(null);
|
|
15
15
|
const [loading, setLoading] = useState(false);
|
|
16
16
|
|
|
17
|
+
// An open (passwordless) audience needs no password field — enter with one click.
|
|
18
|
+
// Default to requiring a password until an audience is chosen so the field
|
|
19
|
+
// doesn't flash for the "Select audience..." state.
|
|
20
|
+
const selected = audiences.find((a) => a.name === audience);
|
|
21
|
+
const needsPassword = selected ? selected.hasPassword !== false : true;
|
|
22
|
+
|
|
17
23
|
async function handleSubmit(e: FormEvent) {
|
|
18
24
|
e.preventDefault();
|
|
19
25
|
setLoading(true);
|
|
20
26
|
setError(null);
|
|
21
27
|
|
|
22
|
-
const body: { audience: string; password: string; next?: string } = {
|
|
28
|
+
const body: { audience: string; password: string; next?: string } = {
|
|
29
|
+
audience,
|
|
30
|
+
password: needsPassword ? password : "",
|
|
31
|
+
};
|
|
23
32
|
if (next) body.next = next;
|
|
24
33
|
|
|
25
34
|
try {
|
|
@@ -67,12 +76,14 @@ export function ViewerLoginForm({ audiences, next }: Props) {
|
|
|
67
76
|
</select>
|
|
68
77
|
</div>
|
|
69
78
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
{needsPassword && (
|
|
80
|
+
<PasswordInput
|
|
81
|
+
label="Password"
|
|
82
|
+
value={password}
|
|
83
|
+
onChange={setPassword}
|
|
84
|
+
autoComplete="current-password"
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
76
87
|
|
|
77
88
|
{error && (
|
|
78
89
|
<p className="text-sm text-red-600">{error}</p>
|
|
@@ -10,6 +10,9 @@ export interface PreviewImageInputs {
|
|
|
10
10
|
invertLogo: boolean;
|
|
11
11
|
siteName: string;
|
|
12
12
|
headingFont: string;
|
|
13
|
+
/** The site's Google Fonts CSS URL (or null for system-ui), used to load the
|
|
14
|
+
* heading font before drawing so text mode never falls back to system-ui. */
|
|
15
|
+
googleFontsUrl: string | null;
|
|
13
16
|
/** Computed contrasting text color (text mode). */
|
|
14
17
|
textColor: string;
|
|
15
18
|
}
|
|
@@ -17,6 +20,53 @@ export interface PreviewImageInputs {
|
|
|
17
20
|
const WIDTH = 1200;
|
|
18
21
|
const HEIGHT = 630;
|
|
19
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Bump when the composition logic changes so already-composed rasters (stamped
|
|
25
|
+
* with the old version, or 0 for pre-versioning ones) regenerate on next editor
|
|
26
|
+
* open. v2 = load the Google heading font before drawing + draw at weight 700.
|
|
27
|
+
*/
|
|
28
|
+
export const PREVIEW_COMPOSE_VERSION = 2;
|
|
29
|
+
|
|
30
|
+
// The raster draws the site name at this weight; the loader ensures this exact
|
|
31
|
+
// weight is available so canvas doesn't silently fall back to a nearest weight.
|
|
32
|
+
const HEADING_WEIGHT = 700;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ensure the heading font is actually loaded before canvas draws with it.
|
|
36
|
+
* `document.fonts.load` only fetches a family that already has a matching
|
|
37
|
+
* @font-face rule registered; in the editor that rule comes from a <link> that
|
|
38
|
+
* may not be parsed yet when composition runs, so an ambient load() can resolve
|
|
39
|
+
* empty and the draw silently falls back to system-ui. If the ambient load
|
|
40
|
+
* yields nothing, fetch the Google Fonts CSS and register the weight-700
|
|
41
|
+
* face(s) explicitly via the CSS Font Loading API.
|
|
42
|
+
*/
|
|
43
|
+
async function loadHeadingFont(family: string, googleFontsUrl: string | null): Promise<void> {
|
|
44
|
+
if (!family || family === "system-ui" || !googleFontsUrl || typeof FontFace === "undefined") return;
|
|
45
|
+
const spec = `${HEADING_WEIGHT} 84px "${family}"`;
|
|
46
|
+
try {
|
|
47
|
+
const loaded = await document.fonts.load(spec);
|
|
48
|
+
if (loaded.length > 0) return;
|
|
49
|
+
} catch {
|
|
50
|
+
// fall through to explicit registration
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const css = await (await fetch(googleFontsUrl)).text();
|
|
54
|
+
const blocks = css.match(/@font-face\s*\{[^}]*\}/g) ?? [];
|
|
55
|
+
const wanted = blocks.filter((b) => new RegExp(`font-weight:\\s*${HEADING_WEIGHT}\\b`).test(b));
|
|
56
|
+
await Promise.all(
|
|
57
|
+
(wanted.length ? wanted : blocks).map(async (block) => {
|
|
58
|
+
const url = block.match(/url\(([^)]+)\)/)?.[1];
|
|
59
|
+
if (!url) return;
|
|
60
|
+
const face = new FontFace(family, `url(${url})`, { weight: String(HEADING_WEIGHT) });
|
|
61
|
+
await face.load();
|
|
62
|
+
document.fonts.add(face);
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
} catch {
|
|
66
|
+
// give up — the draw below falls back to system-ui
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
20
70
|
/**
|
|
21
71
|
* Composes the 1200×630 social-preview raster in the browser (Canvas API only,
|
|
22
72
|
* no dependency). Returns a WebP blob. Must run client-side (uses document/Image
|
|
@@ -60,13 +110,15 @@ export async function composePreviewImage(inputs: PreviewImageInputs): Promise<B
|
|
|
60
110
|
|
|
61
111
|
if (!drewLogo) {
|
|
62
112
|
if (document.fonts?.ready) await document.fonts.ready;
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
await
|
|
113
|
+
// Load the heading font (exact weight) before drawing — fillText doesn't
|
|
114
|
+
// itself trigger a webfont load, so without this canvas falls back silently.
|
|
115
|
+
await loadHeadingFont(inputs.headingFont, inputs.googleFontsUrl);
|
|
66
116
|
ctx.fillStyle = inputs.textColor;
|
|
67
117
|
ctx.textAlign = "center";
|
|
68
118
|
ctx.textBaseline = "middle";
|
|
69
|
-
|
|
119
|
+
// Quote the family: multi-word names ("Playfair Display") break the CSS font
|
|
120
|
+
// shorthand parse when unquoted, which would drop the whole declaration.
|
|
121
|
+
ctx.font = `${HEADING_WEIGHT} 84px "${inputs.headingFont}", system-ui, sans-serif`;
|
|
70
122
|
ctx.fillText(inputs.siteName, WIDTH / 2, HEIGHT / 2, WIDTH * 0.86);
|
|
71
123
|
}
|
|
72
124
|
|
|
@@ -19,7 +19,7 @@ export function derivePreviewInputs(
|
|
|
19
19
|
siteConfig: SiteConfig,
|
|
20
20
|
resolveLogoUrl: (imageId: string) => string | null,
|
|
21
21
|
): { inputs: PreviewImageInputs; signature: string } {
|
|
22
|
-
const { logo, darkMode, primaryColor, siteName, headingFont } = siteConfig;
|
|
22
|
+
const { logo, darkMode, primaryColor, siteName, headingFont, googleFontsUrl } = siteConfig;
|
|
23
23
|
const dark = darkMode === "dark";
|
|
24
24
|
|
|
25
25
|
let mode: "logo" | "text" = "text";
|
|
@@ -43,6 +43,7 @@ export function derivePreviewInputs(
|
|
|
43
43
|
invertLogo,
|
|
44
44
|
siteName,
|
|
45
45
|
headingFont,
|
|
46
|
+
googleFontsUrl,
|
|
46
47
|
textColor: contrastColor(primaryColor),
|
|
47
48
|
};
|
|
48
49
|
|
|
@@ -53,6 +54,7 @@ export function derivePreviewInputs(
|
|
|
53
54
|
invertLogo: inputs.invertLogo,
|
|
54
55
|
siteName: inputs.siteName,
|
|
55
56
|
headingFont: inputs.headingFont,
|
|
57
|
+
googleFontsUrl: inputs.googleFontsUrl,
|
|
56
58
|
textColor: inputs.textColor,
|
|
57
59
|
});
|
|
58
60
|
|
|
@@ -245,6 +245,10 @@ export const SiteConfigSchema = z.object({
|
|
|
245
245
|
imageId: z.string(),
|
|
246
246
|
width: z.number().int().positive(),
|
|
247
247
|
height: z.number().int().positive(),
|
|
248
|
+
// Compose-logic version that produced this raster. Pre-existing rasters
|
|
249
|
+
// (and manifests written before this field) default to 0, which is below
|
|
250
|
+
// any current PREVIEW_COMPOSE_VERSION, so the editor regenerates them.
|
|
251
|
+
version: z.number().int().nonnegative().default(0),
|
|
248
252
|
})
|
|
249
253
|
.nullable()
|
|
250
254
|
.default(null),
|