@emdash-cms/admin 0.8.0 → 0.9.0
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/{LocaleDirectionProvider-B43-InGI.js → LocaleDirectionProvider-BGcmv_N7.js} +21 -15
- package/dist/LocaleDirectionProvider-BGcmv_N7.js.map +1 -0
- package/dist/{config-h7tWQRka.d.ts → config-YdN-uv6S.d.ts} +1 -1
- package/dist/{config-h7tWQRka.d.ts.map → config-YdN-uv6S.d.ts.map} +1 -1
- package/dist/index.d.ts +68 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1312 -952
- package/dist/index.js.map +1 -1
- package/dist/locales/ar/messages.mjs +1 -1
- package/dist/locales/de/messages.mjs +1 -1
- package/dist/locales/en/messages.mjs +1 -1
- package/dist/locales/es-419/messages.mjs +1 -1
- package/dist/locales/eu/messages.mjs +1 -1
- package/dist/locales/fa/messages.mjs +1 -1
- package/dist/locales/fr/messages.mjs +1 -1
- package/dist/locales/id/messages.mjs +1 -0
- package/dist/locales/index.d.ts +1 -1
- package/dist/locales/index.js +1 -1
- package/dist/locales/ja/messages.mjs +1 -1
- package/dist/locales/ko/messages.mjs +1 -1
- package/dist/locales/pl/messages.mjs +1 -1
- package/dist/locales/pseudo/messages.mjs +1 -1
- package/dist/locales/pt-BR/messages.mjs +1 -1
- package/dist/locales/zh-CN/messages.mjs +1 -1
- package/dist/locales/zh-TW/messages.mjs +1 -1
- package/dist/messages-B2njiT3N.js +6 -0
- package/dist/messages-B2njiT3N.js.map +1 -0
- package/dist/messages-BH4d_eo3.js +6 -0
- package/dist/messages-BH4d_eo3.js.map +1 -0
- package/dist/messages-BI3LQfHj.js +6 -0
- package/dist/messages-BI3LQfHj.js.map +1 -0
- package/dist/messages-BLk7kRSt.js +6 -0
- package/dist/messages-BLk7kRSt.js.map +1 -0
- package/dist/messages-C0fFDufP.js +6 -0
- package/dist/messages-C0fFDufP.js.map +1 -0
- package/dist/messages-C2W1GOne.js +6 -0
- package/dist/messages-C2W1GOne.js.map +1 -0
- package/dist/messages-C7bn0sIO.js +6 -0
- package/dist/messages-C7bn0sIO.js.map +1 -0
- package/dist/messages-CKVC1F84.js +6 -0
- package/dist/messages-CKVC1F84.js.map +1 -0
- package/dist/messages-D6pXx3hr.js +6 -0
- package/dist/messages-D6pXx3hr.js.map +1 -0
- package/dist/messages-DQUgrzIz.js +6 -0
- package/dist/messages-DQUgrzIz.js.map +1 -0
- package/dist/messages-DfGxk-9q.js +6 -0
- package/dist/messages-DfGxk-9q.js.map +1 -0
- package/dist/messages-Dkb9TndV.js +6 -0
- package/dist/messages-Dkb9TndV.js.map +1 -0
- package/dist/messages-DzS9GmJY.js +6 -0
- package/dist/messages-DzS9GmJY.js.map +1 -0
- package/dist/messages-FaHuWwRM.js +6 -0
- package/dist/messages-FaHuWwRM.js.map +1 -0
- package/dist/messages-lrnW_v3N.js +6 -0
- package/dist/messages-lrnW_v3N.js.map +1 -0
- package/dist/plugins-DUJY_8HN.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +2 -4
- package/dist/LocaleDirectionProvider-B43-InGI.js.map +0 -1
- package/dist/messages-B6ZguVfS.js +0 -6
- package/dist/messages-B6ZguVfS.js.map +0 -1
- package/dist/messages-BOnMi1Vb.js +0 -6
- package/dist/messages-BOnMi1Vb.js.map +0 -1
- package/dist/messages-BZuRVH-D.js +0 -6
- package/dist/messages-BZuRVH-D.js.map +0 -1
- package/dist/messages-Bc_PDiKw.js +0 -6
- package/dist/messages-Bc_PDiKw.js.map +0 -1
- package/dist/messages-Bddg68eH.js +0 -6
- package/dist/messages-Bddg68eH.js.map +0 -1
- package/dist/messages-BoP-RHEG.js +0 -6
- package/dist/messages-BoP-RHEG.js.map +0 -1
- package/dist/messages-C4hNtyU5.js +0 -6
- package/dist/messages-C4hNtyU5.js.map +0 -1
- package/dist/messages-CMwt5Gv-.js +0 -6
- package/dist/messages-CMwt5Gv-.js.map +0 -1
- package/dist/messages-CnliZpid.js +0 -6
- package/dist/messages-CnliZpid.js.map +0 -1
- package/dist/messages-CseAZVO2.js +0 -6
- package/dist/messages-CseAZVO2.js.map +0 -1
- package/dist/messages-DRyNt1qC.js +0 -6
- package/dist/messages-DRyNt1qC.js.map +0 -1
- package/dist/messages-DTehFUkD.js +0 -6
- package/dist/messages-DTehFUkD.js.map +0 -1
- package/dist/messages-DeQMOYvT.js +0 -6
- package/dist/messages-DeQMOYvT.js.map +0 -1
- package/dist/messages-Do3BokvI.js +0 -6
- package/dist/messages-Do3BokvI.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as fetchAuthMode, d as throwResponseError, i as fetchPlugins, l as fetchManifest, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as parseApiResponse } from "./plugins-DUJY_8HN.js";
|
|
2
|
-
import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-
|
|
2
|
+
import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-BGcmv_N7.js";
|
|
3
3
|
import "./locales/index.js";
|
|
4
4
|
import { Badge, Button, Checkbox, CommandPalette, Dialog, Input, InputArea, Label, LinkButton, Loader, Popover, Select, Sidebar as KumoSidebar, Switch, Tabs, Toast, Toasty, Tooltip, buttonVariants, useSidebar } from "@cloudflare/kumo";
|
|
5
5
|
import { i18n } from "@lingui/core";
|
|
@@ -10,7 +10,7 @@ import * as React from "react";
|
|
|
10
10
|
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
11
11
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
import { Dialog as Dialog$1 } from "@cloudflare/kumo/primitives";
|
|
13
|
-
import { ArrowCounterClockwise,
|
|
13
|
+
import { ArrowCounterClockwise, ArrowRight, ArrowRightIcon, ArrowSquareOut, ArrowUUpLeft, ArrowUUpRight, ArrowsClockwise, ArrowsInSimple, ArrowsLeftRight, ArrowsOutSimple, BracketsCurly, Calendar, CalendarBlank, CaretDown, CaretRightIcon, CaretUp, CaretUpDown, ChatCircle, Check, CheckCircle, CircleDashed, CircleNotch, ClockCounterClockwise, Cloud, Code, CodeBlock, Copy, Cube, Database, DeviceMobile, DotsSixVertical, DownloadSimple, Envelope, EnvelopeSimple, Eye, EyeSlash, File, FileArrowDown, FileText, FloppyDisk, FolderOpen, Gear, GithubLogo, Globe, GlobeSimple, GridFour, HardDrive, Hash, Image as Image$1, ImageSquare, Info, Key, Link as Link$2, LinkBreak, LinkSimple, List, ListBullets, ListChecks, ListNumbers, MagnifyingGlass, Minus, Monitor, Moon, Palette, PaperPlaneTilt, Paragraph, Pencil, PencilSimple, PlugsConnected, Plus, Prohibit, PuzzlePiece, Quotes, Rows, Ruler, ShareNetwork, Shield, ShieldCheck, ShieldWarning, SignOut, SlidersHorizontal, Sparkle, SquaresFour, Stack, Storefront, Sun, TextAlignCenter, TextAlignLeft, TextAlignRight, TextB, TextHOne, TextHThree, TextHTwo, TextItalic, TextStrikethrough, TextT, TextUnderline, ToggleLeft, Trash, Upload, User, UserCircle, UserPlus, Users, Warning, WarningCircle, WebhooksLogo, X, YoutubeLogo } from "@phosphor-icons/react";
|
|
14
14
|
import { clsx } from "clsx";
|
|
15
15
|
import { twMerge } from "tailwind-merge";
|
|
16
16
|
import { DndContext, DragOverlay, KeyboardSensor, PointerSensor, closestCenter, rectIntersection, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
|
|
@@ -219,6 +219,37 @@ function slugify(text) {
|
|
|
219
219
|
return text.toLowerCase().normalize("NFD").replace(DIACRITICS_PATTERN, "").replace(WHITESPACE_UNDERSCORE_PATTERN, "-").replace(NON_ALPHANUMERIC_HYPHEN_PATTERN, "").replace(MULTIPLE_HYPHENS_PATTERN, "-").replace(LEADING_TRAILING_HYPHEN_PATTERN, "");
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/components/ArrowIcons.tsx
|
|
224
|
+
/** Caret pointing in the "forward" direction — right in LTR, left in RTL. */
|
|
225
|
+
function CaretNext({ className, ...props }) {
|
|
226
|
+
return /* @__PURE__ */ jsx(CaretRightIcon, {
|
|
227
|
+
className: cn("rtl:-scale-x-100", className),
|
|
228
|
+
...props
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/** Caret pointing in the "backward" direction — left in LTR, right in RTL. */
|
|
232
|
+
function CaretPrev({ className, ...props }) {
|
|
233
|
+
return /* @__PURE__ */ jsx(CaretRightIcon, {
|
|
234
|
+
className: cn("rotate-180 rtl:rotate-0", className),
|
|
235
|
+
...props
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** Arrow pointing in the "forward" direction — right in LTR, left in RTL. */
|
|
239
|
+
function ArrowNext({ className, ...props }) {
|
|
240
|
+
return /* @__PURE__ */ jsx(ArrowRightIcon, {
|
|
241
|
+
className: cn("rtl:-scale-x-100", className),
|
|
242
|
+
...props
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/** Arrow pointing in the "backward" direction — left in LTR, right in RTL. */
|
|
246
|
+
function ArrowPrev({ className, ...props }) {
|
|
247
|
+
return /* @__PURE__ */ jsx(ArrowRightIcon, {
|
|
248
|
+
className: cn("rotate-180 rtl:rotate-0", className),
|
|
249
|
+
...props
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
222
253
|
//#endregion
|
|
223
254
|
//#region src/components/DialogError.tsx
|
|
224
255
|
/**
|
|
@@ -891,7 +922,7 @@ function CommentInbox({ comments, counts, isLoading, nextCursor, collections, ac
|
|
|
891
922
|
id: "aHKcKc",
|
|
892
923
|
message: "Previous page"
|
|
893
924
|
}),
|
|
894
|
-
children: /* @__PURE__ */ jsx(
|
|
925
|
+
children: /* @__PURE__ */ jsx(CaretPrev, { className: "h-4 w-4" })
|
|
895
926
|
}),
|
|
896
927
|
/* @__PURE__ */ jsxs("span", {
|
|
897
928
|
className: "text-sm",
|
|
@@ -915,7 +946,7 @@ function CommentInbox({ comments, counts, isLoading, nextCursor, collections, ac
|
|
|
915
946
|
id: "/dqduX",
|
|
916
947
|
message: "Next page"
|
|
917
948
|
}),
|
|
918
|
-
children: /* @__PURE__ */ jsx(
|
|
949
|
+
children: /* @__PURE__ */ jsx(CaretNext, { className: "h-4 w-4" })
|
|
919
950
|
})
|
|
920
951
|
]
|
|
921
952
|
})]
|
|
@@ -1226,7 +1257,9 @@ async function unscheduleContent(collection, id) {
|
|
|
1226
1257
|
* Get a preview URL for content
|
|
1227
1258
|
*
|
|
1228
1259
|
* Returns a signed URL that allows viewing draft content.
|
|
1229
|
-
* Returns null if
|
|
1260
|
+
* Returns null if the EmDash runtime isn't initialized on the server
|
|
1261
|
+
* (responds with NOT_CONFIGURED). The preview secret itself no longer
|
|
1262
|
+
* needs to be set explicitly — it auto-generates on first use.
|
|
1230
1263
|
*/
|
|
1231
1264
|
async function getPreviewUrl(collection, id, options) {
|
|
1232
1265
|
try {
|
|
@@ -2489,22 +2522,37 @@ async function uninstallMarketplacePlugin(id, opts = {}) {
|
|
|
2489
2522
|
async function checkPluginUpdates() {
|
|
2490
2523
|
return (await parseApiResponse(await apiFetch(`${API_BASE}/admin/plugins/updates`), "Failed to check for updates")).items;
|
|
2491
2524
|
}
|
|
2492
|
-
/**
|
|
2525
|
+
/**
|
|
2526
|
+
* Human-readable labels for plugin capabilities.
|
|
2527
|
+
*
|
|
2528
|
+
* Canonical names are the keys; legacy names alias to the same labels so
|
|
2529
|
+
* old manifests still render meaningful copy until they're republished.
|
|
2530
|
+
*/
|
|
2493
2531
|
const CAPABILITY_LABELS = {
|
|
2532
|
+
"content:read": "Read your content",
|
|
2533
|
+
"content:write": "Create, update, and delete content",
|
|
2534
|
+
"media:read": "Access your media library",
|
|
2535
|
+
"media:write": "Upload and manage media",
|
|
2536
|
+
"users:read": "Read user accounts",
|
|
2537
|
+
"network:request": "Make network requests",
|
|
2538
|
+
"network:request:unrestricted": "Make network requests to any host (unrestricted)",
|
|
2494
2539
|
"read:content": "Read your content",
|
|
2495
2540
|
"write:content": "Create, update, and delete content",
|
|
2496
2541
|
"read:media": "Access your media library",
|
|
2497
2542
|
"write:media": "Upload and manage media",
|
|
2543
|
+
"read:users": "Read user accounts",
|
|
2498
2544
|
"network:fetch": "Make network requests",
|
|
2499
2545
|
"network:fetch:any": "Make network requests to any host (unrestricted)"
|
|
2500
2546
|
};
|
|
2547
|
+
/** Capability names that grant scoped network access (legacy + canonical). */
|
|
2548
|
+
const NETWORK_REQUEST_CAPABILITIES = new Set(["network:request", "network:fetch"]);
|
|
2501
2549
|
/**
|
|
2502
2550
|
* Get a human-readable description for a capability.
|
|
2503
|
-
* For network
|
|
2551
|
+
* For scoped network capabilities, appends the allowed hosts if provided.
|
|
2504
2552
|
*/
|
|
2505
2553
|
function describeCapability(capability, allowedHosts) {
|
|
2506
2554
|
const base = CAPABILITY_LABELS[capability] ?? capability;
|
|
2507
|
-
if (capability
|
|
2555
|
+
if (NETWORK_REQUEST_CAPABILITIES.has(capability) && allowedHosts && allowedHosts.length > 0) return `${base} to: ${allowedHosts.join(", ")}`;
|
|
2508
2556
|
return base;
|
|
2509
2557
|
}
|
|
2510
2558
|
|
|
@@ -2640,6 +2688,33 @@ function useCurrentUser() {
|
|
|
2640
2688
|
});
|
|
2641
2689
|
}
|
|
2642
2690
|
|
|
2691
|
+
//#endregion
|
|
2692
|
+
//#region src/lib/datetime-local.ts
|
|
2693
|
+
/**
|
|
2694
|
+
* Helpers for round-tripping `datetime` field values through
|
|
2695
|
+
* `<input type="datetime-local">`.
|
|
2696
|
+
*
|
|
2697
|
+
* Stored field values are full ISO 8601 (`YYYY-MM-DDTHH:mm:ss.sssZ`) or
|
|
2698
|
+
* date-only (`YYYY-MM-DD`) per the per-field zod schema in
|
|
2699
|
+
* `packages/core/src/schema/zod-generator.ts`. The browser input only
|
|
2700
|
+
* accepts `YYYY-MM-DDTHH:mm`, so widgets must convert in both directions.
|
|
2701
|
+
*
|
|
2702
|
+
* The widget treats the value as UTC for a stable round-trip. Using
|
|
2703
|
+
* `new Date(...).toISOString()` (the convention in the publish-schedule
|
|
2704
|
+
* UI in `ContentEditor.tsx`) would shift values by the local-UTC offset on
|
|
2705
|
+
* every save, mutating the persisted time on each edit cycle.
|
|
2706
|
+
*/
|
|
2707
|
+
/** Format a stored datetime field value for the input. */
|
|
2708
|
+
function toDatetimeLocalInputValue(value) {
|
|
2709
|
+
if (typeof value !== "string" || value === "") return "";
|
|
2710
|
+
if (value.length === 10) return `${value}T00:00`;
|
|
2711
|
+
return value.slice(0, 16);
|
|
2712
|
+
}
|
|
2713
|
+
/** Convert an input value (`YYYY-MM-DDTHH:mm`) back to the stored ISO shape. */
|
|
2714
|
+
function fromDatetimeLocalInputValue(value) {
|
|
2715
|
+
return value === "" ? "" : `${value}:00.000Z`;
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2643
2718
|
//#endregion
|
|
2644
2719
|
//#region src/lib/url.ts
|
|
2645
2720
|
/**
|
|
@@ -2693,676 +2768,123 @@ function safeIconUrl(url, width) {
|
|
|
2693
2768
|
}
|
|
2694
2769
|
|
|
2695
2770
|
//#endregion
|
|
2696
|
-
//#region src/
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
}, [onChange]);
|
|
2712
|
-
const validElements = elements.filter((el) => el.action_id);
|
|
2713
|
-
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
|
|
2714
|
-
className: "text-sm font-medium leading-none",
|
|
2715
|
-
children: label
|
|
2716
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
2717
|
-
className: "mt-2 space-y-3",
|
|
2718
|
-
children: validElements.map((el) => /* @__PURE__ */ jsx(BlockKitFieldElement, {
|
|
2719
|
-
element: el,
|
|
2720
|
-
value: obj[el.action_id],
|
|
2721
|
-
onChange: handleElementChange
|
|
2722
|
-
}, el.action_id))
|
|
2723
|
-
})] });
|
|
2771
|
+
//#region src/lib/media-utils.ts
|
|
2772
|
+
function providerItemToMediaItem(providerId, item) {
|
|
2773
|
+
return {
|
|
2774
|
+
id: item.id,
|
|
2775
|
+
filename: item.filename,
|
|
2776
|
+
mimeType: item.mimeType,
|
|
2777
|
+
url: item.previewUrl || "",
|
|
2778
|
+
size: item.size || 0,
|
|
2779
|
+
width: item.width,
|
|
2780
|
+
height: item.height,
|
|
2781
|
+
alt: item.alt,
|
|
2782
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2783
|
+
provider: providerId,
|
|
2784
|
+
meta: item.meta
|
|
2785
|
+
};
|
|
2724
2786
|
}
|
|
2725
|
-
function
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
checked: !!value,
|
|
2745
|
-
onCheckedChange: (checked) => onChange(element.action_id, checked)
|
|
2746
|
-
});
|
|
2747
|
-
case "select": {
|
|
2748
|
-
const options = Array.isArray(element.options) ? element.options : [];
|
|
2749
|
-
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2750
|
-
className: "text-sm font-medium mb-1.5 block",
|
|
2751
|
-
children: element.label
|
|
2752
|
-
}), /* @__PURE__ */ jsxs("select", {
|
|
2753
|
-
className: "flex w-full rounded-md border border-kumo-line bg-transparent px-3 py-2 text-sm",
|
|
2754
|
-
value: typeof value === "string" ? value : "",
|
|
2755
|
-
onChange: (e) => onChange(element.action_id, e.target.value),
|
|
2756
|
-
children: [/* @__PURE__ */ jsx("option", {
|
|
2757
|
-
value: "",
|
|
2758
|
-
children: "Select..."
|
|
2759
|
-
}), options.map((opt) => /* @__PURE__ */ jsx("option", {
|
|
2760
|
-
value: opt.value,
|
|
2761
|
-
children: opt.label
|
|
2762
|
-
}, opt.value))]
|
|
2763
|
-
})] });
|
|
2764
|
-
}
|
|
2765
|
-
default: return /* @__PURE__ */ jsxs("div", {
|
|
2766
|
-
className: "text-sm text-kumo-subtle",
|
|
2767
|
-
children: ["Unsupported widget element type: ", element.type]
|
|
2768
|
-
});
|
|
2769
|
-
}
|
|
2787
|
+
function getFileIcon(mimeType) {
|
|
2788
|
+
if (mimeType.startsWith("video/")) return "🎬";
|
|
2789
|
+
if (mimeType.startsWith("audio/")) return "🎵";
|
|
2790
|
+
if (mimeType.includes("pdf")) return "📄";
|
|
2791
|
+
if (mimeType.includes("document") || mimeType.includes("word")) return "📝";
|
|
2792
|
+
if (mimeType.includes("spreadsheet") || mimeType.includes("excel")) return "📊";
|
|
2793
|
+
return "📁";
|
|
2794
|
+
}
|
|
2795
|
+
function formatFileSize(bytes) {
|
|
2796
|
+
if (bytes === 0) return "0 B";
|
|
2797
|
+
const k = 1024;
|
|
2798
|
+
const sizes = [
|
|
2799
|
+
"B",
|
|
2800
|
+
"KB",
|
|
2801
|
+
"MB",
|
|
2802
|
+
"GB"
|
|
2803
|
+
];
|
|
2804
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
2805
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
2770
2806
|
}
|
|
2771
2807
|
|
|
2772
2808
|
//#endregion
|
|
2773
|
-
//#region src/components/
|
|
2809
|
+
//#region src/components/MediaPickerModal.tsx
|
|
2774
2810
|
/**
|
|
2775
|
-
*
|
|
2811
|
+
* Media Picker Modal
|
|
2776
2812
|
*
|
|
2777
|
-
*
|
|
2778
|
-
*
|
|
2779
|
-
*
|
|
2780
|
-
* - Highlights the current section based on cursor position
|
|
2813
|
+
* A modal dialog for selecting media from the library or uploading new files.
|
|
2814
|
+
* Supports multiple media providers with tabbed navigation.
|
|
2815
|
+
* Used by the rich text editor and image field components.
|
|
2781
2816
|
*/
|
|
2782
|
-
function getIndentClass(level) {
|
|
2783
|
-
switch (level) {
|
|
2784
|
-
case 1: return "ps-0";
|
|
2785
|
-
case 2: return "ps-4";
|
|
2786
|
-
case 3: return "ps-8";
|
|
2787
|
-
default: return "ps-0";
|
|
2788
|
-
}
|
|
2789
|
-
}
|
|
2790
|
-
function getTextClass(level) {
|
|
2791
|
-
switch (level) {
|
|
2792
|
-
case 1: return "font-medium";
|
|
2793
|
-
case 2: return "font-normal";
|
|
2794
|
-
case 3: return "font-normal text-kumo-subtle";
|
|
2795
|
-
default: return "font-normal";
|
|
2796
|
-
}
|
|
2797
|
-
}
|
|
2798
2817
|
/**
|
|
2799
|
-
*
|
|
2818
|
+
* Probe image URL to get dimensions
|
|
2800
2819
|
*/
|
|
2801
|
-
function
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
const rawLevel = node.attrs.level;
|
|
2809
|
-
const level = typeof rawLevel === "number" ? rawLevel : 1;
|
|
2810
|
-
const text = node.textContent || "";
|
|
2811
|
-
if (text.trim()) headings.push({
|
|
2812
|
-
level,
|
|
2813
|
-
text,
|
|
2814
|
-
pos,
|
|
2815
|
-
key: `heading-${key++}`
|
|
2820
|
+
function probeImageDimensions(url) {
|
|
2821
|
+
return new Promise((resolve, reject) => {
|
|
2822
|
+
const img = new window.Image();
|
|
2823
|
+
img.onload = () => {
|
|
2824
|
+
resolve({
|
|
2825
|
+
width: img.naturalWidth,
|
|
2826
|
+
height: img.naturalHeight
|
|
2816
2827
|
});
|
|
2817
|
-
}
|
|
2818
|
-
});
|
|
2819
|
-
return headings;
|
|
2820
|
-
}
|
|
2821
|
-
/**
|
|
2822
|
-
* Find the current heading based on cursor position
|
|
2823
|
-
*/
|
|
2824
|
-
function findCurrentHeading(headings, cursorPos) {
|
|
2825
|
-
if (headings.length === 0) return null;
|
|
2826
|
-
let current = null;
|
|
2827
|
-
for (const heading of headings) if (heading.pos <= cursorPos) current = heading;
|
|
2828
|
-
else break;
|
|
2829
|
-
return current;
|
|
2830
|
-
}
|
|
2831
|
-
/**
|
|
2832
|
-
* Document outline component showing heading tree structure
|
|
2833
|
-
*/
|
|
2834
|
-
function DocumentOutline({ editor, className }) {
|
|
2835
|
-
const { _: _t } = useLingui();
|
|
2836
|
-
const [isExpanded, setIsExpanded] = React.useState(true);
|
|
2837
|
-
const [headings, setHeadings] = React.useState([]);
|
|
2838
|
-
const [currentPos, setCurrentPos] = React.useState(0);
|
|
2839
|
-
React.useEffect(() => {
|
|
2840
|
-
if (!editor) return;
|
|
2841
|
-
const updateHeadings = () => {
|
|
2842
|
-
setHeadings(extractHeadings(editor));
|
|
2843
|
-
};
|
|
2844
|
-
updateHeadings();
|
|
2845
|
-
editor.on("update", updateHeadings);
|
|
2846
|
-
return () => {
|
|
2847
|
-
editor.off("update", updateHeadings);
|
|
2848
|
-
};
|
|
2849
|
-
}, [editor]);
|
|
2850
|
-
React.useEffect(() => {
|
|
2851
|
-
if (!editor) return;
|
|
2852
|
-
const updatePosition = () => {
|
|
2853
|
-
const { from } = editor.state.selection;
|
|
2854
|
-
setCurrentPos(from);
|
|
2855
|
-
};
|
|
2856
|
-
updatePosition();
|
|
2857
|
-
editor.on("selectionUpdate", updatePosition);
|
|
2858
|
-
return () => {
|
|
2859
|
-
editor.off("selectionUpdate", updatePosition);
|
|
2860
|
-
};
|
|
2861
|
-
}, [editor]);
|
|
2862
|
-
const currentHeading = findCurrentHeading(headings, currentPos);
|
|
2863
|
-
const handleHeadingClick = (heading) => {
|
|
2864
|
-
if (!editor) return;
|
|
2865
|
-
editor.chain().focus().setTextSelection(heading.pos).scrollIntoView().run();
|
|
2866
|
-
};
|
|
2867
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
2868
|
-
className: cn("space-y-2", className),
|
|
2869
|
-
children: [/* @__PURE__ */ jsxs(Button, {
|
|
2870
|
-
variant: "ghost",
|
|
2871
|
-
size: "sm",
|
|
2872
|
-
className: "w-full justify-between px-2 h-8",
|
|
2873
|
-
onClick: () => setIsExpanded(!isExpanded),
|
|
2874
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
2875
|
-
className: "flex items-center gap-2",
|
|
2876
|
-
children: [/* @__PURE__ */ jsx(List, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
|
|
2877
|
-
className: "font-semibold",
|
|
2878
|
-
children: _t({
|
|
2879
|
-
id: "dD7NPy",
|
|
2880
|
-
message: "Outline"
|
|
2881
|
-
})
|
|
2882
|
-
})]
|
|
2883
|
-
}), isExpanded ? /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(CaretRight, { className: "h-4 w-4" })]
|
|
2884
|
-
}), isExpanded && /* @__PURE__ */ jsx("div", {
|
|
2885
|
-
className: "space-y-0.5",
|
|
2886
|
-
children: headings.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
2887
|
-
className: "text-sm text-kumo-subtle px-2 py-1",
|
|
2888
|
-
children: _t({
|
|
2889
|
-
id: "MMd//L",
|
|
2890
|
-
message: "No headings in document"
|
|
2891
|
-
})
|
|
2892
|
-
}) : headings.map((heading) => {
|
|
2893
|
-
const isCurrent = currentHeading?.key === heading.key;
|
|
2894
|
-
return /* @__PURE__ */ jsx("button", {
|
|
2895
|
-
type: "button",
|
|
2896
|
-
onClick: () => handleHeadingClick(heading),
|
|
2897
|
-
className: cn("w-full text-start px-2 py-1 text-sm rounded transition-colors", "hover:bg-kumo-tint/50 cursor-pointer", "truncate", getIndentClass(heading.level), getTextClass(heading.level), isCurrent && "bg-kumo-tint text-kumo-default"),
|
|
2898
|
-
title: heading.text,
|
|
2899
|
-
children: heading.text
|
|
2900
|
-
}, heading.key);
|
|
2901
|
-
})
|
|
2902
|
-
})]
|
|
2903
|
-
});
|
|
2904
|
-
}
|
|
2905
|
-
|
|
2906
|
-
//#endregion
|
|
2907
|
-
//#region src/components/PluginFieldErrorBoundary.tsx
|
|
2908
|
-
/**
|
|
2909
|
-
* Error boundary that wraps trusted plugin field widgets.
|
|
2910
|
-
* On render error, shows a warning with a retry button instead of crashing the editor.
|
|
2911
|
-
*/
|
|
2912
|
-
var PluginFieldErrorBoundary = class extends React.Component {
|
|
2913
|
-
constructor(props) {
|
|
2914
|
-
super(props);
|
|
2915
|
-
this.state = { hasError: false };
|
|
2916
|
-
}
|
|
2917
|
-
static getDerivedStateFromError(error) {
|
|
2918
|
-
return {
|
|
2919
|
-
hasError: true,
|
|
2920
|
-
error
|
|
2921
2828
|
};
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
if (this.state.hasError) return /* @__PURE__ */ jsxs("div", {
|
|
2925
|
-
className: "rounded-md border border-kumo-danger/50 bg-kumo-danger/5 p-3",
|
|
2926
|
-
children: [
|
|
2927
|
-
/* @__PURE__ */ jsx("p", {
|
|
2928
|
-
className: "text-sm font-medium text-kumo-danger",
|
|
2929
|
-
children: "Plugin widget error"
|
|
2930
|
-
}),
|
|
2931
|
-
/* @__PURE__ */ jsx("p", {
|
|
2932
|
-
className: "mt-1 text-xs text-kumo-subtle",
|
|
2933
|
-
children: this.state.error?.message || "The plugin field widget failed to render."
|
|
2934
|
-
}),
|
|
2935
|
-
/* @__PURE__ */ jsx("button", {
|
|
2936
|
-
type: "button",
|
|
2937
|
-
className: "mt-2 text-xs font-medium text-kumo-brand underline",
|
|
2938
|
-
onClick: () => this.setState({
|
|
2939
|
-
hasError: false,
|
|
2940
|
-
error: void 0
|
|
2941
|
-
}),
|
|
2942
|
-
children: "Retry"
|
|
2943
|
-
})
|
|
2944
|
-
]
|
|
2945
|
-
});
|
|
2946
|
-
return this.props.children;
|
|
2947
|
-
}
|
|
2948
|
-
};
|
|
2949
|
-
|
|
2950
|
-
//#endregion
|
|
2951
|
-
//#region src/components/RepeaterField.tsx
|
|
2952
|
-
/**
|
|
2953
|
-
* RepeaterField — renders a list of repeating sub-field groups in the content editor.
|
|
2954
|
-
*
|
|
2955
|
-
* Each item is a collapsible card containing the defined sub-fields.
|
|
2956
|
-
* Items can be added, removed, and reordered via drag-and-drop.
|
|
2957
|
-
*/
|
|
2958
|
-
function ensureKeys$1(items) {
|
|
2959
|
-
return items.map((item, i) => {
|
|
2960
|
-
const obj = typeof item === "object" && item !== null ? item : {};
|
|
2961
|
-
return {
|
|
2962
|
-
...obj,
|
|
2963
|
-
_key: obj._key || `item-${i}-${Date.now()}`
|
|
2829
|
+
img.onerror = () => {
|
|
2830
|
+
reject(/* @__PURE__ */ new Error("Failed to load image"));
|
|
2964
2831
|
};
|
|
2832
|
+
img.src = url;
|
|
2965
2833
|
});
|
|
2966
2834
|
}
|
|
2967
|
-
function
|
|
2968
|
-
return items.map(({ _key, ...rest }) => rest);
|
|
2969
|
-
}
|
|
2970
|
-
function RepeaterField({ label, id, value, onChange, subFields, minItems = 0, maxItems }) {
|
|
2835
|
+
function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "image/", title: providedTitle }) {
|
|
2971
2836
|
const { _: _t } = useLingui();
|
|
2972
|
-
const
|
|
2973
|
-
|
|
2974
|
-
|
|
2837
|
+
const title = providedTitle ?? _t({
|
|
2838
|
+
id: "bAiEJP",
|
|
2839
|
+
message: "Select Image"
|
|
2840
|
+
});
|
|
2841
|
+
const queryClient = useQueryClient();
|
|
2842
|
+
const [selectedItem, setSelectedItem] = React.useState(null);
|
|
2843
|
+
const [activeProvider, setActiveProvider] = React.useState("local");
|
|
2844
|
+
const [searchQuery, setSearchQuery] = React.useState("");
|
|
2845
|
+
const fileInputRef = React.useRef(null);
|
|
2846
|
+
const [imageUrl, setImageUrl] = React.useState("");
|
|
2847
|
+
const [isProbing, setIsProbing] = React.useState(false);
|
|
2848
|
+
const [urlError, setUrlError] = React.useState(null);
|
|
2849
|
+
const [providerDimensions, setProviderDimensions] = React.useState({});
|
|
2975
2850
|
React.useEffect(() => {
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
};
|
|
2984
|
-
}
|
|
2985
|
-
}, [
|
|
2986
|
-
const
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
if (maxItems && items.length >= maxItems) return;
|
|
2992
|
-
const newItem = { _key: `item-${Date.now()}` };
|
|
2993
|
-
for (const sf of subFields) newItem[sf.slug] = sf.type === "boolean" ? false : sf.type === "number" || sf.type === "integer" ? null : "";
|
|
2994
|
-
emitChange([...items, newItem]);
|
|
2995
|
-
};
|
|
2996
|
-
const handleRemove = (key) => {
|
|
2997
|
-
if (items.length <= minItems) return;
|
|
2998
|
-
emitChange(items.filter((item) => item._key !== key));
|
|
2999
|
-
};
|
|
3000
|
-
const handleItemChange = (key, fieldSlug, fieldValue) => {
|
|
3001
|
-
emitChange(items.map((item) => item._key === key ? {
|
|
3002
|
-
...item,
|
|
3003
|
-
[fieldSlug]: fieldValue
|
|
3004
|
-
} : item));
|
|
3005
|
-
};
|
|
3006
|
-
const handleDragEnd = (event) => {
|
|
3007
|
-
const { active, over } = event;
|
|
3008
|
-
if (!over || active.id === over.id) return;
|
|
3009
|
-
const oldIndex = items.findIndex((item) => item._key === active.id);
|
|
3010
|
-
const newIndex = items.findIndex((item) => item._key === over.id);
|
|
3011
|
-
if (oldIndex === -1 || newIndex === -1) return;
|
|
3012
|
-
emitChange(arrayMove(items, oldIndex, newIndex));
|
|
3013
|
-
};
|
|
3014
|
-
const toggleCollapse = (key) => {
|
|
3015
|
-
setCollapsedItems((prev) => {
|
|
3016
|
-
const next = new Set(prev);
|
|
3017
|
-
if (next.has(key)) next.delete(key);
|
|
3018
|
-
else next.add(key);
|
|
3019
|
-
return next;
|
|
3020
|
-
});
|
|
3021
|
-
};
|
|
3022
|
-
const canAdd = !maxItems || items.length < maxItems;
|
|
3023
|
-
const canRemove = items.length > minItems;
|
|
3024
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
3025
|
-
className: "space-y-2",
|
|
3026
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
3027
|
-
className: "flex items-center justify-between",
|
|
3028
|
-
children: [/* @__PURE__ */ jsxs("label", {
|
|
3029
|
-
htmlFor: id,
|
|
3030
|
-
className: "text-sm font-medium",
|
|
3031
|
-
children: [label, items.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
3032
|
-
className: "ms-2 text-kumo-subtle font-normal",
|
|
3033
|
-
children: i18n._({
|
|
3034
|
-
id: "JcH6Wc",
|
|
3035
|
-
message: "{0, plural, one {(# item)} other {(# items)}}",
|
|
3036
|
-
values: { 0: items.length }
|
|
3037
|
-
})
|
|
3038
|
-
})]
|
|
3039
|
-
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
3040
|
-
variant: "outline",
|
|
3041
|
-
size: "sm",
|
|
3042
|
-
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
3043
|
-
onClick: handleAdd,
|
|
3044
|
-
children: _t({
|
|
3045
|
-
id: "qgF8ew",
|
|
3046
|
-
message: "Add Item"
|
|
3047
|
-
})
|
|
3048
|
-
})]
|
|
3049
|
-
}), items.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
3050
|
-
className: "border-2 border-dashed rounded-lg p-6 text-center text-kumo-subtle",
|
|
3051
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
3052
|
-
className: "text-sm",
|
|
3053
|
-
children: _t({
|
|
3054
|
-
id: "bWmJ0A",
|
|
3055
|
-
message: "No items yet"
|
|
3056
|
-
})
|
|
3057
|
-
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
3058
|
-
variant: "outline",
|
|
3059
|
-
size: "sm",
|
|
3060
|
-
className: "mt-2",
|
|
3061
|
-
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
3062
|
-
onClick: handleAdd,
|
|
3063
|
-
children: _t({
|
|
3064
|
-
id: "924qJl",
|
|
3065
|
-
message: "Add First Item"
|
|
3066
|
-
})
|
|
3067
|
-
})]
|
|
3068
|
-
}) : /* @__PURE__ */ jsx(DndContext, {
|
|
3069
|
-
collisionDetection: closestCenter,
|
|
3070
|
-
onDragEnd: handleDragEnd,
|
|
3071
|
-
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
3072
|
-
items: items.map((item) => item._key),
|
|
3073
|
-
strategy: verticalListSortingStrategy,
|
|
3074
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
3075
|
-
className: "space-y-2",
|
|
3076
|
-
children: items.map((item, index) => /* @__PURE__ */ jsx(SortableRepeaterItem, {
|
|
3077
|
-
item,
|
|
3078
|
-
index,
|
|
3079
|
-
subFields,
|
|
3080
|
-
isCollapsed: collapsedItems.has(item._key),
|
|
3081
|
-
onToggleCollapse: () => toggleCollapse(item._key),
|
|
3082
|
-
onRemove: canRemove ? () => handleRemove(item._key) : void 0,
|
|
3083
|
-
onChange: (fieldSlug, fieldValue) => handleItemChange(item._key, fieldSlug, fieldValue)
|
|
3084
|
-
}, item._key))
|
|
3085
|
-
})
|
|
3086
|
-
})
|
|
3087
|
-
})]
|
|
2851
|
+
if (open) {
|
|
2852
|
+
setSelectedItem(null);
|
|
2853
|
+
setActiveProvider("local");
|
|
2854
|
+
setSearchQuery("");
|
|
2855
|
+
setImageUrl("");
|
|
2856
|
+
setUrlError(null);
|
|
2857
|
+
setUploadError(null);
|
|
2858
|
+
setProviderDimensions({});
|
|
2859
|
+
}
|
|
2860
|
+
}, [open]);
|
|
2861
|
+
const { data: providers } = useQuery({
|
|
2862
|
+
queryKey: ["media-providers"],
|
|
2863
|
+
queryFn: fetchMediaProviders,
|
|
2864
|
+
enabled: open,
|
|
2865
|
+
placeholderData: []
|
|
3088
2866
|
});
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
});
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
children: [
|
|
3111
|
-
/* @__PURE__ */ jsx(DotsSixVertical, {
|
|
3112
|
-
className: "h-4 w-4 text-kumo-subtle cursor-grab shrink-0",
|
|
3113
|
-
...attributes,
|
|
3114
|
-
...listeners,
|
|
3115
|
-
onClick: (e) => e.stopPropagation()
|
|
3116
|
-
}),
|
|
3117
|
-
isCollapsed ? /* @__PURE__ */ jsx(CaretRight, { className: "h-4 w-4 text-kumo-subtle shrink-0" }) : /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4 text-kumo-subtle shrink-0" }),
|
|
3118
|
-
/* @__PURE__ */ jsx("span", {
|
|
3119
|
-
className: "text-sm font-medium flex-1 truncate",
|
|
3120
|
-
children: summaryLabel
|
|
3121
|
-
}),
|
|
3122
|
-
onRemove && /* @__PURE__ */ jsx(Button, {
|
|
3123
|
-
variant: "ghost",
|
|
3124
|
-
shape: "square",
|
|
3125
|
-
onClick: (e) => {
|
|
3126
|
-
e.stopPropagation();
|
|
3127
|
-
onRemove();
|
|
3128
|
-
},
|
|
3129
|
-
"aria-label": _t2({
|
|
3130
|
-
id: "mU3j1b",
|
|
3131
|
-
message: "Remove item {0}",
|
|
3132
|
-
values: { 0: index + 1 }
|
|
3133
|
-
}),
|
|
3134
|
-
children: /* @__PURE__ */ jsx(Trash, { className: "h-3.5 w-3.5 text-kumo-danger" })
|
|
3135
|
-
})
|
|
3136
|
-
]
|
|
3137
|
-
}), !isCollapsed && /* @__PURE__ */ jsx("div", {
|
|
3138
|
-
className: "p-3 space-y-3",
|
|
3139
|
-
children: subFields.map((sf) => /* @__PURE__ */ jsx(SubFieldInput, {
|
|
3140
|
-
subField: sf,
|
|
3141
|
-
value: item[sf.slug],
|
|
3142
|
-
onChange: (v) => onChange(sf.slug, v)
|
|
3143
|
-
}, sf.slug))
|
|
3144
|
-
})]
|
|
3145
|
-
});
|
|
3146
|
-
}
|
|
3147
|
-
function SubFieldInput({ subField, value, onChange }) {
|
|
3148
|
-
const { _: _t3 } = useLingui();
|
|
3149
|
-
switch (subField.type) {
|
|
3150
|
-
case "string": return /* @__PURE__ */ jsx(Input, {
|
|
3151
|
-
label: subField.label,
|
|
3152
|
-
value: typeof value === "string" ? value : "",
|
|
3153
|
-
onChange: (e) => onChange(e.target.value),
|
|
3154
|
-
required: subField.required,
|
|
3155
|
-
dir: "auto"
|
|
3156
|
-
});
|
|
3157
|
-
case "text": return /* @__PURE__ */ jsx(InputArea, {
|
|
3158
|
-
label: subField.label,
|
|
3159
|
-
value: typeof value === "string" ? value : "",
|
|
3160
|
-
onChange: (e) => onChange(e.target.value),
|
|
3161
|
-
required: subField.required,
|
|
3162
|
-
rows: 3,
|
|
3163
|
-
dir: "auto"
|
|
3164
|
-
});
|
|
3165
|
-
case "number":
|
|
3166
|
-
case "integer": return /* @__PURE__ */ jsx(Input, {
|
|
3167
|
-
label: subField.label,
|
|
3168
|
-
type: "number",
|
|
3169
|
-
value: typeof value === "number" ? String(value) : "",
|
|
3170
|
-
onChange: (e) => onChange(e.target.value ? Number(e.target.value) : null),
|
|
3171
|
-
required: subField.required,
|
|
3172
|
-
step: subField.type === "integer" ? "1" : "any"
|
|
3173
|
-
});
|
|
3174
|
-
case "boolean": return /* @__PURE__ */ jsxs("label", {
|
|
3175
|
-
className: "flex items-center gap-2",
|
|
3176
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
3177
|
-
type: "checkbox",
|
|
3178
|
-
checked: Boolean(value),
|
|
3179
|
-
onChange: (e) => onChange(e.target.checked)
|
|
3180
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
3181
|
-
className: "text-sm",
|
|
3182
|
-
children: subField.label
|
|
3183
|
-
})]
|
|
3184
|
-
});
|
|
3185
|
-
case "datetime": return /* @__PURE__ */ jsx(Input, {
|
|
3186
|
-
label: subField.label,
|
|
3187
|
-
type: "datetime-local",
|
|
3188
|
-
value: typeof value === "string" ? value : "",
|
|
3189
|
-
onChange: (e) => onChange(e.target.value),
|
|
3190
|
-
required: subField.required
|
|
3191
|
-
});
|
|
3192
|
-
case "select": return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
3193
|
-
className: "text-sm font-medium",
|
|
3194
|
-
children: subField.label
|
|
3195
|
-
}), /* @__PURE__ */ jsxs("select", {
|
|
3196
|
-
className: "w-full mt-1 rounded-md border px-3 py-2 text-sm",
|
|
3197
|
-
value: typeof value === "string" ? value : "",
|
|
3198
|
-
onChange: (e) => onChange(e.target.value),
|
|
3199
|
-
required: subField.required,
|
|
3200
|
-
children: [/* @__PURE__ */ jsx("option", {
|
|
3201
|
-
value: "",
|
|
3202
|
-
children: _t3({
|
|
3203
|
-
id: "O/7I0o",
|
|
3204
|
-
message: "Select..."
|
|
3205
|
-
})
|
|
3206
|
-
}), subField.options?.map((opt) => /* @__PURE__ */ jsx("option", {
|
|
3207
|
-
value: opt,
|
|
3208
|
-
children: opt
|
|
3209
|
-
}, opt))]
|
|
3210
|
-
})] });
|
|
3211
|
-
default: return /* @__PURE__ */ jsx(Input, {
|
|
3212
|
-
label: subField.label,
|
|
3213
|
-
value: typeof value === "string" ? value : "",
|
|
3214
|
-
onChange: (e) => onChange(e.target.value)
|
|
3215
|
-
});
|
|
3216
|
-
}
|
|
3217
|
-
}
|
|
3218
|
-
|
|
3219
|
-
//#endregion
|
|
3220
|
-
//#region src/lib/hooks.ts
|
|
3221
|
-
/**
|
|
3222
|
-
* Returns a stable function reference that always calls the latest version
|
|
3223
|
-
* of the provided callback. Useful for event listeners in effects where
|
|
3224
|
-
* you don't want the listener to be torn down and re-added when the
|
|
3225
|
-
* callback identity changes.
|
|
3226
|
-
*/
|
|
3227
|
-
function useStableCallback(callback) {
|
|
3228
|
-
const ref = React.useRef(callback);
|
|
3229
|
-
React.useLayoutEffect(() => {
|
|
3230
|
-
ref.current = callback;
|
|
3231
|
-
});
|
|
3232
|
-
return React.useCallback((...args) => ref.current(...args), []);
|
|
3233
|
-
}
|
|
3234
|
-
/**
|
|
3235
|
-
* Returns a debounced version of a value that only updates after the
|
|
3236
|
-
* specified delay has elapsed since the last change. Useful for search
|
|
3237
|
-
* inputs that trigger API calls.
|
|
3238
|
-
*/
|
|
3239
|
-
function useDebouncedValue$1(value, delay) {
|
|
3240
|
-
const [debouncedValue, setDebouncedValue] = React.useState(value);
|
|
3241
|
-
React.useEffect(() => {
|
|
3242
|
-
const timer = setTimeout(setDebouncedValue, delay, value);
|
|
3243
|
-
return () => clearTimeout(timer);
|
|
3244
|
-
}, [value, delay]);
|
|
3245
|
-
return debouncedValue;
|
|
3246
|
-
}
|
|
3247
|
-
|
|
3248
|
-
//#endregion
|
|
3249
|
-
//#region src/lib/media-utils.ts
|
|
3250
|
-
function providerItemToMediaItem(providerId, item) {
|
|
3251
|
-
return {
|
|
3252
|
-
id: item.id,
|
|
3253
|
-
filename: item.filename,
|
|
3254
|
-
mimeType: item.mimeType,
|
|
3255
|
-
url: item.previewUrl || "",
|
|
3256
|
-
size: item.size || 0,
|
|
3257
|
-
width: item.width,
|
|
3258
|
-
height: item.height,
|
|
3259
|
-
alt: item.alt,
|
|
3260
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3261
|
-
provider: providerId,
|
|
3262
|
-
meta: item.meta
|
|
3263
|
-
};
|
|
3264
|
-
}
|
|
3265
|
-
function getFileIcon(mimeType) {
|
|
3266
|
-
if (mimeType.startsWith("video/")) return "🎬";
|
|
3267
|
-
if (mimeType.startsWith("audio/")) return "🎵";
|
|
3268
|
-
if (mimeType.includes("pdf")) return "📄";
|
|
3269
|
-
if (mimeType.includes("document") || mimeType.includes("word")) return "📝";
|
|
3270
|
-
if (mimeType.includes("spreadsheet") || mimeType.includes("excel")) return "📊";
|
|
3271
|
-
return "📁";
|
|
3272
|
-
}
|
|
3273
|
-
function formatFileSize(bytes) {
|
|
3274
|
-
if (bytes === 0) return "0 B";
|
|
3275
|
-
const k = 1024;
|
|
3276
|
-
const sizes = [
|
|
3277
|
-
"B",
|
|
3278
|
-
"KB",
|
|
3279
|
-
"MB",
|
|
3280
|
-
"GB"
|
|
3281
|
-
];
|
|
3282
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
3283
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
3284
|
-
}
|
|
3285
|
-
|
|
3286
|
-
//#endregion
|
|
3287
|
-
//#region src/components/MediaPickerModal.tsx
|
|
3288
|
-
/**
|
|
3289
|
-
* Media Picker Modal
|
|
3290
|
-
*
|
|
3291
|
-
* A modal dialog for selecting media from the library or uploading new files.
|
|
3292
|
-
* Supports multiple media providers with tabbed navigation.
|
|
3293
|
-
* Used by the rich text editor and image field components.
|
|
3294
|
-
*/
|
|
3295
|
-
/**
|
|
3296
|
-
* Probe image URL to get dimensions
|
|
3297
|
-
*/
|
|
3298
|
-
function probeImageDimensions(url) {
|
|
3299
|
-
return new Promise((resolve, reject) => {
|
|
3300
|
-
const img = new window.Image();
|
|
3301
|
-
img.onload = () => {
|
|
3302
|
-
resolve({
|
|
3303
|
-
width: img.naturalWidth,
|
|
3304
|
-
height: img.naturalHeight
|
|
3305
|
-
});
|
|
3306
|
-
};
|
|
3307
|
-
img.onerror = () => {
|
|
3308
|
-
reject(/* @__PURE__ */ new Error("Failed to load image"));
|
|
3309
|
-
};
|
|
3310
|
-
img.src = url;
|
|
3311
|
-
});
|
|
3312
|
-
}
|
|
3313
|
-
function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "image/", title: providedTitle }) {
|
|
3314
|
-
const { _: _t } = useLingui();
|
|
3315
|
-
const title = providedTitle ?? _t({
|
|
3316
|
-
id: "bAiEJP",
|
|
3317
|
-
message: "Select Image"
|
|
3318
|
-
});
|
|
3319
|
-
const queryClient = useQueryClient();
|
|
3320
|
-
const [selectedItem, setSelectedItem] = React.useState(null);
|
|
3321
|
-
const [activeProvider, setActiveProvider] = React.useState("local");
|
|
3322
|
-
const [searchQuery, setSearchQuery] = React.useState("");
|
|
3323
|
-
const fileInputRef = React.useRef(null);
|
|
3324
|
-
const [imageUrl, setImageUrl] = React.useState("");
|
|
3325
|
-
const [isProbing, setIsProbing] = React.useState(false);
|
|
3326
|
-
const [urlError, setUrlError] = React.useState(null);
|
|
3327
|
-
const [providerDimensions, setProviderDimensions] = React.useState({});
|
|
3328
|
-
React.useEffect(() => {
|
|
3329
|
-
if (open) {
|
|
3330
|
-
setSelectedItem(null);
|
|
3331
|
-
setActiveProvider("local");
|
|
3332
|
-
setSearchQuery("");
|
|
3333
|
-
setImageUrl("");
|
|
3334
|
-
setUrlError(null);
|
|
3335
|
-
setUploadError(null);
|
|
3336
|
-
setProviderDimensions({});
|
|
3337
|
-
}
|
|
3338
|
-
}, [open]);
|
|
3339
|
-
const { data: providers } = useQuery({
|
|
3340
|
-
queryKey: ["media-providers"],
|
|
3341
|
-
queryFn: fetchMediaProviders,
|
|
3342
|
-
enabled: open,
|
|
3343
|
-
placeholderData: []
|
|
3344
|
-
});
|
|
3345
|
-
const activeProviderInfo = React.useMemo(() => {
|
|
3346
|
-
if (activeProvider === "local") return {
|
|
3347
|
-
id: "local",
|
|
3348
|
-
name: "Library",
|
|
3349
|
-
icon: void 0,
|
|
3350
|
-
capabilities: {
|
|
3351
|
-
browse: true,
|
|
3352
|
-
search: false,
|
|
3353
|
-
upload: true,
|
|
3354
|
-
delete: true
|
|
3355
|
-
}
|
|
3356
|
-
};
|
|
3357
|
-
return providers?.find((p) => p.id === activeProvider);
|
|
3358
|
-
}, [activeProvider, providers]);
|
|
3359
|
-
const { data: localData, isLoading: localLoading } = useQuery({
|
|
3360
|
-
queryKey: ["media", mimeTypeFilter],
|
|
3361
|
-
queryFn: () => fetchMediaList({
|
|
3362
|
-
mimeType: mimeTypeFilter,
|
|
3363
|
-
limit: 50
|
|
3364
|
-
}),
|
|
3365
|
-
enabled: open && activeProvider === "local"
|
|
2867
|
+
const activeProviderInfo = React.useMemo(() => {
|
|
2868
|
+
if (activeProvider === "local") return {
|
|
2869
|
+
id: "local",
|
|
2870
|
+
name: "Library",
|
|
2871
|
+
icon: void 0,
|
|
2872
|
+
capabilities: {
|
|
2873
|
+
browse: true,
|
|
2874
|
+
search: false,
|
|
2875
|
+
upload: true,
|
|
2876
|
+
delete: true
|
|
2877
|
+
}
|
|
2878
|
+
};
|
|
2879
|
+
return providers?.find((p) => p.id === activeProvider);
|
|
2880
|
+
}, [activeProvider, providers]);
|
|
2881
|
+
const { data: localData, isLoading: localLoading } = useQuery({
|
|
2882
|
+
queryKey: ["media", mimeTypeFilter],
|
|
2883
|
+
queryFn: () => fetchMediaList({
|
|
2884
|
+
mimeType: mimeTypeFilter,
|
|
2885
|
+
limit: 50
|
|
2886
|
+
}),
|
|
2887
|
+
enabled: open && activeProvider === "local"
|
|
3366
2888
|
});
|
|
3367
2889
|
const { data: providerData, isLoading: providerLoading } = useQuery({
|
|
3368
2890
|
queryKey: [
|
|
@@ -3813,180 +3335,846 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
|
|
|
3813
3335
|
}
|
|
3814
3336
|
}, item.id))
|
|
3815
3337
|
})
|
|
3816
|
-
}),
|
|
3817
|
-
/* @__PURE__ */ jsxs("div", {
|
|
3818
|
-
className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 border-t pt-4",
|
|
3819
|
-
children: [
|
|
3820
|
-
/* @__PURE__ */ jsx("div", {
|
|
3821
|
-
className: "flex-1 text-sm text-kumo-subtle",
|
|
3822
|
-
children: selectedItem && /* @__PURE__ */ jsxs("span", { children: [
|
|
3823
|
-
_t({
|
|
3824
|
-
id: "SVRHAR",
|
|
3825
|
-
message: "Selected:"
|
|
3826
|
-
}),
|
|
3827
|
-
" ",
|
|
3828
|
-
/* @__PURE__ */ jsx("strong", { children: selectedItem.item.filename }),
|
|
3829
|
-
selectedItem.providerId !== "local" && /* @__PURE__ */ jsx("span", {
|
|
3830
|
-
className: "ms-2 text-xs",
|
|
3831
|
-
children: _t({
|
|
3832
|
-
id: "qjriNt",
|
|
3833
|
-
message: "(from {0})",
|
|
3834
|
-
values: { 0: providers?.find((p) => p.id === selectedItem.providerId)?.name }
|
|
3835
|
-
})
|
|
3836
|
-
})
|
|
3837
|
-
] })
|
|
3838
|
-
}),
|
|
3839
|
-
/* @__PURE__ */ jsx(Button, {
|
|
3840
|
-
variant: "outline",
|
|
3841
|
-
onClick: handleClose,
|
|
3842
|
-
children: _t({
|
|
3843
|
-
id: "dEgA5A",
|
|
3844
|
-
message: "Cancel"
|
|
3845
|
-
})
|
|
3846
|
-
}),
|
|
3847
|
-
/* @__PURE__ */ jsx(Button, {
|
|
3848
|
-
onClick: handleConfirm,
|
|
3849
|
-
disabled: !selectedItem,
|
|
3850
|
-
children: _t({
|
|
3851
|
-
id: "S8Yqbl",
|
|
3852
|
-
message: "Insert"
|
|
3853
|
-
})
|
|
3854
|
-
})
|
|
3855
|
-
]
|
|
3338
|
+
}),
|
|
3339
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3340
|
+
className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 border-t pt-4",
|
|
3341
|
+
children: [
|
|
3342
|
+
/* @__PURE__ */ jsx("div", {
|
|
3343
|
+
className: "flex-1 text-sm text-kumo-subtle",
|
|
3344
|
+
children: selectedItem && /* @__PURE__ */ jsxs("span", { children: [
|
|
3345
|
+
_t({
|
|
3346
|
+
id: "SVRHAR",
|
|
3347
|
+
message: "Selected:"
|
|
3348
|
+
}),
|
|
3349
|
+
" ",
|
|
3350
|
+
/* @__PURE__ */ jsx("strong", { children: selectedItem.item.filename }),
|
|
3351
|
+
selectedItem.providerId !== "local" && /* @__PURE__ */ jsx("span", {
|
|
3352
|
+
className: "ms-2 text-xs",
|
|
3353
|
+
children: _t({
|
|
3354
|
+
id: "qjriNt",
|
|
3355
|
+
message: "(from {0})",
|
|
3356
|
+
values: { 0: providers?.find((p) => p.id === selectedItem.providerId)?.name }
|
|
3357
|
+
})
|
|
3358
|
+
})
|
|
3359
|
+
] })
|
|
3360
|
+
}),
|
|
3361
|
+
/* @__PURE__ */ jsx(Button, {
|
|
3362
|
+
variant: "outline",
|
|
3363
|
+
onClick: handleClose,
|
|
3364
|
+
children: _t({
|
|
3365
|
+
id: "dEgA5A",
|
|
3366
|
+
message: "Cancel"
|
|
3367
|
+
})
|
|
3368
|
+
}),
|
|
3369
|
+
/* @__PURE__ */ jsx(Button, {
|
|
3370
|
+
onClick: handleConfirm,
|
|
3371
|
+
disabled: !selectedItem,
|
|
3372
|
+
children: _t({
|
|
3373
|
+
id: "S8Yqbl",
|
|
3374
|
+
message: "Insert"
|
|
3375
|
+
})
|
|
3376
|
+
})
|
|
3377
|
+
]
|
|
3378
|
+
})
|
|
3379
|
+
]
|
|
3380
|
+
})
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
function MediaPickerItem({ item, selected, onClick, onDoubleClick, onDimensionsDetected }) {
|
|
3384
|
+
const { _: _t2 } = useLingui();
|
|
3385
|
+
const isImage = item.mimeType.startsWith("image/");
|
|
3386
|
+
const needsDimensions = isImage && (!item.width || !item.height);
|
|
3387
|
+
const handleImageLoad = React.useCallback((e) => {
|
|
3388
|
+
if (needsDimensions && onDimensionsDetected) {
|
|
3389
|
+
const img = e.currentTarget;
|
|
3390
|
+
if (img.naturalWidth && img.naturalHeight) onDimensionsDetected(item.id, img.naturalWidth, img.naturalHeight);
|
|
3391
|
+
}
|
|
3392
|
+
}, [
|
|
3393
|
+
needsDimensions,
|
|
3394
|
+
onDimensionsDetected,
|
|
3395
|
+
item.id
|
|
3396
|
+
]);
|
|
3397
|
+
return /* @__PURE__ */ jsx("li", {
|
|
3398
|
+
role: "option",
|
|
3399
|
+
"aria-selected": selected,
|
|
3400
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
3401
|
+
type: "button",
|
|
3402
|
+
className: cn("relative aspect-square w-full rounded-lg border-2 overflow-hidden transition-all", "hover:border-kumo-brand/50 focus:outline-none focus:ring-2 focus:ring-kumo-ring", selected ? "border-kumo-brand ring-2 ring-kumo-brand/20" : "border-transparent"),
|
|
3403
|
+
onClick,
|
|
3404
|
+
onDoubleClick,
|
|
3405
|
+
"aria-label": _t2({
|
|
3406
|
+
id: "qSiUsc",
|
|
3407
|
+
message: "{0}{1}",
|
|
3408
|
+
values: {
|
|
3409
|
+
0: item.filename,
|
|
3410
|
+
1: selected ? _t2({
|
|
3411
|
+
id: "QbxreS",
|
|
3412
|
+
message: " (selected)"
|
|
3413
|
+
}) : ""
|
|
3414
|
+
}
|
|
3415
|
+
}),
|
|
3416
|
+
children: [
|
|
3417
|
+
isImage ? /* @__PURE__ */ jsx("img", {
|
|
3418
|
+
src: item.url,
|
|
3419
|
+
alt: "",
|
|
3420
|
+
className: "h-full w-full object-cover",
|
|
3421
|
+
onLoad: handleImageLoad
|
|
3422
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
3423
|
+
className: "flex h-full w-full items-center justify-center bg-kumo-tint",
|
|
3424
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3425
|
+
className: "text-3xl",
|
|
3426
|
+
"aria-hidden": "true",
|
|
3427
|
+
children: getFileIcon(item.mimeType)
|
|
3428
|
+
})
|
|
3429
|
+
}),
|
|
3430
|
+
selected && /* @__PURE__ */ jsx("div", {
|
|
3431
|
+
className: "absolute inset-0 bg-kumo-brand/20 flex items-center justify-center",
|
|
3432
|
+
"aria-hidden": "true",
|
|
3433
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
3434
|
+
className: "bg-kumo-brand text-white rounded-full p-1",
|
|
3435
|
+
children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })
|
|
3436
|
+
})
|
|
3437
|
+
}),
|
|
3438
|
+
/* @__PURE__ */ jsx("div", {
|
|
3439
|
+
className: "absolute bottom-0 start-0 end-0 bg-gradient-to-t from-black/60 to-transparent p-2",
|
|
3440
|
+
"aria-hidden": "true",
|
|
3441
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
3442
|
+
className: "text-xs text-white truncate",
|
|
3443
|
+
children: item.filename
|
|
3444
|
+
})
|
|
3445
|
+
})
|
|
3446
|
+
]
|
|
3447
|
+
})
|
|
3448
|
+
});
|
|
3449
|
+
}
|
|
3450
|
+
function ProviderMediaItem({ item, selected, onClick, onDoubleClick, onDimensionsLoaded }) {
|
|
3451
|
+
const { _: _t3 } = useLingui();
|
|
3452
|
+
const isImage = item.mimeType.startsWith("image/");
|
|
3453
|
+
const needsDimensions = isImage && (!item.width || !item.height);
|
|
3454
|
+
const handleImageLoad = React.useCallback((e) => {
|
|
3455
|
+
if (needsDimensions && onDimensionsLoaded) {
|
|
3456
|
+
const img = e.currentTarget;
|
|
3457
|
+
if (img.naturalWidth && img.naturalHeight) onDimensionsLoaded(img.naturalWidth, img.naturalHeight);
|
|
3458
|
+
}
|
|
3459
|
+
}, [needsDimensions, onDimensionsLoaded]);
|
|
3460
|
+
return /* @__PURE__ */ jsx("li", {
|
|
3461
|
+
role: "option",
|
|
3462
|
+
"aria-selected": selected,
|
|
3463
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
3464
|
+
type: "button",
|
|
3465
|
+
className: cn("relative aspect-square w-full rounded-lg border-2 overflow-hidden transition-all", "hover:border-kumo-brand/50 focus:outline-none focus:ring-2 focus:ring-kumo-ring", selected ? "border-kumo-brand ring-2 ring-kumo-brand/20" : "border-transparent"),
|
|
3466
|
+
onClick,
|
|
3467
|
+
onDoubleClick,
|
|
3468
|
+
"aria-label": _t3({
|
|
3469
|
+
id: "qSiUsc",
|
|
3470
|
+
message: "{0}{1}",
|
|
3471
|
+
values: {
|
|
3472
|
+
0: item.filename,
|
|
3473
|
+
1: selected ? _t3({
|
|
3474
|
+
id: "QbxreS",
|
|
3475
|
+
message: " (selected)"
|
|
3476
|
+
}) : ""
|
|
3477
|
+
}
|
|
3478
|
+
}),
|
|
3479
|
+
children: [
|
|
3480
|
+
isImage && item.previewUrl ? /* @__PURE__ */ jsx("img", {
|
|
3481
|
+
src: item.previewUrl,
|
|
3482
|
+
alt: "",
|
|
3483
|
+
className: "h-full w-full object-cover",
|
|
3484
|
+
onLoad: handleImageLoad
|
|
3485
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
3486
|
+
className: "flex h-full w-full items-center justify-center bg-kumo-tint",
|
|
3487
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3488
|
+
className: "text-3xl",
|
|
3489
|
+
"aria-hidden": "true",
|
|
3490
|
+
children: getFileIcon(item.mimeType)
|
|
3491
|
+
})
|
|
3492
|
+
}),
|
|
3493
|
+
selected && /* @__PURE__ */ jsx("div", {
|
|
3494
|
+
className: "absolute inset-0 bg-kumo-brand/20 flex items-center justify-center",
|
|
3495
|
+
"aria-hidden": "true",
|
|
3496
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
3497
|
+
className: "bg-kumo-brand text-white rounded-full p-1",
|
|
3498
|
+
children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })
|
|
3499
|
+
})
|
|
3500
|
+
}),
|
|
3501
|
+
/* @__PURE__ */ jsx("div", {
|
|
3502
|
+
className: "absolute bottom-0 start-0 end-0 bg-gradient-to-t from-black/60 to-transparent p-2",
|
|
3503
|
+
"aria-hidden": "true",
|
|
3504
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
3505
|
+
className: "text-xs text-white truncate",
|
|
3506
|
+
children: item.filename
|
|
3507
|
+
})
|
|
3508
|
+
})
|
|
3509
|
+
]
|
|
3510
|
+
})
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
//#endregion
|
|
3515
|
+
//#region src/components/BlockKitMediaPickerField.tsx
|
|
3516
|
+
/**
|
|
3517
|
+
* Shared media_picker BlockKit element renderer used by `BlockKitFieldWidget`
|
|
3518
|
+
* (sandboxed plugin field widgets) and the `BlockKitField` switch inside
|
|
3519
|
+
* `PortableTextEditor` (plugin block forms).
|
|
3520
|
+
*
|
|
3521
|
+
* The stored value is the asset URL string, so values are interchangeable
|
|
3522
|
+
* with `text_input`. Existing arbitrary URLs are tolerated but only previewed
|
|
3523
|
+
* when they pass scheme/path safety checks.
|
|
3524
|
+
*/
|
|
3525
|
+
function BlockKitMediaPickerField({ actionId, label, placeholder, mimeTypeFilter, value, onChange }) {
|
|
3526
|
+
const { _: _t } = useLingui();
|
|
3527
|
+
const [pickerOpen, setPickerOpen] = React.useState(false);
|
|
3528
|
+
const url = typeof value === "string" && value.length > 0 ? value : "";
|
|
3529
|
+
const filter = mimeTypeFilter ?? "image/";
|
|
3530
|
+
const canPreview = isSafePreviewUrl(url);
|
|
3531
|
+
const handleSelect = (item) => {
|
|
3532
|
+
const isLocalMedia = item.provider === "local" || !!item.storageKey;
|
|
3533
|
+
const localKey = item.storageKey || item.id;
|
|
3534
|
+
const nextUrl = isLocalMedia && localKey ? `/_emdash/api/media/file/${localKey}` : item.url;
|
|
3535
|
+
if (!nextUrl) return;
|
|
3536
|
+
onChange(actionId, nextUrl);
|
|
3537
|
+
};
|
|
3538
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
3539
|
+
/* @__PURE__ */ jsx("label", {
|
|
3540
|
+
className: "text-sm font-medium mb-1.5 block",
|
|
3541
|
+
children: label
|
|
3542
|
+
}),
|
|
3543
|
+
canPreview ? /* @__PURE__ */ jsxs("div", {
|
|
3544
|
+
className: "relative group",
|
|
3545
|
+
children: [/* @__PURE__ */ jsx("img", {
|
|
3546
|
+
src: url,
|
|
3547
|
+
alt: "",
|
|
3548
|
+
className: "max-h-40 w-full rounded-md border border-kumo-line object-contain bg-kumo-muted",
|
|
3549
|
+
referrerPolicy: "no-referrer",
|
|
3550
|
+
loading: "lazy"
|
|
3551
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
3552
|
+
className: "absolute top-2 end-2 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto group-focus-within:opacity-100 group-focus-within:pointer-events-auto transition-opacity flex gap-1",
|
|
3553
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
3554
|
+
type: "button",
|
|
3555
|
+
size: "sm",
|
|
3556
|
+
variant: "secondary",
|
|
3557
|
+
onClick: () => setPickerOpen(true),
|
|
3558
|
+
children: _t({
|
|
3559
|
+
id: "o+XJ9D",
|
|
3560
|
+
message: "Change"
|
|
3561
|
+
})
|
|
3562
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
3563
|
+
type: "button",
|
|
3564
|
+
shape: "square",
|
|
3565
|
+
variant: "destructive",
|
|
3566
|
+
className: "h-8 w-8",
|
|
3567
|
+
onClick: () => onChange(actionId, ""),
|
|
3568
|
+
"aria-label": _t({
|
|
3569
|
+
id: "t/YqKh",
|
|
3570
|
+
message: "Remove"
|
|
3571
|
+
}),
|
|
3572
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
3573
|
+
})]
|
|
3574
|
+
})]
|
|
3575
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
3576
|
+
type: "button",
|
|
3577
|
+
variant: "outline",
|
|
3578
|
+
className: "w-full h-24 border-dashed",
|
|
3579
|
+
onClick: () => setPickerOpen(true),
|
|
3580
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
3581
|
+
className: "flex flex-col items-center gap-1.5 text-kumo-subtle",
|
|
3582
|
+
children: [/* @__PURE__ */ jsx(Image$1, { className: "h-6 w-6" }), /* @__PURE__ */ jsx("span", {
|
|
3583
|
+
className: "text-sm",
|
|
3584
|
+
children: placeholder ?? _t({
|
|
3585
|
+
id: "1Tq98c",
|
|
3586
|
+
message: "Select media"
|
|
3587
|
+
})
|
|
3588
|
+
})]
|
|
3589
|
+
})
|
|
3590
|
+
}),
|
|
3591
|
+
/* @__PURE__ */ jsx(MediaPickerModal, {
|
|
3592
|
+
open: pickerOpen,
|
|
3593
|
+
onOpenChange: setPickerOpen,
|
|
3594
|
+
onSelect: handleSelect,
|
|
3595
|
+
mimeTypeFilter: filter,
|
|
3596
|
+
title: _t({
|
|
3597
|
+
id: "pov/PW",
|
|
3598
|
+
message: "Select {label}",
|
|
3599
|
+
values: { label }
|
|
3600
|
+
})
|
|
3601
|
+
})
|
|
3602
|
+
] });
|
|
3603
|
+
}
|
|
3604
|
+
const HAS_SCHEME_RE = /^[a-z][a-z0-9+.-]*:/i;
|
|
3605
|
+
/**
|
|
3606
|
+
* Returns true when `url` is safe to preview via `<img src={url}>`:
|
|
3607
|
+
* - Same-origin relative path starting with `/` (but not `//`)
|
|
3608
|
+
* - External `http://` or `https://` URL
|
|
3609
|
+
*
|
|
3610
|
+
* Rejects `javascript:`, `data:`, protocol-relative `//host`, and other
|
|
3611
|
+
* schemes whose preview could leak credentials or trigger surprises.
|
|
3612
|
+
*/
|
|
3613
|
+
function isSafePreviewUrl(url) {
|
|
3614
|
+
if (!url) return false;
|
|
3615
|
+
if (HAS_SCHEME_RE.test(url)) return isSafeUrl$1(url);
|
|
3616
|
+
return url.startsWith("/") && !url.startsWith("//");
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
//#endregion
|
|
3620
|
+
//#region src/components/BlockKitFieldWidget.tsx
|
|
3621
|
+
/**
|
|
3622
|
+
* Renders Block Kit elements as a field widget for sandboxed plugins.
|
|
3623
|
+
* Decomposes a JSON value into per-element values keyed by action_id,
|
|
3624
|
+
* and recomposes on change.
|
|
3625
|
+
*/
|
|
3626
|
+
function BlockKitFieldWidget({ label, elements, value, onChange }) {
|
|
3627
|
+
const obj = value && typeof value === "object" ? value : {};
|
|
3628
|
+
const objRef = React.useRef(obj);
|
|
3629
|
+
objRef.current = obj;
|
|
3630
|
+
const handleElementChange = React.useCallback((actionId, elementValue) => {
|
|
3631
|
+
onChange({
|
|
3632
|
+
...objRef.current,
|
|
3633
|
+
[actionId]: elementValue
|
|
3634
|
+
});
|
|
3635
|
+
}, [onChange]);
|
|
3636
|
+
const validElements = elements.filter((el) => el.action_id);
|
|
3637
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
|
|
3638
|
+
className: "text-sm font-medium leading-none",
|
|
3639
|
+
children: label
|
|
3640
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
3641
|
+
className: "mt-2 space-y-3",
|
|
3642
|
+
children: validElements.map((el) => /* @__PURE__ */ jsx(BlockKitFieldElement, {
|
|
3643
|
+
element: el,
|
|
3644
|
+
value: obj[el.action_id],
|
|
3645
|
+
onChange: handleElementChange
|
|
3646
|
+
}, el.action_id))
|
|
3647
|
+
})] });
|
|
3648
|
+
}
|
|
3649
|
+
function BlockKitFieldElement({ element, value, onChange }) {
|
|
3650
|
+
switch (element.type) {
|
|
3651
|
+
case "text_input": return /* @__PURE__ */ jsx(Input, {
|
|
3652
|
+
label: element.label,
|
|
3653
|
+
placeholder: element.placeholder,
|
|
3654
|
+
value: typeof value === "string" ? value : "",
|
|
3655
|
+
onChange: (e) => onChange(element.action_id, e.target.value)
|
|
3656
|
+
});
|
|
3657
|
+
case "number_input": return /* @__PURE__ */ jsx(Input, {
|
|
3658
|
+
label: element.label,
|
|
3659
|
+
type: "number",
|
|
3660
|
+
value: typeof value === "number" ? String(value) : "",
|
|
3661
|
+
onChange: (e) => {
|
|
3662
|
+
const n = Number(e.target.value);
|
|
3663
|
+
onChange(element.action_id, e.target.value && Number.isFinite(n) ? n : void 0);
|
|
3664
|
+
}
|
|
3665
|
+
});
|
|
3666
|
+
case "toggle": return /* @__PURE__ */ jsx(Switch, {
|
|
3667
|
+
label: element.label,
|
|
3668
|
+
checked: !!value,
|
|
3669
|
+
onCheckedChange: (checked) => onChange(element.action_id, checked)
|
|
3670
|
+
});
|
|
3671
|
+
case "select": {
|
|
3672
|
+
const options = Array.isArray(element.options) ? element.options : [];
|
|
3673
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
3674
|
+
className: "text-sm font-medium mb-1.5 block",
|
|
3675
|
+
children: element.label
|
|
3676
|
+
}), /* @__PURE__ */ jsxs("select", {
|
|
3677
|
+
className: "flex w-full rounded-md border border-kumo-line bg-transparent px-3 py-2 text-sm",
|
|
3678
|
+
value: typeof value === "string" ? value : "",
|
|
3679
|
+
onChange: (e) => onChange(element.action_id, e.target.value),
|
|
3680
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
3681
|
+
value: "",
|
|
3682
|
+
children: "Select..."
|
|
3683
|
+
}), options.map((opt) => /* @__PURE__ */ jsx("option", {
|
|
3684
|
+
value: opt.value,
|
|
3685
|
+
children: opt.label
|
|
3686
|
+
}, opt.value))]
|
|
3687
|
+
})] });
|
|
3688
|
+
}
|
|
3689
|
+
case "media_picker": return /* @__PURE__ */ jsx(BlockKitMediaPickerField, {
|
|
3690
|
+
actionId: element.action_id,
|
|
3691
|
+
label: element.label,
|
|
3692
|
+
placeholder: element.placeholder,
|
|
3693
|
+
mimeTypeFilter: element.mime_type_filter,
|
|
3694
|
+
value,
|
|
3695
|
+
onChange
|
|
3696
|
+
});
|
|
3697
|
+
default: return /* @__PURE__ */ jsxs("div", {
|
|
3698
|
+
className: "text-sm text-kumo-subtle",
|
|
3699
|
+
children: ["Unsupported widget element type: ", element.type]
|
|
3700
|
+
});
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
//#endregion
|
|
3705
|
+
//#region src/components/editor/DocumentOutline.tsx
|
|
3706
|
+
/**
|
|
3707
|
+
* Document Outline
|
|
3708
|
+
*
|
|
3709
|
+
* Displays a tree structure of headings from the TipTap editor.
|
|
3710
|
+
* - Shows H1 at root, H2 indented, H3 further indented
|
|
3711
|
+
* - Click-to-navigate to heading position
|
|
3712
|
+
* - Highlights the current section based on cursor position
|
|
3713
|
+
*/
|
|
3714
|
+
function getIndentClass(level) {
|
|
3715
|
+
switch (level) {
|
|
3716
|
+
case 1: return "ps-0";
|
|
3717
|
+
case 2: return "ps-4";
|
|
3718
|
+
case 3: return "ps-8";
|
|
3719
|
+
default: return "ps-0";
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
function getTextClass(level) {
|
|
3723
|
+
switch (level) {
|
|
3724
|
+
case 1: return "font-medium";
|
|
3725
|
+
case 2: return "font-normal";
|
|
3726
|
+
case 3: return "font-normal text-kumo-subtle";
|
|
3727
|
+
default: return "font-normal";
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
/**
|
|
3731
|
+
* Extract headings from the TipTap editor document
|
|
3732
|
+
*/
|
|
3733
|
+
function extractHeadings(editor) {
|
|
3734
|
+
if (!editor) return [];
|
|
3735
|
+
const headings = [];
|
|
3736
|
+
const doc = editor.state.doc;
|
|
3737
|
+
let key = 0;
|
|
3738
|
+
doc.descendants((node, pos) => {
|
|
3739
|
+
if (node.type.name === "heading") {
|
|
3740
|
+
const rawLevel = node.attrs.level;
|
|
3741
|
+
const level = typeof rawLevel === "number" ? rawLevel : 1;
|
|
3742
|
+
const text = node.textContent || "";
|
|
3743
|
+
if (text.trim()) headings.push({
|
|
3744
|
+
level,
|
|
3745
|
+
text,
|
|
3746
|
+
pos,
|
|
3747
|
+
key: `heading-${key++}`
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
3750
|
+
});
|
|
3751
|
+
return headings;
|
|
3752
|
+
}
|
|
3753
|
+
/**
|
|
3754
|
+
* Find the current heading based on cursor position
|
|
3755
|
+
*/
|
|
3756
|
+
function findCurrentHeading(headings, cursorPos) {
|
|
3757
|
+
if (headings.length === 0) return null;
|
|
3758
|
+
let current = null;
|
|
3759
|
+
for (const heading of headings) if (heading.pos <= cursorPos) current = heading;
|
|
3760
|
+
else break;
|
|
3761
|
+
return current;
|
|
3762
|
+
}
|
|
3763
|
+
/**
|
|
3764
|
+
* Document outline component showing heading tree structure
|
|
3765
|
+
*/
|
|
3766
|
+
function DocumentOutline({ editor, className }) {
|
|
3767
|
+
const { _: _t } = useLingui();
|
|
3768
|
+
const [isExpanded, setIsExpanded] = React.useState(true);
|
|
3769
|
+
const [headings, setHeadings] = React.useState([]);
|
|
3770
|
+
const [currentPos, setCurrentPos] = React.useState(0);
|
|
3771
|
+
React.useEffect(() => {
|
|
3772
|
+
if (!editor) return;
|
|
3773
|
+
const updateHeadings = () => {
|
|
3774
|
+
setHeadings(extractHeadings(editor));
|
|
3775
|
+
};
|
|
3776
|
+
updateHeadings();
|
|
3777
|
+
editor.on("update", updateHeadings);
|
|
3778
|
+
return () => {
|
|
3779
|
+
editor.off("update", updateHeadings);
|
|
3780
|
+
};
|
|
3781
|
+
}, [editor]);
|
|
3782
|
+
React.useEffect(() => {
|
|
3783
|
+
if (!editor) return;
|
|
3784
|
+
const updatePosition = () => {
|
|
3785
|
+
const { from } = editor.state.selection;
|
|
3786
|
+
setCurrentPos(from);
|
|
3787
|
+
};
|
|
3788
|
+
updatePosition();
|
|
3789
|
+
editor.on("selectionUpdate", updatePosition);
|
|
3790
|
+
return () => {
|
|
3791
|
+
editor.off("selectionUpdate", updatePosition);
|
|
3792
|
+
};
|
|
3793
|
+
}, [editor]);
|
|
3794
|
+
const currentHeading = findCurrentHeading(headings, currentPos);
|
|
3795
|
+
const handleHeadingClick = (heading) => {
|
|
3796
|
+
if (!editor) return;
|
|
3797
|
+
editor.chain().focus().setTextSelection(heading.pos).scrollIntoView().run();
|
|
3798
|
+
};
|
|
3799
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3800
|
+
className: cn("space-y-2", className),
|
|
3801
|
+
children: [/* @__PURE__ */ jsxs(Button, {
|
|
3802
|
+
variant: "ghost",
|
|
3803
|
+
size: "sm",
|
|
3804
|
+
className: "w-full justify-between px-2 h-8",
|
|
3805
|
+
onClick: () => setIsExpanded(!isExpanded),
|
|
3806
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
3807
|
+
className: "flex items-center gap-2",
|
|
3808
|
+
children: [/* @__PURE__ */ jsx(List, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
|
|
3809
|
+
className: "font-semibold",
|
|
3810
|
+
children: _t({
|
|
3811
|
+
id: "dD7NPy",
|
|
3812
|
+
message: "Outline"
|
|
3813
|
+
})
|
|
3814
|
+
})]
|
|
3815
|
+
}), isExpanded ? /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(CaretNext, { className: "h-4 w-4" })]
|
|
3816
|
+
}), isExpanded && /* @__PURE__ */ jsx("div", {
|
|
3817
|
+
className: "space-y-0.5",
|
|
3818
|
+
children: headings.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
3819
|
+
className: "text-sm text-kumo-subtle px-2 py-1",
|
|
3820
|
+
children: _t({
|
|
3821
|
+
id: "MMd//L",
|
|
3822
|
+
message: "No headings in document"
|
|
3823
|
+
})
|
|
3824
|
+
}) : headings.map((heading) => {
|
|
3825
|
+
const isCurrent = currentHeading?.key === heading.key;
|
|
3826
|
+
return /* @__PURE__ */ jsx("button", {
|
|
3827
|
+
type: "button",
|
|
3828
|
+
onClick: () => handleHeadingClick(heading),
|
|
3829
|
+
className: cn("w-full text-start px-2 py-1 text-sm rounded transition-colors", "hover:bg-kumo-tint/50 cursor-pointer", "truncate", getIndentClass(heading.level), getTextClass(heading.level), isCurrent && "bg-kumo-tint text-kumo-default"),
|
|
3830
|
+
title: heading.text,
|
|
3831
|
+
children: heading.text
|
|
3832
|
+
}, heading.key);
|
|
3833
|
+
})
|
|
3834
|
+
})]
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
//#endregion
|
|
3839
|
+
//#region src/components/PluginFieldErrorBoundary.tsx
|
|
3840
|
+
/**
|
|
3841
|
+
* Error boundary that wraps trusted plugin field widgets.
|
|
3842
|
+
* On render error, shows a warning with a retry button instead of crashing the editor.
|
|
3843
|
+
*/
|
|
3844
|
+
var PluginFieldErrorBoundary = class extends React.Component {
|
|
3845
|
+
constructor(props) {
|
|
3846
|
+
super(props);
|
|
3847
|
+
this.state = { hasError: false };
|
|
3848
|
+
}
|
|
3849
|
+
static getDerivedStateFromError(error) {
|
|
3850
|
+
return {
|
|
3851
|
+
hasError: true,
|
|
3852
|
+
error
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
render() {
|
|
3856
|
+
if (this.state.hasError) return /* @__PURE__ */ jsxs("div", {
|
|
3857
|
+
className: "rounded-md border border-kumo-danger/50 bg-kumo-danger/5 p-3",
|
|
3858
|
+
children: [
|
|
3859
|
+
/* @__PURE__ */ jsx("p", {
|
|
3860
|
+
className: "text-sm font-medium text-kumo-danger",
|
|
3861
|
+
children: "Plugin widget error"
|
|
3862
|
+
}),
|
|
3863
|
+
/* @__PURE__ */ jsx("p", {
|
|
3864
|
+
className: "mt-1 text-xs text-kumo-subtle",
|
|
3865
|
+
children: this.state.error?.message || "The plugin field widget failed to render."
|
|
3866
|
+
}),
|
|
3867
|
+
/* @__PURE__ */ jsx("button", {
|
|
3868
|
+
type: "button",
|
|
3869
|
+
className: "mt-2 text-xs font-medium text-kumo-brand underline",
|
|
3870
|
+
onClick: () => this.setState({
|
|
3871
|
+
hasError: false,
|
|
3872
|
+
error: void 0
|
|
3873
|
+
}),
|
|
3874
|
+
children: "Retry"
|
|
3875
|
+
})
|
|
3876
|
+
]
|
|
3877
|
+
});
|
|
3878
|
+
return this.props.children;
|
|
3879
|
+
}
|
|
3880
|
+
};
|
|
3881
|
+
|
|
3882
|
+
//#endregion
|
|
3883
|
+
//#region src/components/RepeaterField.tsx
|
|
3884
|
+
/**
|
|
3885
|
+
* RepeaterField — renders a list of repeating sub-field groups in the content editor.
|
|
3886
|
+
*
|
|
3887
|
+
* Each item is a collapsible card containing the defined sub-fields.
|
|
3888
|
+
* Items can be added, removed, and reordered via drag-and-drop.
|
|
3889
|
+
*/
|
|
3890
|
+
function ensureKeys$1(items) {
|
|
3891
|
+
return items.map((item, i) => {
|
|
3892
|
+
const obj = typeof item === "object" && item !== null ? item : {};
|
|
3893
|
+
return {
|
|
3894
|
+
...obj,
|
|
3895
|
+
_key: obj._key || `item-${i}-${Date.now()}`
|
|
3896
|
+
};
|
|
3897
|
+
});
|
|
3898
|
+
}
|
|
3899
|
+
function stripKeys$1(items) {
|
|
3900
|
+
return items.map(({ _key, ...rest }) => rest);
|
|
3901
|
+
}
|
|
3902
|
+
function RepeaterField({ label, id, value, onChange, subFields, minItems = 0, maxItems }) {
|
|
3903
|
+
const { _: _t } = useLingui();
|
|
3904
|
+
const rawItems = Array.isArray(value) ? value : [];
|
|
3905
|
+
const [items, setItems] = React.useState(() => ensureKeys$1(rawItems));
|
|
3906
|
+
const [collapsedItems, setCollapsedItems] = React.useState(/* @__PURE__ */ new Set());
|
|
3907
|
+
React.useEffect(() => {
|
|
3908
|
+
const incoming = Array.isArray(value) ? value : [];
|
|
3909
|
+
setItems((prev) => incoming.map((item, i) => {
|
|
3910
|
+
const obj = typeof item === "object" && item !== null ? item : {};
|
|
3911
|
+
const existingKey = obj._key || prev[i]?._key;
|
|
3912
|
+
return {
|
|
3913
|
+
...obj,
|
|
3914
|
+
_key: existingKey || `item-${i}-${Date.now()}`
|
|
3915
|
+
};
|
|
3916
|
+
}));
|
|
3917
|
+
}, [value]);
|
|
3918
|
+
const emitChange = (updated) => {
|
|
3919
|
+
setItems(updated);
|
|
3920
|
+
onChange(stripKeys$1(updated));
|
|
3921
|
+
};
|
|
3922
|
+
const handleAdd = () => {
|
|
3923
|
+
if (maxItems && items.length >= maxItems) return;
|
|
3924
|
+
const newItem = { _key: `item-${Date.now()}` };
|
|
3925
|
+
for (const sf of subFields) newItem[sf.slug] = sf.type === "boolean" ? false : sf.type === "number" || sf.type === "integer" ? null : "";
|
|
3926
|
+
emitChange([...items, newItem]);
|
|
3927
|
+
};
|
|
3928
|
+
const handleRemove = (key) => {
|
|
3929
|
+
if (items.length <= minItems) return;
|
|
3930
|
+
emitChange(items.filter((item) => item._key !== key));
|
|
3931
|
+
};
|
|
3932
|
+
const handleItemChange = (key, fieldSlug, fieldValue) => {
|
|
3933
|
+
emitChange(items.map((item) => item._key === key ? {
|
|
3934
|
+
...item,
|
|
3935
|
+
[fieldSlug]: fieldValue
|
|
3936
|
+
} : item));
|
|
3937
|
+
};
|
|
3938
|
+
const handleDragEnd = (event) => {
|
|
3939
|
+
const { active, over } = event;
|
|
3940
|
+
if (!over || active.id === over.id) return;
|
|
3941
|
+
const oldIndex = items.findIndex((item) => item._key === active.id);
|
|
3942
|
+
const newIndex = items.findIndex((item) => item._key === over.id);
|
|
3943
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
3944
|
+
emitChange(arrayMove(items, oldIndex, newIndex));
|
|
3945
|
+
};
|
|
3946
|
+
const toggleCollapse = (key) => {
|
|
3947
|
+
setCollapsedItems((prev) => {
|
|
3948
|
+
const next = new Set(prev);
|
|
3949
|
+
if (next.has(key)) next.delete(key);
|
|
3950
|
+
else next.add(key);
|
|
3951
|
+
return next;
|
|
3952
|
+
});
|
|
3953
|
+
};
|
|
3954
|
+
const canAdd = !maxItems || items.length < maxItems;
|
|
3955
|
+
const canRemove = items.length > minItems;
|
|
3956
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3957
|
+
className: "space-y-2",
|
|
3958
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3959
|
+
className: "flex items-center justify-between",
|
|
3960
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
3961
|
+
htmlFor: id,
|
|
3962
|
+
className: "text-sm font-medium",
|
|
3963
|
+
children: [label, items.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
3964
|
+
className: "ms-2 text-kumo-subtle font-normal",
|
|
3965
|
+
children: i18n._({
|
|
3966
|
+
id: "JcH6Wc",
|
|
3967
|
+
message: "{0, plural, one {(# item)} other {(# items)}}",
|
|
3968
|
+
values: { 0: items.length }
|
|
3969
|
+
})
|
|
3970
|
+
})]
|
|
3971
|
+
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
3972
|
+
variant: "outline",
|
|
3973
|
+
size: "sm",
|
|
3974
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
3975
|
+
onClick: handleAdd,
|
|
3976
|
+
children: _t({
|
|
3977
|
+
id: "qgF8ew",
|
|
3978
|
+
message: "Add Item"
|
|
3856
3979
|
})
|
|
3857
|
-
]
|
|
3858
|
-
})
|
|
3980
|
+
})]
|
|
3981
|
+
}), items.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
3982
|
+
className: "border-2 border-dashed rounded-lg p-6 text-center text-kumo-subtle",
|
|
3983
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
3984
|
+
className: "text-sm",
|
|
3985
|
+
children: _t({
|
|
3986
|
+
id: "bWmJ0A",
|
|
3987
|
+
message: "No items yet"
|
|
3988
|
+
})
|
|
3989
|
+
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
3990
|
+
variant: "outline",
|
|
3991
|
+
size: "sm",
|
|
3992
|
+
className: "mt-2",
|
|
3993
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
3994
|
+
onClick: handleAdd,
|
|
3995
|
+
children: _t({
|
|
3996
|
+
id: "924qJl",
|
|
3997
|
+
message: "Add First Item"
|
|
3998
|
+
})
|
|
3999
|
+
})]
|
|
4000
|
+
}) : /* @__PURE__ */ jsx(DndContext, {
|
|
4001
|
+
collisionDetection: closestCenter,
|
|
4002
|
+
onDragEnd: handleDragEnd,
|
|
4003
|
+
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
4004
|
+
items: items.map((item) => item._key),
|
|
4005
|
+
strategy: verticalListSortingStrategy,
|
|
4006
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
4007
|
+
className: "space-y-2",
|
|
4008
|
+
children: items.map((item, index) => /* @__PURE__ */ jsx(SortableRepeaterItem, {
|
|
4009
|
+
item,
|
|
4010
|
+
index,
|
|
4011
|
+
subFields,
|
|
4012
|
+
isCollapsed: collapsedItems.has(item._key),
|
|
4013
|
+
onToggleCollapse: () => toggleCollapse(item._key),
|
|
4014
|
+
onRemove: canRemove ? () => handleRemove(item._key) : void 0,
|
|
4015
|
+
onChange: (fieldSlug, fieldValue) => handleItemChange(item._key, fieldSlug, fieldValue)
|
|
4016
|
+
}, item._key))
|
|
4017
|
+
})
|
|
4018
|
+
})
|
|
4019
|
+
})]
|
|
3859
4020
|
});
|
|
3860
4021
|
}
|
|
3861
|
-
function
|
|
4022
|
+
function SortableRepeaterItem({ item, index, subFields, isCollapsed, onToggleCollapse, onRemove, onChange }) {
|
|
3862
4023
|
const { _: _t2 } = useLingui();
|
|
3863
|
-
const
|
|
3864
|
-
const
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
"
|
|
3878
|
-
children: /* @__PURE__ */ jsxs("
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
onClick,
|
|
3882
|
-
onDoubleClick,
|
|
3883
|
-
"aria-label": _t2({
|
|
3884
|
-
id: "qSiUsc",
|
|
3885
|
-
message: "{0}{1}",
|
|
3886
|
-
values: {
|
|
3887
|
-
0: item.filename,
|
|
3888
|
-
1: selected ? _t2({
|
|
3889
|
-
id: "QbxreS",
|
|
3890
|
-
message: " (selected)"
|
|
3891
|
-
}) : ""
|
|
3892
|
-
}
|
|
3893
|
-
}),
|
|
4024
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: item._key });
|
|
4025
|
+
const style = {
|
|
4026
|
+
transform: CSS.Transform.toString(transform),
|
|
4027
|
+
transition
|
|
4028
|
+
};
|
|
4029
|
+
const summaryField = subFields.find((sf) => sf.type === "string" || sf.type === "text");
|
|
4030
|
+
const summaryLabel = (summaryField ? item[summaryField.slug] || "" : "") || _t2({
|
|
4031
|
+
id: "y/5CE8",
|
|
4032
|
+
message: "Item {0}",
|
|
4033
|
+
values: { 0: index + 1 }
|
|
4034
|
+
});
|
|
4035
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4036
|
+
ref: setNodeRef,
|
|
4037
|
+
style,
|
|
4038
|
+
className: cn("border rounded-lg bg-kumo-base", isDragging && "opacity-50 ring-2 ring-kumo-brand"),
|
|
4039
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
4040
|
+
className: "flex items-center gap-2 px-3 py-2 border-b cursor-pointer",
|
|
4041
|
+
onClick: onToggleCollapse,
|
|
3894
4042
|
children: [
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
3901
|
-
className: "flex h-full w-full items-center justify-center bg-kumo-tint",
|
|
3902
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
3903
|
-
className: "text-3xl",
|
|
3904
|
-
"aria-hidden": "true",
|
|
3905
|
-
children: getFileIcon(item.mimeType)
|
|
3906
|
-
})
|
|
4043
|
+
/* @__PURE__ */ jsx(DotsSixVertical, {
|
|
4044
|
+
className: "h-4 w-4 text-kumo-subtle cursor-grab shrink-0",
|
|
4045
|
+
...attributes,
|
|
4046
|
+
...listeners,
|
|
4047
|
+
onClick: (e) => e.stopPropagation()
|
|
3907
4048
|
}),
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
"
|
|
3911
|
-
children:
|
|
3912
|
-
className: "bg-kumo-brand text-white rounded-full p-1",
|
|
3913
|
-
children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })
|
|
3914
|
-
})
|
|
4049
|
+
isCollapsed ? /* @__PURE__ */ jsx(CaretNext, { className: "h-4 w-4 text-kumo-subtle shrink-0" }) : /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4 text-kumo-subtle shrink-0" }),
|
|
4050
|
+
/* @__PURE__ */ jsx("span", {
|
|
4051
|
+
className: "text-sm font-medium flex-1 truncate",
|
|
4052
|
+
children: summaryLabel
|
|
3915
4053
|
}),
|
|
3916
|
-
/* @__PURE__ */ jsx(
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
}
|
|
4054
|
+
onRemove && /* @__PURE__ */ jsx(Button, {
|
|
4055
|
+
variant: "ghost",
|
|
4056
|
+
shape: "square",
|
|
4057
|
+
onClick: (e) => {
|
|
4058
|
+
e.stopPropagation();
|
|
4059
|
+
onRemove();
|
|
4060
|
+
},
|
|
4061
|
+
"aria-label": _t2({
|
|
4062
|
+
id: "mU3j1b",
|
|
4063
|
+
message: "Remove item {0}",
|
|
4064
|
+
values: { 0: index + 1 }
|
|
4065
|
+
}),
|
|
4066
|
+
children: /* @__PURE__ */ jsx(Trash, { className: "h-3.5 w-3.5 text-kumo-danger" })
|
|
3923
4067
|
})
|
|
3924
4068
|
]
|
|
3925
|
-
})
|
|
4069
|
+
}), !isCollapsed && /* @__PURE__ */ jsx("div", {
|
|
4070
|
+
className: "p-3 space-y-3",
|
|
4071
|
+
children: subFields.map((sf) => /* @__PURE__ */ jsx(SubFieldInput, {
|
|
4072
|
+
subField: sf,
|
|
4073
|
+
value: item[sf.slug],
|
|
4074
|
+
onChange: (v) => onChange(sf.slug, v)
|
|
4075
|
+
}, sf.slug))
|
|
4076
|
+
})]
|
|
3926
4077
|
});
|
|
3927
4078
|
}
|
|
3928
|
-
function
|
|
4079
|
+
function SubFieldInput({ subField, value, onChange }) {
|
|
3929
4080
|
const { _: _t3 } = useLingui();
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
children: [
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
4081
|
+
switch (subField.type) {
|
|
4082
|
+
case "string": return /* @__PURE__ */ jsx(Input, {
|
|
4083
|
+
label: subField.label,
|
|
4084
|
+
value: typeof value === "string" ? value : "",
|
|
4085
|
+
onChange: (e) => onChange(e.target.value),
|
|
4086
|
+
required: subField.required,
|
|
4087
|
+
dir: "auto"
|
|
4088
|
+
});
|
|
4089
|
+
case "text": return /* @__PURE__ */ jsx(InputArea, {
|
|
4090
|
+
label: subField.label,
|
|
4091
|
+
value: typeof value === "string" ? value : "",
|
|
4092
|
+
onChange: (e) => onChange(e.target.value),
|
|
4093
|
+
required: subField.required,
|
|
4094
|
+
rows: 3,
|
|
4095
|
+
dir: "auto"
|
|
4096
|
+
});
|
|
4097
|
+
case "number":
|
|
4098
|
+
case "integer": return /* @__PURE__ */ jsx(Input, {
|
|
4099
|
+
label: subField.label,
|
|
4100
|
+
type: "number",
|
|
4101
|
+
value: typeof value === "number" ? String(value) : "",
|
|
4102
|
+
onChange: (e) => onChange(e.target.value ? Number(e.target.value) : null),
|
|
4103
|
+
required: subField.required,
|
|
4104
|
+
step: subField.type === "integer" ? "1" : "any"
|
|
4105
|
+
});
|
|
4106
|
+
case "boolean": return /* @__PURE__ */ jsxs("label", {
|
|
4107
|
+
className: "flex items-center gap-2",
|
|
4108
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
4109
|
+
type: "checkbox",
|
|
4110
|
+
checked: Boolean(value),
|
|
4111
|
+
onChange: (e) => onChange(e.target.checked)
|
|
4112
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
4113
|
+
className: "text-sm",
|
|
4114
|
+
children: subField.label
|
|
4115
|
+
})]
|
|
4116
|
+
});
|
|
4117
|
+
case "datetime": return /* @__PURE__ */ jsx(Input, {
|
|
4118
|
+
label: subField.label,
|
|
4119
|
+
type: "datetime-local",
|
|
4120
|
+
value: toDatetimeLocalInputValue(value),
|
|
4121
|
+
onChange: (e) => onChange(fromDatetimeLocalInputValue(e.target.value)),
|
|
4122
|
+
required: subField.required
|
|
4123
|
+
});
|
|
4124
|
+
case "select": return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
4125
|
+
className: "text-sm font-medium",
|
|
4126
|
+
children: subField.label
|
|
4127
|
+
}), /* @__PURE__ */ jsxs("select", {
|
|
4128
|
+
className: "w-full mt-1 rounded-md border px-3 py-2 text-sm",
|
|
4129
|
+
value: typeof value === "string" ? value : "",
|
|
4130
|
+
onChange: (e) => onChange(e.target.value),
|
|
4131
|
+
required: subField.required,
|
|
4132
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
4133
|
+
value: "",
|
|
4134
|
+
children: _t3({
|
|
4135
|
+
id: "O/7I0o",
|
|
4136
|
+
message: "Select..."
|
|
3986
4137
|
})
|
|
3987
|
-
|
|
3988
|
-
|
|
4138
|
+
}), subField.options?.map((opt) => /* @__PURE__ */ jsx("option", {
|
|
4139
|
+
value: opt,
|
|
4140
|
+
children: opt
|
|
4141
|
+
}, opt))]
|
|
4142
|
+
})] });
|
|
4143
|
+
default: return /* @__PURE__ */ jsx(Input, {
|
|
4144
|
+
label: subField.label,
|
|
4145
|
+
value: typeof value === "string" ? value : "",
|
|
4146
|
+
onChange: (e) => onChange(e.target.value)
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
//#endregion
|
|
4152
|
+
//#region src/lib/hooks.ts
|
|
4153
|
+
/**
|
|
4154
|
+
* Returns a stable function reference that always calls the latest version
|
|
4155
|
+
* of the provided callback. Useful for event listeners in effects where
|
|
4156
|
+
* you don't want the listener to be torn down and re-added when the
|
|
4157
|
+
* callback identity changes.
|
|
4158
|
+
*/
|
|
4159
|
+
function useStableCallback(callback) {
|
|
4160
|
+
const ref = React.useRef(callback);
|
|
4161
|
+
React.useLayoutEffect(() => {
|
|
4162
|
+
ref.current = callback;
|
|
3989
4163
|
});
|
|
4164
|
+
return React.useCallback((...args) => ref.current(...args), []);
|
|
4165
|
+
}
|
|
4166
|
+
/**
|
|
4167
|
+
* Returns a debounced version of a value that only updates after the
|
|
4168
|
+
* specified delay has elapsed since the last change. Useful for search
|
|
4169
|
+
* inputs that trigger API calls.
|
|
4170
|
+
*/
|
|
4171
|
+
function useDebouncedValue$1(value, delay) {
|
|
4172
|
+
const [debouncedValue, setDebouncedValue] = React.useState(value);
|
|
4173
|
+
React.useEffect(() => {
|
|
4174
|
+
const timer = setTimeout(setDebouncedValue, delay, value);
|
|
4175
|
+
return () => clearTimeout(timer);
|
|
4176
|
+
}, [value, delay]);
|
|
4177
|
+
return debouncedValue;
|
|
3990
4178
|
}
|
|
3991
4179
|
|
|
3992
4180
|
//#endregion
|
|
@@ -5098,7 +5286,7 @@ function BlockMenu({ editor, anchorElement, isOpen, onClose }) {
|
|
|
5098
5286
|
type: "button",
|
|
5099
5287
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm hover:bg-kumo-tint text-start",
|
|
5100
5288
|
onClick: () => setShowTransforms(false),
|
|
5101
|
-
children: [/* @__PURE__ */ jsx(
|
|
5289
|
+
children: [/* @__PURE__ */ jsx(CaretPrev, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", { children: "Back" })]
|
|
5102
5290
|
}),
|
|
5103
5291
|
/* @__PURE__ */ jsx("div", { className: "h-px bg-kumo-line my-1" }),
|
|
5104
5292
|
blockTransforms.map((transform) => /* @__PURE__ */ jsxs("button", {
|
|
@@ -5118,7 +5306,7 @@ function BlockMenu({ editor, anchorElement, isOpen, onClose }) {
|
|
|
5118
5306
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
5119
5307
|
className: "flex items-center gap-2",
|
|
5120
5308
|
children: [/* @__PURE__ */ jsx(Paragraph, { className: "h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsx("span", { children: "Turn into" })]
|
|
5121
|
-
}), /* @__PURE__ */ jsx(
|
|
5309
|
+
}), /* @__PURE__ */ jsx(CaretNext, { className: "h-4 w-4 text-kumo-subtle" })]
|
|
5122
5310
|
}),
|
|
5123
5311
|
/* @__PURE__ */ jsxs("button", {
|
|
5124
5312
|
type: "button",
|
|
@@ -6723,12 +6911,19 @@ function PluginBlockModal({ block, initialValues, onClose, onInsert }) {
|
|
|
6723
6911
|
const isEditing = !!initialValues;
|
|
6724
6912
|
const hasFields = block?.fields && block.fields.length > 0;
|
|
6725
6913
|
const canSubmit = hasFields ? hasPluginBlockFormData(formValues) : typeof formValues.id === "string" && formValues.id.trim().length > 0;
|
|
6914
|
+
const dialogSize = (() => {
|
|
6915
|
+
if (!hasFields) return "sm";
|
|
6916
|
+
const fields = block?.fields ?? [];
|
|
6917
|
+
if (fields.some((f) => f.type === "repeater")) return "xl";
|
|
6918
|
+
if (fields.length > 3) return "lg";
|
|
6919
|
+
return "base";
|
|
6920
|
+
})();
|
|
6726
6921
|
return /* @__PURE__ */ jsx(Dialog.Root, {
|
|
6727
6922
|
open: !!block,
|
|
6728
6923
|
onOpenChange: (open) => !open && onClose(),
|
|
6729
6924
|
children: /* @__PURE__ */ jsxs(Dialog, {
|
|
6730
6925
|
className: "p-6",
|
|
6731
|
-
size:
|
|
6926
|
+
size: dialogSize,
|
|
6732
6927
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
6733
6928
|
className: "flex items-start justify-between gap-4 mb-4",
|
|
6734
6929
|
children: [/* @__PURE__ */ jsxs(Dialog.Title, {
|
|
@@ -6764,6 +6959,7 @@ function PluginBlockModal({ block, initialValues, onClose, onInsert }) {
|
|
|
6764
6959
|
}, field.action_id)) : /* @__PURE__ */ jsx(Input, {
|
|
6765
6960
|
ref: inputRef,
|
|
6766
6961
|
type: "url",
|
|
6962
|
+
className: "w-full",
|
|
6767
6963
|
placeholder: block?.placeholder || "Enter URL...",
|
|
6768
6964
|
value: typeof formValues.id === "string" ? formValues.id : "",
|
|
6769
6965
|
onChange: (e) => handleFieldChange("id", e.target.value)
|
|
@@ -6805,6 +7001,7 @@ function BlockKitField({ field, pluginId, value, onChange }) {
|
|
|
6805
7001
|
onChange: (e) => onChange(field.action_id, e.target.value)
|
|
6806
7002
|
}) : /* @__PURE__ */ jsx(Input, {
|
|
6807
7003
|
type: "text",
|
|
7004
|
+
className: "w-full",
|
|
6808
7005
|
placeholder,
|
|
6809
7006
|
value: typeof value === "string" ? value : "",
|
|
6810
7007
|
onChange: (e) => onChange(field.action_id, e.target.value)
|
|
@@ -6818,6 +7015,7 @@ function BlockKitField({ field, pluginId, value, onChange }) {
|
|
|
6818
7015
|
children: field.label
|
|
6819
7016
|
}), /* @__PURE__ */ jsx(Input, {
|
|
6820
7017
|
type: "number",
|
|
7018
|
+
className: "w-full",
|
|
6821
7019
|
min,
|
|
6822
7020
|
max,
|
|
6823
7021
|
value: typeof value === "number" ? String(value) : "",
|
|
@@ -6848,6 +7046,14 @@ function BlockKitField({ field, pluginId, value, onChange }) {
|
|
|
6848
7046
|
value,
|
|
6849
7047
|
onChange
|
|
6850
7048
|
});
|
|
7049
|
+
case "media_picker": return /* @__PURE__ */ jsx(BlockKitMediaPickerField, {
|
|
7050
|
+
actionId: field.action_id,
|
|
7051
|
+
label: field.label,
|
|
7052
|
+
placeholder: field.placeholder,
|
|
7053
|
+
mimeTypeFilter: field.mime_type_filter,
|
|
7054
|
+
value,
|
|
7055
|
+
onChange
|
|
7056
|
+
});
|
|
6851
7057
|
default: return /* @__PURE__ */ jsxs("div", {
|
|
6852
7058
|
className: "text-sm text-kumo-subtle",
|
|
6853
7059
|
children: ["Unknown field type: ", field.type]
|
|
@@ -7048,10 +7254,10 @@ function BlockKitRepeaterItem({ item, index, fields, pluginId, isCollapsed, onTo
|
|
|
7048
7254
|
}),
|
|
7049
7255
|
/* @__PURE__ */ jsxs("button", {
|
|
7050
7256
|
type: "button",
|
|
7051
|
-
className: "flex items-center gap-2 flex-1 min-w-0 text-
|
|
7257
|
+
className: "flex items-center gap-2 flex-1 min-w-0 text-start cursor-pointer",
|
|
7052
7258
|
onClick: onToggleCollapse,
|
|
7053
7259
|
"aria-expanded": !isCollapsed,
|
|
7054
|
-
children: [isCollapsed ? /* @__PURE__ */ jsx(
|
|
7260
|
+
children: [isCollapsed ? /* @__PURE__ */ jsx(CaretNext, { className: "h-4 w-4 text-kumo-subtle shrink-0" }) : /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4 text-kumo-subtle shrink-0" }), /* @__PURE__ */ jsx("span", {
|
|
7055
7261
|
className: "text-sm font-medium flex-1 truncate",
|
|
7056
7262
|
children: summaryLabel
|
|
7057
7263
|
})]
|
|
@@ -7269,7 +7475,7 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
7269
7475
|
}),
|
|
7270
7476
|
icon: resolveIcon(block.icon),
|
|
7271
7477
|
aliases: [block.type],
|
|
7272
|
-
category: {
|
|
7478
|
+
category: block.category ?? {
|
|
7273
7479
|
id: "aTofd0",
|
|
7274
7480
|
message: "Embeds"
|
|
7275
7481
|
},
|
|
@@ -7350,7 +7556,12 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
7350
7556
|
}
|
|
7351
7557
|
});
|
|
7352
7558
|
React.useEffect(() => {
|
|
7353
|
-
if (editor && onEditorReady)
|
|
7559
|
+
if (editor && onEditorReady) {
|
|
7560
|
+
onEditorReady(editor);
|
|
7561
|
+
return () => {
|
|
7562
|
+
onEditorReady(null);
|
|
7563
|
+
};
|
|
7564
|
+
}
|
|
7354
7565
|
}, [editor, onEditorReady]);
|
|
7355
7566
|
React.useEffect(() => {
|
|
7356
7567
|
if (editor) registerPluginBlocks(editor, pluginBlocks);
|
|
@@ -8956,7 +9167,7 @@ function TagInput({ terms, selectedIds, onAdd, onRemove, onCreate, isCreating, l
|
|
|
8956
9167
|
type: "button",
|
|
8957
9168
|
onClick: handleCreate,
|
|
8958
9169
|
disabled: isCreating,
|
|
8959
|
-
className: "w-full text-
|
|
9170
|
+
className: "w-full text-start px-3 py-2 text-sm hover:bg-kumo-tint text-kumo-accent flex items-center gap-1 border-t",
|
|
8960
9171
|
children: [/* @__PURE__ */ jsx(Plus, { className: "w-3 h-3" }), isCreating ? "Creating..." : `Create "${trimmedInput}"`]
|
|
8961
9172
|
})]
|
|
8962
9173
|
})]
|
|
@@ -9229,6 +9440,25 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
9229
9440
|
})) ?? []
|
|
9230
9441
|
}));
|
|
9231
9442
|
const pendingAutosaveStateRef = React.useRef(null);
|
|
9443
|
+
const [previousItemId, setPreviousItemId] = React.useState(item?.id ?? null);
|
|
9444
|
+
if (item && item.id !== previousItemId) {
|
|
9445
|
+
setPreviousItemId(item.id);
|
|
9446
|
+
setFormData(item.data);
|
|
9447
|
+
setSlug(item.slug || "");
|
|
9448
|
+
setSlugTouched(!!item.slug);
|
|
9449
|
+
setStatus(item.status);
|
|
9450
|
+
const nextBylines = item.bylines?.map((entry) => ({
|
|
9451
|
+
bylineId: entry.byline.id,
|
|
9452
|
+
roleLabel: entry.roleLabel
|
|
9453
|
+
})) ?? [];
|
|
9454
|
+
setInternalBylines(nextBylines);
|
|
9455
|
+
setLastSavedData(serializeEditorState({
|
|
9456
|
+
data: item.data,
|
|
9457
|
+
slug: item.slug || "",
|
|
9458
|
+
bylines: nextBylines
|
|
9459
|
+
}));
|
|
9460
|
+
pendingAutosaveStateRef.current = null;
|
|
9461
|
+
}
|
|
9232
9462
|
const itemDataString = React.useMemo(() => item ? JSON.stringify(item.data) : "", [item?.data]);
|
|
9233
9463
|
React.useEffect(() => {
|
|
9234
9464
|
if (item) {
|
|
@@ -9395,7 +9625,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
9395
9625
|
onSubmit: handleSubmit,
|
|
9396
9626
|
className: cn("space-y-6 transition-all duration-300", isDistractionFree && "fixed inset-0 z-50 bg-kumo-base p-8 overflow-auto"),
|
|
9397
9627
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
9398
|
-
className: cn("flex flex-wrap items-center justify-between gap-y-2", isDistractionFree && "opacity-0 hover:opacity-100 transition-opacity duration-200 fixed top-0 start-0 end-0 bg-kumo-base/95 backdrop-blur p-4 z-10"),
|
|
9628
|
+
className: cn("flex flex-wrap items-center justify-between gap-y-2", !isDistractionFree && "sticky top-0 z-30 -mx-6 -mt-6 px-6 pt-6 pb-3 mb-3 border-b border-kumo-line bg-kumo-base/95 supports-[backdrop-filter]:bg-kumo-base/80 backdrop-blur", isDistractionFree && "opacity-0 hover:opacity-100 transition-opacity duration-200 fixed top-0 start-0 end-0 bg-kumo-base/95 backdrop-blur p-4 z-10"),
|
|
9399
9629
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
9400
9630
|
className: "flex items-center space-x-4",
|
|
9401
9631
|
children: [
|
|
@@ -9412,7 +9642,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
9412
9642
|
variant: "ghost",
|
|
9413
9643
|
shape: "square"
|
|
9414
9644
|
}),
|
|
9415
|
-
children: /* @__PURE__ */ jsx(
|
|
9645
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, {
|
|
9416
9646
|
className: "h-5 w-5",
|
|
9417
9647
|
"aria-hidden": "true"
|
|
9418
9648
|
})
|
|
@@ -9607,6 +9837,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
9607
9837
|
children: /* @__PURE__ */ jsx("div", {
|
|
9608
9838
|
className: "space-y-4",
|
|
9609
9839
|
children: Object.entries(fields).map(([name, field]) => {
|
|
9840
|
+
const fieldKey = `${name}:${item?.id ?? "new"}`;
|
|
9610
9841
|
const fieldEl = /* @__PURE__ */ jsx(FieldRenderer, {
|
|
9611
9842
|
name,
|
|
9612
9843
|
field,
|
|
@@ -9618,14 +9849,14 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
9618
9849
|
onBlockSidebarOpen: field.kind === "portableText" ? handleBlockSidebarOpen : void 0,
|
|
9619
9850
|
onBlockSidebarClose: field.kind === "portableText" ? handleBlockSidebarClose : void 0,
|
|
9620
9851
|
manifest
|
|
9621
|
-
},
|
|
9852
|
+
}, fieldKey);
|
|
9622
9853
|
if (name === "featured_image" && field.kind === "image" && hasSeo && !isNew && onSeoChange) return /* @__PURE__ */ jsxs("div", {
|
|
9623
9854
|
className: "grid grid-cols-1 gap-6 md:grid-cols-2",
|
|
9624
9855
|
children: [/* @__PURE__ */ jsx("div", { children: fieldEl }), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(SeoImageField, {
|
|
9625
9856
|
seo: item?.seo,
|
|
9626
9857
|
onChange: onSeoChange
|
|
9627
9858
|
}) })]
|
|
9628
|
-
}, `${
|
|
9859
|
+
}, `${fieldKey}-with-seo`);
|
|
9629
9860
|
return fieldEl;
|
|
9630
9861
|
})
|
|
9631
9862
|
})
|
|
@@ -10092,28 +10323,30 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
10092
10323
|
})
|
|
10093
10324
|
});
|
|
10094
10325
|
case "select": {
|
|
10326
|
+
const selectOptions = Array.isArray(field.options) ? field.options : [];
|
|
10095
10327
|
const selectItems = {};
|
|
10096
|
-
for (const opt of
|
|
10328
|
+
for (const opt of selectOptions) selectItems[opt.value] = opt.label;
|
|
10097
10329
|
return /* @__PURE__ */ jsx(Select, {
|
|
10098
10330
|
id,
|
|
10099
10331
|
label,
|
|
10100
10332
|
value: typeof value === "string" ? value : "",
|
|
10101
10333
|
onValueChange: (v) => handleChange(v ?? ""),
|
|
10102
10334
|
items: selectItems,
|
|
10103
|
-
children:
|
|
10335
|
+
children: selectOptions.map((opt) => /* @__PURE__ */ jsx(Select.Option, {
|
|
10104
10336
|
value: opt.value,
|
|
10105
10337
|
children: opt.label
|
|
10106
10338
|
}, opt.value))
|
|
10107
10339
|
});
|
|
10108
10340
|
}
|
|
10109
10341
|
case "multiSelect": {
|
|
10342
|
+
const multiSelectOptions = Array.isArray(field.options) ? field.options : [];
|
|
10110
10343
|
const selected = Array.isArray(value) ? value : [];
|
|
10111
10344
|
return /* @__PURE__ */ jsxs("fieldset", { children: [/* @__PURE__ */ jsx(Label, {
|
|
10112
10345
|
className: labelClass,
|
|
10113
10346
|
children: label
|
|
10114
10347
|
}), /* @__PURE__ */ jsx("div", {
|
|
10115
10348
|
className: "mt-2 flex flex-wrap gap-x-4 gap-y-2",
|
|
10116
|
-
children:
|
|
10349
|
+
children: multiSelectOptions.map((opt) => {
|
|
10117
10350
|
const isChecked = selected.includes(opt.value);
|
|
10118
10351
|
return /* @__PURE__ */ jsx(Checkbox, {
|
|
10119
10352
|
label: opt.label,
|
|
@@ -10129,8 +10362,8 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
10129
10362
|
label,
|
|
10130
10363
|
id,
|
|
10131
10364
|
type: "datetime-local",
|
|
10132
|
-
value:
|
|
10133
|
-
onChange: (e) => handleChange(e.target.value),
|
|
10365
|
+
value: toDatetimeLocalInputValue(value),
|
|
10366
|
+
onChange: (e) => handleChange(fromDatetimeLocalInputValue(e.target.value)),
|
|
10134
10367
|
required: field.required
|
|
10135
10368
|
});
|
|
10136
10369
|
case "image": {
|
|
@@ -10984,7 +11217,17 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
10984
11217
|
})
|
|
10985
11218
|
})
|
|
10986
11219
|
]
|
|
10987
|
-
}) }), /* @__PURE__ */ jsx("tbody", { children: items.length === 0
|
|
11220
|
+
}) }), /* @__PURE__ */ jsx("tbody", { children: isLoading && items.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
11221
|
+
colSpan: i18n$1 ? 5 : 4,
|
|
11222
|
+
className: "px-4 py-8 text-center text-kumo-subtle",
|
|
11223
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
11224
|
+
className: "inline-flex items-center gap-2",
|
|
11225
|
+
children: [/* @__PURE__ */ jsx(Loader, { size: "sm" }), _t({
|
|
11226
|
+
id: "Z3FXyt",
|
|
11227
|
+
message: "Loading..."
|
|
11228
|
+
})]
|
|
11229
|
+
})
|
|
11230
|
+
}) }) : items.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
|
|
10988
11231
|
colSpan: i18n$1 ? 5 : 4,
|
|
10989
11232
|
className: "px-4 py-8 text-center text-kumo-subtle",
|
|
10990
11233
|
children: [
|
|
@@ -11055,7 +11298,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
11055
11298
|
id: "aHKcKc",
|
|
11056
11299
|
message: "Previous page"
|
|
11057
11300
|
}),
|
|
11058
|
-
children: /* @__PURE__ */ jsx(
|
|
11301
|
+
children: /* @__PURE__ */ jsx(CaretPrev, {
|
|
11059
11302
|
className: "h-4 w-4",
|
|
11060
11303
|
"aria-hidden": "true"
|
|
11061
11304
|
})
|
|
@@ -11077,7 +11320,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
11077
11320
|
id: "/dqduX",
|
|
11078
11321
|
message: "Next page"
|
|
11079
11322
|
}),
|
|
11080
|
-
children: /* @__PURE__ */ jsx(
|
|
11323
|
+
children: /* @__PURE__ */ jsx(CaretNext, {
|
|
11081
11324
|
className: "h-4 w-4",
|
|
11082
11325
|
"aria-hidden": "true"
|
|
11083
11326
|
})
|
|
@@ -11132,7 +11375,17 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
11132
11375
|
})
|
|
11133
11376
|
})
|
|
11134
11377
|
]
|
|
11135
|
-
}) }), /* @__PURE__ */ jsx("tbody", { children: trashedItems.length === 0
|
|
11378
|
+
}) }), /* @__PURE__ */ jsx("tbody", { children: isTrashedLoading && trashedItems.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
11379
|
+
colSpan: 3,
|
|
11380
|
+
className: "px-4 py-8 text-center text-kumo-subtle",
|
|
11381
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
11382
|
+
className: "inline-flex items-center gap-2",
|
|
11383
|
+
children: [/* @__PURE__ */ jsx(Loader, { size: "sm" }), _t({
|
|
11384
|
+
id: "Z3FXyt",
|
|
11385
|
+
message: "Loading..."
|
|
11386
|
+
})]
|
|
11387
|
+
})
|
|
11388
|
+
}) }) : trashedItems.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
11136
11389
|
colSpan: 3,
|
|
11137
11390
|
className: "px-4 py-8 text-center text-kumo-subtle",
|
|
11138
11391
|
children: _t({
|
|
@@ -11491,6 +11744,37 @@ function StatusBadge({ status, hasPendingChanges }) {
|
|
|
11491
11744
|
});
|
|
11492
11745
|
}
|
|
11493
11746
|
|
|
11747
|
+
//#endregion
|
|
11748
|
+
//#region src/components/EditorHeader.tsx
|
|
11749
|
+
/**
|
|
11750
|
+
* Sticky editor header with consistent placement of save / primary actions.
|
|
11751
|
+
*
|
|
11752
|
+
* Usage:
|
|
11753
|
+
*
|
|
11754
|
+
* <EditorHeader
|
|
11755
|
+
* leading={<BackLink />}
|
|
11756
|
+
* actions={<SaveButton ... />}
|
|
11757
|
+
* >
|
|
11758
|
+
* <h1 className="text-2xl font-bold">{title}</h1>
|
|
11759
|
+
* </EditorHeader>
|
|
11760
|
+
*/
|
|
11761
|
+
function EditorHeader({ leading, children, actions, sticky = true, className }) {
|
|
11762
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
11763
|
+
"data-editor-header": true,
|
|
11764
|
+
className: cn(sticky && "sticky top-0 z-30 -mx-6 -mt-6 px-6 pt-6", sticky && "bg-kumo-base/95 supports-[backdrop-filter]:bg-kumo-base/80 backdrop-blur", sticky && "pb-3 mb-3 border-b border-kumo-line", "flex flex-wrap items-center justify-between gap-y-2 gap-x-4", className),
|
|
11765
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
11766
|
+
className: "flex items-center gap-4 min-w-0",
|
|
11767
|
+
children: [leading, /* @__PURE__ */ jsx("div", {
|
|
11768
|
+
className: "min-w-0",
|
|
11769
|
+
children
|
|
11770
|
+
})]
|
|
11771
|
+
}), actions && /* @__PURE__ */ jsx("div", {
|
|
11772
|
+
className: "flex items-center gap-2 flex-wrap",
|
|
11773
|
+
children: actions
|
|
11774
|
+
})]
|
|
11775
|
+
});
|
|
11776
|
+
}
|
|
11777
|
+
|
|
11494
11778
|
//#endregion
|
|
11495
11779
|
//#region src/components/FieldEditor.tsx
|
|
11496
11780
|
const SLUG_INVALID_CHARS_REGEX = /[^a-z0-9]+/g;
|
|
@@ -12529,30 +12813,43 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
12529
12813
|
return /* @__PURE__ */ jsxs("div", {
|
|
12530
12814
|
className: "space-y-6",
|
|
12531
12815
|
children: [
|
|
12532
|
-
/* @__PURE__ */ jsxs(
|
|
12533
|
-
|
|
12534
|
-
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
12816
|
+
/* @__PURE__ */ jsxs(EditorHeader, {
|
|
12817
|
+
leading: /* @__PURE__ */ jsx(Link$1, {
|
|
12535
12818
|
to: "/content-types",
|
|
12536
|
-
"aria-label":
|
|
12819
|
+
"aria-label": _t({
|
|
12820
|
+
id: "oqi/nm",
|
|
12821
|
+
message: "Back to Content Types"
|
|
12822
|
+
}),
|
|
12537
12823
|
className: buttonVariants({
|
|
12538
12824
|
variant: "ghost",
|
|
12539
12825
|
shape: "square"
|
|
12540
12826
|
}),
|
|
12541
|
-
children: /* @__PURE__ */ jsx(
|
|
12542
|
-
}),
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12827
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-5 w-5" })
|
|
12828
|
+
}),
|
|
12829
|
+
actions: !isFromCode && !isNew ? /* @__PURE__ */ jsx(SaveButton, {
|
|
12830
|
+
type: "submit",
|
|
12831
|
+
form: "content-type-editor-form",
|
|
12832
|
+
isDirty: !!hasChanges,
|
|
12833
|
+
isSaving: !!isSaving,
|
|
12834
|
+
disabled: !urlPatternValid
|
|
12835
|
+
}) : null,
|
|
12836
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
12837
|
+
className: "text-2xl font-bold truncate",
|
|
12838
|
+
children: isNew ? _t({
|
|
12839
|
+
id: "NFFxa9",
|
|
12840
|
+
message: "New Content Type"
|
|
12841
|
+
}) : collection?.label
|
|
12842
|
+
}), !isNew && /* @__PURE__ */ jsxs("p", {
|
|
12843
|
+
className: "text-kumo-subtle text-sm",
|
|
12844
|
+
children: [/* @__PURE__ */ jsx("code", {
|
|
12845
|
+
className: "bg-kumo-tint px-1.5 py-0.5 rounded",
|
|
12846
|
+
children: collection?.slug
|
|
12847
|
+
}), isFromCode && /* @__PURE__ */ jsx("span", {
|
|
12848
|
+
className: "ms-2 text-purple-600 dark:text-purple-400",
|
|
12849
|
+
children: _t({
|
|
12850
|
+
id: "7tGkih",
|
|
12851
|
+
message: "Defined in code"
|
|
12852
|
+
})
|
|
12556
12853
|
})]
|
|
12557
12854
|
})]
|
|
12558
12855
|
}),
|
|
@@ -12571,6 +12868,7 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
12571
12868
|
children: [/* @__PURE__ */ jsx("div", {
|
|
12572
12869
|
className: "lg:col-span-1",
|
|
12573
12870
|
children: /* @__PURE__ */ jsxs("form", {
|
|
12871
|
+
id: "content-type-editor-form",
|
|
12574
12872
|
onSubmit: handleSubmit,
|
|
12575
12873
|
className: "space-y-4",
|
|
12576
12874
|
children: [
|
|
@@ -16278,7 +16576,7 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
16278
16576
|
to: "/plugins/$pluginId/$",
|
|
16279
16577
|
params: {
|
|
16280
16578
|
pluginId: plugin.id,
|
|
16281
|
-
_splat: "
|
|
16579
|
+
_splat: ""
|
|
16282
16580
|
},
|
|
16283
16581
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
16284
16582
|
variant: "ghost",
|
|
@@ -16320,7 +16618,7 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
16320
16618
|
}),
|
|
16321
16619
|
onClick: () => setExpanded(!expanded),
|
|
16322
16620
|
"aria-expanded": expanded,
|
|
16323
|
-
children: [expanded ? /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(
|
|
16621
|
+
children: [expanded ? /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(CaretNext, { className: "h-4 w-4" }), /* @__PURE__ */ jsxs("span", {
|
|
16324
16622
|
className: "sr-only",
|
|
16325
16623
|
children: [
|
|
16326
16624
|
expanded ? _t2({
|
|
@@ -16923,7 +17221,7 @@ function BackLink() {
|
|
|
16923
17221
|
return /* @__PURE__ */ jsxs(Link$1, {
|
|
16924
17222
|
to: "/plugins/marketplace",
|
|
16925
17223
|
className: "inline-flex items-center gap-1 text-sm text-kumo-subtle hover:text-kumo-default",
|
|
16926
|
-
children: [/* @__PURE__ */ jsx(
|
|
17224
|
+
children: [/* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" }), _t2({
|
|
16927
17225
|
id: "x5HVV+",
|
|
16928
17226
|
message: "Back to marketplace"
|
|
16929
17227
|
})]
|
|
@@ -16970,7 +17268,7 @@ function ScreenshotLightbox({ screenshots, index, isBlurred = false, onClose, on
|
|
|
16970
17268
|
id: "k7xxm/",
|
|
16971
17269
|
message: "Previous screenshot"
|
|
16972
17270
|
}),
|
|
16973
|
-
children: /* @__PURE__ */ jsx(
|
|
17271
|
+
children: /* @__PURE__ */ jsx(CaretPrev, { className: "h-5 w-5" })
|
|
16974
17272
|
}),
|
|
16975
17273
|
/* @__PURE__ */ jsx("img", {
|
|
16976
17274
|
src: screenshots[index],
|
|
@@ -16991,7 +17289,7 @@ function ScreenshotLightbox({ screenshots, index, isBlurred = false, onClose, on
|
|
|
16991
17289
|
id: "tgnDjk",
|
|
16992
17290
|
message: "Next screenshot"
|
|
16993
17291
|
}),
|
|
16994
|
-
children: /* @__PURE__ */ jsx(
|
|
17292
|
+
children: /* @__PURE__ */ jsx(CaretNext, { className: "h-5 w-5" })
|
|
16995
17293
|
}),
|
|
16996
17294
|
/* @__PURE__ */ jsxs("div", {
|
|
16997
17295
|
className: "absolute bottom-4 start-1/2 -translate-x-1/2 rounded-full bg-black/50 px-3 py-1 text-sm text-white",
|
|
@@ -18533,7 +18831,7 @@ function MenuEditor() {
|
|
|
18533
18831
|
message: "Back"
|
|
18534
18832
|
}),
|
|
18535
18833
|
onClick: () => navigate({ to: "/menus" }),
|
|
18536
|
-
children: /* @__PURE__ */ jsx(
|
|
18834
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
18537
18835
|
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
18538
18836
|
className: "text-3xl font-bold",
|
|
18539
18837
|
children: menu.label
|
|
@@ -19097,7 +19395,7 @@ function MenuList() {
|
|
|
19097
19395
|
name: "name",
|
|
19098
19396
|
required: true,
|
|
19099
19397
|
placeholder: "primary",
|
|
19100
|
-
pattern: "[a-z0-9
|
|
19398
|
+
pattern: "[a-z0-9\\-]+",
|
|
19101
19399
|
title: _t({
|
|
19102
19400
|
id: "6ZQzu/",
|
|
19103
19401
|
message: "Only lowercase letters, numbers, and hyphens"
|
|
@@ -19837,7 +20135,7 @@ function Redirects() {
|
|
|
19837
20135
|
}),
|
|
19838
20136
|
/* @__PURE__ */ jsx("div", {
|
|
19839
20137
|
className: "w-8 text-center text-kumo-subtle",
|
|
19840
|
-
children: /* @__PURE__ */ jsx(
|
|
20138
|
+
children: /* @__PURE__ */ jsx(ArrowNext, { size: 14 })
|
|
19841
20139
|
}),
|
|
19842
20140
|
/* @__PURE__ */ jsx("div", {
|
|
19843
20141
|
className: "flex-1 font-mono text-xs truncate",
|
|
@@ -20117,7 +20415,7 @@ function SectionEditor() {
|
|
|
20117
20415
|
id: "t4JBeQ",
|
|
20118
20416
|
message: "Back to sections"
|
|
20119
20417
|
}),
|
|
20120
|
-
children: /* @__PURE__ */ jsx(
|
|
20418
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-5 w-5" })
|
|
20121
20419
|
})
|
|
20122
20420
|
}), /* @__PURE__ */ jsx("h1", {
|
|
20123
20421
|
className: "text-2xl font-bold",
|
|
@@ -20179,6 +20477,16 @@ function SectionEditorForm({ section, isSaving, onSave }) {
|
|
|
20179
20477
|
keywords,
|
|
20180
20478
|
content
|
|
20181
20479
|
]) !== lastSavedData;
|
|
20480
|
+
const [blockSidebarPanel, setBlockSidebarPanel] = React.useState(null);
|
|
20481
|
+
const handleBlockSidebarOpen = React.useCallback((panel) => {
|
|
20482
|
+
setBlockSidebarPanel(panel);
|
|
20483
|
+
}, []);
|
|
20484
|
+
const handleBlockSidebarClose = React.useCallback(() => {
|
|
20485
|
+
setBlockSidebarPanel((prev) => {
|
|
20486
|
+
prev?.onClose();
|
|
20487
|
+
return null;
|
|
20488
|
+
});
|
|
20489
|
+
}, []);
|
|
20182
20490
|
const handleSave = () => {
|
|
20183
20491
|
const keywordsArray = keywords.split(",").map((k) => k.trim()).filter(Boolean);
|
|
20184
20492
|
onSave({
|
|
@@ -20191,43 +20499,41 @@ function SectionEditorForm({ section, isSaving, onSave }) {
|
|
|
20191
20499
|
};
|
|
20192
20500
|
return /* @__PURE__ */ jsxs("div", {
|
|
20193
20501
|
className: "space-y-6",
|
|
20194
|
-
children: [/* @__PURE__ */ jsxs(
|
|
20195
|
-
|
|
20196
|
-
|
|
20197
|
-
|
|
20198
|
-
|
|
20199
|
-
|
|
20200
|
-
|
|
20201
|
-
|
|
20202
|
-
|
|
20203
|
-
|
|
20204
|
-
|
|
20205
|
-
|
|
20206
|
-
|
|
20207
|
-
|
|
20208
|
-
})
|
|
20209
|
-
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
20210
|
-
className: "text-2xl font-bold",
|
|
20211
|
-
children: section.title
|
|
20212
|
-
}), /* @__PURE__ */ jsxs("p", {
|
|
20213
|
-
className: "text-sm text-kumo-subtle",
|
|
20214
|
-
children: [
|
|
20215
|
-
section.source === "theme" ? _t2({
|
|
20216
|
-
id: "wg2j4A",
|
|
20217
|
-
message: "Theme Section"
|
|
20218
|
-
}) : _t2({
|
|
20219
|
-
id: "dqjp2n",
|
|
20220
|
-
message: "Custom Section"
|
|
20221
|
-
}),
|
|
20222
|
-
" ·",
|
|
20223
|
-
" ",
|
|
20224
|
-
section.slug
|
|
20225
|
-
]
|
|
20226
|
-
})] })]
|
|
20227
|
-
}), /* @__PURE__ */ jsx(SaveButton, {
|
|
20502
|
+
children: [/* @__PURE__ */ jsxs(EditorHeader, {
|
|
20503
|
+
leading: /* @__PURE__ */ jsx(Link$1, {
|
|
20504
|
+
to: "/sections",
|
|
20505
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
20506
|
+
variant: "ghost",
|
|
20507
|
+
shape: "square",
|
|
20508
|
+
"aria-label": _t2({
|
|
20509
|
+
id: "t4JBeQ",
|
|
20510
|
+
message: "Back to sections"
|
|
20511
|
+
}),
|
|
20512
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-5 w-5" })
|
|
20513
|
+
})
|
|
20514
|
+
}),
|
|
20515
|
+
actions: /* @__PURE__ */ jsx(SaveButton, {
|
|
20228
20516
|
isSaving,
|
|
20229
20517
|
isDirty,
|
|
20230
20518
|
onClick: handleSave
|
|
20519
|
+
}),
|
|
20520
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
20521
|
+
className: "text-2xl font-bold truncate",
|
|
20522
|
+
children: section.title
|
|
20523
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
20524
|
+
className: "text-sm text-kumo-subtle",
|
|
20525
|
+
children: [
|
|
20526
|
+
section.source === "theme" ? _t2({
|
|
20527
|
+
id: "wg2j4A",
|
|
20528
|
+
message: "Theme Section"
|
|
20529
|
+
}) : _t2({
|
|
20530
|
+
id: "dqjp2n",
|
|
20531
|
+
message: "Custom Section"
|
|
20532
|
+
}),
|
|
20533
|
+
" ·",
|
|
20534
|
+
" ",
|
|
20535
|
+
section.slug
|
|
20536
|
+
]
|
|
20231
20537
|
})]
|
|
20232
20538
|
}), /* @__PURE__ */ jsxs("div", {
|
|
20233
20539
|
className: "grid grid-cols-12 gap-6",
|
|
@@ -20243,12 +20549,24 @@ function SectionEditorForm({ section, isSaving, onSave }) {
|
|
|
20243
20549
|
})
|
|
20244
20550
|
}), /* @__PURE__ */ jsx(PortableTextEditor, {
|
|
20245
20551
|
value: content,
|
|
20246
|
-
onChange: (value) => setContent(value)
|
|
20552
|
+
onChange: (value) => setContent(value),
|
|
20553
|
+
onBlockSidebarOpen: handleBlockSidebarOpen,
|
|
20554
|
+
onBlockSidebarClose: handleBlockSidebarClose
|
|
20247
20555
|
})]
|
|
20248
20556
|
})
|
|
20249
|
-
}), /* @__PURE__ */
|
|
20557
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
20250
20558
|
className: "col-span-4 space-y-6",
|
|
20251
|
-
children:
|
|
20559
|
+
children: blockSidebarPanel?.type === "image" ? /* @__PURE__ */ jsx(ImageDetailPanel, {
|
|
20560
|
+
attributes: blockSidebarPanel.attrs,
|
|
20561
|
+
onUpdate: (attrs) => blockSidebarPanel.onUpdate(attrs),
|
|
20562
|
+
onReplace: (attrs) => blockSidebarPanel.onReplace(attrs),
|
|
20563
|
+
onDelete: () => {
|
|
20564
|
+
blockSidebarPanel.onDelete();
|
|
20565
|
+
setBlockSidebarPanel(null);
|
|
20566
|
+
},
|
|
20567
|
+
onClose: handleBlockSidebarClose,
|
|
20568
|
+
inline: true
|
|
20569
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
20252
20570
|
className: "rounded-lg border bg-kumo-base p-6 space-y-4",
|
|
20253
20571
|
children: [
|
|
20254
20572
|
/* @__PURE__ */ jsx("h2", {
|
|
@@ -20281,7 +20599,7 @@ function SectionEditorForm({ section, isSaving, onSave }) {
|
|
|
20281
20599
|
setSlugTouched(true);
|
|
20282
20600
|
},
|
|
20283
20601
|
placeholder: "section-slug",
|
|
20284
|
-
pattern: "[a-z0-9
|
|
20602
|
+
pattern: "[a-z0-9\\-]+"
|
|
20285
20603
|
}), /* @__PURE__ */ jsx("p", {
|
|
20286
20604
|
className: "text-xs text-kumo-subtle mt-1",
|
|
20287
20605
|
children: _t2({
|
|
@@ -20354,7 +20672,7 @@ function SectionEditorForm({ section, isSaving, onSave }) {
|
|
|
20354
20672
|
})
|
|
20355
20673
|
})
|
|
20356
20674
|
]
|
|
20357
|
-
})]
|
|
20675
|
+
})] })
|
|
20358
20676
|
})]
|
|
20359
20677
|
})]
|
|
20360
20678
|
});
|
|
@@ -20549,7 +20867,7 @@ function Sections() {
|
|
|
20549
20867
|
},
|
|
20550
20868
|
required: true,
|
|
20551
20869
|
placeholder: "hero-banner",
|
|
20552
|
-
pattern: "[a-z0-9
|
|
20870
|
+
pattern: "[a-z0-9\\-]+",
|
|
20553
20871
|
title: _t({
|
|
20554
20872
|
id: "BjF0Jv",
|
|
20555
20873
|
message: "Lowercase letters, numbers, and hyphens only"
|
|
@@ -20878,7 +21196,7 @@ function SettingsLink({ to, icon, title, description }) {
|
|
|
20878
21196
|
className: "text-sm text-kumo-subtle",
|
|
20879
21197
|
children: description
|
|
20880
21198
|
})] })]
|
|
20881
|
-
}), /* @__PURE__ */ jsx(
|
|
21199
|
+
}), /* @__PURE__ */ jsx(CaretNext, { className: "h-5 w-5 text-kumo-subtle" })]
|
|
20882
21200
|
});
|
|
20883
21201
|
}
|
|
20884
21202
|
/**
|
|
@@ -21316,7 +21634,7 @@ function AllowedDomainsSettings() {
|
|
|
21316
21634
|
id: "9aZHfH",
|
|
21317
21635
|
message: "Back to settings"
|
|
21318
21636
|
}),
|
|
21319
|
-
children: /* @__PURE__ */ jsx(
|
|
21637
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
21320
21638
|
})
|
|
21321
21639
|
}), /* @__PURE__ */ jsx("h1", {
|
|
21322
21640
|
className: "text-2xl font-bold",
|
|
@@ -21897,7 +22215,7 @@ function ApiTokenSettings() {
|
|
|
21897
22215
|
id: "9aZHfH",
|
|
21898
22216
|
message: "Back to settings"
|
|
21899
22217
|
}),
|
|
21900
|
-
children: /* @__PURE__ */ jsx(
|
|
22218
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
21901
22219
|
})
|
|
21902
22220
|
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
21903
22221
|
className: "text-2xl font-bold",
|
|
@@ -22306,7 +22624,7 @@ function EmailSettings() {
|
|
|
22306
22624
|
id: "9aZHfH",
|
|
22307
22625
|
message: "Back to settings"
|
|
22308
22626
|
}),
|
|
22309
|
-
children: /* @__PURE__ */ jsx(
|
|
22627
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
22310
22628
|
})
|
|
22311
22629
|
}), /* @__PURE__ */ jsx("h1", {
|
|
22312
22630
|
className: "text-2xl font-bold",
|
|
@@ -22337,7 +22655,7 @@ function EmailSettings() {
|
|
|
22337
22655
|
id: "9aZHfH",
|
|
22338
22656
|
message: "Back to settings"
|
|
22339
22657
|
}),
|
|
22340
|
-
children: /* @__PURE__ */ jsx(
|
|
22658
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
22341
22659
|
})
|
|
22342
22660
|
}), /* @__PURE__ */ jsx("h1", {
|
|
22343
22661
|
className: "text-2xl font-bold",
|
|
@@ -22639,7 +22957,7 @@ function GeneralSettings() {
|
|
|
22639
22957
|
id: "9aZHfH",
|
|
22640
22958
|
message: "Back to settings"
|
|
22641
22959
|
}),
|
|
22642
|
-
children: /* @__PURE__ */ jsx(
|
|
22960
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
22643
22961
|
})
|
|
22644
22962
|
}), /* @__PURE__ */ jsx("h1", {
|
|
22645
22963
|
className: "text-2xl font-bold",
|
|
@@ -22662,9 +22980,8 @@ function GeneralSettings() {
|
|
|
22662
22980
|
return /* @__PURE__ */ jsxs("div", {
|
|
22663
22981
|
className: "space-y-6",
|
|
22664
22982
|
children: [
|
|
22665
|
-
/* @__PURE__ */
|
|
22666
|
-
|
|
22667
|
-
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
22983
|
+
/* @__PURE__ */ jsx(EditorHeader, {
|
|
22984
|
+
leading: /* @__PURE__ */ jsx(Link$1, {
|
|
22668
22985
|
to: "/settings",
|
|
22669
22986
|
children: /* @__PURE__ */ jsx(Button, {
|
|
22670
22987
|
variant: "ghost",
|
|
@@ -22673,21 +22990,36 @@ function GeneralSettings() {
|
|
|
22673
22990
|
id: "9aZHfH",
|
|
22674
22991
|
message: "Back to settings"
|
|
22675
22992
|
}),
|
|
22676
|
-
children: /* @__PURE__ */ jsx(
|
|
22993
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
22677
22994
|
})
|
|
22678
|
-
}),
|
|
22679
|
-
|
|
22995
|
+
}),
|
|
22996
|
+
actions: /* @__PURE__ */ jsx(Button, {
|
|
22997
|
+
type: "submit",
|
|
22998
|
+
form: "general-settings-form",
|
|
22999
|
+
disabled: saveMutation.isPending,
|
|
23000
|
+
icon: /* @__PURE__ */ jsx(FloppyDisk, {}),
|
|
23001
|
+
children: saveMutation.isPending ? _t({
|
|
23002
|
+
id: "XvjC4F",
|
|
23003
|
+
message: "Saving..."
|
|
23004
|
+
}) : _t({
|
|
23005
|
+
id: "UGT5vp",
|
|
23006
|
+
message: "Save Settings"
|
|
23007
|
+
})
|
|
23008
|
+
}),
|
|
23009
|
+
children: /* @__PURE__ */ jsx("h1", {
|
|
23010
|
+
className: "text-2xl font-bold truncate",
|
|
22680
23011
|
children: _t({
|
|
22681
23012
|
id: "Cb/Jr5",
|
|
22682
23013
|
message: "General Settings"
|
|
22683
23014
|
})
|
|
22684
|
-
})
|
|
23015
|
+
})
|
|
22685
23016
|
}),
|
|
22686
23017
|
saveStatus && /* @__PURE__ */ jsxs("div", {
|
|
22687
23018
|
className: `flex items-center gap-2 rounded-lg border p-3 text-sm ${saveStatus.type === "success" ? "border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-200" : "border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"}`,
|
|
22688
23019
|
children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 flex-shrink-0" }) : /* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), saveStatus.message]
|
|
22689
23020
|
}),
|
|
22690
23021
|
/* @__PURE__ */ jsxs("form", {
|
|
23022
|
+
id: "general-settings-form",
|
|
22691
23023
|
onSubmit: handleSubmit,
|
|
22692
23024
|
className: "space-y-6",
|
|
22693
23025
|
children: [
|
|
@@ -23270,7 +23602,7 @@ function SecuritySettings() {
|
|
|
23270
23602
|
id: "9aZHfH",
|
|
23271
23603
|
message: "Back to settings"
|
|
23272
23604
|
}),
|
|
23273
|
-
children: /* @__PURE__ */ jsx(
|
|
23605
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
23274
23606
|
})
|
|
23275
23607
|
}), /* @__PURE__ */ jsx("h1", {
|
|
23276
23608
|
className: "text-2xl font-bold",
|
|
@@ -23491,7 +23823,7 @@ function SeoSettings() {
|
|
|
23491
23823
|
id: "9aZHfH",
|
|
23492
23824
|
message: "Back to settings"
|
|
23493
23825
|
}),
|
|
23494
|
-
children: /* @__PURE__ */ jsx(
|
|
23826
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
23495
23827
|
})
|
|
23496
23828
|
}), /* @__PURE__ */ jsx("h1", {
|
|
23497
23829
|
className: "text-2xl font-bold",
|
|
@@ -23514,9 +23846,8 @@ function SeoSettings() {
|
|
|
23514
23846
|
return /* @__PURE__ */ jsxs("div", {
|
|
23515
23847
|
className: "space-y-6",
|
|
23516
23848
|
children: [
|
|
23517
|
-
/* @__PURE__ */
|
|
23518
|
-
|
|
23519
|
-
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
23849
|
+
/* @__PURE__ */ jsx(EditorHeader, {
|
|
23850
|
+
leading: /* @__PURE__ */ jsx(Link$1, {
|
|
23520
23851
|
to: "/settings",
|
|
23521
23852
|
children: /* @__PURE__ */ jsx(Button, {
|
|
23522
23853
|
variant: "ghost",
|
|
@@ -23525,21 +23856,36 @@ function SeoSettings() {
|
|
|
23525
23856
|
id: "9aZHfH",
|
|
23526
23857
|
message: "Back to settings"
|
|
23527
23858
|
}),
|
|
23528
|
-
children: /* @__PURE__ */ jsx(
|
|
23859
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
23529
23860
|
})
|
|
23530
|
-
}),
|
|
23531
|
-
|
|
23861
|
+
}),
|
|
23862
|
+
actions: /* @__PURE__ */ jsx(Button, {
|
|
23863
|
+
type: "submit",
|
|
23864
|
+
form: "seo-settings-form",
|
|
23865
|
+
disabled: saveMutation.isPending,
|
|
23866
|
+
icon: /* @__PURE__ */ jsx(FloppyDisk, {}),
|
|
23867
|
+
children: saveMutation.isPending ? _t({
|
|
23868
|
+
id: "XvjC4F",
|
|
23869
|
+
message: "Saving..."
|
|
23870
|
+
}) : _t({
|
|
23871
|
+
id: "TJnLTa",
|
|
23872
|
+
message: "Save SEO Settings"
|
|
23873
|
+
})
|
|
23874
|
+
}),
|
|
23875
|
+
children: /* @__PURE__ */ jsx("h1", {
|
|
23876
|
+
className: "text-2xl font-bold truncate",
|
|
23532
23877
|
children: _t({
|
|
23533
23878
|
id: "GfWoKv",
|
|
23534
23879
|
message: "SEO Settings"
|
|
23535
23880
|
})
|
|
23536
|
-
})
|
|
23881
|
+
})
|
|
23537
23882
|
}),
|
|
23538
23883
|
saveStatus && /* @__PURE__ */ jsxs("div", {
|
|
23539
23884
|
className: `flex items-center gap-2 rounded-lg border p-3 text-sm ${saveStatus.type === "success" ? "border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-200" : "border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"}`,
|
|
23540
23885
|
children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 flex-shrink-0" }) : /* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), saveStatus.message]
|
|
23541
23886
|
}),
|
|
23542
23887
|
/* @__PURE__ */ jsxs("form", {
|
|
23888
|
+
id: "seo-settings-form",
|
|
23543
23889
|
onSubmit: handleSubmit,
|
|
23544
23890
|
className: "space-y-6",
|
|
23545
23891
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -23698,7 +24044,7 @@ function SocialSettings() {
|
|
|
23698
24044
|
id: "9aZHfH",
|
|
23699
24045
|
message: "Back to settings"
|
|
23700
24046
|
}),
|
|
23701
|
-
children: /* @__PURE__ */ jsx(
|
|
24047
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
23702
24048
|
})
|
|
23703
24049
|
}), /* @__PURE__ */ jsx("h1", {
|
|
23704
24050
|
className: "text-2xl font-bold",
|
|
@@ -23721,9 +24067,8 @@ function SocialSettings() {
|
|
|
23721
24067
|
return /* @__PURE__ */ jsxs("div", {
|
|
23722
24068
|
className: "space-y-6",
|
|
23723
24069
|
children: [
|
|
23724
|
-
/* @__PURE__ */
|
|
23725
|
-
|
|
23726
|
-
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
24070
|
+
/* @__PURE__ */ jsx(EditorHeader, {
|
|
24071
|
+
leading: /* @__PURE__ */ jsx(Link$1, {
|
|
23727
24072
|
to: "/settings",
|
|
23728
24073
|
children: /* @__PURE__ */ jsx(Button, {
|
|
23729
24074
|
variant: "ghost",
|
|
@@ -23732,21 +24077,36 @@ function SocialSettings() {
|
|
|
23732
24077
|
id: "9aZHfH",
|
|
23733
24078
|
message: "Back to settings"
|
|
23734
24079
|
}),
|
|
23735
|
-
children: /* @__PURE__ */ jsx(
|
|
24080
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
23736
24081
|
})
|
|
23737
|
-
}),
|
|
23738
|
-
|
|
24082
|
+
}),
|
|
24083
|
+
actions: /* @__PURE__ */ jsx(Button, {
|
|
24084
|
+
type: "submit",
|
|
24085
|
+
form: "social-settings-form",
|
|
24086
|
+
disabled: saveMutation.isPending,
|
|
24087
|
+
icon: /* @__PURE__ */ jsx(FloppyDisk, {}),
|
|
24088
|
+
children: saveMutation.isPending ? _t({
|
|
24089
|
+
id: "XvjC4F",
|
|
24090
|
+
message: "Saving..."
|
|
24091
|
+
}) : _t({
|
|
24092
|
+
id: "KZrfYJ",
|
|
24093
|
+
message: "Save Social Links"
|
|
24094
|
+
})
|
|
24095
|
+
}),
|
|
24096
|
+
children: /* @__PURE__ */ jsx("h1", {
|
|
24097
|
+
className: "text-2xl font-bold truncate",
|
|
23739
24098
|
children: _t({
|
|
23740
24099
|
id: "d0rUsW",
|
|
23741
24100
|
message: "Social Links"
|
|
23742
24101
|
})
|
|
23743
|
-
})
|
|
24102
|
+
})
|
|
23744
24103
|
}),
|
|
23745
24104
|
saveStatus && /* @__PURE__ */ jsxs("div", {
|
|
23746
24105
|
className: `flex items-center gap-2 rounded-lg border p-3 text-sm ${saveStatus.type === "success" ? "border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-200" : "border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"}`,
|
|
23747
24106
|
children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 flex-shrink-0" }) : /* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), saveStatus.message]
|
|
23748
24107
|
}),
|
|
23749
24108
|
/* @__PURE__ */ jsxs("form", {
|
|
24109
|
+
id: "social-settings-form",
|
|
23750
24110
|
onSubmit: handleSubmit,
|
|
23751
24111
|
className: "space-y-6",
|
|
23752
24112
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -25197,7 +25557,7 @@ function SidebarNav({ manifest }) {
|
|
|
25197
25557
|
collapsible: true,
|
|
25198
25558
|
defaultOpen: true,
|
|
25199
25559
|
children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
25200
|
-
className: "[&>span]:text-start",
|
|
25560
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
25201
25561
|
children: _t({
|
|
25202
25562
|
id: "4b3oEV",
|
|
25203
25563
|
message: "Content"
|
|
@@ -25209,7 +25569,7 @@ function SidebarNav({ manifest }) {
|
|
|
25209
25569
|
collapsible: true,
|
|
25210
25570
|
defaultOpen: true,
|
|
25211
25571
|
children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
25212
|
-
className: "[&>span]:text-start",
|
|
25572
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
25213
25573
|
children: _t({
|
|
25214
25574
|
id: "wckWOP",
|
|
25215
25575
|
message: "Manage"
|
|
@@ -25221,7 +25581,7 @@ function SidebarNav({ manifest }) {
|
|
|
25221
25581
|
collapsible: true,
|
|
25222
25582
|
defaultOpen: true,
|
|
25223
25583
|
children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
25224
|
-
className: "[&>span]:text-start",
|
|
25584
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
25225
25585
|
children: _t({
|
|
25226
25586
|
id: "U3pytU",
|
|
25227
25587
|
message: "Admin"
|
|
@@ -25232,7 +25592,7 @@ function SidebarNav({ manifest }) {
|
|
|
25232
25592
|
collapsible: true,
|
|
25233
25593
|
defaultOpen: true,
|
|
25234
25594
|
children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
25235
|
-
className: "[&>span]:text-start",
|
|
25595
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
25236
25596
|
children: _t({
|
|
25237
25597
|
id: "ohUJJM",
|
|
25238
25598
|
message: "Plugins"
|
|
@@ -27240,7 +27600,7 @@ function ThemeMarketplaceDetail({ themeId }) {
|
|
|
27240
27600
|
children: [/* @__PURE__ */ jsxs(Link$1, {
|
|
27241
27601
|
to: "/themes/marketplace",
|
|
27242
27602
|
className: "inline-flex items-center gap-1 text-sm text-kumo-subtle hover:text-kumo-default",
|
|
27243
|
-
children: [/* @__PURE__ */ jsx(
|
|
27603
|
+
children: [/* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" }), _t({
|
|
27244
27604
|
id: "6wOzc6",
|
|
27245
27605
|
message: "Back to Themes"
|
|
27246
27606
|
})]
|
|
@@ -27268,7 +27628,7 @@ function ThemeMarketplaceDetail({ themeId }) {
|
|
|
27268
27628
|
/* @__PURE__ */ jsxs(Link$1, {
|
|
27269
27629
|
to: "/themes/marketplace",
|
|
27270
27630
|
className: "inline-flex items-center gap-1 text-sm text-kumo-subtle hover:text-kumo-default",
|
|
27271
|
-
children: [/* @__PURE__ */ jsx(
|
|
27631
|
+
children: [/* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" }), _t({
|
|
27272
27632
|
id: "6wOzc6",
|
|
27273
27633
|
message: "Back to Themes"
|
|
27274
27634
|
})]
|
|
@@ -27466,7 +27826,7 @@ function Lightbox({ urls, index, onClose, onPrev, onNext }) {
|
|
|
27466
27826
|
id: "DHhJ7s",
|
|
27467
27827
|
message: "Previous"
|
|
27468
27828
|
}),
|
|
27469
|
-
children: /* @__PURE__ */ jsx(
|
|
27829
|
+
children: /* @__PURE__ */ jsx(CaretPrev, { className: "h-5 w-5" })
|
|
27470
27830
|
}), /* @__PURE__ */ jsx("button", {
|
|
27471
27831
|
onClick: onNext,
|
|
27472
27832
|
className: "absolute end-2 top-1/2 -translate-y-1/2 rounded-full bg-kumo-base/80 p-2 shadow hover:bg-kumo-base",
|
|
@@ -27474,7 +27834,7 @@ function Lightbox({ urls, index, onClose, onPrev, onNext }) {
|
|
|
27474
27834
|
id: "hXzOVo",
|
|
27475
27835
|
message: "Next"
|
|
27476
27836
|
}),
|
|
27477
|
-
children: /* @__PURE__ */ jsx(
|
|
27837
|
+
children: /* @__PURE__ */ jsx(CaretNext, { className: "h-5 w-5" })
|
|
27478
27838
|
})] }),
|
|
27479
27839
|
/* @__PURE__ */ jsxs("div", {
|
|
27480
27840
|
className: "absolute bottom-2 start-1/2 -translate-x-1/2 rounded-full bg-kumo-base/80 px-3 py-1 text-xs",
|
|
@@ -27749,7 +28109,7 @@ function Widgets() {
|
|
|
27749
28109
|
name: "name",
|
|
27750
28110
|
required: true,
|
|
27751
28111
|
placeholder: "sidebar",
|
|
27752
|
-
pattern: "[a-z0-9
|
|
28112
|
+
pattern: "[a-z0-9\\-]+"
|
|
27753
28113
|
}),
|
|
27754
28114
|
/* @__PURE__ */ jsx(Input, {
|
|
27755
28115
|
label: "Label",
|
|
@@ -28047,7 +28407,7 @@ function WidgetItem({ widget, areaName, isExpanded, onToggle, components, plugin
|
|
|
28047
28407
|
children: /* @__PURE__ */ jsxs("div", {
|
|
28048
28408
|
className: "flex items-center gap-2",
|
|
28049
28409
|
children: [
|
|
28050
|
-
isExpanded ? /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(
|
|
28410
|
+
isExpanded ? /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(CaretNext, { className: "h-4 w-4" }),
|
|
28051
28411
|
/* @__PURE__ */ jsx("span", {
|
|
28052
28412
|
className: "font-medium",
|
|
28053
28413
|
children: widget.title || "Untitled Widget"
|
|
@@ -30064,7 +30424,7 @@ function PostTypeRow({ postType, selection, expanded, onToggleExpand, onToggleEn
|
|
|
30064
30424
|
onClick: onToggleExpand,
|
|
30065
30425
|
className: "flex items-center gap-1 text-start",
|
|
30066
30426
|
"aria-expanded": expanded,
|
|
30067
|
-
children: [expanded ? /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4 text-kumo-subtle" }) : /* @__PURE__ */ jsx(
|
|
30427
|
+
children: [expanded ? /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4 text-kumo-subtle" }) : /* @__PURE__ */ jsx(CaretNext, { className: "h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
30068
30428
|
className: "font-medium",
|
|
30069
30429
|
children: postType.name
|
|
30070
30430
|
}), /* @__PURE__ */ jsx("p", {
|
|
@@ -33654,5 +34014,5 @@ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, authProviders = EMPTY_AUTH_PRO
|
|
|
33654
34014
|
}
|
|
33655
34015
|
|
|
33656
34016
|
//#endregion
|
|
33657
|
-
export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, AuthProviderProvider, CAPABILITY_LABELS, ContentEditor, ContentList, DEFAULT_LOCALE, Dashboard, Header, Link, LoginPage, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, SUPPORTED_LOCALES, SUPPORTED_LOCALE_CODES, SaveButton, Settings, SetupWizard, Shell, KumoSidebar as Sidebar, SidebarNav, analyzeWpPluginSite, analyzeWxr, apiFetch, bulkCommentAction, checkPluginUpdates, cn, compareRevisions, completeSignup, createAdminRouter, createAllowedDomain, createApiToken, createByline, createCollection, createContent, createField, createMenu, createMenuItem, createRedirect, createSection, createTaxonomy, createTerm, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteCollection, deleteComment, deleteContent, deleteField, deleteFromProvider, deleteMedia, deleteMenu, deleteMenuItem, deletePasskey, deleteRedirect, deleteSection, deleteTerm, deleteWidget, deleteWidgetArea, describeCapability, disablePlugin, disableUser, discardDraft, duplicateContent, enablePlugin, enableUser, executeWpPluginImport, executeWxrImport, fetch404Summary, fetchAllowedDomains, fetchApiTokens, fetchAuthMode, fetchByline, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, installMarketplacePlugin, inviteUser, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, resolveLocale, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useAuthProviderList, useAuthProviders, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, validateInviteToken, verifySignupToken };
|
|
34017
|
+
export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, AuthProviderProvider, CAPABILITY_LABELS, ContentEditor, ContentList, DEFAULT_LOCALE, Dashboard, EditorHeader, Header, Link, LoginPage, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, SUPPORTED_LOCALES, SUPPORTED_LOCALE_CODES, SaveButton, Settings, SetupWizard, Shell, KumoSidebar as Sidebar, SidebarNav, analyzeWpPluginSite, analyzeWxr, apiFetch, bulkCommentAction, checkPluginUpdates, cn, compareRevisions, completeSignup, createAdminRouter, createAllowedDomain, createApiToken, createByline, createCollection, createContent, createField, createMenu, createMenuItem, createRedirect, createSection, createTaxonomy, createTerm, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteCollection, deleteComment, deleteContent, deleteField, deleteFromProvider, deleteMedia, deleteMenu, deleteMenuItem, deletePasskey, deleteRedirect, deleteSection, deleteTerm, deleteWidget, deleteWidgetArea, describeCapability, disablePlugin, disableUser, discardDraft, duplicateContent, enablePlugin, enableUser, executeWpPluginImport, executeWxrImport, fetch404Summary, fetchAllowedDomains, fetchApiTokens, fetchAuthMode, fetchByline, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, installMarketplacePlugin, inviteUser, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, resolveLocale, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useAuthProviderList, useAuthProviders, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, validateInviteToken, verifySignupToken };
|
|
33658
34018
|
//# sourceMappingURL=index.js.map
|