@drawnagency/primitives 0.1.73 → 0.1.75
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-N6AE6NBB.js → chunk-Q3T23HVW.js} +29 -3
- package/dist/{chunk-XTRTK26E.js → chunk-U4E6S4PX.js} +16 -10
- package/dist/{chunk-RBTSUR5V.js → chunk-Y4JQGO6G.js} +10 -1
- package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
- package/dist/components/editor/index.js +8 -5
- package/dist/components/sections/Container/Container.d.ts.map +1 -1
- package/dist/components/sections/Video/VideoBlock.d.ts +2 -1
- package/dist/components/sections/Video/VideoBlock.d.ts.map +1 -1
- package/dist/components/sections/Video/index.d.ts +2 -1
- package/dist/components/sections/Video/index.d.ts.map +1 -1
- package/dist/components/sections/all-sections.d.ts +2 -1
- package/dist/components/sections/all-sections.d.ts.map +1 -1
- package/dist/components/sections/core-sections.d.ts +2 -1
- package/dist/components/sections/core-sections.d.ts.map +1 -1
- package/dist/components/sections/register-schemas.js +3 -3
- package/dist/components/sections/register.js +3 -3
- package/dist/components/shared/Tabs.d.ts.map +1 -1
- package/dist/components/shared/VideoSourceField.d.ts.map +1 -1
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/lib/video-url.d.ts +10 -1
- package/dist/lib/video-url.d.ts.map +1 -1
- package/dist/lib/video-url.js +1 -1
- package/package.json +1 -1
- package/src/components/editor/SectionWrapper.tsx +10 -5
- package/src/components/sections/Container/Container.tsx +15 -2
- package/src/components/sections/Video/VideoBlock.tsx +9 -1
- package/src/components/sections/Video/index.tsx +11 -6
- package/src/components/shared/Tabs.tsx +4 -1
- package/src/components/shared/VideoSourceField.tsx +32 -1
- package/src/hooks/useEditorPublish.ts +5 -2
- package/src/lib/video-url.ts +18 -5
|
@@ -211,6 +211,11 @@ function LinkField({ label, value, onChange }) {
|
|
|
211
211
|
// src/components/shared/VideoSourceField.tsx
|
|
212
212
|
import { useState } from "react";
|
|
213
213
|
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
214
|
+
var ASPECTS = [
|
|
215
|
+
{ label: "9:16", width: 9, height: 16 },
|
|
216
|
+
{ label: "4:5", width: 4, height: 5 },
|
|
217
|
+
{ label: "1:1", width: 1, height: 1 }
|
|
218
|
+
];
|
|
214
219
|
function VideoSourceField({ value, onChange }) {
|
|
215
220
|
const mediaLibrary = useMediaLibrary();
|
|
216
221
|
const [mode, setMode] = useState(value?.type === "external" ? "embed" : "library");
|
|
@@ -238,6 +243,7 @@ function VideoSourceField({ value, onChange }) {
|
|
|
238
243
|
type: "external",
|
|
239
244
|
provider: body.provider,
|
|
240
245
|
id: body.id,
|
|
246
|
+
...body.kind ? { kind: body.kind } : {},
|
|
241
247
|
url: body.url,
|
|
242
248
|
width: body.width,
|
|
243
249
|
height: body.height
|
|
@@ -300,7 +306,7 @@ function VideoSourceField({ value, onChange }) {
|
|
|
300
306
|
void submitEmbed();
|
|
301
307
|
}
|
|
302
308
|
},
|
|
303
|
-
placeholder: "Paste a YouTube or
|
|
309
|
+
placeholder: "Paste a YouTube, Vimeo, or Instagram URL\u2026",
|
|
304
310
|
className: "flex-1 rounded border border-base-200 bg-base px-2 py-1 text-sm text-base-contrast placeholder:text-base-contrast-light focus:outline-none focus:ring-1 focus:ring-primary"
|
|
305
311
|
}
|
|
306
312
|
),
|
|
@@ -317,7 +323,27 @@ function VideoSourceField({ value, onChange }) {
|
|
|
317
323
|
)
|
|
318
324
|
] }),
|
|
319
325
|
error ? /* @__PURE__ */ jsx7("p", { className: "text-xs text-red-600", children: error }) : null,
|
|
320
|
-
!error && fallback ? /* @__PURE__ */ jsx7("p", { className: "text-xs text-base-contrast-light/70", children: "Added \u2014 couldn't fetch exact dimensions, using 16:9." }) : null
|
|
326
|
+
!error && fallback ? /* @__PURE__ */ jsx7("p", { className: "text-xs text-base-contrast-light/70", children: "Added \u2014 couldn't fetch exact dimensions, using 16:9." }) : null,
|
|
327
|
+
value?.type === "external" && value.provider === "instagram" ? /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
|
|
328
|
+
/* @__PURE__ */ jsx7("span", { className: "text-xs text-base-contrast-light", children: "Aspect" }),
|
|
329
|
+
ASPECTS.map(({ label, width, height }) => {
|
|
330
|
+
const active = value.width === width && value.height === height;
|
|
331
|
+
return /* @__PURE__ */ jsx7(
|
|
332
|
+
"button",
|
|
333
|
+
{
|
|
334
|
+
type: "button",
|
|
335
|
+
"aria-pressed": active,
|
|
336
|
+
onClick: () => onChange({ ...value, width, height }),
|
|
337
|
+
className: cn(
|
|
338
|
+
"cursor-pointer rounded border px-2 py-0.5 text-xs",
|
|
339
|
+
active ? "border-primary bg-primary text-primary-contrast" : "border-base-200 text-base-contrast"
|
|
340
|
+
),
|
|
341
|
+
children: label
|
|
342
|
+
},
|
|
343
|
+
label
|
|
344
|
+
);
|
|
345
|
+
})
|
|
346
|
+
] }) : null
|
|
321
347
|
] })
|
|
322
348
|
] });
|
|
323
349
|
}
|
|
@@ -350,7 +376,7 @@ function Tabs({ tabs, defaultTabId, activeTabId, onTabChange, fullBleedTabBar })
|
|
|
350
376
|
},
|
|
351
377
|
tab.id
|
|
352
378
|
)) }) }),
|
|
353
|
-
/* @__PURE__ */ jsx8("div", { role: "tabpanel", className: cn(fullBleedTabBar && "pt-4"), children: activeTab?.content })
|
|
379
|
+
/* @__PURE__ */ jsx8("div", { role: "tabpanel", className: cn("px-px", fullBleedTabBar && "pt-4"), children: activeTab?.content })
|
|
354
380
|
] });
|
|
355
381
|
}
|
|
356
382
|
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-NKVZGPR6.js";
|
|
5
5
|
import {
|
|
6
6
|
embedUrl
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Y4JQGO6G.js";
|
|
8
8
|
import {
|
|
9
9
|
brandGuideSectionDefs,
|
|
10
10
|
pluralize,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
buildBlockDropData,
|
|
32
32
|
canDropBlock,
|
|
33
33
|
isBlockDragData
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-Q3T23HVW.js";
|
|
35
35
|
import {
|
|
36
36
|
useMediaLibrary,
|
|
37
37
|
useResolvedMedia
|
|
@@ -322,16 +322,17 @@ var LibrarySource = z3.object({
|
|
|
322
322
|
});
|
|
323
323
|
var ExternalSource = z3.object({
|
|
324
324
|
type: z3.literal("external"),
|
|
325
|
-
provider: z3.enum(["youtube", "vimeo"]).describe("Hosting provider, derived from the pasted URL."),
|
|
326
|
-
id: z3.string().describe("Provider video id parsed from the pasted URL."),
|
|
325
|
+
provider: z3.enum(["youtube", "vimeo", "instagram"]).describe("Hosting provider, derived from the pasted URL."),
|
|
326
|
+
id: z3.string().describe("Provider video id parsed from the pasted URL (Instagram: the post/reel shortcode)."),
|
|
327
|
+
kind: z3.enum(["post", "reel", "tv"]).optional().describe("Instagram only: which embed path the shortcode came from ('p' \u2192 post). Omitted for other providers."),
|
|
327
328
|
url: z3.string().describe("The original video page URL as pasted."),
|
|
328
|
-
width: z3.number().positive().describe("
|
|
329
|
-
height: z3.number().positive().describe("
|
|
329
|
+
width: z3.number().positive().describe("Aspect-ratio width (from oEmbed; Instagram: 9 for reels/tv, 4 for posts; editors can override)."),
|
|
330
|
+
height: z3.number().positive().describe("Aspect-ratio height (from oEmbed; Instagram: 16 for reels/tv, 5 for posts; editors can override).")
|
|
330
331
|
});
|
|
331
332
|
var schema3 = z3.object({
|
|
332
333
|
type: z3.literal("video"),
|
|
333
334
|
content: z3.object({
|
|
334
|
-
source: z3.discriminatedUnion("type", [LibrarySource, ExternalSource]).describe("Where the video comes from: a media-library upload or an external YouTube/Vimeo URL."),
|
|
335
|
+
source: z3.discriminatedUnion("type", [LibrarySource, ExternalSource]).describe("Where the video comes from: a media-library upload or an external YouTube/Vimeo/Instagram URL."),
|
|
335
336
|
caption: z3.string().optional().describe("Optional caption rendered beneath the player when Show caption is on.")
|
|
336
337
|
}),
|
|
337
338
|
options: z3.object({
|
|
@@ -344,7 +345,7 @@ var Video_default = defineSection({
|
|
|
344
345
|
label: "Video",
|
|
345
346
|
icon: /* @__PURE__ */ jsx8(Clapperboard, { size: 18 }),
|
|
346
347
|
schema: schema3,
|
|
347
|
-
authoringNote: "A watchable video player \u2014 self-hosted from the media library or embedded from YouTube/Vimeo. Use playback 'click' for content viewers press play on (controls + audio) and 'ambient' for silent looping footage. For decorative loops inside grids prefer the Media block; large self-hosted files require the site's asset storage.",
|
|
348
|
+
authoringNote: "A watchable video player \u2014 self-hosted from the media library or embedded from YouTube/Vimeo/Instagram. Instagram embeds render the post card at a kind-derived aspect ratio (reels 9:16, posts 4:5) that editors can override. Use playback 'click' for content viewers press play on (controls + audio) and 'ambient' for silent looping footage. For decorative loops inside grids prefer the Media block; large self-hosted files require the site's asset storage.",
|
|
348
349
|
component: ({ content, options, onChange }) => /* @__PURE__ */ jsx8(
|
|
349
350
|
VideoBlock,
|
|
350
351
|
{
|
|
@@ -857,7 +858,12 @@ function Container({ content, isEditMode, onChange, openModal }) {
|
|
|
857
858
|
};
|
|
858
859
|
const emit = (nextSection) => onChange?.({ ...content, content: nextSection.content });
|
|
859
860
|
const updateChild = (index, childContent) => {
|
|
860
|
-
const next = children.map((c, i) =>
|
|
861
|
+
const next = children.map((c, i) => {
|
|
862
|
+
if (i !== index) return c;
|
|
863
|
+
const merged = { ...childContent, id: c.id ?? newId() };
|
|
864
|
+
if (c.layout !== void 0) merged.layout = c.layout;
|
|
865
|
+
return merged;
|
|
866
|
+
});
|
|
861
867
|
onChange?.({ ...content, content: { ...content.content, children: next } });
|
|
862
868
|
};
|
|
863
869
|
const gridClass = cn(
|
|
@@ -963,7 +969,7 @@ function Container({ content, isEditMode, onChange, openModal }) {
|
|
|
963
969
|
}
|
|
964
970
|
const occupied = occupiedColumns(children, columns);
|
|
965
971
|
const emptyCols = occupied < columns ? columns - occupied : 0;
|
|
966
|
-
return /* @__PURE__ */ jsx13("div", { className: "@container", children: /* @__PURE__ */ jsxs5("div", { className: gridClass, children: [
|
|
972
|
+
return /* @__PURE__ */ jsx13("div", { className: "@container", children: /* @__PURE__ */ jsxs5("div", { className: cn(gridClass, "pt-8 xl:pt-0"), children: [
|
|
967
973
|
children.map(renderEditChild),
|
|
968
974
|
Array.from({ length: emptyCols }, (_, i) => /* @__PURE__ */ jsx13(
|
|
969
975
|
EmptyColumnCell,
|
|
@@ -25,10 +25,19 @@ function parseVideoUrl(raw) {
|
|
|
25
25
|
const m = url.pathname.match(/\/(\d{6,12})$/);
|
|
26
26
|
return m ? { provider: "vimeo", id: m[1] } : null;
|
|
27
27
|
}
|
|
28
|
+
if (host === "instagram.com" || host === "m.instagram.com") {
|
|
29
|
+
const m = url.pathname.match(/^(?:\/([\w.]+))?\/(p|reel|reels|tv)\/([\w-]{5,20})\/?$/);
|
|
30
|
+
if (!m || m[1] === "share") return null;
|
|
31
|
+
const kind = m[2] === "p" ? "post" : m[2] === "tv" ? "tv" : "reel";
|
|
32
|
+
return { provider: "instagram", id: m[3], kind };
|
|
33
|
+
}
|
|
28
34
|
return null;
|
|
29
35
|
}
|
|
36
|
+
var IG_PATH = { post: "p", reel: "reel", tv: "tv" };
|
|
30
37
|
function embedUrl(p) {
|
|
31
|
-
|
|
38
|
+
if (p.provider === "youtube") return `https://www.youtube-nocookie.com/embed/${p.id}`;
|
|
39
|
+
if (p.provider === "instagram") return `https://www.instagram.com/${IG_PATH[p.kind ?? "post"]}/${p.id}/embed/`;
|
|
40
|
+
return `https://player.vimeo.com/video/${p.id}`;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SectionWrapper.d.ts","sourceRoot":"","sources":["../../../src/components/editor/SectionWrapper.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,WAAW,EACX,MAAM,EACN,KAAK,EACL,KAAK,EACL,MAAM,EACN,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,OAAO,EACP,SAAS,EACT,MAAM,EACN,cAAc,EACd,cAAc,EACd,eAAe,EACf,SAAS,EACT,eAAe,EACf,QAAQ,EACR,aAAa,EACb,UAAU,EACV,UAAU,EACV,sBAAsB,EACtB,WAAW,EACX,QAAQ,GACT,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"SectionWrapper.d.ts","sourceRoot":"","sources":["../../../src/components/editor/SectionWrapper.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,WAAW,EACX,MAAM,EACN,KAAK,EACL,KAAK,EACL,MAAM,EACN,WAAW,EACX,gBAAgB,EAChB,UAAU,EACV,OAAO,EACP,SAAS,EACT,MAAM,EACN,cAAc,EACd,cAAc,EACd,eAAe,EACf,SAAS,EACT,eAAe,EACf,QAAQ,EACR,aAAa,EACb,UAAU,EACV,UAAU,EACV,sBAAsB,EACtB,WAAW,EACX,QAAQ,GACT,EAAE,YAAY,2CAqTd"}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
buildBlockDropData,
|
|
12
12
|
canDropBlock,
|
|
13
13
|
isBlockDragData
|
|
14
|
-
} from "../../chunk-
|
|
14
|
+
} from "../../chunk-Q3T23HVW.js";
|
|
15
15
|
import "../../chunk-ZSPCE6MZ.js";
|
|
16
16
|
import "../../chunk-5THS4K5D.js";
|
|
17
17
|
import {
|
|
@@ -540,7 +540,7 @@ function SectionWrapper({
|
|
|
540
540
|
"div",
|
|
541
541
|
{
|
|
542
542
|
className: cn(
|
|
543
|
-
"absolute -left-16 bottom-full z-30 mb-1 hidden xl:block transition-opacity",
|
|
543
|
+
"absolute -left-16 bottom-full z-30 hover:z-[60] mb-1 hidden xl:block transition-opacity",
|
|
544
544
|
alwaysShow ? "opacity-100" : "opacity-0 group-hover:opacity-100"
|
|
545
545
|
),
|
|
546
546
|
children: /* @__PURE__ */ jsx7(InsertButton, { index, onInsert: onRequestInsert })
|
|
@@ -550,13 +550,16 @@ function SectionWrapper({
|
|
|
550
550
|
"div",
|
|
551
551
|
{
|
|
552
552
|
className: cn(
|
|
553
|
-
"absolute left-0 bottom-full z-30 mb-1 flex items-center gap-1.5 transition-opacity",
|
|
553
|
+
"absolute left-0 bottom-full z-30 hover:z-[60] mb-1 flex items-center gap-1.5 transition-opacity",
|
|
554
554
|
alwaysShow ? "opacity-100" : "opacity-0 group-hover:opacity-100"
|
|
555
555
|
),
|
|
556
556
|
children: [
|
|
557
|
-
/* @__PURE__ */ jsxs5("div", { className: "-ml-[34px] flex flex-col items-center", children: [
|
|
557
|
+
/* @__PURE__ */ jsxs5("div", { className: "relative -ml-[34px] flex flex-col items-center", children: [
|
|
558
558
|
/* @__PURE__ */ jsx7(DragHandle, { ref: handleRef }),
|
|
559
|
-
onRequestInsert && !isDragging && chromeReady &&
|
|
559
|
+
onRequestInsert && !isDragging && chromeReady && // Below xl the gutter can't fit the plus beside the handle, so it
|
|
560
|
+
// hangs below it (absolute → no row-height contribution: the handle
|
|
561
|
+
// stays put and the pair stacks DOWN instead of centering).
|
|
562
|
+
/* @__PURE__ */ jsx7("div", { className: "absolute top-full xl:hidden", children: /* @__PURE__ */ jsx7(InsertButton, { index, onInsert: onRequestInsert }) })
|
|
560
563
|
] }),
|
|
561
564
|
/* @__PURE__ */ jsx7("span", { className: "pointer-events-none select-none whitespace-nowrap text-sm capitalize text-base-contrast-light/80", children: definition.label })
|
|
562
565
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Container.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Container/Container.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAgB3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAqGzD,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,gBAAgB,CAAA;KAAE,CAAC;IACpD,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,KAAK,IAAI,CAAC;CACzD;AAkDD,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"Container.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Container/Container.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAgB3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAqGzD,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,gBAAgB,CAAA;KAAE,CAAC;IACpD,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,KAAK,IAAI,CAAC;CACzD;AAkDD,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,cAAc,2CA2LrF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoBlock.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Video/VideoBlock.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACzF;
|
|
1
|
+
{"version":3,"file":"VideoBlock.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Video/VideoBlock.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACzF;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,SAAS,GAAG,OAAO,GAAG,WAAW,CAAC;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEN,UAAU,eAAe;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACnE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IAC/C,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAED,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,EAAE,eAAe,2CAgIpH"}
|
|
@@ -11,11 +11,12 @@ declare const _default: import("../../..").SectionDefinition<{
|
|
|
11
11
|
} | undefined;
|
|
12
12
|
} | {
|
|
13
13
|
type: "external";
|
|
14
|
-
provider: "youtube" | "vimeo";
|
|
14
|
+
provider: "youtube" | "vimeo" | "instagram";
|
|
15
15
|
id: string;
|
|
16
16
|
url: string;
|
|
17
17
|
width: number;
|
|
18
18
|
height: number;
|
|
19
|
+
kind?: "post" | "reel" | "tv" | undefined;
|
|
19
20
|
};
|
|
20
21
|
caption?: string | undefined;
|
|
21
22
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Video/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/Video/index.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,wBA2DG"}
|
|
@@ -80,11 +80,12 @@ export declare const allSectionDefs: (import("../..").SectionDefinition<{
|
|
|
80
80
|
} | undefined;
|
|
81
81
|
} | {
|
|
82
82
|
type: "external";
|
|
83
|
-
provider: "youtube" | "vimeo";
|
|
83
|
+
provider: "youtube" | "vimeo" | "instagram";
|
|
84
84
|
id: string;
|
|
85
85
|
url: string;
|
|
86
86
|
width: number;
|
|
87
87
|
height: number;
|
|
88
|
+
kind?: "post" | "reel" | "tv" | undefined;
|
|
88
89
|
};
|
|
89
90
|
caption?: string | undefined;
|
|
90
91
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"all-sections.d.ts","sourceRoot":"","sources":["../../../src/components/sections/all-sections.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"all-sections.d.ts","sourceRoot":"","sources":["../../../src/components/sections/all-sections.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAAiD,CAAC"}
|
|
@@ -75,11 +75,12 @@ export declare const coreSectionDefs: (import("../..").SectionDefinition<{
|
|
|
75
75
|
} | undefined;
|
|
76
76
|
} | {
|
|
77
77
|
type: "external";
|
|
78
|
-
provider: "youtube" | "vimeo";
|
|
78
|
+
provider: "youtube" | "vimeo" | "instagram";
|
|
79
79
|
id: string;
|
|
80
80
|
url: string;
|
|
81
81
|
width: number;
|
|
82
82
|
height: number;
|
|
83
|
+
kind?: "post" | "reel" | "tv" | undefined;
|
|
83
84
|
};
|
|
84
85
|
caption?: string | undefined;
|
|
85
86
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core-sections.d.ts","sourceRoot":"","sources":["../../../src/components/sections/core-sections.ts"],"names":[],"mappings":"AAUA;;+BAE+B;AAK/B,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"core-sections.d.ts","sourceRoot":"","sources":["../../../src/components/sections/core-sections.ts"],"names":[],"mappings":"AAUA;;+BAE+B;AAK/B,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAU3B,CAAC"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allSectionDefs
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-U4E6S4PX.js";
|
|
4
4
|
import "../../chunk-NKVZGPR6.js";
|
|
5
|
-
import "../../chunk-
|
|
5
|
+
import "../../chunk-Y4JQGO6G.js";
|
|
6
6
|
import "../../chunk-7T2WQSWF.js";
|
|
7
7
|
import "../../chunk-T7MP7ANP.js";
|
|
8
|
-
import "../../chunk-
|
|
8
|
+
import "../../chunk-Q3T23HVW.js";
|
|
9
9
|
import "../../chunk-ZSPCE6MZ.js";
|
|
10
10
|
import "../../chunk-5THS4K5D.js";
|
|
11
11
|
import "../../chunk-IFHRHV7S.js";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allSectionDefs
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-U4E6S4PX.js";
|
|
4
4
|
import "../../chunk-NKVZGPR6.js";
|
|
5
|
-
import "../../chunk-
|
|
5
|
+
import "../../chunk-Y4JQGO6G.js";
|
|
6
6
|
import "../../chunk-7T2WQSWF.js";
|
|
7
7
|
import "../../chunk-T7MP7ANP.js";
|
|
8
|
-
import "../../chunk-
|
|
8
|
+
import "../../chunk-Q3T23HVW.js";
|
|
9
9
|
import "../../chunk-ZSPCE6MZ.js";
|
|
10
10
|
import "../../chunk-5THS4K5D.js";
|
|
11
11
|
import "../../chunk-IFHRHV7S.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tabs.d.ts","sourceRoot":"","sources":["../../../src/components/shared/Tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,CAAC;CACpB;AAED,UAAU,SAAS;IACjB,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"Tabs.d.ts","sourceRoot":"","sources":["../../../src/components/shared/Tabs.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,CAAC;CACpB;AAED,UAAU,SAAS;IACjB,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,EAAE,SAAS,2CAwChG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoSourceField.d.ts","sourceRoot":"","sources":["../../../src/components/shared/VideoSourceField.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAGhE,UAAU,KAAK;IACb,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACzC;
|
|
1
|
+
{"version":3,"file":"VideoSourceField.d.ts","sourceRoot":"","sources":["../../../src/components/shared/VideoSourceField.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAGhE,UAAU,KAAK;IACb,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACzC;AAYD,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,2CAuJ1D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAgD/D,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,iBAAiB,EAAE,SAAS,EAAE,CAAC;IAC/B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gBAAgB,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtF,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAI/B,cAAc,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACpC,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAQD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,eAAe,GAChB,EAAE,WAAW;;;;;;;EAoXb"}
|
package/dist/lib/video-url.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
export type InstagramKind = "post" | "reel" | "tv";
|
|
1
2
|
export type ParsedVideoUrl = {
|
|
2
3
|
provider: "youtube" | "vimeo";
|
|
3
4
|
id: string;
|
|
5
|
+
} | {
|
|
6
|
+
provider: "instagram";
|
|
7
|
+
id: string;
|
|
8
|
+
kind: InstagramKind;
|
|
4
9
|
};
|
|
5
10
|
export declare function parseVideoUrl(raw: string): ParsedVideoUrl | null;
|
|
6
|
-
export declare function embedUrl(p:
|
|
11
|
+
export declare function embedUrl(p: {
|
|
12
|
+
provider: "youtube" | "vimeo" | "instagram";
|
|
13
|
+
id: string;
|
|
14
|
+
kind?: InstagramKind;
|
|
15
|
+
}): string;
|
|
7
16
|
//# sourceMappingURL=video-url.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video-url.d.ts","sourceRoot":"","sources":["../../src/lib/video-url.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"video-url.d.ts","sourceRoot":"","sources":["../../src/lib/video-url.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AACnD,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,EAAE,SAAS,GAAG,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,QAAQ,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAI/D,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAoChE;AAID,wBAAgB,QAAQ,CAAC,CAAC,EAAE;IAAE,QAAQ,EAAE,SAAS,GAAG,OAAO,GAAG,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,aAAa,CAAA;CAAE,GAAG,MAAM,CAIrH"}
|
package/dist/lib/video-url.js
CHANGED
package/package.json
CHANGED
|
@@ -239,11 +239,13 @@ export function SectionWrapper({
|
|
|
239
239
|
data-section-id={sectionId}
|
|
240
240
|
data-section-type={sectionType}
|
|
241
241
|
>
|
|
242
|
-
{/* Plus button — left gutter on xl+, hidden below
|
|
242
|
+
{/* Plus button — left gutter on xl+, hidden below. hover:z-[60] lifts the
|
|
243
|
+
whole slot (a z-30 stacking context the tooltip can't escape) above the
|
|
244
|
+
inline section-type picker (z-50) while the tooltip is showing. */}
|
|
243
245
|
{onRequestInsert && !isDragging && chromeReady && (
|
|
244
246
|
<div
|
|
245
247
|
className={cn(
|
|
246
|
-
"absolute -left-16 bottom-full z-30 mb-1 hidden xl:block transition-opacity",
|
|
248
|
+
"absolute -left-16 bottom-full z-30 hover:z-[60] mb-1 hidden xl:block transition-opacity",
|
|
247
249
|
alwaysShow ? "opacity-100" : "opacity-0 group-hover:opacity-100",
|
|
248
250
|
)}
|
|
249
251
|
>
|
|
@@ -254,14 +256,17 @@ export function SectionWrapper({
|
|
|
254
256
|
{/* Drag handle + section label */}
|
|
255
257
|
<div
|
|
256
258
|
className={cn(
|
|
257
|
-
"absolute left-0 bottom-full z-30 mb-1 flex items-center gap-1.5 transition-opacity",
|
|
259
|
+
"absolute left-0 bottom-full z-30 hover:z-[60] mb-1 flex items-center gap-1.5 transition-opacity",
|
|
258
260
|
alwaysShow ? "opacity-100" : "opacity-0 group-hover:opacity-100",
|
|
259
261
|
)}
|
|
260
262
|
>
|
|
261
|
-
<div className="-ml-[34px] flex flex-col items-center">
|
|
263
|
+
<div className="relative -ml-[34px] flex flex-col items-center">
|
|
262
264
|
<DragHandle ref={handleRef} />
|
|
263
265
|
{onRequestInsert && !isDragging && chromeReady && (
|
|
264
|
-
|
|
266
|
+
// Below xl the gutter can't fit the plus beside the handle, so it
|
|
267
|
+
// hangs below it (absolute → no row-height contribution: the handle
|
|
268
|
+
// stays put and the pair stacks DOWN instead of centering).
|
|
269
|
+
<div className="absolute top-full xl:hidden">
|
|
265
270
|
<InsertButton index={index} onInsert={onRequestInsert} />
|
|
266
271
|
</div>
|
|
267
272
|
)}
|
|
@@ -192,8 +192,18 @@ export function Container({ content, isEditMode, onChange, openModal }: Containe
|
|
|
192
192
|
onChange?.({ ...content, content: nextSection.content });
|
|
193
193
|
|
|
194
194
|
// Per-child inline content edits (e.g. prose/media bubble their own onChange).
|
|
195
|
+
// Section components emit id-less SectionContent (EditorShell re-attaches the id
|
|
196
|
+
// at the root) and know nothing of the layout envelope, so preserve both from
|
|
197
|
+
// the existing child — an id-less child fails save validation for the whole
|
|
198
|
+
// container. Minting when the existing child has no id self-heals drafts saved
|
|
199
|
+
// by the pre-fix path.
|
|
195
200
|
const updateChild = (index: number, childContent: unknown) => {
|
|
196
|
-
const next = children.map((c, i) =>
|
|
201
|
+
const next = children.map((c, i) => {
|
|
202
|
+
if (i !== index) return c;
|
|
203
|
+
const merged = { ...(childContent as Section), id: c.id ?? newId() };
|
|
204
|
+
if (c.layout !== undefined) merged.layout = c.layout;
|
|
205
|
+
return merged;
|
|
206
|
+
});
|
|
197
207
|
onChange?.({ ...content, content: { ...content.content, children: next } });
|
|
198
208
|
};
|
|
199
209
|
|
|
@@ -338,7 +348,10 @@ export function Container({ content, isEditMode, onChange, openModal }: Containe
|
|
|
338
348
|
const emptyCols = occupied < columns ? columns - occupied : 0;
|
|
339
349
|
return (
|
|
340
350
|
<div className="@container">
|
|
341
|
-
|
|
351
|
+
{/* Below xl the section's "+" stacks under the drag handle and hangs down
|
|
352
|
+
into the left gutter, where the first child's control rail would collide
|
|
353
|
+
with it — push children down until the gutter is side-by-side again. */}
|
|
354
|
+
<div className={cn(gridClass, "pt-8 xl:pt-0")}>
|
|
342
355
|
{children.map(renderEditChild)}
|
|
343
356
|
{Array.from({ length: emptyCols }, (_, i) => (
|
|
344
357
|
<EmptyColumnCell
|
|
@@ -7,7 +7,15 @@ import { EditablePlainText } from "../../primitives/EditablePlainText";
|
|
|
7
7
|
|
|
8
8
|
export type VideoSource =
|
|
9
9
|
| { type: "library"; ref: { imageId: string }; poster?: { imageId: string; src?: string } }
|
|
10
|
-
| {
|
|
10
|
+
| {
|
|
11
|
+
type: "external";
|
|
12
|
+
provider: "youtube" | "vimeo" | "instagram";
|
|
13
|
+
id: string;
|
|
14
|
+
kind?: "post" | "reel" | "tv";
|
|
15
|
+
url: string;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
};
|
|
11
19
|
|
|
12
20
|
interface VideoBlockProps {
|
|
13
21
|
source: VideoSource;
|
|
@@ -24,11 +24,15 @@ const LibrarySource = z.object({
|
|
|
24
24
|
|
|
25
25
|
const ExternalSource = z.object({
|
|
26
26
|
type: z.literal("external"),
|
|
27
|
-
provider: z.enum(["youtube", "vimeo"]).describe("Hosting provider, derived from the pasted URL."),
|
|
28
|
-
id: z.string().describe("Provider video id parsed from the pasted URL."),
|
|
27
|
+
provider: z.enum(["youtube", "vimeo", "instagram"]).describe("Hosting provider, derived from the pasted URL."),
|
|
28
|
+
id: z.string().describe("Provider video id parsed from the pasted URL (Instagram: the post/reel shortcode)."),
|
|
29
|
+
kind: z
|
|
30
|
+
.enum(["post", "reel", "tv"])
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Instagram only: which embed path the shortcode came from ('p' → post). Omitted for other providers."),
|
|
29
33
|
url: z.string().describe("The original video page URL as pasted."),
|
|
30
|
-
width: z.number().positive().describe("
|
|
31
|
-
height: z.number().positive().describe("
|
|
34
|
+
width: z.number().positive().describe("Aspect-ratio width (from oEmbed; Instagram: 9 for reels/tv, 4 for posts; editors can override)."),
|
|
35
|
+
height: z.number().positive().describe("Aspect-ratio height (from oEmbed; Instagram: 16 for reels/tv, 5 for posts; editors can override)."),
|
|
32
36
|
});
|
|
33
37
|
|
|
34
38
|
const schema = z.object({
|
|
@@ -36,7 +40,7 @@ const schema = z.object({
|
|
|
36
40
|
content: z.object({
|
|
37
41
|
source: z
|
|
38
42
|
.discriminatedUnion("type", [LibrarySource, ExternalSource])
|
|
39
|
-
.describe("Where the video comes from: a media-library upload or an external YouTube/Vimeo URL."),
|
|
43
|
+
.describe("Where the video comes from: a media-library upload or an external YouTube/Vimeo/Instagram URL."),
|
|
40
44
|
caption: z.string().optional().describe("Optional caption rendered beneath the player when Show caption is on."),
|
|
41
45
|
}),
|
|
42
46
|
options: z
|
|
@@ -56,7 +60,8 @@ export default defineSection({
|
|
|
56
60
|
icon: <Clapperboard size={18} />,
|
|
57
61
|
schema,
|
|
58
62
|
authoringNote:
|
|
59
|
-
"A watchable video player — self-hosted from the media library or embedded from YouTube/Vimeo. " +
|
|
63
|
+
"A watchable video player — self-hosted from the media library or embedded from YouTube/Vimeo/Instagram. " +
|
|
64
|
+
"Instagram embeds render the post card at a kind-derived aspect ratio (reels 9:16, posts 4:5) that editors can override. " +
|
|
60
65
|
"Use playback 'click' for content viewers press play on (controls + audio) and 'ambient' for silent looping footage. " +
|
|
61
66
|
"For decorative loops inside grids prefer the Media block; large self-hosted files require the site's asset storage.",
|
|
62
67
|
component: ({ content, options, onChange }) => (
|
|
@@ -57,7 +57,10 @@ export function Tabs({ tabs, defaultTabId, activeTabId, onTabChange, fullBleedTa
|
|
|
57
57
|
))}
|
|
58
58
|
</div>
|
|
59
59
|
</div>
|
|
60
|
-
|
|
60
|
+
{/* px-px: the root's overflow-hidden would clip the 1px focus ring of
|
|
61
|
+
inputs that span the full panel width (rings are box-shadows drawn
|
|
62
|
+
outside the border box) — give them 1px to paint into. */}
|
|
63
|
+
<div role="tabpanel" className={cn("px-px", fullBleedTabBar && "pt-4")}>{activeTab?.content}</div>
|
|
61
64
|
</div>
|
|
62
65
|
);
|
|
63
66
|
}
|
|
@@ -13,6 +13,14 @@ interface Props {
|
|
|
13
13
|
|
|
14
14
|
type Mode = "library" | "embed";
|
|
15
15
|
|
|
16
|
+
// Instagram embeds only: the URL kind picks the initial ratio (reels 9:16,
|
|
17
|
+
// posts 4:5), but /p/ shortcodes are ambiguous so editors can correct it.
|
|
18
|
+
const ASPECTS = [
|
|
19
|
+
{ label: "9:16", width: 9, height: 16 },
|
|
20
|
+
{ label: "4:5", width: 4, height: 5 },
|
|
21
|
+
{ label: "1:1", width: 1, height: 1 },
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
16
24
|
export function VideoSourceField({ value, onChange }: Props) {
|
|
17
25
|
const mediaLibrary = useMediaLibrary();
|
|
18
26
|
const [mode, setMode] = useState<Mode>(value?.type === "external" ? "embed" : "library");
|
|
@@ -42,6 +50,7 @@ export function VideoSourceField({ value, onChange }: Props) {
|
|
|
42
50
|
type: "external",
|
|
43
51
|
provider: body.provider,
|
|
44
52
|
id: body.id,
|
|
53
|
+
...(body.kind ? { kind: body.kind } : {}),
|
|
45
54
|
url: body.url,
|
|
46
55
|
width: body.width,
|
|
47
56
|
height: body.height,
|
|
@@ -118,7 +127,7 @@ export function VideoSourceField({ value, onChange }: Props) {
|
|
|
118
127
|
void submitEmbed();
|
|
119
128
|
}
|
|
120
129
|
}}
|
|
121
|
-
placeholder="Paste a YouTube or
|
|
130
|
+
placeholder="Paste a YouTube, Vimeo, or Instagram URL…"
|
|
122
131
|
className="flex-1 rounded border border-base-200 bg-base px-2 py-1 text-sm text-base-contrast placeholder:text-base-contrast-light focus:outline-none focus:ring-1 focus:ring-primary"
|
|
123
132
|
/>
|
|
124
133
|
<Button
|
|
@@ -137,6 +146,28 @@ export function VideoSourceField({ value, onChange }: Props) {
|
|
|
137
146
|
Added — couldn't fetch exact dimensions, using 16:9.
|
|
138
147
|
</p>
|
|
139
148
|
) : null}
|
|
149
|
+
{value?.type === "external" && value.provider === "instagram" ? (
|
|
150
|
+
<div className="flex items-center gap-2">
|
|
151
|
+
<span className="text-xs text-base-contrast-light">Aspect</span>
|
|
152
|
+
{ASPECTS.map(({ label, width, height }) => {
|
|
153
|
+
const active = value.width === width && value.height === height;
|
|
154
|
+
return (
|
|
155
|
+
<button
|
|
156
|
+
key={label}
|
|
157
|
+
type="button"
|
|
158
|
+
aria-pressed={active}
|
|
159
|
+
onClick={() => onChange({ ...value, width, height })}
|
|
160
|
+
className={cn(
|
|
161
|
+
"cursor-pointer rounded border px-2 py-0.5 text-xs",
|
|
162
|
+
active ? "border-primary bg-primary text-primary-contrast" : "border-base-200 text-base-contrast",
|
|
163
|
+
)}
|
|
164
|
+
>
|
|
165
|
+
{label}
|
|
166
|
+
</button>
|
|
167
|
+
);
|
|
168
|
+
})}
|
|
169
|
+
</div>
|
|
170
|
+
) : null}
|
|
140
171
|
</div>
|
|
141
172
|
)}
|
|
142
173
|
</div>
|
|
@@ -34,8 +34,11 @@ async function postSave(payload: object): Promise<{ sha: string }> {
|
|
|
34
34
|
throw new Error("Network error while saving — check your connection and try again.");
|
|
35
35
|
}
|
|
36
36
|
if (!response.ok) {
|
|
37
|
-
const errorBody = await response.json().catch(() => null) as { message?: string; error?: string } | null;
|
|
38
|
-
|
|
37
|
+
const errorBody = await response.json().catch(() => null) as { message?: string; error?: string; details?: string } | null;
|
|
38
|
+
// `details` carries the field-level cause (e.g. the Zod path inside a failing
|
|
39
|
+
// container child) — without it a validation failure only names the section.
|
|
40
|
+
const baseMessage = errorBody?.message || errorBody?.error;
|
|
41
|
+
const serverMessage = baseMessage && errorBody?.details ? `${baseMessage} — ${errorBody.details}` : baseMessage;
|
|
39
42
|
const fallback = !errorBody && body.length > 5_500_000
|
|
40
43
|
? "This save is too large to send in one request — remove some images from the media library and try again."
|
|
41
44
|
: `Save failed (HTTP ${response.status})`;
|
package/src/lib/video-url.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type InstagramKind = "post" | "reel" | "tv";
|
|
2
|
+
export type ParsedVideoUrl =
|
|
3
|
+
| { provider: "youtube" | "vimeo"; id: string }
|
|
4
|
+
| { provider: "instagram"; id: string; kind: InstagramKind };
|
|
2
5
|
|
|
3
6
|
const YT_ID = /^[\w-]{6,20}$/;
|
|
4
7
|
|
|
@@ -29,11 +32,21 @@ export function parseVideoUrl(raw: string): ParsedVideoUrl | null {
|
|
|
29
32
|
const m = url.pathname.match(/\/(\d{6,12})$/);
|
|
30
33
|
return m ? { provider: "vimeo", id: m[1] } : null;
|
|
31
34
|
}
|
|
35
|
+
if (host === "instagram.com" || host === "m.instagram.com") {
|
|
36
|
+
// Canonical (/reel/{code}) or username-prefixed (/{user}/p/{code}) URLs.
|
|
37
|
+
// NOT /share/ links — their ids are opaque redirect tokens, not shortcodes.
|
|
38
|
+
const m = url.pathname.match(/^(?:\/([\w.]+))?\/(p|reel|reels|tv)\/([\w-]{5,20})\/?$/);
|
|
39
|
+
if (!m || m[1] === "share") return null;
|
|
40
|
+
const kind: InstagramKind = m[2] === "p" ? "post" : m[2] === "tv" ? "tv" : "reel";
|
|
41
|
+
return { provider: "instagram", id: m[3], kind };
|
|
42
|
+
}
|
|
32
43
|
return null;
|
|
33
44
|
}
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
const IG_PATH: Record<InstagramKind, string> = { post: "p", reel: "reel", tv: "tv" };
|
|
47
|
+
|
|
48
|
+
export function embedUrl(p: { provider: "youtube" | "vimeo" | "instagram"; id: string; kind?: InstagramKind }): string {
|
|
49
|
+
if (p.provider === "youtube") return `https://www.youtube-nocookie.com/embed/${p.id}`;
|
|
50
|
+
if (p.provider === "instagram") return `https://www.instagram.com/${IG_PATH[p.kind ?? "post"]}/${p.id}/embed/`;
|
|
51
|
+
return `https://player.vimeo.com/video/${p.id}`;
|
|
39
52
|
}
|