@drawnagency/primitives 0.1.71 → 0.1.73
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-RTYJZAKR.js → chunk-7T2WQSWF.js} +12 -12
- package/dist/chunk-IFHRHV7S.js +117 -0
- package/dist/{chunk-2BHPWO7M.js → chunk-N6AE6NBB.js} +158 -32
- package/dist/{chunk-WXVKX6PY.js → chunk-NKVZGPR6.js} +41 -95
- package/dist/{chunk-3DCG4DNU.js → chunk-PZJL2S3Z.js} +8 -4
- package/dist/chunk-RBTSUR5V.js +37 -0
- package/dist/{chunk-6UDYJQL6.js → chunk-S55OZ7F2.js} +9 -1
- package/dist/{chunk-NR7UB6DZ.js → chunk-SFKYUKVN.js} +1 -1
- package/dist/{chunk-YMVYTYGQ.js → chunk-SWIHUIFT.js} +28 -16
- package/dist/{chunk-KWBM27UR.js → chunk-T7MP7ANP.js} +13 -64
- package/dist/{chunk-MU3CDERQ.js → chunk-XTRTK26E.js} +276 -96
- package/dist/{chunk-GKV77AA6.js → chunk-ZRWGV5O6.js} +3 -3
- package/dist/chunk-ZSPCE6MZ.js +70 -0
- package/dist/components/editor/SettingsForm.d.ts.map +1 -1
- package/dist/components/editor/index.js +6 -3
- package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
- package/dist/components/primitives/index.js +7 -7
- package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -1
- package/dist/components/sections/Video/VideoBlock.d.ts +31 -0
- package/dist/components/sections/Video/VideoBlock.d.ts.map +1 -0
- package/dist/components/sections/Video/index.d.ts +28 -0
- package/dist/components/sections/Video/index.d.ts.map +1 -0
- package/dist/components/sections/all-sections.d.ts +25 -0
- package/dist/components/sections/all-sections.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.js +7 -7
- package/dist/components/sections/core-sections.d.ts +25 -0
- package/dist/components/sections/core-sections.d.ts.map +1 -1
- package/dist/components/sections/register-schemas.js +13 -12
- package/dist/components/sections/register.js +13 -12
- package/dist/components/shared/Tabs.d.ts +1 -1
- package/dist/components/shared/VideoSourceField.d.ts +8 -0
- package/dist/components/shared/VideoSourceField.d.ts.map +1 -0
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/MediaLibraryModal.d.ts +4 -1
- package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
- package/dist/hooks/useEditorPublish.d.ts +4 -1
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/hooks/useMediaPipeline.d.ts +9 -1
- package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
- package/dist/index.js +28 -24
- package/dist/lib/index.js +10 -10
- package/dist/lib/registry.d.ts +6 -0
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/lib/save-chunks.d.ts +28 -0
- package/dist/lib/save-chunks.d.ts.map +1 -0
- package/dist/lib/video-url.d.ts +7 -0
- package/dist/lib/video-url.d.ts.map +1 -0
- package/dist/lib/video-url.js +9 -0
- package/dist/media/index.js +6 -2
- package/dist/media/resolve.d.ts +14 -0
- package/dist/media/resolve.d.ts.map +1 -1
- package/dist/media/videoPoster.d.ts +1 -0
- package/dist/media/videoPoster.d.ts.map +1 -1
- package/dist/schemas/index.js +4 -4
- package/dist/schemas/link.d.ts.map +1 -1
- package/dist/schemas/link.js +1 -1
- package/dist/schemas/media.d.ts +13 -0
- package/dist/schemas/media.d.ts.map +1 -1
- package/dist/schemas/shared.d.ts.map +1 -1
- package/dist/schemas/site-config.d.ts +1 -0
- package/dist/schemas/site-config.d.ts.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/types.d.ts +26 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/package.json +5 -1
- package/src/components/editor/SettingsForm.tsx +11 -0
- package/src/components/primitives/ResolvedMedia.tsx +10 -5
- package/src/components/sections/Media/MediaBlock.tsx +2 -1
- package/src/components/sections/Video/VideoBlock.tsx +149 -0
- package/src/components/sections/Video/index.tsx +111 -0
- package/src/components/sections/core-sections.ts +2 -0
- package/src/components/shared/Tabs.tsx +2 -2
- package/src/components/shared/VideoSourceField.tsx +144 -0
- package/src/components/shell/EditorShell.tsx +16 -2
- package/src/components/shell/MediaLibraryModal.tsx +47 -1
- package/src/hooks/useEditorPublish.ts +137 -50
- package/src/hooks/useMediaPipeline.ts +188 -25
- package/src/lib/registry.ts +7 -0
- package/src/lib/save-chunks.ts +53 -0
- package/src/lib/video-url.ts +39 -0
- package/src/media/resolve.ts +18 -0
- package/src/media/videoPoster.ts +2 -2
- package/src/schemas/link.ts +15 -4
- package/src/schemas/media.ts +12 -0
- package/src/schemas/shared.ts +23 -14
- package/src/storage/index.ts +1 -1
- package/src/storage/types.ts +23 -0
- package/dist/chunk-4E66N6UV.js +0 -10
- package/dist/chunk-I5737FJY.js +0 -62
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type ParsedVideoUrl = { provider: "youtube" | "vimeo"; id: string };
|
|
2
|
+
|
|
3
|
+
const YT_ID = /^[\w-]{6,20}$/;
|
|
4
|
+
|
|
5
|
+
export function parseVideoUrl(raw: string): ParsedVideoUrl | null {
|
|
6
|
+
let url: URL;
|
|
7
|
+
try {
|
|
8
|
+
url = new URL(raw.trim());
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
13
|
+
const host = url.hostname.replace(/^www\./, "");
|
|
14
|
+
|
|
15
|
+
if (host === "youtube.com" || host === "m.youtube.com" || host === "youtube-nocookie.com") {
|
|
16
|
+
if (url.pathname === "/watch") {
|
|
17
|
+
const v = url.searchParams.get("v");
|
|
18
|
+
return v && YT_ID.test(v) ? { provider: "youtube", id: v } : null;
|
|
19
|
+
}
|
|
20
|
+
const m = url.pathname.match(/^\/(?:embed|shorts|live)\/([\w-]{6,20})$/);
|
|
21
|
+
return m ? { provider: "youtube", id: m[1] } : null;
|
|
22
|
+
}
|
|
23
|
+
if (host === "youtu.be") {
|
|
24
|
+
const m = url.pathname.match(/^\/([\w-]{6,20})$/);
|
|
25
|
+
return m ? { provider: "youtube", id: m[1] } : null;
|
|
26
|
+
}
|
|
27
|
+
if (host === "vimeo.com" || host === "player.vimeo.com") {
|
|
28
|
+
// Last numeric path segment: /76979871, /video/76979871, /channels/x/76979871
|
|
29
|
+
const m = url.pathname.match(/\/(\d{6,12})$/);
|
|
30
|
+
return m ? { provider: "vimeo", id: m[1] } : null;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function embedUrl(p: ParsedVideoUrl): string {
|
|
36
|
+
return p.provider === "youtube"
|
|
37
|
+
? `https://www.youtube-nocookie.com/embed/${p.id}`
|
|
38
|
+
: `https://player.vimeo.com/video/${p.id}`;
|
|
39
|
+
}
|
package/src/media/resolve.ts
CHANGED
|
@@ -68,3 +68,21 @@ export function resolveManifestReferences(
|
|
|
68
68
|
|
|
69
69
|
return content;
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Keys stamped onto media refs by resolveManifestReferences above — derived
|
|
74
|
+
* from the referenced media item, never authored by the editor. Must stay in
|
|
75
|
+
* sync with the `patch` object built in resolveManifestReferences.
|
|
76
|
+
*/
|
|
77
|
+
export const BAKED_MEDIA_KEYS = ["src", "srcset", "alt", "width", "height", "poster"] as const;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Re-point a media ref at a different library item. Drops every baked field so
|
|
81
|
+
* stale values resolved for the PREVIOUS image can't shadow the new one in the
|
|
82
|
+
* editor (ResolvedMedia prefers an explicit src prop over live resolution).
|
|
83
|
+
*/
|
|
84
|
+
export function rebindImageRef<T extends { imageId?: string }>(ref: T, imageId: string): T {
|
|
85
|
+
const fresh = { ...ref, imageId } as Record<string, unknown>;
|
|
86
|
+
for (const key of BAKED_MEDIA_KEYS) delete fresh[key];
|
|
87
|
+
return fresh as T;
|
|
88
|
+
}
|
package/src/media/videoPoster.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function generateVideoPoster(
|
|
2
2
|
blob: Blob,
|
|
3
3
|
quality: number,
|
|
4
|
-
): Promise<{ posterBlob: Blob; width: number; height: number }> {
|
|
4
|
+
): Promise<{ posterBlob: Blob; width: number; height: number; duration: number }> {
|
|
5
5
|
return new Promise((resolve, reject) => {
|
|
6
6
|
const video = document.createElement("video");
|
|
7
7
|
const url = URL.createObjectURL(blob);
|
|
@@ -59,7 +59,7 @@ export function generateVideoPoster(
|
|
|
59
59
|
reject(new Error("Failed to create poster blob"));
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
-
resolve({ posterBlob, width: w, height: h });
|
|
62
|
+
resolve({ posterBlob, width: w, height: h, duration: video.duration });
|
|
63
63
|
},
|
|
64
64
|
"image/webp",
|
|
65
65
|
quality / 100,
|
package/src/schemas/link.ts
CHANGED
|
@@ -21,12 +21,23 @@ export function isSafeHref(href: string): boolean {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export const LinkValueSchema = z.discriminatedUnion("kind", [
|
|
24
|
-
z.object({
|
|
24
|
+
z.object({
|
|
25
|
+
kind: z.literal("external"),
|
|
26
|
+
href: z
|
|
27
|
+
.string()
|
|
28
|
+
.refine(isSafeHref, "Unsafe href scheme")
|
|
29
|
+
.describe("An absolute http(s)/mailto URL, a relative path, or empty (unset). javascript:, data:, and protocol-relative URLs are rejected."),
|
|
30
|
+
target: LinkTargetSchema.describe("Where the link opens: '_self' (same tab) or '_blank' (new tab)."),
|
|
31
|
+
}),
|
|
25
32
|
z.object({
|
|
26
33
|
kind: z.literal("internal"),
|
|
27
|
-
pageId: z.string(),
|
|
28
|
-
anchorSectionId: z
|
|
29
|
-
|
|
34
|
+
pageId: z.string().describe("Id of the page to link to, within the same site."),
|
|
35
|
+
anchorSectionId: z
|
|
36
|
+
.string()
|
|
37
|
+
.nullable()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Optional section id on the target page to scroll to; null or omitted links to the page itself."),
|
|
40
|
+
target: LinkTargetSchema.describe("Where the link opens: '_self' (same tab) or '_blank' (new tab)."),
|
|
30
41
|
}),
|
|
31
42
|
]);
|
|
32
43
|
|
package/src/schemas/media.ts
CHANGED
|
@@ -20,6 +20,15 @@ export const MediaItemSchema = z.object({
|
|
|
20
20
|
folder: z.string(),
|
|
21
21
|
variants: z.array(VariantSchema),
|
|
22
22
|
alt: z.string().default(""),
|
|
23
|
+
// Where the original bytes live. "github" = committed to the repo (images,
|
|
24
|
+
// small loops); "assets" = large original in the AssetStore bucket. Legacy
|
|
25
|
+
// manifests have no field — default keeps them on the git path.
|
|
26
|
+
storage: z.enum(["github", "assets"]).default("github"),
|
|
27
|
+
// Public CDN URL for assets-backed originals, baked at upload time so
|
|
28
|
+
// resolution needs no env access in the browser or at SSR.
|
|
29
|
+
url: z.string().optional(),
|
|
30
|
+
// Seconds; videos only. Informational (library UI).
|
|
31
|
+
duration: z.number().positive().optional(),
|
|
23
32
|
});
|
|
24
33
|
|
|
25
34
|
export type MediaItem = z.infer<typeof MediaItemSchema>;
|
|
@@ -34,6 +43,9 @@ export const MediaConfigSchema = z.object({
|
|
|
34
43
|
sizes: z.array(z.number()).default([640, 1080, 1920]),
|
|
35
44
|
maxFileSize: z.number().default(5242880),
|
|
36
45
|
quality: z.number().min(1).max(100).default(85),
|
|
46
|
+
// Per-file cap for bucket-backed video uploads (bytes). The git-path cap is
|
|
47
|
+
// maxFileSize; anything between the two routes to the AssetStore.
|
|
48
|
+
maxAssetSize: z.number().default(209_715_200),
|
|
37
49
|
});
|
|
38
50
|
|
|
39
51
|
export type MediaConfig = z.infer<typeof MediaConfigSchema>;
|
package/src/schemas/shared.ts
CHANGED
|
@@ -33,12 +33,21 @@ export type TextLine = z.infer<typeof TextLineSchema>;
|
|
|
33
33
|
|
|
34
34
|
// --- Media References ---
|
|
35
35
|
const BaseMediaRef = z.object({
|
|
36
|
-
imageId: z.string().default(""),
|
|
37
|
-
caption: z
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
imageId: z.string().default("").describe("Media-library id of the image/video asset."),
|
|
37
|
+
caption: z
|
|
38
|
+
.union([z.string(), z.array(z.string())])
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Optional caption: a single string, or multiple lines as an array of strings."),
|
|
41
|
+
background: z.string().optional().describe("Optional CSS background color/value shown behind a transparent image."),
|
|
42
|
+
invertFrom: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Optional CSS color; the image inverts when the page background matches it (e.g. for dark-mode logo swaps)."),
|
|
46
|
+
border: z.boolean().optional().describe("When true, draw a border around the media."),
|
|
47
|
+
objectFit: z
|
|
48
|
+
.enum(["cover", "contain"])
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("How the image fills its box: 'contain' (fit whole image) or 'cover' (fill and crop)."),
|
|
42
51
|
});
|
|
43
52
|
|
|
44
53
|
const ImageRef = BaseMediaRef.extend({
|
|
@@ -47,22 +56,22 @@ const ImageRef = BaseMediaRef.extend({
|
|
|
47
56
|
|
|
48
57
|
const VideoRef = BaseMediaRef.extend({
|
|
49
58
|
type: z.literal("video"),
|
|
50
|
-
poster: z.string().optional(),
|
|
51
|
-
autoplay: z.boolean().optional(),
|
|
52
|
-
loop: z.boolean().optional(),
|
|
53
|
-
muted: z.boolean().optional(),
|
|
59
|
+
poster: z.string().optional().describe("Optional poster-image URL shown before playback starts."),
|
|
60
|
+
autoplay: z.boolean().optional().describe("When true, the video starts playing automatically."),
|
|
61
|
+
loop: z.boolean().optional().describe("When true, the video repeats when it finishes."),
|
|
62
|
+
muted: z.boolean().optional().describe("When true, the video plays without sound."),
|
|
54
63
|
});
|
|
55
64
|
|
|
56
65
|
const DoDontImageRef = BaseMediaRef.extend({
|
|
57
66
|
type: z.literal("doDontImage"),
|
|
58
|
-
doDont: z.enum(["do", "dont"]),
|
|
67
|
+
doDont: z.enum(["do", "dont"]).describe("Whether this is a 'do' (correct) or 'dont' (incorrect) example."),
|
|
59
68
|
});
|
|
60
69
|
|
|
61
70
|
const LinkedImageRef = BaseMediaRef.extend({
|
|
62
71
|
type: z.literal("linkedImage"),
|
|
63
|
-
href: z.string(),
|
|
64
|
-
target: z.string().optional(),
|
|
65
|
-
linkText: z.string().optional(),
|
|
72
|
+
href: z.string().describe("URL the image links to."),
|
|
73
|
+
target: z.string().optional().describe("Link target, e.g. '_self' or '_blank'."),
|
|
74
|
+
linkText: z.string().optional().describe("Optional accessible link text/label."),
|
|
66
75
|
});
|
|
67
76
|
|
|
68
77
|
export const MediaReferenceSchema = z.discriminatedUnion("type", [
|
package/src/storage/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { StorageProvider, FileWrite } from "./types";
|
|
1
|
+
export type { StorageProvider, FileWrite, AssetStore } from "./types";
|
|
2
2
|
export { StorageConflictError } from "./types";
|
package/src/storage/types.ts
CHANGED
|
@@ -38,3 +38,26 @@ export type FileWrite = {
|
|
|
38
38
|
content: string | Uint8Array | null;
|
|
39
39
|
encoding?: "utf-8" | "base64";
|
|
40
40
|
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Optional large-asset store (Supabase Storage bucket). Holds video originals
|
|
44
|
+
* too big for the git path; deliberately generic so future large kinds (zips)
|
|
45
|
+
* can slot in. All paths are SITE-RELATIVE ("media/{hash}/original.mp4") —
|
|
46
|
+
* implementations prepend the tenant prefix ("sites/{siteId}/") themselves so
|
|
47
|
+
* a caller can never address another tenant's objects.
|
|
48
|
+
*/
|
|
49
|
+
export interface AssetStore {
|
|
50
|
+
/** Mint a signed direct-upload URL for the browser (PUT). */
|
|
51
|
+
createUploadUrl(
|
|
52
|
+
path: string,
|
|
53
|
+
opts: { contentType: string },
|
|
54
|
+
): Promise<{ uploadUrl: string; path: string; publicUrl: string }>;
|
|
55
|
+
|
|
56
|
+
/** Deterministic public CDN URL for an object (no network call). */
|
|
57
|
+
getPublicUrl(path: string): string;
|
|
58
|
+
|
|
59
|
+
delete(paths: string[]): Promise<void>;
|
|
60
|
+
|
|
61
|
+
/** Recursive listing under a site-relative prefix. */
|
|
62
|
+
list(prefix: string): Promise<{ path: string; size: number; updatedAt: string }[]>;
|
|
63
|
+
}
|
package/dist/chunk-4E66N6UV.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// src/components/shared/PagesContext.tsx
|
|
2
|
-
import { createContext, useContext } from "react";
|
|
3
|
-
var PagesContext = createContext({ pages: [], getPageHeadings: () => [] });
|
|
4
|
-
function usePages() {
|
|
5
|
-
return useContext(PagesContext);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
usePages
|
|
10
|
-
};
|
package/dist/chunk-I5737FJY.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
cn
|
|
3
|
-
} from "./chunk-7EOSDFS3.js";
|
|
4
|
-
|
|
5
|
-
// src/components/shared/icons.tsx
|
|
6
|
-
import { GripVertical, Plus, Trash2, X, Settings } from "lucide-react";
|
|
7
|
-
import { GripVertical as GripVertical2, Plus as Plus2, Trash2 as Trash22, X as X2, Settings as Settings2 } from "lucide-react";
|
|
8
|
-
import { jsx } from "react/jsx-runtime";
|
|
9
|
-
function DragHandle({ size = 16 }) {
|
|
10
|
-
return /* @__PURE__ */ jsx(GripVertical, { size });
|
|
11
|
-
}
|
|
12
|
-
function AddIcon({ size = 14 }) {
|
|
13
|
-
return /* @__PURE__ */ jsx(Plus, { size });
|
|
14
|
-
}
|
|
15
|
-
function DeleteIcon({ size = 14 }) {
|
|
16
|
-
return /* @__PURE__ */ jsx(Trash2, { size });
|
|
17
|
-
}
|
|
18
|
-
function SettingsIcon({ size = 14 }) {
|
|
19
|
-
return /* @__PURE__ */ jsx(Settings, { size });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// src/components/shared/IconButton.tsx
|
|
23
|
-
import { forwardRef } from "react";
|
|
24
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
25
|
-
var sizeClasses = {
|
|
26
|
-
sm: "h-chrome-sm w-chrome-sm",
|
|
27
|
-
md: "h-chrome-md w-chrome-md",
|
|
28
|
-
lg: "h-chrome-lg w-chrome-lg"
|
|
29
|
-
};
|
|
30
|
-
var intentClasses = {
|
|
31
|
-
default: "text-base-contrast-light hover:text-base-contrast",
|
|
32
|
-
destructive: "text-destructive hover:text-destructive-hover",
|
|
33
|
-
primary: "text-primary/60 hover:text-primary"
|
|
34
|
-
};
|
|
35
|
-
var IconButton = forwardRef(
|
|
36
|
-
function IconButton2({ icon, label, size = "md", intent = "default", className, ...rest }, ref) {
|
|
37
|
-
return /* @__PURE__ */ jsx2(
|
|
38
|
-
"button",
|
|
39
|
-
{
|
|
40
|
-
ref,
|
|
41
|
-
type: "button",
|
|
42
|
-
className: cn(
|
|
43
|
-
"cursor-pointer flex items-center justify-center rounded transition-colors",
|
|
44
|
-
sizeClasses[size],
|
|
45
|
-
intentClasses[intent],
|
|
46
|
-
className
|
|
47
|
-
),
|
|
48
|
-
"aria-label": label,
|
|
49
|
-
...rest,
|
|
50
|
-
children: icon
|
|
51
|
-
}
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
export {
|
|
57
|
-
DragHandle,
|
|
58
|
-
AddIcon,
|
|
59
|
-
DeleteIcon,
|
|
60
|
-
SettingsIcon,
|
|
61
|
-
IconButton
|
|
62
|
-
};
|