@drawnagency/primitives 0.1.64 → 0.1.66
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/auth/errors.d.ts +12 -0
- package/dist/auth/errors.d.ts.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -1
- package/dist/{chunk-FECCYOPJ.js → chunk-DQTKWKSK.js} +1 -1
- package/dist/{chunk-UNWNT52N.js → chunk-EM5DFHDZ.js} +8 -1
- package/dist/{chunk-QYQ3DQHI.js → chunk-VEIDANEU.js} +1 -1
- package/dist/{chunk-CBWK3KPV.js → chunk-VU3XR362.js} +4 -0
- package/dist/{chunk-VMDKWUMT.js → chunk-XNGG5AGI.js} +74 -17
- package/dist/components/primitives/ResolvedMedia.d.ts +3 -1
- package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
- package/dist/components/primitives/index.js +3 -3
- package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.js +3 -3
- package/dist/components/sections/register-schemas.js +4 -4
- package/dist/components/shell/EditorLoading.d.ts +13 -0
- package/dist/components/shell/EditorLoading.d.ts.map +1 -0
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
- package/dist/hooks/useResolvedMedia.d.ts +2 -0
- package/dist/hooks/useResolvedMedia.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/media/index.js +1 -1
- package/dist/media/resolve.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/auth/errors.ts +16 -0
- package/src/auth/index.ts +1 -1
- package/src/components/primitives/ResolvedMedia.tsx +51 -7
- package/src/components/sections/Media/MediaBlock.tsx +7 -0
- package/src/components/shared/PasswordInput.tsx +3 -3
- package/src/components/shell/EditorLoading.tsx +37 -0
- package/src/components/shell/EditorShell.tsx +2 -1
- package/src/components/shell/SiteSettingsDisplay.tsx +28 -2
- package/src/hooks/useResolvedMedia.ts +9 -3
- package/src/media/resolve.ts +4 -0
package/dist/auth/errors.d.ts
CHANGED
|
@@ -5,4 +5,16 @@
|
|
|
5
5
|
export declare class LastOwnerError extends Error {
|
|
6
6
|
constructor(message?: string);
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Thrown when an authenticated operation cannot proceed because the underlying
|
|
10
|
+
* Supabase session is missing, expired, or its refresh token is stale/used.
|
|
11
|
+
* This is distinct from a never-authenticated request: the caller passed the
|
|
12
|
+
* middleware gate (e.g. via a still-valid app session cookie) but the Supabase
|
|
13
|
+
* session needed for the operation is dead. API routes should translate this to
|
|
14
|
+
* HTTP 401 so the UI prompts the user to request a fresh link, rather than
|
|
15
|
+
* surfacing an opaque 500.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SessionExpiredError extends Error {
|
|
18
|
+
constructor(message?: string);
|
|
19
|
+
}
|
|
8
20
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/auth/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,SAAiC;CAIrD"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/auth/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,SAAiC;CAIrD;AAED;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,SAAyD;CAI7E"}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export type { UiCapabilities } from "./capabilities";
|
|
|
3
3
|
export { deriveUiCapabilities } from "./capabilities";
|
|
4
4
|
export { requireSessionSecret, isSameOriginRequest, safeNextPath } from "./security";
|
|
5
5
|
export { signSessionToken, verifySessionToken, signAudienceToken, verifyAudienceToken, setSessionCookie, SESSION_COOKIE, AUDIENCE_COOKIE, SESSION_MAX_AGE_SECONDS, AUDIENCE_MAX_AGE_SECONDS } from "./cookies";
|
|
6
|
-
export { LastOwnerError } from "./errors";
|
|
6
|
+
export { LastOwnerError, SessionExpiredError } from "./errors";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/auth/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAC/M,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAC/M,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/auth/index.js
CHANGED
|
@@ -4,13 +4,14 @@ import {
|
|
|
4
4
|
LastOwnerError,
|
|
5
5
|
SESSION_COOKIE,
|
|
6
6
|
SESSION_MAX_AGE_SECONDS,
|
|
7
|
+
SessionExpiredError,
|
|
7
8
|
deriveUiCapabilities,
|
|
8
9
|
setSessionCookie,
|
|
9
10
|
signAudienceToken,
|
|
10
11
|
signSessionToken,
|
|
11
12
|
verifyAudienceToken,
|
|
12
13
|
verifySessionToken
|
|
13
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-EM5DFHDZ.js";
|
|
14
15
|
import {
|
|
15
16
|
isSameOriginRequest,
|
|
16
17
|
requireSessionSecret,
|
|
@@ -24,6 +25,7 @@ export {
|
|
|
24
25
|
LastOwnerError,
|
|
25
26
|
SESSION_COOKIE,
|
|
26
27
|
SESSION_MAX_AGE_SECONDS,
|
|
28
|
+
SessionExpiredError,
|
|
27
29
|
deriveUiCapabilities,
|
|
28
30
|
isSameOriginRequest,
|
|
29
31
|
requireSessionSecret,
|
|
@@ -74,6 +74,12 @@ var LastOwnerError = class extends Error {
|
|
|
74
74
|
this.name = "LastOwnerError";
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
|
+
var SessionExpiredError = class extends Error {
|
|
78
|
+
constructor(message = "Your session has expired. Please request a new link.") {
|
|
79
|
+
super(message);
|
|
80
|
+
this.name = "SessionExpiredError";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
77
83
|
|
|
78
84
|
export {
|
|
79
85
|
deriveUiCapabilities,
|
|
@@ -86,5 +92,6 @@ export {
|
|
|
86
92
|
signAudienceToken,
|
|
87
93
|
verifyAudienceToken,
|
|
88
94
|
setSessionCookie,
|
|
89
|
-
LastOwnerError
|
|
95
|
+
LastOwnerError,
|
|
96
|
+
SessionExpiredError
|
|
90
97
|
};
|
|
@@ -200,11 +200,15 @@ function resolveManifestReferences(content, manifest, sizes, provider) {
|
|
|
200
200
|
if ("srcSet" in resolved && resolved.srcSet) {
|
|
201
201
|
patch.srcset = resolved.srcSet;
|
|
202
202
|
}
|
|
203
|
+
patch.width = resolved.width;
|
|
204
|
+
patch.height = resolved.height;
|
|
203
205
|
} else if (resolved && resolved.tag === "video") {
|
|
204
206
|
patch.src = resolved.src;
|
|
205
207
|
if (resolved.poster) {
|
|
206
208
|
patch.poster = resolved.poster;
|
|
207
209
|
}
|
|
210
|
+
patch.width = resolved.width;
|
|
211
|
+
patch.height = resolved.height;
|
|
208
212
|
}
|
|
209
213
|
return { ...obj, ...patch };
|
|
210
214
|
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./chunk-I5737FJY.js";
|
|
8
8
|
import {
|
|
9
9
|
getMediaProvider
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-VU3XR362.js";
|
|
11
11
|
import {
|
|
12
12
|
cn
|
|
13
13
|
} from "./chunk-7EOSDFS3.js";
|
|
@@ -126,6 +126,7 @@ function EditablePlainText({
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
// src/components/primitives/ResolvedMedia.tsx
|
|
129
|
+
import { useState as useState2 } from "react";
|
|
129
130
|
import { ImageIcon } from "lucide-react";
|
|
130
131
|
|
|
131
132
|
// src/hooks/useResolvedMedia.ts
|
|
@@ -152,9 +153,11 @@ function useResolvedMedia(imageId) {
|
|
|
152
153
|
const item = manifestItem ?? pendingItem;
|
|
153
154
|
const alt = item?.alt ?? "";
|
|
154
155
|
const kind = item?.kind ?? "image";
|
|
156
|
+
const width = item?.width;
|
|
157
|
+
const height = item?.height;
|
|
155
158
|
const localUrl = pendingLocalUrls[imageId];
|
|
156
159
|
if (localUrl) {
|
|
157
|
-
return { src: localUrl, srcset: void 0, poster: void 0, alt, kind };
|
|
160
|
+
return { src: localUrl, srcset: void 0, poster: void 0, alt, kind, width, height };
|
|
158
161
|
}
|
|
159
162
|
if (manifestItem) {
|
|
160
163
|
const provider = getMediaProvider();
|
|
@@ -165,24 +168,28 @@ function useResolvedMedia(imageId) {
|
|
|
165
168
|
srcset: "srcSet" in resolved ? resolved.srcSet : void 0,
|
|
166
169
|
poster: void 0,
|
|
167
170
|
alt,
|
|
168
|
-
kind
|
|
171
|
+
kind,
|
|
172
|
+
width: resolved.width,
|
|
173
|
+
height: resolved.height
|
|
169
174
|
};
|
|
170
175
|
}
|
|
171
176
|
if (resolved && resolved.tag === "video") {
|
|
172
|
-
return { src: resolved.src, srcset: void 0, poster: resolved.poster, alt, kind };
|
|
177
|
+
return { src: resolved.src, srcset: void 0, poster: resolved.poster, alt, kind, width: resolved.width, height: resolved.height };
|
|
173
178
|
}
|
|
174
179
|
}
|
|
175
|
-
return { src: void 0, srcset: void 0, poster: void 0, alt, kind };
|
|
180
|
+
return { src: void 0, srcset: void 0, poster: void 0, alt, kind, width, height };
|
|
176
181
|
}, [imageId, ctx]);
|
|
177
182
|
}
|
|
178
183
|
|
|
179
184
|
// src/components/primitives/ResolvedMedia.tsx
|
|
180
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
185
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
181
186
|
function ResolvedMedia({
|
|
182
187
|
imageId,
|
|
183
188
|
src: propSrc,
|
|
184
189
|
srcset: propSrcset,
|
|
185
190
|
alt: propAlt,
|
|
191
|
+
width: propWidth,
|
|
192
|
+
height: propHeight,
|
|
186
193
|
className,
|
|
187
194
|
imgClassName,
|
|
188
195
|
invertFrom
|
|
@@ -193,22 +200,66 @@ function ResolvedMedia({
|
|
|
193
200
|
const poster = resolved.poster;
|
|
194
201
|
const alt = propAlt ?? resolved.alt;
|
|
195
202
|
const kind = resolved.kind;
|
|
203
|
+
const width = propWidth ?? resolved.width;
|
|
204
|
+
const height = propHeight ?? resolved.height;
|
|
205
|
+
const [loaded, setLoaded] = useState2(false);
|
|
196
206
|
const invertClass = invertFrom === "light" ? "invert dark:invert-0" : invertFrom === "dark" ? "dark:invert" : void 0;
|
|
197
|
-
const mediaClass = cn("h-full w-full", imgClassName, invertClass);
|
|
198
|
-
return /* @__PURE__ */ jsx("div", { className
|
|
207
|
+
const mediaClass = cn("relative h-full w-full", imgClassName, invertClass);
|
|
208
|
+
return /* @__PURE__ */ jsx("div", { className: cn("relative", className), children: src ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
209
|
+
/* @__PURE__ */ jsx(
|
|
210
|
+
"div",
|
|
211
|
+
{
|
|
212
|
+
"aria-hidden": "true",
|
|
213
|
+
"data-testid": "media-placeholder",
|
|
214
|
+
className: cn(
|
|
215
|
+
"pointer-events-none absolute inset-0 bg-base-accent transition-opacity duration-300",
|
|
216
|
+
loaded ? "opacity-0" : "opacity-100"
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
),
|
|
220
|
+
kind === "video" ? /* @__PURE__ */ jsx(
|
|
221
|
+
"video",
|
|
222
|
+
{
|
|
223
|
+
src,
|
|
224
|
+
poster,
|
|
225
|
+
width,
|
|
226
|
+
height,
|
|
227
|
+
muted: true,
|
|
228
|
+
playsInline: true,
|
|
229
|
+
loop: true,
|
|
230
|
+
autoPlay: true,
|
|
231
|
+
className: mediaClass,
|
|
232
|
+
onLoadedData: () => setLoaded(true)
|
|
233
|
+
}
|
|
234
|
+
) : /* @__PURE__ */ jsx(
|
|
235
|
+
"img",
|
|
236
|
+
{
|
|
237
|
+
src,
|
|
238
|
+
srcSet: srcset,
|
|
239
|
+
alt,
|
|
240
|
+
width,
|
|
241
|
+
height,
|
|
242
|
+
loading: "lazy",
|
|
243
|
+
decoding: "async",
|
|
244
|
+
className: mediaClass,
|
|
245
|
+
onLoad: () => setLoaded(true),
|
|
246
|
+
onError: () => setLoaded(true)
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex aspect-video h-full w-full flex-col items-center justify-center gap-1 rounded bg-base-accent text-base-contrast-light/50", children: [
|
|
199
250
|
/* @__PURE__ */ jsx(ImageIcon, { size: 24 }),
|
|
200
251
|
/* @__PURE__ */ jsx("span", { className: "text-sm", children: "No Image" })
|
|
201
252
|
] }) });
|
|
202
253
|
}
|
|
203
254
|
|
|
204
255
|
// src/components/primitives/ImageDropZone.tsx
|
|
205
|
-
import { useState as
|
|
206
|
-
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
256
|
+
import { useState as useState3 } from "react";
|
|
257
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
207
258
|
var ACCEPTED = /^(image|video)\//;
|
|
208
259
|
function ImageDropZone({ onImageSelected, className, children }) {
|
|
209
260
|
const mediaLibrary = useMediaLibrary();
|
|
210
|
-
const [dragging, setDragging] =
|
|
211
|
-
if (!mediaLibrary) return /* @__PURE__ */ jsx2(
|
|
261
|
+
const [dragging, setDragging] = useState3(false);
|
|
262
|
+
if (!mediaLibrary) return /* @__PURE__ */ jsx2(Fragment2, { children });
|
|
212
263
|
const maxFileSize = mediaLibrary.siteConfig?.media.maxFileSize;
|
|
213
264
|
const hasFiles = (e) => e.dataTransfer.types.includes("Files");
|
|
214
265
|
const handleDragOver = (e) => {
|
|
@@ -265,6 +316,9 @@ function MediaBlock({ refValue, options, link, isEditMode, onRefChange }) {
|
|
|
265
316
|
const captionStr = refValue.caption ? Array.isArray(refValue.caption) ? refValue.caption.join("\n") : refValue.caption : "";
|
|
266
317
|
const replace = onRefChange ? (imageId) => onRefChange({ ...refValue, imageId }) : void 0;
|
|
267
318
|
const baked = refValue;
|
|
319
|
+
const bakedWidth = typeof baked.width === "number" ? baked.width : void 0;
|
|
320
|
+
const bakedHeight = typeof baked.height === "number" ? baked.height : void 0;
|
|
321
|
+
const aspectRatio = bakedWidth && bakedHeight && !options.square ? `${bakedWidth} / ${bakedHeight}` : void 0;
|
|
268
322
|
const resolved = /* @__PURE__ */ jsx3(
|
|
269
323
|
ResolvedMedia,
|
|
270
324
|
{
|
|
@@ -272,6 +326,8 @@ function MediaBlock({ refValue, options, link, isEditMode, onRefChange }) {
|
|
|
272
326
|
src: baked.src,
|
|
273
327
|
srcset: baked.srcset,
|
|
274
328
|
alt: baked.alt,
|
|
329
|
+
width: bakedWidth,
|
|
330
|
+
height: bakedHeight,
|
|
275
331
|
className: "h-full w-full",
|
|
276
332
|
imgClassName: fitClass,
|
|
277
333
|
invertFrom: refValue.invertFrom
|
|
@@ -293,6 +349,7 @@ function MediaBlock({ refValue, options, link, isEditMode, onRefChange }) {
|
|
|
293
349
|
options.square && "aspect-square",
|
|
294
350
|
onImageClick && "cursor-pointer"
|
|
295
351
|
),
|
|
352
|
+
style: aspectRatio ? { aspectRatio } : void 0,
|
|
296
353
|
onClick: onImageClick,
|
|
297
354
|
onPointerDown: onImageClick ? (e) => e.stopPropagation() : void 0,
|
|
298
355
|
children: media
|
|
@@ -313,7 +370,7 @@ function MediaBlock({ refValue, options, link, isEditMode, onRefChange }) {
|
|
|
313
370
|
}
|
|
314
371
|
|
|
315
372
|
// src/components/primitives/EditableGrid.tsx
|
|
316
|
-
import { useRef as useRef4, useEffect as useEffect4, useState as
|
|
373
|
+
import { useRef as useRef4, useEffect as useEffect4, useState as useState5 } from "react";
|
|
317
374
|
import { ImageIcon as ImageIcon2 } from "lucide-react";
|
|
318
375
|
|
|
319
376
|
// src/lib/container-grid.ts
|
|
@@ -344,7 +401,7 @@ var spacerHiddenClass = {
|
|
|
344
401
|
};
|
|
345
402
|
|
|
346
403
|
// src/components/primitives/useEditableCollection.ts
|
|
347
|
-
import { useState as
|
|
404
|
+
import { useState as useState4, useCallback as useCallback3, useRef as useRef3, useEffect as useEffect3 } from "react";
|
|
348
405
|
var nextId = 0;
|
|
349
406
|
function uid() {
|
|
350
407
|
return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `_id_${nextId++}_${Date.now()}`;
|
|
@@ -357,7 +414,7 @@ function useEditableCollection({
|
|
|
357
414
|
onChange,
|
|
358
415
|
createItem
|
|
359
416
|
}) {
|
|
360
|
-
const [wrappedItems, setWrappedItems] =
|
|
417
|
+
const [wrappedItems, setWrappedItems] = useState4(
|
|
361
418
|
() => wrapItems(items)
|
|
362
419
|
);
|
|
363
420
|
const prevItemsRef = useRef3(items);
|
|
@@ -437,8 +494,8 @@ function EditableGrid({
|
|
|
437
494
|
onChange,
|
|
438
495
|
createItem
|
|
439
496
|
});
|
|
440
|
-
const [dragState, setDragState] =
|
|
441
|
-
const [isDragging, setIsDragging] =
|
|
497
|
+
const [dragState, setDragState] = useState5({ sourceId: null, closestEdge: null });
|
|
498
|
+
const [isDragging, setIsDragging] = useState5(false);
|
|
442
499
|
useEffect4(() => {
|
|
443
500
|
let cleanup;
|
|
444
501
|
let cancelled = false;
|
|
@@ -3,10 +3,12 @@ interface ResolvedMediaProps {
|
|
|
3
3
|
src?: string;
|
|
4
4
|
srcset?: string;
|
|
5
5
|
alt?: string;
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
6
8
|
className?: string;
|
|
7
9
|
imgClassName?: string;
|
|
8
10
|
invertFrom?: string;
|
|
9
11
|
}
|
|
10
|
-
export declare function ResolvedMedia({ imageId, src: propSrc, srcset: propSrcset, alt: propAlt, className, imgClassName, invertFrom, }: ResolvedMediaProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function ResolvedMedia({ imageId, src: propSrc, srcset: propSrcset, alt: propAlt, width: propWidth, height: propHeight, className, imgClassName, invertFrom, }: ResolvedMediaProps): import("react/jsx-runtime").JSX.Element;
|
|
11
13
|
export {};
|
|
12
14
|
//# sourceMappingURL=ResolvedMedia.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResolvedMedia.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/ResolvedMedia.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ResolvedMedia.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/ResolvedMedia.tsx"],"names":[],"mappings":"AAKA,UAAU,kBAAkB;IAC1B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,UAAU,EAClB,SAAS,EACT,YAAY,EACZ,UAAU,GACX,EAAE,kBAAkB,2CAsEpB"}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
EditableRichText,
|
|
3
3
|
LinkPopover,
|
|
4
4
|
defineHeadingSection
|
|
5
|
-
} from "../../chunk-
|
|
5
|
+
} from "../../chunk-DQTKWKSK.js";
|
|
6
6
|
import {
|
|
7
7
|
EditableGrid,
|
|
8
8
|
EditablePlainText,
|
|
@@ -10,14 +10,14 @@ import {
|
|
|
10
10
|
MediaBlock,
|
|
11
11
|
ResolvedMedia,
|
|
12
12
|
useEditableCollection
|
|
13
|
-
} from "../../chunk-
|
|
13
|
+
} from "../../chunk-XNGG5AGI.js";
|
|
14
14
|
import "../../chunk-4E66N6UV.js";
|
|
15
15
|
import {
|
|
16
16
|
DeleteIcon,
|
|
17
17
|
DragHandle,
|
|
18
18
|
IconButton
|
|
19
19
|
} from "../../chunk-I5737FJY.js";
|
|
20
|
-
import "../../chunk-
|
|
20
|
+
import "../../chunk-VU3XR362.js";
|
|
21
21
|
import "../../chunk-53DKVAB6.js";
|
|
22
22
|
import "../../chunk-ZGFAYZWB.js";
|
|
23
23
|
import {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaBlock.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Media/MediaBlock.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAOvD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,CAAC;IACxG,4EAA4E;IAC5E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACnD;AAED,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"MediaBlock.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Media/MediaBlock.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAOvD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,CAAC;IACxG,4EAA4E;IAC5E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACnD;AAED,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,eAAe,2CA2F/F"}
|
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
ColorItemSchema,
|
|
3
3
|
ColorSpaceSchema,
|
|
4
4
|
brandGuideSectionDefs
|
|
5
|
-
} from "../../chunk-
|
|
5
|
+
} from "../../chunk-VEIDANEU.js";
|
|
6
6
|
import "../../chunk-5THS4K5D.js";
|
|
7
|
-
import "../../chunk-
|
|
7
|
+
import "../../chunk-XNGG5AGI.js";
|
|
8
8
|
import "../../chunk-I5737FJY.js";
|
|
9
|
-
import "../../chunk-
|
|
9
|
+
import "../../chunk-VU3XR362.js";
|
|
10
10
|
import "../../chunk-3DCG4DNU.js";
|
|
11
11
|
import "../../chunk-WDSVHKAH.js";
|
|
12
12
|
import "../../chunk-ZGFAYZWB.js";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
pluralize,
|
|
4
4
|
stripHtmlToPlainText,
|
|
5
5
|
truncate
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-VEIDANEU.js";
|
|
7
7
|
import {
|
|
8
8
|
DeleteButton,
|
|
9
9
|
DragHandle,
|
|
@@ -20,7 +20,7 @@ import "../../chunk-5THS4K5D.js";
|
|
|
20
20
|
import {
|
|
21
21
|
EditableRichText,
|
|
22
22
|
defineHeadingSection
|
|
23
|
-
} from "../../chunk-
|
|
23
|
+
} from "../../chunk-DQTKWKSK.js";
|
|
24
24
|
import {
|
|
25
25
|
EditablePlainText,
|
|
26
26
|
MAX_CONTAINER_COLUMNS,
|
|
@@ -28,10 +28,10 @@ import {
|
|
|
28
28
|
colSpanClass,
|
|
29
29
|
containerGridClass,
|
|
30
30
|
spacerHiddenClass
|
|
31
|
-
} from "../../chunk-
|
|
31
|
+
} from "../../chunk-XNGG5AGI.js";
|
|
32
32
|
import "../../chunk-4E66N6UV.js";
|
|
33
33
|
import "../../chunk-I5737FJY.js";
|
|
34
|
-
import "../../chunk-
|
|
34
|
+
import "../../chunk-VU3XR362.js";
|
|
35
35
|
import {
|
|
36
36
|
DEFAULT_LINK,
|
|
37
37
|
LinkValueSchema
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface EditorLoadingProps {
|
|
2
|
+
/** Delay before the spinner appears, to avoid a flash on instant cache hits. */
|
|
3
|
+
delayMs?: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Full-screen loading state shown while EditorShell hydrates and loads content.
|
|
7
|
+
* Keeps the existing aria-busy contract on the wrapper at all times; the visible
|
|
8
|
+
* spinner is delayed so a fast Dexie cache hit doesn't flash it. SSR-safe: the
|
|
9
|
+
* initial render shows the wrapper with no spinner, matching the server output.
|
|
10
|
+
*/
|
|
11
|
+
export declare function EditorLoading({ delayMs }: EditorLoadingProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=EditorLoading.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EditorLoading.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorLoading.tsx"],"names":[],"mappings":"AAGA,UAAU,kBAAkB;IAC1B,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,OAAa,EAAE,EAAE,kBAAkB,2CAsBlE"}
|
|
@@ -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;
|
|
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;AAuEjD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQxD,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;CACjE;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,YAAY,EACZ,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,GACZ,EAAE,KAAK,2CAo6BP"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SiteSettingsDisplay.d.ts","sourceRoot":"","sources":["../../../src/components/shell/SiteSettingsDisplay.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,UAAU,KAAK;IACb,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CACxC;AAWD,wBAAgB,mBAAmB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"SiteSettingsDisplay.d.ts","sourceRoot":"","sources":["../../../src/components/shell/SiteSettingsDisplay.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,UAAU,KAAK;IACb,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CACxC;AAWD,wBAAgB,mBAAmB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,KAAK,2CAiKlE"}
|
|
@@ -4,6 +4,8 @@ export interface ResolvedMediaResult {
|
|
|
4
4
|
poster: string | undefined;
|
|
5
5
|
alt: string;
|
|
6
6
|
kind: "image" | "animated" | "video";
|
|
7
|
+
width?: number;
|
|
8
|
+
height?: number;
|
|
7
9
|
}
|
|
8
10
|
export declare function useResolvedMedia(imageId: string | undefined): ResolvedMediaResult;
|
|
9
11
|
//# sourceMappingURL=useResolvedMedia.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useResolvedMedia.d.ts","sourceRoot":"","sources":["../../src/hooks/useResolvedMedia.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useResolvedMedia.d.ts","sourceRoot":"","sources":["../../src/hooks/useResolvedMedia.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,mBAAmB,CA6CjF"}
|
package/dist/index.js
CHANGED
|
@@ -13,20 +13,21 @@ import {
|
|
|
13
13
|
resolveMedia,
|
|
14
14
|
sanitizeMediaName,
|
|
15
15
|
setMediaProvider
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VU3XR362.js";
|
|
17
17
|
import {
|
|
18
18
|
AUDIENCE_COOKIE,
|
|
19
19
|
AUDIENCE_MAX_AGE_SECONDS,
|
|
20
20
|
LastOwnerError,
|
|
21
21
|
SESSION_COOKIE,
|
|
22
22
|
SESSION_MAX_AGE_SECONDS,
|
|
23
|
+
SessionExpiredError,
|
|
23
24
|
deriveUiCapabilities,
|
|
24
25
|
setSessionCookie,
|
|
25
26
|
signAudienceToken,
|
|
26
27
|
signSessionToken,
|
|
27
28
|
verifyAudienceToken,
|
|
28
29
|
verifySessionToken
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-EM5DFHDZ.js";
|
|
30
31
|
import {
|
|
31
32
|
AudienceColorSchema,
|
|
32
33
|
AudienceNameSchema,
|
|
@@ -157,6 +158,7 @@ export {
|
|
|
157
158
|
SESSION_COOKIE,
|
|
158
159
|
SESSION_MAX_AGE_SECONDS,
|
|
159
160
|
SectionMetaSchema,
|
|
161
|
+
SessionExpiredError,
|
|
160
162
|
SessionSchema,
|
|
161
163
|
SingleMediaReferenceSchema,
|
|
162
164
|
SiteConfigSchema,
|
package/dist/media/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/media/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,wBAAgB,YAAY,CAC1B,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACxC,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,aAAa,GACtB,aAAa,GAAG,IAAI,CAKtB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,aAAa,GACtB,OAAO,
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/media/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,wBAAgB,YAAY,CAC1B,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACxC,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,aAAa,GACtB,aAAa,GAAG,IAAI,CAKtB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,EAAE,aAAa,GACtB,OAAO,CA0CT"}
|
package/package.json
CHANGED
package/src/auth/errors.ts
CHANGED
|
@@ -8,3 +8,19 @@ export class LastOwnerError extends Error {
|
|
|
8
8
|
this.name = "LastOwnerError";
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Thrown when an authenticated operation cannot proceed because the underlying
|
|
14
|
+
* Supabase session is missing, expired, or its refresh token is stale/used.
|
|
15
|
+
* This is distinct from a never-authenticated request: the caller passed the
|
|
16
|
+
* middleware gate (e.g. via a still-valid app session cookie) but the Supabase
|
|
17
|
+
* session needed for the operation is dead. API routes should translate this to
|
|
18
|
+
* HTTP 401 so the UI prompts the user to request a fresh link, rather than
|
|
19
|
+
* surfacing an opaque 500.
|
|
20
|
+
*/
|
|
21
|
+
export class SessionExpiredError extends Error {
|
|
22
|
+
constructor(message = "Your session has expired. Please request a new link.") {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "SessionExpiredError";
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/auth/index.ts
CHANGED
|
@@ -17,4 +17,4 @@ export { deriveUiCapabilities } from "./capabilities";
|
|
|
17
17
|
|
|
18
18
|
export { requireSessionSecret, isSameOriginRequest, safeNextPath } from "./security";
|
|
19
19
|
export { signSessionToken, verifySessionToken, signAudienceToken, verifyAudienceToken, setSessionCookie, SESSION_COOKIE, AUDIENCE_COOKIE, SESSION_MAX_AGE_SECONDS, AUDIENCE_MAX_AGE_SECONDS } from "./cookies";
|
|
20
|
-
export { LastOwnerError } from "./errors";
|
|
20
|
+
export { LastOwnerError, SessionExpiredError } from "./errors";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
1
2
|
import { cn } from "../../lib/cn";
|
|
2
3
|
import { ImageIcon } from "lucide-react";
|
|
3
4
|
import { useResolvedMedia } from "../../hooks/useResolvedMedia";
|
|
@@ -7,6 +8,8 @@ interface ResolvedMediaProps {
|
|
|
7
8
|
src?: string;
|
|
8
9
|
srcset?: string;
|
|
9
10
|
alt?: string;
|
|
11
|
+
width?: number;
|
|
12
|
+
height?: number;
|
|
10
13
|
className?: string;
|
|
11
14
|
imgClassName?: string;
|
|
12
15
|
invertFrom?: string;
|
|
@@ -17,6 +20,8 @@ export function ResolvedMedia({
|
|
|
17
20
|
src: propSrc,
|
|
18
21
|
srcset: propSrcset,
|
|
19
22
|
alt: propAlt,
|
|
23
|
+
width: propWidth,
|
|
24
|
+
height: propHeight,
|
|
20
25
|
className,
|
|
21
26
|
imgClassName,
|
|
22
27
|
invertFrom,
|
|
@@ -27,22 +32,61 @@ export function ResolvedMedia({
|
|
|
27
32
|
const poster = resolved.poster;
|
|
28
33
|
const alt = propAlt ?? resolved.alt;
|
|
29
34
|
const kind = resolved.kind;
|
|
35
|
+
const width = propWidth ?? resolved.width;
|
|
36
|
+
const height = propHeight ?? resolved.height;
|
|
37
|
+
|
|
38
|
+
// `loaded` fades the placeholder out via onLoad in the hydrated editor. In the
|
|
39
|
+
// zero-JS viewer this stays false (no JS), so the placeholder remains painted
|
|
40
|
+
// behind the opaque loaded image — correct, no flash, no layout shift.
|
|
41
|
+
const [loaded, setLoaded] = useState(false);
|
|
30
42
|
|
|
31
43
|
const invertClass =
|
|
32
44
|
invertFrom === "light" ? "invert dark:invert-0" :
|
|
33
45
|
invertFrom === "dark" ? "dark:invert" :
|
|
34
46
|
undefined;
|
|
35
47
|
|
|
36
|
-
const mediaClass = cn("h-full w-full", imgClassName, invertClass);
|
|
48
|
+
const mediaClass = cn("relative h-full w-full", imgClassName, invertClass);
|
|
37
49
|
|
|
38
50
|
return (
|
|
39
|
-
<div className={className}>
|
|
51
|
+
<div className={cn("relative", className)}>
|
|
40
52
|
{src ? (
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
<>
|
|
54
|
+
<div
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
data-testid="media-placeholder"
|
|
57
|
+
className={cn(
|
|
58
|
+
"pointer-events-none absolute inset-0 bg-base-accent transition-opacity duration-300",
|
|
59
|
+
loaded ? "opacity-0" : "opacity-100",
|
|
60
|
+
)}
|
|
61
|
+
/>
|
|
62
|
+
{kind === "video" ? (
|
|
63
|
+
<video
|
|
64
|
+
src={src}
|
|
65
|
+
poster={poster}
|
|
66
|
+
width={width}
|
|
67
|
+
height={height}
|
|
68
|
+
muted
|
|
69
|
+
playsInline
|
|
70
|
+
loop
|
|
71
|
+
autoPlay
|
|
72
|
+
className={mediaClass}
|
|
73
|
+
onLoadedData={() => setLoaded(true)}
|
|
74
|
+
/>
|
|
75
|
+
) : (
|
|
76
|
+
<img
|
|
77
|
+
src={src}
|
|
78
|
+
srcSet={srcset}
|
|
79
|
+
alt={alt}
|
|
80
|
+
width={width}
|
|
81
|
+
height={height}
|
|
82
|
+
loading="lazy"
|
|
83
|
+
decoding="async"
|
|
84
|
+
className={mediaClass}
|
|
85
|
+
onLoad={() => setLoaded(true)}
|
|
86
|
+
onError={() => setLoaded(true)}
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
</>
|
|
46
90
|
) : (
|
|
47
91
|
<div className="flex aspect-video h-full w-full flex-col items-center justify-center gap-1 rounded bg-base-accent text-base-contrast-light/50">
|
|
48
92
|
<ImageIcon size={24} />
|
|
@@ -33,12 +33,18 @@ export function MediaBlock({ refValue, options, link, isEditMode, onRefChange }:
|
|
|
33
33
|
// placeholder. In edit mode these are absent and ResolvedMedia falls back to the
|
|
34
34
|
// live manifest via context. (Mirrors a3e8913, which did this for MediaGrid/SplitContent.)
|
|
35
35
|
const baked = refValue as Record<string, unknown>;
|
|
36
|
+
const bakedWidth = typeof baked.width === "number" ? baked.width : undefined;
|
|
37
|
+
const bakedHeight = typeof baked.height === "number" ? baked.height : undefined;
|
|
38
|
+
const aspectRatio =
|
|
39
|
+
bakedWidth && bakedHeight && !options.square ? `${bakedWidth} / ${bakedHeight}` : undefined;
|
|
36
40
|
const resolved = (
|
|
37
41
|
<ResolvedMedia
|
|
38
42
|
imageId={refValue.imageId || undefined}
|
|
39
43
|
src={baked.src as string | undefined}
|
|
40
44
|
srcset={baked.srcset as string | undefined}
|
|
41
45
|
alt={baked.alt as string | undefined}
|
|
46
|
+
width={bakedWidth}
|
|
47
|
+
height={bakedHeight}
|
|
42
48
|
className="h-full w-full"
|
|
43
49
|
imgClassName={fitClass}
|
|
44
50
|
invertFrom={refValue.invertFrom}
|
|
@@ -75,6 +81,7 @@ export function MediaBlock({ refValue, options, link, isEditMode, onRefChange }:
|
|
|
75
81
|
options.square && "aspect-square",
|
|
76
82
|
onImageClick && "cursor-pointer",
|
|
77
83
|
)}
|
|
84
|
+
style={aspectRatio ? { aspectRatio } : undefined}
|
|
78
85
|
onClick={onImageClick}
|
|
79
86
|
onPointerDown={onImageClick ? (e) => e.stopPropagation() : undefined}
|
|
80
87
|
>
|
|
@@ -43,15 +43,15 @@ export function PasswordInput({
|
|
|
43
43
|
className={cn(
|
|
44
44
|
"w-full rounded border border-base-200 bg-base px-3 py-2 text-sm text-base-contrast",
|
|
45
45
|
"focus:border-base-contrast focus:outline-none focus:ring-1 focus:ring-base-contrast",
|
|
46
|
-
showToggle && "pr-
|
|
46
|
+
showToggle && "pr-10",
|
|
47
47
|
disabled && "cursor-not-allowed opacity-50",
|
|
48
48
|
)}
|
|
49
49
|
/>
|
|
50
50
|
{showToggle && (
|
|
51
51
|
<IconButton
|
|
52
|
-
icon={revealed ? <EyeOff size={
|
|
52
|
+
icon={revealed ? <EyeOff size={20} /> : <Eye size={20} />}
|
|
53
53
|
label={revealed ? "Hide password" : "Show password"}
|
|
54
|
-
size="
|
|
54
|
+
size="md"
|
|
55
55
|
aria-pressed={revealed}
|
|
56
56
|
disabled={disabled}
|
|
57
57
|
onClick={() => setRevealed((v) => !v)}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Loader2 } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
interface EditorLoadingProps {
|
|
5
|
+
/** Delay before the spinner appears, to avoid a flash on instant cache hits. */
|
|
6
|
+
delayMs?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Full-screen loading state shown while EditorShell hydrates and loads content.
|
|
11
|
+
* Keeps the existing aria-busy contract on the wrapper at all times; the visible
|
|
12
|
+
* spinner is delayed so a fast Dexie cache hit doesn't flash it. SSR-safe: the
|
|
13
|
+
* initial render shows the wrapper with no spinner, matching the server output.
|
|
14
|
+
*/
|
|
15
|
+
export function EditorLoading({ delayMs = 200 }: EditorLoadingProps) {
|
|
16
|
+
const [showSpinner, setShowSpinner] = useState(false);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const timer = setTimeout(() => setShowSpinner(true), delayMs);
|
|
20
|
+
return () => clearTimeout(timer);
|
|
21
|
+
}, [delayMs]);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className="flex min-h-screen items-center justify-center"
|
|
26
|
+
aria-busy="true"
|
|
27
|
+
aria-label="Loading content"
|
|
28
|
+
>
|
|
29
|
+
{showSpinner && (
|
|
30
|
+
<Loader2
|
|
31
|
+
className="size-8 animate-spin text-primary"
|
|
32
|
+
data-testid="editor-loading-spinner"
|
|
33
|
+
/>
|
|
34
|
+
)}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -75,6 +75,7 @@ import { ImageIcon, ListOrderedIcon, FilesIcon, X } from "lucide-react";
|
|
|
75
75
|
import { ErrorBoundary } from "../shared/ErrorBoundary";
|
|
76
76
|
import { HistoryToolbar } from "./HistoryToolbar";
|
|
77
77
|
import { RestoreModal } from "./RestoreModal";
|
|
78
|
+
import { EditorLoading } from "./EditorLoading";
|
|
78
79
|
import ViewRenderer from "../sections/ViewRenderer";
|
|
79
80
|
|
|
80
81
|
export { useMediaLibrary } from "./MediaLibraryContext";
|
|
@@ -808,7 +809,7 @@ export default function EditorShell({
|
|
|
808
809
|
// --- Render ---
|
|
809
810
|
|
|
810
811
|
if (shellState.phase === "loading-content") {
|
|
811
|
-
return <
|
|
812
|
+
return <EditorLoading />;
|
|
812
813
|
}
|
|
813
814
|
|
|
814
815
|
if (shellState.phase === "error") {
|
|
@@ -48,8 +48,15 @@ export function SiteSettingsDisplay({ siteConfig, onChange }: Props) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function handleColorChange(color: string) {
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
update({ primaryColor: color });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function handleContrastChange(color: string) {
|
|
55
|
+
update({ primaryContrast: color });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resetContrastToAuto() {
|
|
59
|
+
update({ primaryContrast: deriveContrast(siteConfig.primaryColor) });
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
function handleFontChange(field: "headingFont" | "bodyFont", family: string) {
|
|
@@ -79,6 +86,25 @@ export function SiteSettingsDisplay({ siteConfig, onChange }: Props) {
|
|
|
79
86
|
</div>
|
|
80
87
|
</div>
|
|
81
88
|
|
|
89
|
+
<div>
|
|
90
|
+
<FormLabel>Accent (button & highlight text)</FormLabel>
|
|
91
|
+
<div className="flex items-center gap-3">
|
|
92
|
+
<ColorPicker
|
|
93
|
+
value={siteConfig.primaryContrast}
|
|
94
|
+
onChange={handleContrastChange}
|
|
95
|
+
label="Accent color"
|
|
96
|
+
/>
|
|
97
|
+
<span className="text-sm text-base-contrast-light">{siteConfig.primaryContrast}</span>
|
|
98
|
+
<Button type="button" variant="ghost" size="sm" onClick={resetContrastToAuto}>
|
|
99
|
+
Reset to auto
|
|
100
|
+
</Button>
|
|
101
|
+
</div>
|
|
102
|
+
<p className="mt-1 text-xs text-base-contrast/70">
|
|
103
|
+
Shown as text on primary-colored buttons and as the text-selection color. "Reset to auto" derives a
|
|
104
|
+
legible light or dark value from your primary brand color.
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
82
108
|
<div>
|
|
83
109
|
<FormLabel>Favicon</FormLabel>
|
|
84
110
|
<div className="flex items-center gap-3">
|
|
@@ -8,6 +8,8 @@ export interface ResolvedMediaResult {
|
|
|
8
8
|
poster: string | undefined;
|
|
9
9
|
alt: string;
|
|
10
10
|
kind: "image" | "animated" | "video";
|
|
11
|
+
width?: number;
|
|
12
|
+
height?: number;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function useResolvedMedia(imageId: string | undefined): ResolvedMediaResult {
|
|
@@ -26,10 +28,12 @@ export function useResolvedMedia(imageId: string | undefined): ResolvedMediaResu
|
|
|
26
28
|
const item = manifestItem ?? pendingItem;
|
|
27
29
|
const alt = item?.alt ?? "";
|
|
28
30
|
const kind = item?.kind ?? "image";
|
|
31
|
+
const width = item?.width;
|
|
32
|
+
const height = item?.height;
|
|
29
33
|
|
|
30
34
|
const localUrl = pendingLocalUrls[imageId];
|
|
31
35
|
if (localUrl) {
|
|
32
|
-
return { src: localUrl, srcset: undefined, poster: undefined, alt, kind };
|
|
36
|
+
return { src: localUrl, srcset: undefined, poster: undefined, alt, kind, width, height };
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
if (manifestItem) {
|
|
@@ -42,13 +46,15 @@ export function useResolvedMedia(imageId: string | undefined): ResolvedMediaResu
|
|
|
42
46
|
poster: undefined,
|
|
43
47
|
alt,
|
|
44
48
|
kind,
|
|
49
|
+
width: resolved.width,
|
|
50
|
+
height: resolved.height,
|
|
45
51
|
};
|
|
46
52
|
}
|
|
47
53
|
if (resolved && resolved.tag === "video") {
|
|
48
|
-
return { src: resolved.src, srcset: undefined, poster: resolved.poster, alt, kind };
|
|
54
|
+
return { src: resolved.src, srcset: undefined, poster: resolved.poster, alt, kind, width: resolved.width, height: resolved.height };
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
return { src: undefined, srcset: undefined, poster: undefined, alt, kind };
|
|
58
|
+
return { src: undefined, srcset: undefined, poster: undefined, alt, kind, width, height };
|
|
53
59
|
}, [imageId, ctx]);
|
|
54
60
|
}
|
package/src/media/resolve.ts
CHANGED
|
@@ -45,11 +45,15 @@ export function resolveManifestReferences(
|
|
|
45
45
|
if ("srcSet" in resolved && resolved.srcSet) {
|
|
46
46
|
patch.srcset = resolved.srcSet;
|
|
47
47
|
}
|
|
48
|
+
patch.width = resolved.width;
|
|
49
|
+
patch.height = resolved.height;
|
|
48
50
|
} else if (resolved && resolved.tag === "video") {
|
|
49
51
|
patch.src = resolved.src;
|
|
50
52
|
if (resolved.poster) {
|
|
51
53
|
patch.poster = resolved.poster;
|
|
52
54
|
}
|
|
55
|
+
patch.width = resolved.width;
|
|
56
|
+
patch.height = resolved.height;
|
|
53
57
|
}
|
|
54
58
|
return { ...obj, ...patch };
|
|
55
59
|
}
|