@emdash-cms/admin 0.7.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-CI7U6BBr.js → LocaleDirectionProvider-BGcmv_N7.js} +26 -14
- package/dist/LocaleDirectionProvider-BGcmv_N7.js.map +1 -0
- package/dist/{config-D8i8U4bV.d.ts → config-YdN-uv6S.d.ts} +1 -1
- package/dist/{config-D8i8U4bV.d.ts.map → config-YdN-uv6S.d.ts.map} +1 -1
- package/dist/index.d.ts +148 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2080 -1257
- 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 -0
- 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-XhZqfegd.js → plugins-DUJY_8HN.js} +9 -2
- package/dist/plugins-DUJY_8HN.js.map +1 -0
- package/dist/styles.css +1 -1
- package/package.json +2 -4
- package/dist/LocaleDirectionProvider-CI7U6BBr.js.map +0 -1
- package/dist/messages-57qx1FXa.js +0 -6
- package/dist/messages-57qx1FXa.js.map +0 -1
- package/dist/messages-8uPA5VDB.js +0 -6
- package/dist/messages-8uPA5VDB.js.map +0 -1
- package/dist/messages-Bc3iE3Xx.js +0 -6
- package/dist/messages-Bc3iE3Xx.js.map +0 -1
- package/dist/messages-BudiTpRZ.js +0 -6
- package/dist/messages-BudiTpRZ.js.map +0 -1
- package/dist/messages-CLJeZfc0.js +0 -6
- package/dist/messages-CLJeZfc0.js.map +0 -1
- package/dist/messages-CXyBDum7.js +0 -6
- package/dist/messages-CXyBDum7.js.map +0 -1
- package/dist/messages-DFXk1gAB.js +0 -6
- package/dist/messages-DFXk1gAB.js.map +0 -1
- package/dist/messages-DHg9t0ML.js +0 -6
- package/dist/messages-DHg9t0ML.js.map +0 -1
- package/dist/messages-DT-_3Gqh.js +0 -6
- package/dist/messages-DT-_3Gqh.js.map +0 -1
- package/dist/messages-DYWnQ-qc.js +0 -6
- package/dist/messages-DYWnQ-qc.js.map +0 -1
- package/dist/messages-DrHBJ5Tm.js +0 -6
- package/dist/messages-DrHBJ5Tm.js.map +0 -1
- package/dist/messages-TukFV9EF.js +0 -6
- package/dist/messages-TukFV9EF.js.map +0 -1
- package/dist/messages-cadUq4Pc.js +0 -6
- package/dist/messages-cadUq4Pc.js.map +0 -1
- package/dist/plugins-XhZqfegd.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as
|
|
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-
|
|
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-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";
|
|
@@ -100,6 +100,34 @@ function useTheme() {
|
|
|
100
100
|
return context;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/lib/auth-provider-context.tsx
|
|
105
|
+
const AuthProviderContext = createContext({});
|
|
106
|
+
/**
|
|
107
|
+
* Provider that makes auth provider components available to all descendants
|
|
108
|
+
*/
|
|
109
|
+
function AuthProviderProvider({ children, authProviders }) {
|
|
110
|
+
return /* @__PURE__ */ jsx(AuthProviderContext.Provider, {
|
|
111
|
+
value: authProviders,
|
|
112
|
+
children
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get all auth provider modules
|
|
117
|
+
*/
|
|
118
|
+
function useAuthProviders() {
|
|
119
|
+
return useContext(AuthProviderContext);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get auth providers as an ordered array (buttons first, then forms)
|
|
123
|
+
*/
|
|
124
|
+
function useAuthProviderList() {
|
|
125
|
+
const providers = useContext(AuthProviderContext);
|
|
126
|
+
return Object.values(providers).toSorted((a, b) => {
|
|
127
|
+
return (a.LoginForm ? 1 : 0) - (b.LoginForm ? 1 : 0);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
103
131
|
//#endregion
|
|
104
132
|
//#region src/lib/plugin-context.tsx
|
|
105
133
|
const PluginAdminContext = createContext({});
|
|
@@ -191,6 +219,37 @@ function slugify(text) {
|
|
|
191
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, "");
|
|
192
220
|
}
|
|
193
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
|
+
|
|
194
253
|
//#endregion
|
|
195
254
|
//#region src/components/DialogError.tsx
|
|
196
255
|
/**
|
|
@@ -863,7 +922,7 @@ function CommentInbox({ comments, counts, isLoading, nextCursor, collections, ac
|
|
|
863
922
|
id: "aHKcKc",
|
|
864
923
|
message: "Previous page"
|
|
865
924
|
}),
|
|
866
|
-
children: /* @__PURE__ */ jsx(
|
|
925
|
+
children: /* @__PURE__ */ jsx(CaretPrev, { className: "h-4 w-4" })
|
|
867
926
|
}),
|
|
868
927
|
/* @__PURE__ */ jsxs("span", {
|
|
869
928
|
className: "text-sm",
|
|
@@ -887,7 +946,7 @@ function CommentInbox({ comments, counts, isLoading, nextCursor, collections, ac
|
|
|
887
946
|
id: "/dqduX",
|
|
888
947
|
message: "Next page"
|
|
889
948
|
}),
|
|
890
|
-
children: /* @__PURE__ */ jsx(
|
|
949
|
+
children: /* @__PURE__ */ jsx(CaretNext, { className: "h-4 w-4" })
|
|
891
950
|
})
|
|
892
951
|
]
|
|
893
952
|
})]
|
|
@@ -1105,6 +1164,8 @@ async function fetchContentList(collection, options) {
|
|
|
1105
1164
|
if (options?.limit) params.set("limit", String(options.limit));
|
|
1106
1165
|
if (options?.status) params.set("status", options.status);
|
|
1107
1166
|
if (options?.locale) params.set("locale", options.locale);
|
|
1167
|
+
if (options?.orderBy) params.set("orderBy", options.orderBy);
|
|
1168
|
+
if (options?.order) params.set("order", options.order);
|
|
1108
1169
|
return parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}${params.toString() ? `?${params}` : ""}`), "Failed to fetch content");
|
|
1109
1170
|
}
|
|
1110
1171
|
/**
|
|
@@ -1196,7 +1257,9 @@ async function unscheduleContent(collection, id) {
|
|
|
1196
1257
|
* Get a preview URL for content
|
|
1197
1258
|
*
|
|
1198
1259
|
* Returns a signed URL that allows viewing draft content.
|
|
1199
|
-
* 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.
|
|
1200
1263
|
*/
|
|
1201
1264
|
async function getPreviewUrl(collection, id, options) {
|
|
1202
1265
|
try {
|
|
@@ -2261,6 +2324,10 @@ const API_TOKEN_SCOPES = {
|
|
|
2261
2324
|
MediaWrite: "media:write",
|
|
2262
2325
|
SchemaRead: "schema:read",
|
|
2263
2326
|
SchemaWrite: "schema:write",
|
|
2327
|
+
TaxonomiesManage: "taxonomies:manage",
|
|
2328
|
+
MenusManage: "menus:manage",
|
|
2329
|
+
SettingsRead: "settings:read",
|
|
2330
|
+
SettingsManage: "settings:manage",
|
|
2264
2331
|
Admin: "admin"
|
|
2265
2332
|
};
|
|
2266
2333
|
/**
|
|
@@ -2455,22 +2522,37 @@ async function uninstallMarketplacePlugin(id, opts = {}) {
|
|
|
2455
2522
|
async function checkPluginUpdates() {
|
|
2456
2523
|
return (await parseApiResponse(await apiFetch(`${API_BASE}/admin/plugins/updates`), "Failed to check for updates")).items;
|
|
2457
2524
|
}
|
|
2458
|
-
/**
|
|
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
|
+
*/
|
|
2459
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)",
|
|
2460
2539
|
"read:content": "Read your content",
|
|
2461
2540
|
"write:content": "Create, update, and delete content",
|
|
2462
2541
|
"read:media": "Access your media library",
|
|
2463
2542
|
"write:media": "Upload and manage media",
|
|
2543
|
+
"read:users": "Read user accounts",
|
|
2464
2544
|
"network:fetch": "Make network requests",
|
|
2465
2545
|
"network:fetch:any": "Make network requests to any host (unrestricted)"
|
|
2466
2546
|
};
|
|
2547
|
+
/** Capability names that grant scoped network access (legacy + canonical). */
|
|
2548
|
+
const NETWORK_REQUEST_CAPABILITIES = new Set(["network:request", "network:fetch"]);
|
|
2467
2549
|
/**
|
|
2468
2550
|
* Get a human-readable description for a capability.
|
|
2469
|
-
* For network
|
|
2551
|
+
* For scoped network capabilities, appends the allowed hosts if provided.
|
|
2470
2552
|
*/
|
|
2471
2553
|
function describeCapability(capability, allowedHosts) {
|
|
2472
2554
|
const base = CAPABILITY_LABELS[capability] ?? capability;
|
|
2473
|
-
if (capability
|
|
2555
|
+
if (NETWORK_REQUEST_CAPABILITIES.has(capability) && allowedHosts && allowedHosts.length > 0) return `${base} to: ${allowedHosts.join(", ")}`;
|
|
2474
2556
|
return base;
|
|
2475
2557
|
}
|
|
2476
2558
|
|
|
@@ -2606,6 +2688,33 @@ function useCurrentUser() {
|
|
|
2606
2688
|
});
|
|
2607
2689
|
}
|
|
2608
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
|
+
|
|
2609
2718
|
//#endregion
|
|
2610
2719
|
//#region src/lib/url.ts
|
|
2611
2720
|
/**
|
|
@@ -2659,652 +2768,101 @@ function safeIconUrl(url, width) {
|
|
|
2659
2768
|
}
|
|
2660
2769
|
|
|
2661
2770
|
//#endregion
|
|
2662
|
-
//#region src/
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
}, [onChange]);
|
|
2678
|
-
const validElements = elements.filter((el) => el.action_id);
|
|
2679
|
-
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
|
|
2680
|
-
className: "text-sm font-medium leading-none",
|
|
2681
|
-
children: label
|
|
2682
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
2683
|
-
className: "mt-2 space-y-3",
|
|
2684
|
-
children: validElements.map((el) => /* @__PURE__ */ jsx(BlockKitFieldElement, {
|
|
2685
|
-
element: el,
|
|
2686
|
-
value: obj[el.action_id],
|
|
2687
|
-
onChange: handleElementChange
|
|
2688
|
-
}, el.action_id))
|
|
2689
|
-
})] });
|
|
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
|
+
};
|
|
2690
2786
|
}
|
|
2691
|
-
function
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
checked: !!value,
|
|
2711
|
-
onCheckedChange: (checked) => onChange(element.action_id, checked)
|
|
2712
|
-
});
|
|
2713
|
-
case "select": {
|
|
2714
|
-
const options = Array.isArray(element.options) ? element.options : [];
|
|
2715
|
-
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2716
|
-
className: "text-sm font-medium mb-1.5 block",
|
|
2717
|
-
children: element.label
|
|
2718
|
-
}), /* @__PURE__ */ jsxs("select", {
|
|
2719
|
-
className: "flex w-full rounded-md border border-kumo-line bg-transparent px-3 py-2 text-sm",
|
|
2720
|
-
value: typeof value === "string" ? value : "",
|
|
2721
|
-
onChange: (e) => onChange(element.action_id, e.target.value),
|
|
2722
|
-
children: [/* @__PURE__ */ jsx("option", {
|
|
2723
|
-
value: "",
|
|
2724
|
-
children: "Select..."
|
|
2725
|
-
}), options.map((opt) => /* @__PURE__ */ jsx("option", {
|
|
2726
|
-
value: opt.value,
|
|
2727
|
-
children: opt.label
|
|
2728
|
-
}, opt.value))]
|
|
2729
|
-
})] });
|
|
2730
|
-
}
|
|
2731
|
-
default: return /* @__PURE__ */ jsxs("div", {
|
|
2732
|
-
className: "text-sm text-kumo-subtle",
|
|
2733
|
-
children: ["Unsupported widget element type: ", element.type]
|
|
2734
|
-
});
|
|
2735
|
-
}
|
|
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];
|
|
2736
2806
|
}
|
|
2737
2807
|
|
|
2738
2808
|
//#endregion
|
|
2739
|
-
//#region src/components/
|
|
2809
|
+
//#region src/components/MediaPickerModal.tsx
|
|
2740
2810
|
/**
|
|
2741
|
-
*
|
|
2811
|
+
* Media Picker Modal
|
|
2742
2812
|
*
|
|
2743
|
-
*
|
|
2744
|
-
*
|
|
2745
|
-
*
|
|
2746
|
-
* - 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.
|
|
2747
2816
|
*/
|
|
2748
|
-
function getIndentClass(level) {
|
|
2749
|
-
switch (level) {
|
|
2750
|
-
case 1: return "ps-0";
|
|
2751
|
-
case 2: return "ps-4";
|
|
2752
|
-
case 3: return "ps-8";
|
|
2753
|
-
default: return "ps-0";
|
|
2754
|
-
}
|
|
2755
|
-
}
|
|
2756
|
-
function getTextClass(level) {
|
|
2757
|
-
switch (level) {
|
|
2758
|
-
case 1: return "font-medium";
|
|
2759
|
-
case 2: return "font-normal";
|
|
2760
|
-
case 3: return "font-normal text-kumo-subtle";
|
|
2761
|
-
default: return "font-normal";
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
2817
|
/**
|
|
2765
|
-
*
|
|
2818
|
+
* Probe image URL to get dimensions
|
|
2766
2819
|
*/
|
|
2767
|
-
function
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
const rawLevel = node.attrs.level;
|
|
2775
|
-
const level = typeof rawLevel === "number" ? rawLevel : 1;
|
|
2776
|
-
const text = node.textContent || "";
|
|
2777
|
-
if (text.trim()) headings.push({
|
|
2778
|
-
level,
|
|
2779
|
-
text,
|
|
2780
|
-
pos,
|
|
2781
|
-
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
|
|
2782
2827
|
});
|
|
2783
|
-
}
|
|
2784
|
-
});
|
|
2785
|
-
return headings;
|
|
2786
|
-
}
|
|
2787
|
-
/**
|
|
2788
|
-
* Find the current heading based on cursor position
|
|
2789
|
-
*/
|
|
2790
|
-
function findCurrentHeading(headings, cursorPos) {
|
|
2791
|
-
if (headings.length === 0) return null;
|
|
2792
|
-
let current = null;
|
|
2793
|
-
for (const heading of headings) if (heading.pos <= cursorPos) current = heading;
|
|
2794
|
-
else break;
|
|
2795
|
-
return current;
|
|
2796
|
-
}
|
|
2797
|
-
/**
|
|
2798
|
-
* Document outline component showing heading tree structure
|
|
2799
|
-
*/
|
|
2800
|
-
function DocumentOutline({ editor, className }) {
|
|
2801
|
-
const { _: _t } = useLingui();
|
|
2802
|
-
const [isExpanded, setIsExpanded] = React.useState(true);
|
|
2803
|
-
const [headings, setHeadings] = React.useState([]);
|
|
2804
|
-
const [currentPos, setCurrentPos] = React.useState(0);
|
|
2805
|
-
React.useEffect(() => {
|
|
2806
|
-
if (!editor) return;
|
|
2807
|
-
const updateHeadings = () => {
|
|
2808
|
-
setHeadings(extractHeadings(editor));
|
|
2809
|
-
};
|
|
2810
|
-
updateHeadings();
|
|
2811
|
-
editor.on("update", updateHeadings);
|
|
2812
|
-
return () => {
|
|
2813
|
-
editor.off("update", updateHeadings);
|
|
2814
|
-
};
|
|
2815
|
-
}, [editor]);
|
|
2816
|
-
React.useEffect(() => {
|
|
2817
|
-
if (!editor) return;
|
|
2818
|
-
const updatePosition = () => {
|
|
2819
|
-
const { from } = editor.state.selection;
|
|
2820
|
-
setCurrentPos(from);
|
|
2821
|
-
};
|
|
2822
|
-
updatePosition();
|
|
2823
|
-
editor.on("selectionUpdate", updatePosition);
|
|
2824
|
-
return () => {
|
|
2825
|
-
editor.off("selectionUpdate", updatePosition);
|
|
2826
|
-
};
|
|
2827
|
-
}, [editor]);
|
|
2828
|
-
const currentHeading = findCurrentHeading(headings, currentPos);
|
|
2829
|
-
const handleHeadingClick = (heading) => {
|
|
2830
|
-
if (!editor) return;
|
|
2831
|
-
editor.chain().focus().setTextSelection(heading.pos).scrollIntoView().run();
|
|
2832
|
-
};
|
|
2833
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
2834
|
-
className: cn("space-y-2", className),
|
|
2835
|
-
children: [/* @__PURE__ */ jsxs(Button, {
|
|
2836
|
-
variant: "ghost",
|
|
2837
|
-
size: "sm",
|
|
2838
|
-
className: "w-full justify-between px-2 h-8",
|
|
2839
|
-
onClick: () => setIsExpanded(!isExpanded),
|
|
2840
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
2841
|
-
className: "flex items-center gap-2",
|
|
2842
|
-
children: [/* @__PURE__ */ jsx(List, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
|
|
2843
|
-
className: "font-semibold",
|
|
2844
|
-
children: _t({
|
|
2845
|
-
id: "dD7NPy",
|
|
2846
|
-
message: "Outline"
|
|
2847
|
-
})
|
|
2848
|
-
})]
|
|
2849
|
-
}), isExpanded ? /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(CaretRight, { className: "h-4 w-4" })]
|
|
2850
|
-
}), isExpanded && /* @__PURE__ */ jsx("div", {
|
|
2851
|
-
className: "space-y-0.5",
|
|
2852
|
-
children: headings.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
2853
|
-
className: "text-sm text-kumo-subtle px-2 py-1",
|
|
2854
|
-
children: _t({
|
|
2855
|
-
id: "MMd//L",
|
|
2856
|
-
message: "No headings in document"
|
|
2857
|
-
})
|
|
2858
|
-
}) : headings.map((heading) => {
|
|
2859
|
-
const isCurrent = currentHeading?.key === heading.key;
|
|
2860
|
-
return /* @__PURE__ */ jsx("button", {
|
|
2861
|
-
type: "button",
|
|
2862
|
-
onClick: () => handleHeadingClick(heading),
|
|
2863
|
-
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"),
|
|
2864
|
-
title: heading.text,
|
|
2865
|
-
children: heading.text
|
|
2866
|
-
}, heading.key);
|
|
2867
|
-
})
|
|
2868
|
-
})]
|
|
2869
|
-
});
|
|
2870
|
-
}
|
|
2871
|
-
|
|
2872
|
-
//#endregion
|
|
2873
|
-
//#region src/components/PluginFieldErrorBoundary.tsx
|
|
2874
|
-
/**
|
|
2875
|
-
* Error boundary that wraps trusted plugin field widgets.
|
|
2876
|
-
* On render error, shows a warning with a retry button instead of crashing the editor.
|
|
2877
|
-
*/
|
|
2878
|
-
var PluginFieldErrorBoundary = class extends React.Component {
|
|
2879
|
-
constructor(props) {
|
|
2880
|
-
super(props);
|
|
2881
|
-
this.state = { hasError: false };
|
|
2882
|
-
}
|
|
2883
|
-
static getDerivedStateFromError(error) {
|
|
2884
|
-
return {
|
|
2885
|
-
hasError: true,
|
|
2886
|
-
error
|
|
2887
2828
|
};
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
if (this.state.hasError) return /* @__PURE__ */ jsxs("div", {
|
|
2891
|
-
className: "rounded-md border border-kumo-danger/50 bg-kumo-danger/5 p-3",
|
|
2892
|
-
children: [
|
|
2893
|
-
/* @__PURE__ */ jsx("p", {
|
|
2894
|
-
className: "text-sm font-medium text-kumo-danger",
|
|
2895
|
-
children: "Plugin widget error"
|
|
2896
|
-
}),
|
|
2897
|
-
/* @__PURE__ */ jsx("p", {
|
|
2898
|
-
className: "mt-1 text-xs text-kumo-subtle",
|
|
2899
|
-
children: this.state.error?.message || "The plugin field widget failed to render."
|
|
2900
|
-
}),
|
|
2901
|
-
/* @__PURE__ */ jsx("button", {
|
|
2902
|
-
type: "button",
|
|
2903
|
-
className: "mt-2 text-xs font-medium text-kumo-brand underline",
|
|
2904
|
-
onClick: () => this.setState({
|
|
2905
|
-
hasError: false,
|
|
2906
|
-
error: void 0
|
|
2907
|
-
}),
|
|
2908
|
-
children: "Retry"
|
|
2909
|
-
})
|
|
2910
|
-
]
|
|
2911
|
-
});
|
|
2912
|
-
return this.props.children;
|
|
2913
|
-
}
|
|
2914
|
-
};
|
|
2915
|
-
|
|
2916
|
-
//#endregion
|
|
2917
|
-
//#region src/components/RepeaterField.tsx
|
|
2918
|
-
/**
|
|
2919
|
-
* RepeaterField — renders a list of repeating sub-field groups in the content editor.
|
|
2920
|
-
*
|
|
2921
|
-
* Each item is a collapsible card containing the defined sub-fields.
|
|
2922
|
-
* Items can be added, removed, and reordered via drag-and-drop.
|
|
2923
|
-
*/
|
|
2924
|
-
function ensureKeys(items) {
|
|
2925
|
-
return items.map((item, i) => {
|
|
2926
|
-
const obj = typeof item === "object" && item !== null ? item : {};
|
|
2927
|
-
return {
|
|
2928
|
-
...obj,
|
|
2929
|
-
_key: obj._key || `item-${i}-${Date.now()}`
|
|
2829
|
+
img.onerror = () => {
|
|
2830
|
+
reject(/* @__PURE__ */ new Error("Failed to load image"));
|
|
2930
2831
|
};
|
|
2832
|
+
img.src = url;
|
|
2931
2833
|
});
|
|
2932
2834
|
}
|
|
2933
|
-
function
|
|
2934
|
-
return items.map(({ _key, ...rest }) => rest);
|
|
2935
|
-
}
|
|
2936
|
-
function RepeaterField({ label, id, value, onChange, subFields, minItems = 0, maxItems }) {
|
|
2835
|
+
function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "image/", title: providedTitle }) {
|
|
2937
2836
|
const { _: _t } = useLingui();
|
|
2938
|
-
const
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
React.useEffect(() => {
|
|
2942
|
-
const incoming = Array.isArray(value) ? value : [];
|
|
2943
|
-
setItems((prev) => incoming.map((item, i) => {
|
|
2944
|
-
const obj = typeof item === "object" && item !== null ? item : {};
|
|
2945
|
-
const existingKey = obj._key || prev[i]?._key;
|
|
2946
|
-
return {
|
|
2947
|
-
...obj,
|
|
2948
|
-
_key: existingKey || `item-${i}-${Date.now()}`
|
|
2949
|
-
};
|
|
2950
|
-
}));
|
|
2951
|
-
}, [value]);
|
|
2952
|
-
const emitChange = (updated) => {
|
|
2953
|
-
setItems(updated);
|
|
2954
|
-
onChange(stripKeys(updated));
|
|
2955
|
-
};
|
|
2956
|
-
const handleAdd = () => {
|
|
2957
|
-
if (maxItems && items.length >= maxItems) return;
|
|
2958
|
-
const newItem = { _key: `item-${Date.now()}` };
|
|
2959
|
-
for (const sf of subFields) newItem[sf.slug] = sf.type === "boolean" ? false : sf.type === "number" || sf.type === "integer" ? null : "";
|
|
2960
|
-
emitChange([...items, newItem]);
|
|
2961
|
-
};
|
|
2962
|
-
const handleRemove = (key) => {
|
|
2963
|
-
if (items.length <= minItems) return;
|
|
2964
|
-
emitChange(items.filter((item) => item._key !== key));
|
|
2965
|
-
};
|
|
2966
|
-
const handleItemChange = (key, fieldSlug, fieldValue) => {
|
|
2967
|
-
emitChange(items.map((item) => item._key === key ? {
|
|
2968
|
-
...item,
|
|
2969
|
-
[fieldSlug]: fieldValue
|
|
2970
|
-
} : item));
|
|
2971
|
-
};
|
|
2972
|
-
const handleDragEnd = (event) => {
|
|
2973
|
-
const { active, over } = event;
|
|
2974
|
-
if (!over || active.id === over.id) return;
|
|
2975
|
-
const oldIndex = items.findIndex((item) => item._key === active.id);
|
|
2976
|
-
const newIndex = items.findIndex((item) => item._key === over.id);
|
|
2977
|
-
if (oldIndex === -1 || newIndex === -1) return;
|
|
2978
|
-
emitChange(arrayMove(items, oldIndex, newIndex));
|
|
2979
|
-
};
|
|
2980
|
-
const toggleCollapse = (key) => {
|
|
2981
|
-
setCollapsedItems((prev) => {
|
|
2982
|
-
const next = new Set(prev);
|
|
2983
|
-
if (next.has(key)) next.delete(key);
|
|
2984
|
-
else next.add(key);
|
|
2985
|
-
return next;
|
|
2986
|
-
});
|
|
2987
|
-
};
|
|
2988
|
-
const canAdd = !maxItems || items.length < maxItems;
|
|
2989
|
-
const canRemove = items.length > minItems;
|
|
2990
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
2991
|
-
className: "space-y-2",
|
|
2992
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
2993
|
-
className: "flex items-center justify-between",
|
|
2994
|
-
children: [/* @__PURE__ */ jsxs("label", {
|
|
2995
|
-
htmlFor: id,
|
|
2996
|
-
className: "text-sm font-medium",
|
|
2997
|
-
children: [label, items.length > 0 && /* @__PURE__ */ jsx("span", {
|
|
2998
|
-
className: "ms-2 text-kumo-subtle font-normal",
|
|
2999
|
-
children: i18n._({
|
|
3000
|
-
id: "JcH6Wc",
|
|
3001
|
-
message: "{0, plural, one {(# item)} other {(# items)}}",
|
|
3002
|
-
values: { 0: items.length }
|
|
3003
|
-
})
|
|
3004
|
-
})]
|
|
3005
|
-
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
3006
|
-
variant: "outline",
|
|
3007
|
-
size: "sm",
|
|
3008
|
-
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
3009
|
-
onClick: handleAdd,
|
|
3010
|
-
children: _t({
|
|
3011
|
-
id: "qgF8ew",
|
|
3012
|
-
message: "Add Item"
|
|
3013
|
-
})
|
|
3014
|
-
})]
|
|
3015
|
-
}), items.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
3016
|
-
className: "border-2 border-dashed rounded-lg p-6 text-center text-kumo-subtle",
|
|
3017
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
3018
|
-
className: "text-sm",
|
|
3019
|
-
children: _t({
|
|
3020
|
-
id: "bWmJ0A",
|
|
3021
|
-
message: "No items yet"
|
|
3022
|
-
})
|
|
3023
|
-
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
3024
|
-
variant: "outline",
|
|
3025
|
-
size: "sm",
|
|
3026
|
-
className: "mt-2",
|
|
3027
|
-
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
3028
|
-
onClick: handleAdd,
|
|
3029
|
-
children: _t({
|
|
3030
|
-
id: "924qJl",
|
|
3031
|
-
message: "Add First Item"
|
|
3032
|
-
})
|
|
3033
|
-
})]
|
|
3034
|
-
}) : /* @__PURE__ */ jsx(DndContext, {
|
|
3035
|
-
collisionDetection: closestCenter,
|
|
3036
|
-
onDragEnd: handleDragEnd,
|
|
3037
|
-
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
3038
|
-
items: items.map((item) => item._key),
|
|
3039
|
-
strategy: verticalListSortingStrategy,
|
|
3040
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
3041
|
-
className: "space-y-2",
|
|
3042
|
-
children: items.map((item, index) => /* @__PURE__ */ jsx(SortableRepeaterItem, {
|
|
3043
|
-
item,
|
|
3044
|
-
index,
|
|
3045
|
-
subFields,
|
|
3046
|
-
isCollapsed: collapsedItems.has(item._key),
|
|
3047
|
-
onToggleCollapse: () => toggleCollapse(item._key),
|
|
3048
|
-
onRemove: canRemove ? () => handleRemove(item._key) : void 0,
|
|
3049
|
-
onChange: (fieldSlug, fieldValue) => handleItemChange(item._key, fieldSlug, fieldValue)
|
|
3050
|
-
}, item._key))
|
|
3051
|
-
})
|
|
3052
|
-
})
|
|
3053
|
-
})]
|
|
2837
|
+
const title = providedTitle ?? _t({
|
|
2838
|
+
id: "bAiEJP",
|
|
2839
|
+
message: "Select Image"
|
|
3054
2840
|
});
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
const
|
|
3058
|
-
const
|
|
3059
|
-
const
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
const
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
...listeners,
|
|
3081
|
-
onClick: (e) => e.stopPropagation()
|
|
3082
|
-
}),
|
|
3083
|
-
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" }),
|
|
3084
|
-
/* @__PURE__ */ jsx("span", {
|
|
3085
|
-
className: "text-sm font-medium flex-1 truncate",
|
|
3086
|
-
children: summaryLabel
|
|
3087
|
-
}),
|
|
3088
|
-
onRemove && /* @__PURE__ */ jsx(Button, {
|
|
3089
|
-
variant: "ghost",
|
|
3090
|
-
shape: "square",
|
|
3091
|
-
onClick: (e) => {
|
|
3092
|
-
e.stopPropagation();
|
|
3093
|
-
onRemove();
|
|
3094
|
-
},
|
|
3095
|
-
"aria-label": _t2({
|
|
3096
|
-
id: "mU3j1b",
|
|
3097
|
-
message: "Remove item {0}",
|
|
3098
|
-
values: { 0: index + 1 }
|
|
3099
|
-
}),
|
|
3100
|
-
children: /* @__PURE__ */ jsx(Trash, { className: "h-3.5 w-3.5 text-kumo-danger" })
|
|
3101
|
-
})
|
|
3102
|
-
]
|
|
3103
|
-
}), !isCollapsed && /* @__PURE__ */ jsx("div", {
|
|
3104
|
-
className: "p-3 space-y-3",
|
|
3105
|
-
children: subFields.map((sf) => /* @__PURE__ */ jsx(SubFieldInput, {
|
|
3106
|
-
subField: sf,
|
|
3107
|
-
value: item[sf.slug],
|
|
3108
|
-
onChange: (v) => onChange(sf.slug, v)
|
|
3109
|
-
}, sf.slug))
|
|
3110
|
-
})]
|
|
3111
|
-
});
|
|
3112
|
-
}
|
|
3113
|
-
function SubFieldInput({ subField, value, onChange }) {
|
|
3114
|
-
const { _: _t3 } = useLingui();
|
|
3115
|
-
switch (subField.type) {
|
|
3116
|
-
case "string": return /* @__PURE__ */ jsx(Input, {
|
|
3117
|
-
label: subField.label,
|
|
3118
|
-
value: typeof value === "string" ? value : "",
|
|
3119
|
-
onChange: (e) => onChange(e.target.value),
|
|
3120
|
-
required: subField.required
|
|
3121
|
-
});
|
|
3122
|
-
case "text": return /* @__PURE__ */ jsx(InputArea, {
|
|
3123
|
-
label: subField.label,
|
|
3124
|
-
value: typeof value === "string" ? value : "",
|
|
3125
|
-
onChange: (e) => onChange(e.target.value),
|
|
3126
|
-
required: subField.required,
|
|
3127
|
-
rows: 3
|
|
3128
|
-
});
|
|
3129
|
-
case "number":
|
|
3130
|
-
case "integer": return /* @__PURE__ */ jsx(Input, {
|
|
3131
|
-
label: subField.label,
|
|
3132
|
-
type: "number",
|
|
3133
|
-
value: typeof value === "number" ? String(value) : "",
|
|
3134
|
-
onChange: (e) => onChange(e.target.value ? Number(e.target.value) : null),
|
|
3135
|
-
required: subField.required,
|
|
3136
|
-
step: subField.type === "integer" ? "1" : "any"
|
|
3137
|
-
});
|
|
3138
|
-
case "boolean": return /* @__PURE__ */ jsxs("label", {
|
|
3139
|
-
className: "flex items-center gap-2",
|
|
3140
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
3141
|
-
type: "checkbox",
|
|
3142
|
-
checked: Boolean(value),
|
|
3143
|
-
onChange: (e) => onChange(e.target.checked)
|
|
3144
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
3145
|
-
className: "text-sm",
|
|
3146
|
-
children: subField.label
|
|
3147
|
-
})]
|
|
3148
|
-
});
|
|
3149
|
-
case "datetime": return /* @__PURE__ */ jsx(Input, {
|
|
3150
|
-
label: subField.label,
|
|
3151
|
-
type: "datetime-local",
|
|
3152
|
-
value: typeof value === "string" ? value : "",
|
|
3153
|
-
onChange: (e) => onChange(e.target.value),
|
|
3154
|
-
required: subField.required
|
|
3155
|
-
});
|
|
3156
|
-
case "select": return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
3157
|
-
className: "text-sm font-medium",
|
|
3158
|
-
children: subField.label
|
|
3159
|
-
}), /* @__PURE__ */ jsxs("select", {
|
|
3160
|
-
className: "w-full mt-1 rounded-md border px-3 py-2 text-sm",
|
|
3161
|
-
value: typeof value === "string" ? value : "",
|
|
3162
|
-
onChange: (e) => onChange(e.target.value),
|
|
3163
|
-
required: subField.required,
|
|
3164
|
-
children: [/* @__PURE__ */ jsx("option", {
|
|
3165
|
-
value: "",
|
|
3166
|
-
children: _t3({
|
|
3167
|
-
id: "O/7I0o",
|
|
3168
|
-
message: "Select..."
|
|
3169
|
-
})
|
|
3170
|
-
}), subField.options?.map((opt) => /* @__PURE__ */ jsx("option", {
|
|
3171
|
-
value: opt,
|
|
3172
|
-
children: opt
|
|
3173
|
-
}, opt))]
|
|
3174
|
-
})] });
|
|
3175
|
-
default: return /* @__PURE__ */ jsx(Input, {
|
|
3176
|
-
label: subField.label,
|
|
3177
|
-
value: typeof value === "string" ? value : "",
|
|
3178
|
-
onChange: (e) => onChange(e.target.value)
|
|
3179
|
-
});
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
|
|
3183
|
-
//#endregion
|
|
3184
|
-
//#region src/lib/hooks.ts
|
|
3185
|
-
/**
|
|
3186
|
-
* Returns a stable function reference that always calls the latest version
|
|
3187
|
-
* of the provided callback. Useful for event listeners in effects where
|
|
3188
|
-
* you don't want the listener to be torn down and re-added when the
|
|
3189
|
-
* callback identity changes.
|
|
3190
|
-
*/
|
|
3191
|
-
function useStableCallback(callback) {
|
|
3192
|
-
const ref = React.useRef(callback);
|
|
3193
|
-
React.useLayoutEffect(() => {
|
|
3194
|
-
ref.current = callback;
|
|
3195
|
-
});
|
|
3196
|
-
return React.useCallback((...args) => ref.current(...args), []);
|
|
3197
|
-
}
|
|
3198
|
-
/**
|
|
3199
|
-
* Returns a debounced version of a value that only updates after the
|
|
3200
|
-
* specified delay has elapsed since the last change. Useful for search
|
|
3201
|
-
* inputs that trigger API calls.
|
|
3202
|
-
*/
|
|
3203
|
-
function useDebouncedValue$1(value, delay) {
|
|
3204
|
-
const [debouncedValue, setDebouncedValue] = React.useState(value);
|
|
3205
|
-
React.useEffect(() => {
|
|
3206
|
-
const timer = setTimeout(setDebouncedValue, delay, value);
|
|
3207
|
-
return () => clearTimeout(timer);
|
|
3208
|
-
}, [value, delay]);
|
|
3209
|
-
return debouncedValue;
|
|
3210
|
-
}
|
|
3211
|
-
|
|
3212
|
-
//#endregion
|
|
3213
|
-
//#region src/lib/media-utils.ts
|
|
3214
|
-
function providerItemToMediaItem(providerId, item) {
|
|
3215
|
-
return {
|
|
3216
|
-
id: item.id,
|
|
3217
|
-
filename: item.filename,
|
|
3218
|
-
mimeType: item.mimeType,
|
|
3219
|
-
url: item.previewUrl || "",
|
|
3220
|
-
size: item.size || 0,
|
|
3221
|
-
width: item.width,
|
|
3222
|
-
height: item.height,
|
|
3223
|
-
alt: item.alt,
|
|
3224
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3225
|
-
provider: providerId,
|
|
3226
|
-
meta: item.meta
|
|
3227
|
-
};
|
|
3228
|
-
}
|
|
3229
|
-
function getFileIcon(mimeType) {
|
|
3230
|
-
if (mimeType.startsWith("video/")) return "🎬";
|
|
3231
|
-
if (mimeType.startsWith("audio/")) return "🎵";
|
|
3232
|
-
if (mimeType.includes("pdf")) return "📄";
|
|
3233
|
-
if (mimeType.includes("document") || mimeType.includes("word")) return "📝";
|
|
3234
|
-
if (mimeType.includes("spreadsheet") || mimeType.includes("excel")) return "📊";
|
|
3235
|
-
return "📁";
|
|
3236
|
-
}
|
|
3237
|
-
function formatFileSize(bytes) {
|
|
3238
|
-
if (bytes === 0) return "0 B";
|
|
3239
|
-
const k = 1024;
|
|
3240
|
-
const sizes = [
|
|
3241
|
-
"B",
|
|
3242
|
-
"KB",
|
|
3243
|
-
"MB",
|
|
3244
|
-
"GB"
|
|
3245
|
-
];
|
|
3246
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
3247
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
3248
|
-
}
|
|
3249
|
-
|
|
3250
|
-
//#endregion
|
|
3251
|
-
//#region src/components/MediaPickerModal.tsx
|
|
3252
|
-
/**
|
|
3253
|
-
* Media Picker Modal
|
|
3254
|
-
*
|
|
3255
|
-
* A modal dialog for selecting media from the library or uploading new files.
|
|
3256
|
-
* Supports multiple media providers with tabbed navigation.
|
|
3257
|
-
* Used by the rich text editor and image field components.
|
|
3258
|
-
*/
|
|
3259
|
-
/**
|
|
3260
|
-
* Probe image URL to get dimensions
|
|
3261
|
-
*/
|
|
3262
|
-
function probeImageDimensions(url) {
|
|
3263
|
-
return new Promise((resolve, reject) => {
|
|
3264
|
-
const img = new window.Image();
|
|
3265
|
-
img.onload = () => {
|
|
3266
|
-
resolve({
|
|
3267
|
-
width: img.naturalWidth,
|
|
3268
|
-
height: img.naturalHeight
|
|
3269
|
-
});
|
|
3270
|
-
};
|
|
3271
|
-
img.onerror = () => {
|
|
3272
|
-
reject(/* @__PURE__ */ new Error("Failed to load image"));
|
|
3273
|
-
};
|
|
3274
|
-
img.src = url;
|
|
3275
|
-
});
|
|
3276
|
-
}
|
|
3277
|
-
function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "image/", title: providedTitle }) {
|
|
3278
|
-
const { _: _t } = useLingui();
|
|
3279
|
-
const title = providedTitle ?? _t({
|
|
3280
|
-
id: "bAiEJP",
|
|
3281
|
-
message: "Select Image"
|
|
3282
|
-
});
|
|
3283
|
-
const queryClient = useQueryClient();
|
|
3284
|
-
const [selectedItem, setSelectedItem] = React.useState(null);
|
|
3285
|
-
const [activeProvider, setActiveProvider] = React.useState("local");
|
|
3286
|
-
const [searchQuery, setSearchQuery] = React.useState("");
|
|
3287
|
-
const fileInputRef = React.useRef(null);
|
|
3288
|
-
const [imageUrl, setImageUrl] = React.useState("");
|
|
3289
|
-
const [isProbing, setIsProbing] = React.useState(false);
|
|
3290
|
-
const [urlError, setUrlError] = React.useState(null);
|
|
3291
|
-
const [providerDimensions, setProviderDimensions] = React.useState({});
|
|
3292
|
-
React.useEffect(() => {
|
|
3293
|
-
if (open) {
|
|
3294
|
-
setSelectedItem(null);
|
|
3295
|
-
setActiveProvider("local");
|
|
3296
|
-
setSearchQuery("");
|
|
3297
|
-
setImageUrl("");
|
|
3298
|
-
setUrlError(null);
|
|
3299
|
-
setUploadError(null);
|
|
3300
|
-
setProviderDimensions({});
|
|
3301
|
-
}
|
|
3302
|
-
}, [open]);
|
|
3303
|
-
const { data: providers } = useQuery({
|
|
3304
|
-
queryKey: ["media-providers"],
|
|
3305
|
-
queryFn: fetchMediaProviders,
|
|
3306
|
-
enabled: open,
|
|
3307
|
-
placeholderData: []
|
|
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({});
|
|
2850
|
+
React.useEffect(() => {
|
|
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: []
|
|
3308
2866
|
});
|
|
3309
2867
|
const activeProviderInfo = React.useMemo(() => {
|
|
3310
2868
|
if (activeProvider === "local") return {
|
|
@@ -3818,139 +3376,805 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
|
|
|
3818
3376
|
})
|
|
3819
3377
|
]
|
|
3820
3378
|
})
|
|
3821
|
-
]
|
|
3822
|
-
})
|
|
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"
|
|
3979
|
+
})
|
|
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
|
+
})]
|
|
3823
4020
|
});
|
|
3824
4021
|
}
|
|
3825
|
-
function
|
|
4022
|
+
function SortableRepeaterItem({ item, index, subFields, isCollapsed, onToggleCollapse, onRemove, onChange }) {
|
|
3826
4023
|
const { _: _t2 } = useLingui();
|
|
3827
|
-
const
|
|
3828
|
-
const
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
"
|
|
3842
|
-
children: /* @__PURE__ */ jsxs("
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
onClick,
|
|
3846
|
-
onDoubleClick,
|
|
3847
|
-
"aria-label": _t2({
|
|
3848
|
-
id: "qSiUsc",
|
|
3849
|
-
message: "{0}{1}",
|
|
3850
|
-
values: {
|
|
3851
|
-
0: item.filename,
|
|
3852
|
-
1: selected ? _t2({
|
|
3853
|
-
id: "QbxreS",
|
|
3854
|
-
message: " (selected)"
|
|
3855
|
-
}) : ""
|
|
3856
|
-
}
|
|
3857
|
-
}),
|
|
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,
|
|
3858
4042
|
children: [
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
3865
|
-
className: "flex h-full w-full items-center justify-center bg-kumo-tint",
|
|
3866
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
3867
|
-
className: "text-3xl",
|
|
3868
|
-
"aria-hidden": "true",
|
|
3869
|
-
children: getFileIcon(item.mimeType)
|
|
3870
|
-
})
|
|
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()
|
|
3871
4048
|
}),
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
"
|
|
3875
|
-
children:
|
|
3876
|
-
className: "bg-kumo-brand text-white rounded-full p-1",
|
|
3877
|
-
children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })
|
|
3878
|
-
})
|
|
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
|
|
3879
4053
|
}),
|
|
3880
|
-
/* @__PURE__ */ jsx(
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
}
|
|
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" })
|
|
3887
4067
|
})
|
|
3888
4068
|
]
|
|
3889
|
-
})
|
|
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
|
+
})]
|
|
3890
4077
|
});
|
|
3891
4078
|
}
|
|
3892
|
-
function
|
|
4079
|
+
function SubFieldInput({ subField, value, onChange }) {
|
|
3893
4080
|
const { _: _t3 } = useLingui();
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
children: [
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
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..."
|
|
3950
4137
|
})
|
|
3951
|
-
|
|
3952
|
-
|
|
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;
|
|
3953
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;
|
|
3954
4178
|
}
|
|
3955
4179
|
|
|
3956
4180
|
//#endregion
|
|
@@ -5062,7 +5286,7 @@ function BlockMenu({ editor, anchorElement, isOpen, onClose }) {
|
|
|
5062
5286
|
type: "button",
|
|
5063
5287
|
className: "flex items-center gap-2 w-full px-3 py-2 text-sm hover:bg-kumo-tint text-start",
|
|
5064
5288
|
onClick: () => setShowTransforms(false),
|
|
5065
|
-
children: [/* @__PURE__ */ jsx(
|
|
5289
|
+
children: [/* @__PURE__ */ jsx(CaretPrev, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", { children: "Back" })]
|
|
5066
5290
|
}),
|
|
5067
5291
|
/* @__PURE__ */ jsx("div", { className: "h-px bg-kumo-line my-1" }),
|
|
5068
5292
|
blockTransforms.map((transform) => /* @__PURE__ */ jsxs("button", {
|
|
@@ -5082,7 +5306,7 @@ function BlockMenu({ editor, anchorElement, isOpen, onClose }) {
|
|
|
5082
5306
|
children: [/* @__PURE__ */ jsxs("span", {
|
|
5083
5307
|
className: "flex items-center gap-2",
|
|
5084
5308
|
children: [/* @__PURE__ */ jsx(Paragraph, { className: "h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsx("span", { children: "Turn into" })]
|
|
5085
|
-
}), /* @__PURE__ */ jsx(
|
|
5309
|
+
}), /* @__PURE__ */ jsx(CaretNext, { className: "h-4 w-4 text-kumo-subtle" })]
|
|
5086
5310
|
}),
|
|
5087
5311
|
/* @__PURE__ */ jsxs("button", {
|
|
5088
5312
|
type: "button",
|
|
@@ -6638,6 +6862,24 @@ function SlashCommandMenu({ state, onCommand, onClose: _onClose, setSelectedInde
|
|
|
6638
6862
|
}, item.id))
|
|
6639
6863
|
}), document.body);
|
|
6640
6864
|
}
|
|
6865
|
+
function getPluginBlockDefaultValues(fields) {
|
|
6866
|
+
const defaults = {};
|
|
6867
|
+
for (const field of fields ?? []) {
|
|
6868
|
+
const initialValue = "initial_value" in field ? field.initial_value : void 0;
|
|
6869
|
+
if (initialValue !== void 0) defaults[field.action_id] = initialValue;
|
|
6870
|
+
}
|
|
6871
|
+
return defaults;
|
|
6872
|
+
}
|
|
6873
|
+
function buildPluginBlockFormValues(block, initialValues) {
|
|
6874
|
+
const defaults = getPluginBlockDefaultValues(block?.fields);
|
|
6875
|
+
return initialValues ? {
|
|
6876
|
+
...defaults,
|
|
6877
|
+
...initialValues
|
|
6878
|
+
} : defaults;
|
|
6879
|
+
}
|
|
6880
|
+
function hasPluginBlockFormData(values) {
|
|
6881
|
+
return Object.values(values).some((value) => value !== void 0 && value !== null && value !== "");
|
|
6882
|
+
}
|
|
6641
6883
|
/**
|
|
6642
6884
|
* Plugin block insertion/editing modal.
|
|
6643
6885
|
* When the block has `fields`, renders Block Kit elements.
|
|
@@ -6648,8 +6890,7 @@ function PluginBlockModal({ block, initialValues, onClose, onInsert }) {
|
|
|
6648
6890
|
const inputRef = React.useRef(null);
|
|
6649
6891
|
React.useEffect(() => {
|
|
6650
6892
|
if (block) {
|
|
6651
|
-
|
|
6652
|
-
else setFormValues({});
|
|
6893
|
+
setFormValues(buildPluginBlockFormValues(block, initialValues));
|
|
6653
6894
|
if (!block.fields || block.fields.length === 0) setTimeout(() => inputRef.current?.focus(), 0);
|
|
6654
6895
|
}
|
|
6655
6896
|
}, [block, initialValues]);
|
|
@@ -6669,13 +6910,20 @@ function PluginBlockModal({ block, initialValues, onClose, onInsert }) {
|
|
|
6669
6910
|
};
|
|
6670
6911
|
const isEditing = !!initialValues;
|
|
6671
6912
|
const hasFields = block?.fields && block.fields.length > 0;
|
|
6672
|
-
const canSubmit = hasFields ?
|
|
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
|
+
})();
|
|
6673
6921
|
return /* @__PURE__ */ jsx(Dialog.Root, {
|
|
6674
6922
|
open: !!block,
|
|
6675
6923
|
onOpenChange: (open) => !open && onClose(),
|
|
6676
6924
|
children: /* @__PURE__ */ jsxs(Dialog, {
|
|
6677
6925
|
className: "p-6",
|
|
6678
|
-
size:
|
|
6926
|
+
size: dialogSize,
|
|
6679
6927
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
6680
6928
|
className: "flex items-start justify-between gap-4 mb-4",
|
|
6681
6929
|
children: [/* @__PURE__ */ jsxs(Dialog.Title, {
|
|
@@ -6702,7 +6950,7 @@ function PluginBlockModal({ block, initialValues, onClose, onInsert }) {
|
|
|
6702
6950
|
}), /* @__PURE__ */ jsxs("form", {
|
|
6703
6951
|
onSubmit: handleSubmit,
|
|
6704
6952
|
children: [/* @__PURE__ */ jsx("div", {
|
|
6705
|
-
className: "py-4 space-y-4",
|
|
6953
|
+
className: "py-4 space-y-4 max-h-[70vh] overflow-y-auto -mx-1 px-1",
|
|
6706
6954
|
children: hasFields ? block.fields.map((field) => /* @__PURE__ */ jsx(BlockKitField, {
|
|
6707
6955
|
field,
|
|
6708
6956
|
pluginId: block.pluginId,
|
|
@@ -6711,6 +6959,7 @@ function PluginBlockModal({ block, initialValues, onClose, onInsert }) {
|
|
|
6711
6959
|
}, field.action_id)) : /* @__PURE__ */ jsx(Input, {
|
|
6712
6960
|
ref: inputRef,
|
|
6713
6961
|
type: "url",
|
|
6962
|
+
className: "w-full",
|
|
6714
6963
|
placeholder: block?.placeholder || "Enter URL...",
|
|
6715
6964
|
value: typeof formValues.id === "string" ? formValues.id : "",
|
|
6716
6965
|
onChange: (e) => handleFieldChange("id", e.target.value)
|
|
@@ -6752,6 +7001,7 @@ function BlockKitField({ field, pluginId, value, onChange }) {
|
|
|
6752
7001
|
onChange: (e) => onChange(field.action_id, e.target.value)
|
|
6753
7002
|
}) : /* @__PURE__ */ jsx(Input, {
|
|
6754
7003
|
type: "text",
|
|
7004
|
+
className: "w-full",
|
|
6755
7005
|
placeholder,
|
|
6756
7006
|
value: typeof value === "string" ? value : "",
|
|
6757
7007
|
onChange: (e) => onChange(field.action_id, e.target.value)
|
|
@@ -6765,6 +7015,7 @@ function BlockKitField({ field, pluginId, value, onChange }) {
|
|
|
6765
7015
|
children: field.label
|
|
6766
7016
|
}), /* @__PURE__ */ jsx(Input, {
|
|
6767
7017
|
type: "number",
|
|
7018
|
+
className: "w-full",
|
|
6768
7019
|
min,
|
|
6769
7020
|
max,
|
|
6770
7021
|
value: typeof value === "number" ? String(value) : "",
|
|
@@ -6789,12 +7040,252 @@ function BlockKitField({ field, pluginId, value, onChange }) {
|
|
|
6789
7040
|
children: field.label
|
|
6790
7041
|
})]
|
|
6791
7042
|
});
|
|
7043
|
+
case "repeater": return /* @__PURE__ */ jsx(BlockKitRepeater, {
|
|
7044
|
+
field,
|
|
7045
|
+
pluginId,
|
|
7046
|
+
value,
|
|
7047
|
+
onChange
|
|
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
|
+
});
|
|
6792
7057
|
default: return /* @__PURE__ */ jsxs("div", {
|
|
6793
7058
|
className: "text-sm text-kumo-subtle",
|
|
6794
7059
|
children: ["Unknown field type: ", field.type]
|
|
6795
7060
|
});
|
|
6796
7061
|
}
|
|
6797
7062
|
}
|
|
7063
|
+
function ensureKeys(items) {
|
|
7064
|
+
return items.map((item, i) => {
|
|
7065
|
+
const obj = typeof item === "object" && item !== null ? item : {};
|
|
7066
|
+
return {
|
|
7067
|
+
...obj,
|
|
7068
|
+
_key: obj._key || `item-${i}-${Date.now()}`
|
|
7069
|
+
};
|
|
7070
|
+
});
|
|
7071
|
+
}
|
|
7072
|
+
function stripKeys(items) {
|
|
7073
|
+
return items.map(({ _key, ...rest }) => rest);
|
|
7074
|
+
}
|
|
7075
|
+
function BlockKitRepeater({ field, pluginId, value, onChange }) {
|
|
7076
|
+
const { _: _t2 } = useLingui();
|
|
7077
|
+
const rawItems = React.useMemo(() => Array.isArray(value) ? value : [], [value]);
|
|
7078
|
+
const [items, setItems] = React.useState(() => ensureKeys(rawItems));
|
|
7079
|
+
const [expanded, setExpanded] = React.useState(/* @__PURE__ */ new Set());
|
|
7080
|
+
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
7081
|
+
const lastEmittedRef = React.useRef(null);
|
|
7082
|
+
React.useEffect(() => {
|
|
7083
|
+
if (lastEmittedRef.current === rawItems) return;
|
|
7084
|
+
setItems((prev) => rawItems.map((item, i) => {
|
|
7085
|
+
const obj = typeof item === "object" && item !== null ? item : {};
|
|
7086
|
+
const existingKey = obj._key || prev[i]?._key;
|
|
7087
|
+
return {
|
|
7088
|
+
...obj,
|
|
7089
|
+
_key: existingKey || `item-${i}-${Date.now()}`
|
|
7090
|
+
};
|
|
7091
|
+
}));
|
|
7092
|
+
}, [rawItems]);
|
|
7093
|
+
const minItems = field.min_items ?? 0;
|
|
7094
|
+
const maxItems = field.max_items;
|
|
7095
|
+
const canAdd = maxItems === void 0 || items.length < maxItems;
|
|
7096
|
+
const canRemove = items.length > minItems;
|
|
7097
|
+
const addButtonLabel = field.item_label ? _t2({
|
|
7098
|
+
id: "3nLdaX",
|
|
7099
|
+
message: "Add {0}",
|
|
7100
|
+
values: { 0: field.item_label }
|
|
7101
|
+
}) : _t2({
|
|
7102
|
+
id: "NQ8llq",
|
|
7103
|
+
message: "Add item"
|
|
7104
|
+
});
|
|
7105
|
+
const emit = (next) => {
|
|
7106
|
+
setItems(next);
|
|
7107
|
+
const stripped = stripKeys(next);
|
|
7108
|
+
lastEmittedRef.current = stripped;
|
|
7109
|
+
onChange(field.action_id, stripped);
|
|
7110
|
+
};
|
|
7111
|
+
const handleAdd = () => {
|
|
7112
|
+
if (!canAdd) return;
|
|
7113
|
+
const newItem = { _key: `item-${Date.now()}-${Math.random().toString(36).slice(2, 7)}` };
|
|
7114
|
+
for (const sf of field.fields) switch (sf.type) {
|
|
7115
|
+
case "toggle":
|
|
7116
|
+
newItem[sf.action_id] = false;
|
|
7117
|
+
break;
|
|
7118
|
+
case "number_input":
|
|
7119
|
+
newItem[sf.action_id] = void 0;
|
|
7120
|
+
break;
|
|
7121
|
+
default: newItem[sf.action_id] = "";
|
|
7122
|
+
}
|
|
7123
|
+
setExpanded((prev) => {
|
|
7124
|
+
const next = new Set(prev);
|
|
7125
|
+
next.add(newItem._key);
|
|
7126
|
+
return next;
|
|
7127
|
+
});
|
|
7128
|
+
emit([...items, newItem]);
|
|
7129
|
+
};
|
|
7130
|
+
const handleRemove = (key) => {
|
|
7131
|
+
if (!canRemove) return;
|
|
7132
|
+
setExpanded((prev) => {
|
|
7133
|
+
if (!prev.has(key)) return prev;
|
|
7134
|
+
const next = new Set(prev);
|
|
7135
|
+
next.delete(key);
|
|
7136
|
+
return next;
|
|
7137
|
+
});
|
|
7138
|
+
emit(items.filter((it) => it._key !== key));
|
|
7139
|
+
};
|
|
7140
|
+
const handleItemChange = (key, subActionId, subValue) => {
|
|
7141
|
+
emit(items.map((it) => it._key === key ? {
|
|
7142
|
+
...it,
|
|
7143
|
+
[subActionId]: subValue
|
|
7144
|
+
} : it));
|
|
7145
|
+
};
|
|
7146
|
+
const handleDragEnd = (event) => {
|
|
7147
|
+
const { active, over } = event;
|
|
7148
|
+
if (!over || active.id === over.id) return;
|
|
7149
|
+
const oldIndex = items.findIndex((it) => it._key === active.id);
|
|
7150
|
+
const newIndex = items.findIndex((it) => it._key === over.id);
|
|
7151
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
7152
|
+
emit(arrayMove(items, oldIndex, newIndex));
|
|
7153
|
+
};
|
|
7154
|
+
const toggleExpanded = (key) => {
|
|
7155
|
+
setExpanded((prev) => {
|
|
7156
|
+
const next = new Set(prev);
|
|
7157
|
+
if (next.has(key)) next.delete(key);
|
|
7158
|
+
else next.add(key);
|
|
7159
|
+
return next;
|
|
7160
|
+
});
|
|
7161
|
+
};
|
|
7162
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7163
|
+
className: "space-y-2",
|
|
7164
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
7165
|
+
className: "flex items-center justify-between",
|
|
7166
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
7167
|
+
className: "text-sm font-medium",
|
|
7168
|
+
children: [field.label, items.length > 0 && /* @__PURE__ */ jsxs("span", {
|
|
7169
|
+
className: "ms-2 text-kumo-subtle font-normal",
|
|
7170
|
+
children: [
|
|
7171
|
+
"(",
|
|
7172
|
+
items.length,
|
|
7173
|
+
")"
|
|
7174
|
+
]
|
|
7175
|
+
})]
|
|
7176
|
+
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
7177
|
+
variant: "outline",
|
|
7178
|
+
size: "sm",
|
|
7179
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
7180
|
+
onClick: handleAdd,
|
|
7181
|
+
type: "button",
|
|
7182
|
+
children: addButtonLabel
|
|
7183
|
+
})]
|
|
7184
|
+
}), items.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
7185
|
+
className: "border-2 border-dashed rounded-lg p-6 text-center text-kumo-subtle",
|
|
7186
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
7187
|
+
className: "text-sm",
|
|
7188
|
+
children: _t2({
|
|
7189
|
+
id: "bWmJ0A",
|
|
7190
|
+
message: "No items yet"
|
|
7191
|
+
})
|
|
7192
|
+
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
7193
|
+
variant: "outline",
|
|
7194
|
+
size: "sm",
|
|
7195
|
+
className: "mt-2",
|
|
7196
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
7197
|
+
onClick: handleAdd,
|
|
7198
|
+
type: "button",
|
|
7199
|
+
children: addButtonLabel
|
|
7200
|
+
})]
|
|
7201
|
+
}) : /* @__PURE__ */ jsx(DndContext, {
|
|
7202
|
+
sensors,
|
|
7203
|
+
collisionDetection: closestCenter,
|
|
7204
|
+
onDragEnd: handleDragEnd,
|
|
7205
|
+
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
7206
|
+
items: items.map((it) => it._key),
|
|
7207
|
+
strategy: verticalListSortingStrategy,
|
|
7208
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
7209
|
+
className: "space-y-2",
|
|
7210
|
+
children: items.map((item, index) => /* @__PURE__ */ jsx(BlockKitRepeaterItem, {
|
|
7211
|
+
item,
|
|
7212
|
+
index,
|
|
7213
|
+
fields: field.fields,
|
|
7214
|
+
pluginId,
|
|
7215
|
+
isCollapsed: !expanded.has(item._key),
|
|
7216
|
+
onToggleCollapse: () => toggleExpanded(item._key),
|
|
7217
|
+
onRemove: canRemove ? () => handleRemove(item._key) : void 0,
|
|
7218
|
+
onChange: (subActionId, v) => handleItemChange(item._key, subActionId, v)
|
|
7219
|
+
}, item._key))
|
|
7220
|
+
})
|
|
7221
|
+
})
|
|
7222
|
+
})]
|
|
7223
|
+
});
|
|
7224
|
+
}
|
|
7225
|
+
function BlockKitRepeaterItem({ item, index, fields, pluginId, isCollapsed, onToggleCollapse, onRemove, onChange }) {
|
|
7226
|
+
const { _: _t3 } = useLingui();
|
|
7227
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: item._key });
|
|
7228
|
+
const style = {
|
|
7229
|
+
transform: CSS.Transform.toString(transform),
|
|
7230
|
+
transition
|
|
7231
|
+
};
|
|
7232
|
+
const summaryField = fields.find((f) => f.type === "text_input");
|
|
7233
|
+
const summaryLabel = (summaryField && typeof item[summaryField.action_id] === "string" ? item[summaryField.action_id] : "").trim() || _t3({
|
|
7234
|
+
id: "y/5CE8",
|
|
7235
|
+
message: "Item {0}",
|
|
7236
|
+
values: { 0: index + 1 }
|
|
7237
|
+
});
|
|
7238
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7239
|
+
ref: setNodeRef,
|
|
7240
|
+
style,
|
|
7241
|
+
className: cn("border border-kumo-line rounded-lg bg-kumo-base", isDragging && "opacity-50 ring-2 ring-kumo-brand"),
|
|
7242
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
7243
|
+
className: "flex items-center gap-2 px-3 py-2 border-b border-kumo-line",
|
|
7244
|
+
children: [
|
|
7245
|
+
/* @__PURE__ */ jsx("span", {
|
|
7246
|
+
className: "inline-flex h-4 w-4 text-kumo-subtle cursor-grab shrink-0",
|
|
7247
|
+
"aria-label": _t3({
|
|
7248
|
+
id: "KGi3u9",
|
|
7249
|
+
message: "Drag to reorder"
|
|
7250
|
+
}),
|
|
7251
|
+
...attributes,
|
|
7252
|
+
...listeners,
|
|
7253
|
+
children: /* @__PURE__ */ jsx(DotsSixVertical, { className: "h-4 w-4" })
|
|
7254
|
+
}),
|
|
7255
|
+
/* @__PURE__ */ jsxs("button", {
|
|
7256
|
+
type: "button",
|
|
7257
|
+
className: "flex items-center gap-2 flex-1 min-w-0 text-start cursor-pointer",
|
|
7258
|
+
onClick: onToggleCollapse,
|
|
7259
|
+
"aria-expanded": !isCollapsed,
|
|
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", {
|
|
7261
|
+
className: "text-sm font-medium flex-1 truncate",
|
|
7262
|
+
children: summaryLabel
|
|
7263
|
+
})]
|
|
7264
|
+
}),
|
|
7265
|
+
onRemove && /* @__PURE__ */ jsx(Button, {
|
|
7266
|
+
variant: "ghost",
|
|
7267
|
+
shape: "square",
|
|
7268
|
+
type: "button",
|
|
7269
|
+
onClick: onRemove,
|
|
7270
|
+
"aria-label": _t3({
|
|
7271
|
+
id: "mU3j1b",
|
|
7272
|
+
message: "Remove item {0}",
|
|
7273
|
+
values: { 0: index + 1 }
|
|
7274
|
+
}),
|
|
7275
|
+
children: /* @__PURE__ */ jsx(Trash, { className: "h-3.5 w-3.5 text-kumo-danger" })
|
|
7276
|
+
})
|
|
7277
|
+
]
|
|
7278
|
+
}), !isCollapsed && /* @__PURE__ */ jsx("div", {
|
|
7279
|
+
className: "p-3 space-y-3",
|
|
7280
|
+
children: fields.map((sf) => /* @__PURE__ */ jsx(BlockKitField, {
|
|
7281
|
+
field: sf,
|
|
7282
|
+
pluginId,
|
|
7283
|
+
value: item[sf.action_id],
|
|
7284
|
+
onChange: (actionId, v) => onChange(actionId, v)
|
|
7285
|
+
}, sf.action_id))
|
|
7286
|
+
})]
|
|
7287
|
+
});
|
|
7288
|
+
}
|
|
6798
7289
|
/**
|
|
6799
7290
|
* Select field that supports loading options dynamically via `optionsRoute`.
|
|
6800
7291
|
* When `optionsRoute` is set, fetches `{ items: [{ id, name }] }` from the plugin route.
|
|
@@ -6893,7 +7384,7 @@ function EditorFooter({ editor }) {
|
|
|
6893
7384
|
* Portable Text Editor Component
|
|
6894
7385
|
*/
|
|
6895
7386
|
function PortableTextEditor({ value, onChange, placeholder = "Start writing...", className, editable = true, "aria-labelledby": ariaLabelledby, pluginBlocks = [], focusMode: controlledFocusMode, onFocusModeChange, onEditorReady, minimal = false, onBlockSidebarOpen, onBlockSidebarClose }) {
|
|
6896
|
-
const { _:
|
|
7387
|
+
const { _: _t4 } = useLingui();
|
|
6897
7388
|
const onChangeRef = React.useRef(onChange);
|
|
6898
7389
|
React.useEffect(() => {
|
|
6899
7390
|
onChangeRef.current = onChange;
|
|
@@ -6977,14 +7468,14 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
6977
7468
|
for (const block of pluginBlocks) cmds.push({
|
|
6978
7469
|
id: `plugin-${block.pluginId}-${block.type}`,
|
|
6979
7470
|
title: block.label,
|
|
6980
|
-
description: block.description ??
|
|
7471
|
+
description: block.description ?? _t4({
|
|
6981
7472
|
id: "g0zqOw",
|
|
6982
7473
|
message: "Embed a {0}",
|
|
6983
7474
|
values: { 0: block.label }
|
|
6984
7475
|
}),
|
|
6985
7476
|
icon: resolveIcon(block.icon),
|
|
6986
7477
|
aliases: [block.type],
|
|
6987
|
-
category: {
|
|
7478
|
+
category: block.category ?? {
|
|
6988
7479
|
id: "aTofd0",
|
|
6989
7480
|
message: "Embeds"
|
|
6990
7481
|
},
|
|
@@ -6994,7 +7485,7 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
6994
7485
|
}
|
|
6995
7486
|
});
|
|
6996
7487
|
return cmds;
|
|
6997
|
-
}, [pluginBlocks,
|
|
7488
|
+
}, [pluginBlocks, _t4]);
|
|
6998
7489
|
const filterCommandsRef = React.useRef((_q) => []);
|
|
6999
7490
|
filterCommandsRef.current = (query) => {
|
|
7000
7491
|
if (!query) return slashCommands;
|
|
@@ -7002,8 +7493,8 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
7002
7493
|
const titleMatches = [];
|
|
7003
7494
|
const otherMatches = [];
|
|
7004
7495
|
for (const item of slashCommands) {
|
|
7005
|
-
const titleStr = typeof item.title === "string" ? item.title :
|
|
7006
|
-
const descStr = typeof item.description === "string" ? item.description :
|
|
7496
|
+
const titleStr = typeof item.title === "string" ? item.title : _t4(item.title);
|
|
7497
|
+
const descStr = typeof item.description === "string" ? item.description : _t4(item.description);
|
|
7007
7498
|
if (titleStr.toLowerCase().includes(searchText)) titleMatches.push(item);
|
|
7008
7499
|
else if (descStr.toLowerCase().includes(searchText) || item.aliases?.some((alias) => alias.toLowerCase().includes(searchText))) otherMatches.push(item);
|
|
7009
7500
|
}
|
|
@@ -7055,14 +7546,22 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
7055
7546
|
content: initialContent,
|
|
7056
7547
|
editable,
|
|
7057
7548
|
immediatelyRender: true,
|
|
7058
|
-
editorProps: React.useMemo(() => ({ attributes: {
|
|
7549
|
+
editorProps: React.useMemo(() => ({ attributes: {
|
|
7550
|
+
class: "prose prose-sm sm:prose-base dark:prose-invert max-w-none focus:outline-none min-h-[200px] p-4",
|
|
7551
|
+
dir: "auto"
|
|
7552
|
+
} }), []),
|
|
7059
7553
|
onUpdate: ({ editor: updatedEditor }) => {
|
|
7060
7554
|
const cb = onChangeRef.current;
|
|
7061
7555
|
if (cb) cb(prosemirrorToPortableText(updatedEditor.getJSON()));
|
|
7062
7556
|
}
|
|
7063
7557
|
});
|
|
7064
7558
|
React.useEffect(() => {
|
|
7065
|
-
if (editor && onEditorReady)
|
|
7559
|
+
if (editor && onEditorReady) {
|
|
7560
|
+
onEditorReady(editor);
|
|
7561
|
+
return () => {
|
|
7562
|
+
onEditorReady(null);
|
|
7563
|
+
};
|
|
7564
|
+
}
|
|
7066
7565
|
}, [editor, onEditorReady]);
|
|
7067
7566
|
React.useEffect(() => {
|
|
7068
7567
|
if (editor) registerPluginBlocks(editor, pluginBlocks);
|
|
@@ -8406,7 +8905,8 @@ function SeoPanel({ contentKey, seo, onChange }) {
|
|
|
8406
8905
|
value: draft.title,
|
|
8407
8906
|
onChange: (e) => {
|
|
8408
8907
|
updateDraft({ title: e.target.value });
|
|
8409
|
-
}
|
|
8908
|
+
},
|
|
8909
|
+
dir: "auto"
|
|
8410
8910
|
}),
|
|
8411
8911
|
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(InputArea, {
|
|
8412
8912
|
label: _t({
|
|
@@ -8425,7 +8925,8 @@ function SeoPanel({ contentKey, seo, onChange }) {
|
|
|
8425
8925
|
onChange: (e) => {
|
|
8426
8926
|
updateDraft({ description: e.target.value });
|
|
8427
8927
|
},
|
|
8428
|
-
rows: 3
|
|
8928
|
+
rows: 3,
|
|
8929
|
+
dir: "auto"
|
|
8429
8930
|
}) }),
|
|
8430
8931
|
/* @__PURE__ */ jsx(Input, {
|
|
8431
8932
|
label: _t({
|
|
@@ -8567,7 +9068,7 @@ function CategoryCheckboxTree({ term, level = 0, selectedIds, onToggle }) {
|
|
|
8567
9068
|
const isChecked = selectedIds.has(term.id);
|
|
8568
9069
|
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("label", {
|
|
8569
9070
|
className: "flex items-center py-1 cursor-pointer hover:bg-kumo-tint/50 rounded px-2",
|
|
8570
|
-
style: {
|
|
9071
|
+
style: { marginInlineStart: `${level}rem` },
|
|
8571
9072
|
children: [/* @__PURE__ */ jsx("input", {
|
|
8572
9073
|
type: "checkbox",
|
|
8573
9074
|
checked: isChecked,
|
|
@@ -8666,7 +9167,7 @@ function TagInput({ terms, selectedIds, onAdd, onRemove, onCreate, isCreating, l
|
|
|
8666
9167
|
type: "button",
|
|
8667
9168
|
onClick: handleCreate,
|
|
8668
9169
|
disabled: isCreating,
|
|
8669
|
-
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",
|
|
8670
9171
|
children: [/* @__PURE__ */ jsx(Plus, { className: "w-3 h-3" }), isCreating ? "Creating..." : `Create "${trimmedInput}"`]
|
|
8671
9172
|
})]
|
|
8672
9173
|
})]
|
|
@@ -8917,13 +9418,6 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
8917
9418
|
roleLabel: entry.roleLabel
|
|
8918
9419
|
})) ?? []);
|
|
8919
9420
|
const [portableTextEditor, setPortableTextEditor] = React.useState(null);
|
|
8920
|
-
const ptEditorClaimedRef = React.useRef(false);
|
|
8921
|
-
React.useCallback((editor) => {
|
|
8922
|
-
if (!ptEditorClaimedRef.current) {
|
|
8923
|
-
ptEditorClaimedRef.current = true;
|
|
8924
|
-
setPortableTextEditor(editor);
|
|
8925
|
-
}
|
|
8926
|
-
}, []);
|
|
8927
9421
|
const [blockSidebarPanel, setBlockSidebarPanel] = React.useState(null);
|
|
8928
9422
|
const handleBlockSidebarOpen = React.useCallback((panel) => {
|
|
8929
9423
|
setBlockSidebarPanel(panel);
|
|
@@ -8946,6 +9440,25 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
8946
9440
|
})) ?? []
|
|
8947
9441
|
}));
|
|
8948
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
|
+
}
|
|
8949
9462
|
const itemDataString = React.useMemo(() => item ? JSON.stringify(item.data) : "", [item?.data]);
|
|
8950
9463
|
React.useEffect(() => {
|
|
8951
9464
|
if (item) {
|
|
@@ -9112,7 +9625,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
9112
9625
|
onSubmit: handleSubmit,
|
|
9113
9626
|
className: cn("space-y-6 transition-all duration-300", isDistractionFree && "fixed inset-0 z-50 bg-kumo-base p-8 overflow-auto"),
|
|
9114
9627
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
9115
|
-
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"),
|
|
9116
9629
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
9117
9630
|
className: "flex items-center space-x-4",
|
|
9118
9631
|
children: [
|
|
@@ -9129,7 +9642,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
9129
9642
|
variant: "ghost",
|
|
9130
9643
|
shape: "square"
|
|
9131
9644
|
}),
|
|
9132
|
-
children: /* @__PURE__ */ jsx(
|
|
9645
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, {
|
|
9133
9646
|
className: "h-5 w-5",
|
|
9134
9647
|
"aria-hidden": "true"
|
|
9135
9648
|
})
|
|
@@ -9324,6 +9837,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
9324
9837
|
children: /* @__PURE__ */ jsx("div", {
|
|
9325
9838
|
className: "space-y-4",
|
|
9326
9839
|
children: Object.entries(fields).map(([name, field]) => {
|
|
9840
|
+
const fieldKey = `${name}:${item?.id ?? "new"}`;
|
|
9327
9841
|
const fieldEl = /* @__PURE__ */ jsx(FieldRenderer, {
|
|
9328
9842
|
name,
|
|
9329
9843
|
field,
|
|
@@ -9335,14 +9849,14 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
9335
9849
|
onBlockSidebarOpen: field.kind === "portableText" ? handleBlockSidebarOpen : void 0,
|
|
9336
9850
|
onBlockSidebarClose: field.kind === "portableText" ? handleBlockSidebarClose : void 0,
|
|
9337
9851
|
manifest
|
|
9338
|
-
},
|
|
9852
|
+
}, fieldKey);
|
|
9339
9853
|
if (name === "featured_image" && field.kind === "image" && hasSeo && !isNew && onSeoChange) return /* @__PURE__ */ jsxs("div", {
|
|
9340
9854
|
className: "grid grid-cols-1 gap-6 md:grid-cols-2",
|
|
9341
9855
|
children: [/* @__PURE__ */ jsx("div", { children: fieldEl }), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(SeoImageField, {
|
|
9342
9856
|
seo: item?.seo,
|
|
9343
9857
|
onChange: onSeoChange
|
|
9344
9858
|
}) })]
|
|
9345
|
-
}, `${
|
|
9859
|
+
}, `${fieldKey}-with-seo`);
|
|
9346
9860
|
return fieldEl;
|
|
9347
9861
|
})
|
|
9348
9862
|
})
|
|
@@ -9751,6 +10265,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
9751
10265
|
value: typeof value === "string" ? value : "",
|
|
9752
10266
|
onChange: (e) => handleChange(e.target.value),
|
|
9753
10267
|
required: field.required,
|
|
10268
|
+
dir: "auto",
|
|
9754
10269
|
className: minimal ? "border-0 bg-transparent px-0 text-lg font-medium focus-visible:ring-0 focus-visible:ring-offset-0" : void 0
|
|
9755
10270
|
});
|
|
9756
10271
|
case "number": return /* @__PURE__ */ jsx(Input, {
|
|
@@ -9801,34 +10316,37 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
9801
10316
|
value: typeof value === "string" ? value : "",
|
|
9802
10317
|
onChange: (e) => handleChange(e.target.value),
|
|
9803
10318
|
rows: 10,
|
|
10319
|
+
dir: "auto",
|
|
9804
10320
|
placeholder: _t2({
|
|
9805
10321
|
id: "dPFFy+",
|
|
9806
10322
|
message: "Enter markdown content..."
|
|
9807
10323
|
})
|
|
9808
10324
|
});
|
|
9809
10325
|
case "select": {
|
|
10326
|
+
const selectOptions = Array.isArray(field.options) ? field.options : [];
|
|
9810
10327
|
const selectItems = {};
|
|
9811
|
-
for (const opt of
|
|
10328
|
+
for (const opt of selectOptions) selectItems[opt.value] = opt.label;
|
|
9812
10329
|
return /* @__PURE__ */ jsx(Select, {
|
|
9813
10330
|
id,
|
|
9814
10331
|
label,
|
|
9815
10332
|
value: typeof value === "string" ? value : "",
|
|
9816
10333
|
onValueChange: (v) => handleChange(v ?? ""),
|
|
9817
10334
|
items: selectItems,
|
|
9818
|
-
children:
|
|
10335
|
+
children: selectOptions.map((opt) => /* @__PURE__ */ jsx(Select.Option, {
|
|
9819
10336
|
value: opt.value,
|
|
9820
10337
|
children: opt.label
|
|
9821
10338
|
}, opt.value))
|
|
9822
10339
|
});
|
|
9823
10340
|
}
|
|
9824
10341
|
case "multiSelect": {
|
|
10342
|
+
const multiSelectOptions = Array.isArray(field.options) ? field.options : [];
|
|
9825
10343
|
const selected = Array.isArray(value) ? value : [];
|
|
9826
10344
|
return /* @__PURE__ */ jsxs("fieldset", { children: [/* @__PURE__ */ jsx(Label, {
|
|
9827
10345
|
className: labelClass,
|
|
9828
10346
|
children: label
|
|
9829
10347
|
}), /* @__PURE__ */ jsx("div", {
|
|
9830
10348
|
className: "mt-2 flex flex-wrap gap-x-4 gap-y-2",
|
|
9831
|
-
children:
|
|
10349
|
+
children: multiSelectOptions.map((opt) => {
|
|
9832
10350
|
const isChecked = selected.includes(opt.value);
|
|
9833
10351
|
return /* @__PURE__ */ jsx(Checkbox, {
|
|
9834
10352
|
label: opt.label,
|
|
@@ -9844,8 +10362,8 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
9844
10362
|
label,
|
|
9845
10363
|
id,
|
|
9846
10364
|
type: "datetime-local",
|
|
9847
|
-
value:
|
|
9848
|
-
onChange: (e) => handleChange(e.target.value),
|
|
10365
|
+
value: toDatetimeLocalInputValue(value),
|
|
10366
|
+
onChange: (e) => handleChange(fromDatetimeLocalInputValue(e.target.value)),
|
|
9849
10367
|
required: field.required
|
|
9850
10368
|
});
|
|
9851
10369
|
case "image": {
|
|
@@ -9897,7 +10415,8 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
9897
10415
|
id,
|
|
9898
10416
|
value: typeof value === "string" ? value : "",
|
|
9899
10417
|
onChange: (e) => handleChange(e.target.value),
|
|
9900
|
-
required: field.required
|
|
10418
|
+
required: field.required,
|
|
10419
|
+
dir: "auto"
|
|
9901
10420
|
});
|
|
9902
10421
|
}
|
|
9903
10422
|
}
|
|
@@ -10537,7 +11056,7 @@ function getItemTitle$1(item) {
|
|
|
10537
11056
|
/**
|
|
10538
11057
|
* Content list view with table display and trash tab
|
|
10539
11058
|
*/
|
|
10540
|
-
function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n: i18n$1, activeLocale, onLocaleChange, urlPattern }) {
|
|
11059
|
+
function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n: i18n$1, activeLocale, onLocaleChange, urlPattern, sort, onSortChange }) {
|
|
10541
11060
|
const { _: _t } = useLingui();
|
|
10542
11061
|
const [activeTab, setActiveTab] = React.useState("all");
|
|
10543
11062
|
const [searchQuery, setSearchQuery] = React.useState("");
|
|
@@ -10653,34 +11172,38 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
10653
11172
|
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
|
|
10654
11173
|
className: "border-b bg-kumo-tint/50",
|
|
10655
11174
|
children: [
|
|
10656
|
-
/* @__PURE__ */ jsx(
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
11175
|
+
/* @__PURE__ */ jsx(SortableTh, {
|
|
11176
|
+
field: "title",
|
|
11177
|
+
sort,
|
|
11178
|
+
onSortChange,
|
|
11179
|
+
label: _t({
|
|
10660
11180
|
id: "MHrjPM",
|
|
10661
11181
|
message: "Title"
|
|
10662
11182
|
})
|
|
10663
11183
|
}),
|
|
10664
|
-
/* @__PURE__ */ jsx(
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
11184
|
+
/* @__PURE__ */ jsx(SortableTh, {
|
|
11185
|
+
field: "status",
|
|
11186
|
+
sort,
|
|
11187
|
+
onSortChange,
|
|
11188
|
+
label: _t({
|
|
10668
11189
|
id: "uAQUqI",
|
|
10669
11190
|
message: "Status"
|
|
10670
11191
|
})
|
|
10671
11192
|
}),
|
|
10672
|
-
i18n$1 && /* @__PURE__ */ jsx(
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
11193
|
+
i18n$1 && /* @__PURE__ */ jsx(SortableTh, {
|
|
11194
|
+
field: "locale",
|
|
11195
|
+
sort,
|
|
11196
|
+
onSortChange,
|
|
11197
|
+
label: _t({
|
|
10676
11198
|
id: "8NbHF7",
|
|
10677
11199
|
message: "Locale"
|
|
10678
11200
|
})
|
|
10679
11201
|
}),
|
|
10680
|
-
/* @__PURE__ */ jsx(
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
11202
|
+
/* @__PURE__ */ jsx(SortableTh, {
|
|
11203
|
+
field: "updatedAt",
|
|
11204
|
+
sort,
|
|
11205
|
+
onSortChange,
|
|
11206
|
+
label: _t({
|
|
10684
11207
|
id: "mYGY3B",
|
|
10685
11208
|
message: "Date"
|
|
10686
11209
|
})
|
|
@@ -10694,7 +11217,17 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
10694
11217
|
})
|
|
10695
11218
|
})
|
|
10696
11219
|
]
|
|
10697
|
-
}) }), /* @__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", {
|
|
10698
11231
|
colSpan: i18n$1 ? 5 : 4,
|
|
10699
11232
|
className: "px-4 py-8 text-center text-kumo-subtle",
|
|
10700
11233
|
children: [
|
|
@@ -10765,7 +11298,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
10765
11298
|
id: "aHKcKc",
|
|
10766
11299
|
message: "Previous page"
|
|
10767
11300
|
}),
|
|
10768
|
-
children: /* @__PURE__ */ jsx(
|
|
11301
|
+
children: /* @__PURE__ */ jsx(CaretPrev, {
|
|
10769
11302
|
className: "h-4 w-4",
|
|
10770
11303
|
"aria-hidden": "true"
|
|
10771
11304
|
})
|
|
@@ -10787,7 +11320,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
10787
11320
|
id: "/dqduX",
|
|
10788
11321
|
message: "Next page"
|
|
10789
11322
|
}),
|
|
10790
|
-
children: /* @__PURE__ */ jsx(
|
|
11323
|
+
children: /* @__PURE__ */ jsx(CaretNext, {
|
|
10791
11324
|
className: "h-4 w-4",
|
|
10792
11325
|
"aria-hidden": "true"
|
|
10793
11326
|
})
|
|
@@ -10842,7 +11375,17 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
10842
11375
|
})
|
|
10843
11376
|
})
|
|
10844
11377
|
]
|
|
10845
|
-
}) }), /* @__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", {
|
|
10846
11389
|
colSpan: 3,
|
|
10847
11390
|
className: "px-4 py-8 text-center text-kumo-subtle",
|
|
10848
11391
|
children: _t({
|
|
@@ -10873,6 +11416,52 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
10873
11416
|
]
|
|
10874
11417
|
});
|
|
10875
11418
|
}
|
|
11419
|
+
/**
|
|
11420
|
+
* Table header that doubles as a sort control when the parent opted in by
|
|
11421
|
+
* passing `onSortChange`. When no callback is provided we fall back to a
|
|
11422
|
+
* plain `<th>` so legacy callers (and screen readers) see exactly the same
|
|
11423
|
+
* markup as before this change.
|
|
11424
|
+
*
|
|
11425
|
+
* The button's accessible name is just the column label — the sort state
|
|
11426
|
+
* is conveyed via `aria-sort` on the <th>, which screen readers announce
|
|
11427
|
+
* automatically. Adding a verbose aria-label would make each header re-read
|
|
11428
|
+
* the sort instruction on every focus, which is noisy.
|
|
11429
|
+
*/
|
|
11430
|
+
function SortableTh({ field, sort, onSortChange, label }) {
|
|
11431
|
+
const isActive = sort?.field === field;
|
|
11432
|
+
const direction = isActive ? sort?.direction : void 0;
|
|
11433
|
+
if (!onSortChange) return /* @__PURE__ */ jsx("th", {
|
|
11434
|
+
scope: "col",
|
|
11435
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
11436
|
+
children: label
|
|
11437
|
+
});
|
|
11438
|
+
const ariaSort = isActive ? direction === "asc" ? "ascending" : "descending" : "none";
|
|
11439
|
+
const handleClick = () => {
|
|
11440
|
+
if (isActive) onSortChange({
|
|
11441
|
+
field,
|
|
11442
|
+
direction: direction === "asc" ? "desc" : "asc"
|
|
11443
|
+
});
|
|
11444
|
+
else onSortChange({
|
|
11445
|
+
field,
|
|
11446
|
+
direction: "desc"
|
|
11447
|
+
});
|
|
11448
|
+
};
|
|
11449
|
+
const Icon = isActive ? direction === "asc" ? CaretUp : CaretDown : CaretUpDown;
|
|
11450
|
+
return /* @__PURE__ */ jsx("th", {
|
|
11451
|
+
scope: "col",
|
|
11452
|
+
"aria-sort": ariaSort,
|
|
11453
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
11454
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
11455
|
+
type: "button",
|
|
11456
|
+
onClick: handleClick,
|
|
11457
|
+
className: cn("inline-flex items-center gap-1 rounded hover:text-kumo-brand", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-kumo-brand", isActive ? "text-kumo-fg" : "text-kumo-subtle"),
|
|
11458
|
+
children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx(Icon, {
|
|
11459
|
+
className: "h-3 w-3",
|
|
11460
|
+
"aria-hidden": "true"
|
|
11461
|
+
})]
|
|
11462
|
+
})
|
|
11463
|
+
});
|
|
11464
|
+
}
|
|
10876
11465
|
function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale, urlPattern }) {
|
|
10877
11466
|
const { _: _t2 } = useLingui();
|
|
10878
11467
|
const title = getItemTitle$1(item);
|
|
@@ -11155,6 +11744,37 @@ function StatusBadge({ status, hasPendingChanges }) {
|
|
|
11155
11744
|
});
|
|
11156
11745
|
}
|
|
11157
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
|
+
|
|
11158
11778
|
//#endregion
|
|
11159
11779
|
//#region src/components/FieldEditor.tsx
|
|
11160
11780
|
const SLUG_INVALID_CHARS_REGEX = /[^a-z0-9]+/g;
|
|
@@ -12094,7 +12714,7 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
12094
12714
|
const [labelSingular, setLabelSingular] = React.useState(collection?.labelSingular ?? "");
|
|
12095
12715
|
const [description, setDescription] = React.useState(collection?.description ?? "");
|
|
12096
12716
|
const [urlPattern, setUrlPattern] = React.useState(collection?.urlPattern ?? "");
|
|
12097
|
-
const [supports, setSupports] = React.useState((collection?.supports ?? ["drafts"]).filter((s) => s !== "seo"));
|
|
12717
|
+
const [supports, setSupports] = React.useState((collection?.supports ?? ["drafts", "revisions"]).filter((s) => s !== "seo"));
|
|
12098
12718
|
const [hasSeo, setHasSeo] = React.useState(collection?.hasSeo ?? false);
|
|
12099
12719
|
const [commentsEnabled, setCommentsEnabled] = React.useState(collection?.commentsEnabled ?? false);
|
|
12100
12720
|
const [commentsModeration, setCommentsModeration] = React.useState(collection?.commentsModeration ?? "first_time");
|
|
@@ -12193,30 +12813,43 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
12193
12813
|
return /* @__PURE__ */ jsxs("div", {
|
|
12194
12814
|
className: "space-y-6",
|
|
12195
12815
|
children: [
|
|
12196
|
-
/* @__PURE__ */ jsxs(
|
|
12197
|
-
|
|
12198
|
-
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
12816
|
+
/* @__PURE__ */ jsxs(EditorHeader, {
|
|
12817
|
+
leading: /* @__PURE__ */ jsx(Link$1, {
|
|
12199
12818
|
to: "/content-types",
|
|
12200
|
-
"aria-label":
|
|
12819
|
+
"aria-label": _t({
|
|
12820
|
+
id: "oqi/nm",
|
|
12821
|
+
message: "Back to Content Types"
|
|
12822
|
+
}),
|
|
12201
12823
|
className: buttonVariants({
|
|
12202
12824
|
variant: "ghost",
|
|
12203
12825
|
shape: "square"
|
|
12204
12826
|
}),
|
|
12205
|
-
children: /* @__PURE__ */ jsx(
|
|
12206
|
-
}),
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
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
|
+
})
|
|
12220
12853
|
})]
|
|
12221
12854
|
})]
|
|
12222
12855
|
}),
|
|
@@ -12235,6 +12868,7 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
12235
12868
|
children: [/* @__PURE__ */ jsx("div", {
|
|
12236
12869
|
className: "lg:col-span-1",
|
|
12237
12870
|
children: /* @__PURE__ */ jsxs("form", {
|
|
12871
|
+
id: "content-type-editor-form",
|
|
12238
12872
|
onSubmit: handleSubmit,
|
|
12239
12873
|
className: "space-y-4",
|
|
12240
12874
|
children: [
|
|
@@ -14742,54 +15376,13 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
14742
15376
|
* It's a standalone page for authentication.
|
|
14743
15377
|
*
|
|
14744
15378
|
* Supports:
|
|
14745
|
-
* - Passkey authentication (
|
|
14746
|
-
* -
|
|
15379
|
+
* - Passkey authentication (always available)
|
|
15380
|
+
* - Pluggable auth providers (AT Protocol, GitHub, Google, etc.) when configured
|
|
14747
15381
|
* - Magic link (email) when configured
|
|
14748
15382
|
*
|
|
14749
15383
|
* When external auth (e.g., Cloudflare Access) is configured, this page
|
|
14750
15384
|
* redirects to the admin dashboard since authentication is handled externally.
|
|
14751
15385
|
*/
|
|
14752
|
-
function GitHubIcon({ className }) {
|
|
14753
|
-
return /* @__PURE__ */ jsx("svg", {
|
|
14754
|
-
className,
|
|
14755
|
-
viewBox: "0 0 24 24",
|
|
14756
|
-
fill: "currentColor",
|
|
14757
|
-
children: /* @__PURE__ */ jsx("path", { d: "M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" })
|
|
14758
|
-
});
|
|
14759
|
-
}
|
|
14760
|
-
function GoogleIcon({ className }) {
|
|
14761
|
-
return /* @__PURE__ */ jsxs("svg", {
|
|
14762
|
-
className,
|
|
14763
|
-
viewBox: "0 0 24 24",
|
|
14764
|
-
children: [
|
|
14765
|
-
/* @__PURE__ */ jsx("path", {
|
|
14766
|
-
fill: "#4285F4",
|
|
14767
|
-
d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
14768
|
-
}),
|
|
14769
|
-
/* @__PURE__ */ jsx("path", {
|
|
14770
|
-
fill: "#34A853",
|
|
14771
|
-
d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
|
14772
|
-
}),
|
|
14773
|
-
/* @__PURE__ */ jsx("path", {
|
|
14774
|
-
fill: "#FBBC05",
|
|
14775
|
-
d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
|
14776
|
-
}),
|
|
14777
|
-
/* @__PURE__ */ jsx("path", {
|
|
14778
|
-
fill: "#EA4335",
|
|
14779
|
-
d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
|
14780
|
-
})
|
|
14781
|
-
]
|
|
14782
|
-
});
|
|
14783
|
-
}
|
|
14784
|
-
const OAUTH_PROVIDERS = [{
|
|
14785
|
-
id: "github",
|
|
14786
|
-
name: "GitHub",
|
|
14787
|
-
icon: /* @__PURE__ */ jsx(GitHubIcon, { className: "h-5 w-5" })
|
|
14788
|
-
}, {
|
|
14789
|
-
id: "google",
|
|
14790
|
-
name: "Google",
|
|
14791
|
-
icon: /* @__PURE__ */ jsx(GoogleIcon, { className: "h-5 w-5" })
|
|
14792
|
-
}];
|
|
14793
15386
|
function MagicLinkForm({ onBack }) {
|
|
14794
15387
|
const { _: _t } = useLingui();
|
|
14795
15388
|
const [email, setEmail] = React.useState("");
|
|
@@ -14930,22 +15523,21 @@ function MagicLinkForm({ onBack }) {
|
|
|
14930
15523
|
]
|
|
14931
15524
|
});
|
|
14932
15525
|
}
|
|
14933
|
-
function handleOAuthClick(providerId) {
|
|
14934
|
-
window.location.href = `/_emdash/api/auth/oauth/${providerId}`;
|
|
14935
|
-
}
|
|
14936
15526
|
function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
14937
15527
|
const safeRedirectUrl = sanitizeRedirectUrl(redirectUrl);
|
|
14938
15528
|
const { _: _t2 } = useLingui();
|
|
14939
15529
|
const { locale, setLocale } = useLocale();
|
|
14940
15530
|
const [method, setMethod] = React.useState("passkey");
|
|
14941
15531
|
const [urlError, setUrlError] = React.useState(null);
|
|
14942
|
-
const
|
|
14943
|
-
|
|
14944
|
-
|
|
15532
|
+
const [activeProvider, setActiveProvider] = React.useState(null);
|
|
15533
|
+
const authProviderList = useAuthProviderList();
|
|
15534
|
+
const { data: authInfo, isLoading: authModeLoading } = useQuery({
|
|
15535
|
+
queryKey: ["authMode"],
|
|
15536
|
+
queryFn: fetchAuthMode
|
|
14945
15537
|
});
|
|
14946
15538
|
React.useEffect(() => {
|
|
14947
|
-
if (
|
|
14948
|
-
}, [
|
|
15539
|
+
if (authInfo?.authMode && authInfo.authMode !== "passkey") window.location.href = safeRedirectUrl;
|
|
15540
|
+
}, [authInfo, safeRedirectUrl]);
|
|
14949
15541
|
React.useEffect(() => {
|
|
14950
15542
|
const params = new URLSearchParams(window.location.search);
|
|
14951
15543
|
const error = params.get("error");
|
|
@@ -14962,15 +15554,12 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
14962
15554
|
const handleSuccess = () => {
|
|
14963
15555
|
window.location.href = safeRedirectUrl;
|
|
14964
15556
|
};
|
|
14965
|
-
|
|
15557
|
+
const buttonProviders = authProviderList.filter((p) => p.LoginButton);
|
|
15558
|
+
if (authModeLoading || authInfo?.authMode && authInfo.authMode !== "passkey") return /* @__PURE__ */ jsx("div", {
|
|
14966
15559
|
className: "min-h-screen flex items-center justify-center bg-kumo-base p-4",
|
|
14967
15560
|
children: /* @__PURE__ */ jsxs("div", {
|
|
14968
15561
|
className: "flex flex-col items-center",
|
|
14969
|
-
children: [/* @__PURE__ */ jsx(BrandLogo, {
|
|
14970
|
-
logoUrl: manifest?.admin?.logo,
|
|
14971
|
-
siteName: manifest?.admin?.siteName,
|
|
14972
|
-
className: "h-10 mb-4"
|
|
14973
|
-
}), /* @__PURE__ */ jsx(Loader, {})]
|
|
15562
|
+
children: [/* @__PURE__ */ jsx(BrandLogo, { className: "h-10 mb-4" }), /* @__PURE__ */ jsx(Loader, {})]
|
|
14974
15563
|
})
|
|
14975
15564
|
});
|
|
14976
15565
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -14980,19 +15569,19 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
14980
15569
|
children: [
|
|
14981
15570
|
/* @__PURE__ */ jsxs("div", {
|
|
14982
15571
|
className: "text-center mb-8",
|
|
14983
|
-
children: [/* @__PURE__ */ jsx(BrandLogo, {
|
|
14984
|
-
logoUrl: manifest?.admin?.logo,
|
|
14985
|
-
siteName: manifest?.admin?.siteName,
|
|
14986
|
-
className: "h-10 mx-auto mb-2"
|
|
14987
|
-
}), /* @__PURE__ */ jsxs("h1", {
|
|
15572
|
+
children: [/* @__PURE__ */ jsx(BrandLogo, { className: "h-10 mx-auto mb-2" }), /* @__PURE__ */ jsx("h1", {
|
|
14988
15573
|
className: "text-2xl font-semibold text-kumo-default",
|
|
14989
|
-
children:
|
|
14990
|
-
id: "lE0wHD",
|
|
14991
|
-
message: "Sign in to your site"
|
|
14992
|
-
}), method === "magic-link" && _t2({
|
|
15574
|
+
children: method === "magic-link" ? _t2({
|
|
14993
15575
|
id: "me9L29",
|
|
14994
15576
|
message: "Sign in with email"
|
|
14995
|
-
})
|
|
15577
|
+
}) : activeProvider ? _t2({
|
|
15578
|
+
id: "e/ToF5",
|
|
15579
|
+
message: "Sign in with {0}",
|
|
15580
|
+
values: { 0: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider }
|
|
15581
|
+
}) : _t2({
|
|
15582
|
+
id: "lE0wHD",
|
|
15583
|
+
message: "Sign in to your site"
|
|
15584
|
+
})
|
|
14996
15585
|
})]
|
|
14997
15586
|
}),
|
|
14998
15587
|
urlError && /* @__PURE__ */ jsx("div", {
|
|
@@ -15001,71 +15590,94 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
15001
15590
|
}),
|
|
15002
15591
|
/* @__PURE__ */ jsxs("div", {
|
|
15003
15592
|
className: "bg-kumo-base border rounded-lg shadow-sm p-6",
|
|
15004
|
-
children: [
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15593
|
+
children: [
|
|
15594
|
+
method === "passkey" && !activeProvider && /* @__PURE__ */ jsxs("div", {
|
|
15595
|
+
className: "space-y-6",
|
|
15596
|
+
children: [
|
|
15597
|
+
/* @__PURE__ */ jsx(PasskeyLogin, {
|
|
15598
|
+
optionsEndpoint: "/_emdash/api/auth/passkey/options",
|
|
15599
|
+
verifyEndpoint: "/_emdash/api/auth/passkey/verify",
|
|
15600
|
+
onSuccess: handleSuccess,
|
|
15601
|
+
buttonText: _t2({
|
|
15602
|
+
id: "QjsOMP",
|
|
15603
|
+
message: "Sign in with Passkey"
|
|
15604
|
+
})
|
|
15605
|
+
}),
|
|
15606
|
+
/* @__PURE__ */ jsxs("div", {
|
|
15607
|
+
className: "relative",
|
|
15608
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
15609
|
+
className: "absolute inset-0 flex items-center",
|
|
15610
|
+
children: /* @__PURE__ */ jsx("span", { className: "w-full border-t" })
|
|
15611
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
15612
|
+
className: "relative flex justify-center text-xs uppercase",
|
|
15613
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
15614
|
+
className: "bg-kumo-base px-2 text-kumo-subtle",
|
|
15615
|
+
children: _t2({
|
|
15616
|
+
id: "zW+FpA",
|
|
15617
|
+
message: "Or continue with"
|
|
15618
|
+
})
|
|
15028
15619
|
})
|
|
15620
|
+
})]
|
|
15621
|
+
}),
|
|
15622
|
+
buttonProviders.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
15623
|
+
className: `grid gap-3 ${buttonProviders.length === 1 ? "grid-cols-1" : "grid-cols-2"}`,
|
|
15624
|
+
children: buttonProviders.map((provider) => {
|
|
15625
|
+
const Btn = provider.LoginButton;
|
|
15626
|
+
const hasForm = !!provider.LoginForm;
|
|
15627
|
+
const selectProvider = () => setActiveProvider(provider.id);
|
|
15628
|
+
return /* @__PURE__ */ jsx("div", {
|
|
15629
|
+
onClick: hasForm ? selectProvider : void 0,
|
|
15630
|
+
children: /* @__PURE__ */ jsx(Btn, {})
|
|
15631
|
+
}, provider.id);
|
|
15029
15632
|
})
|
|
15030
|
-
})
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
className: "grid grid-cols-2 gap-3",
|
|
15034
|
-
children: OAUTH_PROVIDERS.map((provider) => /* @__PURE__ */ jsxs(Button, {
|
|
15035
|
-
variant: "outline",
|
|
15036
|
-
type: "button",
|
|
15037
|
-
onClick: () => handleOAuthClick(provider.id),
|
|
15633
|
+
}),
|
|
15634
|
+
/* @__PURE__ */ jsx(Button, {
|
|
15635
|
+
variant: "ghost",
|
|
15038
15636
|
className: "w-full justify-center",
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
/* @__PURE__ */ jsx(Button, {
|
|
15046
|
-
variant: "ghost",
|
|
15047
|
-
className: "w-full justify-center",
|
|
15048
|
-
type: "button",
|
|
15049
|
-
onClick: () => setMethod("magic-link"),
|
|
15050
|
-
children: _t2({
|
|
15051
|
-
id: "RxerEl",
|
|
15052
|
-
message: "Sign in with email link"
|
|
15637
|
+
type: "button",
|
|
15638
|
+
onClick: () => setMethod("magic-link"),
|
|
15639
|
+
children: _t2({
|
|
15640
|
+
id: "RxerEl",
|
|
15641
|
+
message: "Sign in with email link"
|
|
15642
|
+
})
|
|
15053
15643
|
})
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15644
|
+
]
|
|
15645
|
+
}),
|
|
15646
|
+
method === "passkey" && activeProvider && (() => {
|
|
15647
|
+
const provider = authProviderList.find((p) => p.id === activeProvider);
|
|
15648
|
+
if (!provider?.LoginForm) return null;
|
|
15649
|
+
const Form = provider.LoginForm;
|
|
15650
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
15651
|
+
className: "space-y-4",
|
|
15652
|
+
children: [/* @__PURE__ */ jsx(Form, {}), /* @__PURE__ */ jsx(Button, {
|
|
15653
|
+
type: "button",
|
|
15654
|
+
variant: "ghost",
|
|
15655
|
+
className: "w-full justify-center",
|
|
15656
|
+
onClick: () => setActiveProvider(null),
|
|
15657
|
+
children: _t2({
|
|
15658
|
+
id: "VCoEm+",
|
|
15659
|
+
message: "Back to login"
|
|
15660
|
+
})
|
|
15661
|
+
})]
|
|
15662
|
+
});
|
|
15663
|
+
})(),
|
|
15664
|
+
method === "magic-link" && /* @__PURE__ */ jsx(MagicLinkForm, { onBack: () => setMethod("passkey") })
|
|
15665
|
+
]
|
|
15057
15666
|
}),
|
|
15058
15667
|
/* @__PURE__ */ jsx("p", {
|
|
15059
15668
|
className: "text-center mt-6 text-sm text-kumo-subtle",
|
|
15060
|
-
children: method === "
|
|
15061
|
-
id: "+ET/av",
|
|
15062
|
-
message: "Use your registered passkey to sign in securely."
|
|
15063
|
-
}) : _t2({
|
|
15669
|
+
children: method === "magic-link" ? _t2({
|
|
15064
15670
|
id: "pv+wH3",
|
|
15065
15671
|
message: "We'll send you a link to sign in without a password."
|
|
15672
|
+
}) : activeProvider ? _t2({
|
|
15673
|
+
id: "TZGT4o",
|
|
15674
|
+
message: "Enter your handle to sign in."
|
|
15675
|
+
}) : _t2({
|
|
15676
|
+
id: "+ET/av",
|
|
15677
|
+
message: "Use your registered passkey to sign in securely."
|
|
15066
15678
|
})
|
|
15067
15679
|
}),
|
|
15068
|
-
|
|
15680
|
+
authInfo?.signupEnabled && /* @__PURE__ */ jsx("p", {
|
|
15069
15681
|
className: "text-center mt-4 text-sm text-kumo-subtle",
|
|
15070
15682
|
children: /* @__PURE__ */ jsx(Trans, {
|
|
15071
15683
|
id: "352VU2",
|
|
@@ -15964,7 +16576,7 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
15964
16576
|
to: "/plugins/$pluginId/$",
|
|
15965
16577
|
params: {
|
|
15966
16578
|
pluginId: plugin.id,
|
|
15967
|
-
_splat: "
|
|
16579
|
+
_splat: ""
|
|
15968
16580
|
},
|
|
15969
16581
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
15970
16582
|
variant: "ghost",
|
|
@@ -16006,7 +16618,7 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
16006
16618
|
}),
|
|
16007
16619
|
onClick: () => setExpanded(!expanded),
|
|
16008
16620
|
"aria-expanded": expanded,
|
|
16009
|
-
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", {
|
|
16010
16622
|
className: "sr-only",
|
|
16011
16623
|
children: [
|
|
16012
16624
|
expanded ? _t2({
|
|
@@ -16609,7 +17221,7 @@ function BackLink() {
|
|
|
16609
17221
|
return /* @__PURE__ */ jsxs(Link$1, {
|
|
16610
17222
|
to: "/plugins/marketplace",
|
|
16611
17223
|
className: "inline-flex items-center gap-1 text-sm text-kumo-subtle hover:text-kumo-default",
|
|
16612
|
-
children: [/* @__PURE__ */ jsx(
|
|
17224
|
+
children: [/* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" }), _t2({
|
|
16613
17225
|
id: "x5HVV+",
|
|
16614
17226
|
message: "Back to marketplace"
|
|
16615
17227
|
})]
|
|
@@ -16656,7 +17268,7 @@ function ScreenshotLightbox({ screenshots, index, isBlurred = false, onClose, on
|
|
|
16656
17268
|
id: "k7xxm/",
|
|
16657
17269
|
message: "Previous screenshot"
|
|
16658
17270
|
}),
|
|
16659
|
-
children: /* @__PURE__ */ jsx(
|
|
17271
|
+
children: /* @__PURE__ */ jsx(CaretPrev, { className: "h-5 w-5" })
|
|
16660
17272
|
}),
|
|
16661
17273
|
/* @__PURE__ */ jsx("img", {
|
|
16662
17274
|
src: screenshots[index],
|
|
@@ -16677,7 +17289,7 @@ function ScreenshotLightbox({ screenshots, index, isBlurred = false, onClose, on
|
|
|
16677
17289
|
id: "tgnDjk",
|
|
16678
17290
|
message: "Next screenshot"
|
|
16679
17291
|
}),
|
|
16680
|
-
children: /* @__PURE__ */ jsx(
|
|
17292
|
+
children: /* @__PURE__ */ jsx(CaretNext, { className: "h-5 w-5" })
|
|
16681
17293
|
}),
|
|
16682
17294
|
/* @__PURE__ */ jsxs("div", {
|
|
16683
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",
|
|
@@ -18219,7 +18831,7 @@ function MenuEditor() {
|
|
|
18219
18831
|
message: "Back"
|
|
18220
18832
|
}),
|
|
18221
18833
|
onClick: () => navigate({ to: "/menus" }),
|
|
18222
|
-
children: /* @__PURE__ */ jsx(
|
|
18834
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
18223
18835
|
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
18224
18836
|
className: "text-3xl font-bold",
|
|
18225
18837
|
children: menu.label
|
|
@@ -18783,7 +19395,7 @@ function MenuList() {
|
|
|
18783
19395
|
name: "name",
|
|
18784
19396
|
required: true,
|
|
18785
19397
|
placeholder: "primary",
|
|
18786
|
-
pattern: "[a-z0-9
|
|
19398
|
+
pattern: "[a-z0-9\\-]+",
|
|
18787
19399
|
title: _t({
|
|
18788
19400
|
id: "6ZQzu/",
|
|
18789
19401
|
message: "Only lowercase letters, numbers, and hyphens"
|
|
@@ -19523,7 +20135,7 @@ function Redirects() {
|
|
|
19523
20135
|
}),
|
|
19524
20136
|
/* @__PURE__ */ jsx("div", {
|
|
19525
20137
|
className: "w-8 text-center text-kumo-subtle",
|
|
19526
|
-
children: /* @__PURE__ */ jsx(
|
|
20138
|
+
children: /* @__PURE__ */ jsx(ArrowNext, { size: 14 })
|
|
19527
20139
|
}),
|
|
19528
20140
|
/* @__PURE__ */ jsx("div", {
|
|
19529
20141
|
className: "flex-1 font-mono text-xs truncate",
|
|
@@ -19803,7 +20415,7 @@ function SectionEditor() {
|
|
|
19803
20415
|
id: "t4JBeQ",
|
|
19804
20416
|
message: "Back to sections"
|
|
19805
20417
|
}),
|
|
19806
|
-
children: /* @__PURE__ */ jsx(
|
|
20418
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-5 w-5" })
|
|
19807
20419
|
})
|
|
19808
20420
|
}), /* @__PURE__ */ jsx("h1", {
|
|
19809
20421
|
className: "text-2xl font-bold",
|
|
@@ -19865,6 +20477,16 @@ function SectionEditorForm({ section, isSaving, onSave }) {
|
|
|
19865
20477
|
keywords,
|
|
19866
20478
|
content
|
|
19867
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
|
+
}, []);
|
|
19868
20490
|
const handleSave = () => {
|
|
19869
20491
|
const keywordsArray = keywords.split(",").map((k) => k.trim()).filter(Boolean);
|
|
19870
20492
|
onSave({
|
|
@@ -19877,43 +20499,41 @@ function SectionEditorForm({ section, isSaving, onSave }) {
|
|
|
19877
20499
|
};
|
|
19878
20500
|
return /* @__PURE__ */ jsxs("div", {
|
|
19879
20501
|
className: "space-y-6",
|
|
19880
|
-
children: [/* @__PURE__ */ jsxs(
|
|
19881
|
-
|
|
19882
|
-
|
|
19883
|
-
|
|
19884
|
-
|
|
19885
|
-
|
|
19886
|
-
|
|
19887
|
-
|
|
19888
|
-
|
|
19889
|
-
|
|
19890
|
-
|
|
19891
|
-
|
|
19892
|
-
|
|
19893
|
-
|
|
19894
|
-
})
|
|
19895
|
-
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
19896
|
-
className: "text-2xl font-bold",
|
|
19897
|
-
children: section.title
|
|
19898
|
-
}), /* @__PURE__ */ jsxs("p", {
|
|
19899
|
-
className: "text-sm text-kumo-subtle",
|
|
19900
|
-
children: [
|
|
19901
|
-
section.source === "theme" ? _t2({
|
|
19902
|
-
id: "wg2j4A",
|
|
19903
|
-
message: "Theme Section"
|
|
19904
|
-
}) : _t2({
|
|
19905
|
-
id: "dqjp2n",
|
|
19906
|
-
message: "Custom Section"
|
|
19907
|
-
}),
|
|
19908
|
-
" ·",
|
|
19909
|
-
" ",
|
|
19910
|
-
section.slug
|
|
19911
|
-
]
|
|
19912
|
-
})] })]
|
|
19913
|
-
}), /* @__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, {
|
|
19914
20516
|
isSaving,
|
|
19915
20517
|
isDirty,
|
|
19916
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
|
+
]
|
|
19917
20537
|
})]
|
|
19918
20538
|
}), /* @__PURE__ */ jsxs("div", {
|
|
19919
20539
|
className: "grid grid-cols-12 gap-6",
|
|
@@ -19929,12 +20549,24 @@ function SectionEditorForm({ section, isSaving, onSave }) {
|
|
|
19929
20549
|
})
|
|
19930
20550
|
}), /* @__PURE__ */ jsx(PortableTextEditor, {
|
|
19931
20551
|
value: content,
|
|
19932
|
-
onChange: (value) => setContent(value)
|
|
20552
|
+
onChange: (value) => setContent(value),
|
|
20553
|
+
onBlockSidebarOpen: handleBlockSidebarOpen,
|
|
20554
|
+
onBlockSidebarClose: handleBlockSidebarClose
|
|
19933
20555
|
})]
|
|
19934
20556
|
})
|
|
19935
|
-
}), /* @__PURE__ */
|
|
20557
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
19936
20558
|
className: "col-span-4 space-y-6",
|
|
19937
|
-
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", {
|
|
19938
20570
|
className: "rounded-lg border bg-kumo-base p-6 space-y-4",
|
|
19939
20571
|
children: [
|
|
19940
20572
|
/* @__PURE__ */ jsx("h2", {
|
|
@@ -19967,7 +20599,7 @@ function SectionEditorForm({ section, isSaving, onSave }) {
|
|
|
19967
20599
|
setSlugTouched(true);
|
|
19968
20600
|
},
|
|
19969
20601
|
placeholder: "section-slug",
|
|
19970
|
-
pattern: "[a-z0-9
|
|
20602
|
+
pattern: "[a-z0-9\\-]+"
|
|
19971
20603
|
}), /* @__PURE__ */ jsx("p", {
|
|
19972
20604
|
className: "text-xs text-kumo-subtle mt-1",
|
|
19973
20605
|
children: _t2({
|
|
@@ -20040,7 +20672,7 @@ function SectionEditorForm({ section, isSaving, onSave }) {
|
|
|
20040
20672
|
})
|
|
20041
20673
|
})
|
|
20042
20674
|
]
|
|
20043
|
-
})]
|
|
20675
|
+
})] })
|
|
20044
20676
|
})]
|
|
20045
20677
|
})]
|
|
20046
20678
|
});
|
|
@@ -20235,7 +20867,7 @@ function Sections() {
|
|
|
20235
20867
|
},
|
|
20236
20868
|
required: true,
|
|
20237
20869
|
placeholder: "hero-banner",
|
|
20238
|
-
pattern: "[a-z0-9
|
|
20870
|
+
pattern: "[a-z0-9\\-]+",
|
|
20239
20871
|
title: _t({
|
|
20240
20872
|
id: "BjF0Jv",
|
|
20241
20873
|
message: "Lowercase letters, numbers, and hyphens only"
|
|
@@ -20564,7 +21196,7 @@ function SettingsLink({ to, icon, title, description }) {
|
|
|
20564
21196
|
className: "text-sm text-kumo-subtle",
|
|
20565
21197
|
children: description
|
|
20566
21198
|
})] })]
|
|
20567
|
-
}), /* @__PURE__ */ jsx(
|
|
21199
|
+
}), /* @__PURE__ */ jsx(CaretNext, { className: "h-5 w-5 text-kumo-subtle" })]
|
|
20568
21200
|
});
|
|
20569
21201
|
}
|
|
20570
21202
|
/**
|
|
@@ -21002,7 +21634,7 @@ function AllowedDomainsSettings() {
|
|
|
21002
21634
|
id: "9aZHfH",
|
|
21003
21635
|
message: "Back to settings"
|
|
21004
21636
|
}),
|
|
21005
|
-
children: /* @__PURE__ */ jsx(
|
|
21637
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
21006
21638
|
})
|
|
21007
21639
|
}), /* @__PURE__ */ jsx("h1", {
|
|
21008
21640
|
className: "text-2xl font-bold",
|
|
@@ -21459,6 +22091,50 @@ const API_TOKEN_SCOPE_VALUES = [
|
|
|
21459
22091
|
message: "Modify collection schemas"
|
|
21460
22092
|
}
|
|
21461
22093
|
},
|
|
22094
|
+
{
|
|
22095
|
+
scope: API_TOKEN_SCOPES.TaxonomiesManage,
|
|
22096
|
+
label: {
|
|
22097
|
+
id: "cfmuSI",
|
|
22098
|
+
message: "Taxonomies Manage"
|
|
22099
|
+
},
|
|
22100
|
+
description: {
|
|
22101
|
+
id: "mmBufY",
|
|
22102
|
+
message: "Create, update, and delete taxonomy terms"
|
|
22103
|
+
}
|
|
22104
|
+
},
|
|
22105
|
+
{
|
|
22106
|
+
scope: API_TOKEN_SCOPES.MenusManage,
|
|
22107
|
+
label: {
|
|
22108
|
+
id: "Wosws9",
|
|
22109
|
+
message: "Menus Manage"
|
|
22110
|
+
},
|
|
22111
|
+
description: {
|
|
22112
|
+
id: "DGhfmD",
|
|
22113
|
+
message: "Create, update, and delete navigation menus"
|
|
22114
|
+
}
|
|
22115
|
+
},
|
|
22116
|
+
{
|
|
22117
|
+
scope: API_TOKEN_SCOPES.SettingsRead,
|
|
22118
|
+
label: {
|
|
22119
|
+
id: "tYQSLw",
|
|
22120
|
+
message: "Settings Read"
|
|
22121
|
+
},
|
|
22122
|
+
description: {
|
|
22123
|
+
id: "fHKrk0",
|
|
22124
|
+
message: "Read site settings"
|
|
22125
|
+
}
|
|
22126
|
+
},
|
|
22127
|
+
{
|
|
22128
|
+
scope: API_TOKEN_SCOPES.SettingsManage,
|
|
22129
|
+
label: {
|
|
22130
|
+
id: "+K5A49",
|
|
22131
|
+
message: "Settings Manage"
|
|
22132
|
+
},
|
|
22133
|
+
description: {
|
|
22134
|
+
id: "RAXQwS",
|
|
22135
|
+
message: "Update site settings"
|
|
22136
|
+
}
|
|
22137
|
+
},
|
|
21462
22138
|
{
|
|
21463
22139
|
scope: API_TOKEN_SCOPES.Admin,
|
|
21464
22140
|
label: {
|
|
@@ -21539,7 +22215,7 @@ function ApiTokenSettings() {
|
|
|
21539
22215
|
id: "9aZHfH",
|
|
21540
22216
|
message: "Back to settings"
|
|
21541
22217
|
}),
|
|
21542
|
-
children: /* @__PURE__ */ jsx(
|
|
22218
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
21543
22219
|
})
|
|
21544
22220
|
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
21545
22221
|
className: "text-2xl font-bold",
|
|
@@ -21948,7 +22624,7 @@ function EmailSettings() {
|
|
|
21948
22624
|
id: "9aZHfH",
|
|
21949
22625
|
message: "Back to settings"
|
|
21950
22626
|
}),
|
|
21951
|
-
children: /* @__PURE__ */ jsx(
|
|
22627
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
21952
22628
|
})
|
|
21953
22629
|
}), /* @__PURE__ */ jsx("h1", {
|
|
21954
22630
|
className: "text-2xl font-bold",
|
|
@@ -21979,7 +22655,7 @@ function EmailSettings() {
|
|
|
21979
22655
|
id: "9aZHfH",
|
|
21980
22656
|
message: "Back to settings"
|
|
21981
22657
|
}),
|
|
21982
|
-
children: /* @__PURE__ */ jsx(
|
|
22658
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
21983
22659
|
})
|
|
21984
22660
|
}), /* @__PURE__ */ jsx("h1", {
|
|
21985
22661
|
className: "text-2xl font-bold",
|
|
@@ -22281,7 +22957,7 @@ function GeneralSettings() {
|
|
|
22281
22957
|
id: "9aZHfH",
|
|
22282
22958
|
message: "Back to settings"
|
|
22283
22959
|
}),
|
|
22284
|
-
children: /* @__PURE__ */ jsx(
|
|
22960
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
22285
22961
|
})
|
|
22286
22962
|
}), /* @__PURE__ */ jsx("h1", {
|
|
22287
22963
|
className: "text-2xl font-bold",
|
|
@@ -22304,9 +22980,8 @@ function GeneralSettings() {
|
|
|
22304
22980
|
return /* @__PURE__ */ jsxs("div", {
|
|
22305
22981
|
className: "space-y-6",
|
|
22306
22982
|
children: [
|
|
22307
|
-
/* @__PURE__ */
|
|
22308
|
-
|
|
22309
|
-
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
22983
|
+
/* @__PURE__ */ jsx(EditorHeader, {
|
|
22984
|
+
leading: /* @__PURE__ */ jsx(Link$1, {
|
|
22310
22985
|
to: "/settings",
|
|
22311
22986
|
children: /* @__PURE__ */ jsx(Button, {
|
|
22312
22987
|
variant: "ghost",
|
|
@@ -22315,21 +22990,36 @@ function GeneralSettings() {
|
|
|
22315
22990
|
id: "9aZHfH",
|
|
22316
22991
|
message: "Back to settings"
|
|
22317
22992
|
}),
|
|
22318
|
-
children: /* @__PURE__ */ jsx(
|
|
22993
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
22319
22994
|
})
|
|
22320
|
-
}),
|
|
22321
|
-
|
|
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",
|
|
22322
23011
|
children: _t({
|
|
22323
23012
|
id: "Cb/Jr5",
|
|
22324
23013
|
message: "General Settings"
|
|
22325
23014
|
})
|
|
22326
|
-
})
|
|
23015
|
+
})
|
|
22327
23016
|
}),
|
|
22328
23017
|
saveStatus && /* @__PURE__ */ jsxs("div", {
|
|
22329
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"}`,
|
|
22330
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]
|
|
22331
23020
|
}),
|
|
22332
23021
|
/* @__PURE__ */ jsxs("form", {
|
|
23022
|
+
id: "general-settings-form",
|
|
22333
23023
|
onSubmit: handleSubmit,
|
|
22334
23024
|
className: "space-y-6",
|
|
22335
23025
|
children: [
|
|
@@ -22912,7 +23602,7 @@ function SecuritySettings() {
|
|
|
22912
23602
|
id: "9aZHfH",
|
|
22913
23603
|
message: "Back to settings"
|
|
22914
23604
|
}),
|
|
22915
|
-
children: /* @__PURE__ */ jsx(
|
|
23605
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
22916
23606
|
})
|
|
22917
23607
|
}), /* @__PURE__ */ jsx("h1", {
|
|
22918
23608
|
className: "text-2xl font-bold",
|
|
@@ -23133,7 +23823,7 @@ function SeoSettings() {
|
|
|
23133
23823
|
id: "9aZHfH",
|
|
23134
23824
|
message: "Back to settings"
|
|
23135
23825
|
}),
|
|
23136
|
-
children: /* @__PURE__ */ jsx(
|
|
23826
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
23137
23827
|
})
|
|
23138
23828
|
}), /* @__PURE__ */ jsx("h1", {
|
|
23139
23829
|
className: "text-2xl font-bold",
|
|
@@ -23156,9 +23846,8 @@ function SeoSettings() {
|
|
|
23156
23846
|
return /* @__PURE__ */ jsxs("div", {
|
|
23157
23847
|
className: "space-y-6",
|
|
23158
23848
|
children: [
|
|
23159
|
-
/* @__PURE__ */
|
|
23160
|
-
|
|
23161
|
-
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
23849
|
+
/* @__PURE__ */ jsx(EditorHeader, {
|
|
23850
|
+
leading: /* @__PURE__ */ jsx(Link$1, {
|
|
23162
23851
|
to: "/settings",
|
|
23163
23852
|
children: /* @__PURE__ */ jsx(Button, {
|
|
23164
23853
|
variant: "ghost",
|
|
@@ -23167,21 +23856,36 @@ function SeoSettings() {
|
|
|
23167
23856
|
id: "9aZHfH",
|
|
23168
23857
|
message: "Back to settings"
|
|
23169
23858
|
}),
|
|
23170
|
-
children: /* @__PURE__ */ jsx(
|
|
23859
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
23171
23860
|
})
|
|
23172
|
-
}),
|
|
23173
|
-
|
|
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",
|
|
23174
23877
|
children: _t({
|
|
23175
23878
|
id: "GfWoKv",
|
|
23176
23879
|
message: "SEO Settings"
|
|
23177
23880
|
})
|
|
23178
|
-
})
|
|
23881
|
+
})
|
|
23179
23882
|
}),
|
|
23180
23883
|
saveStatus && /* @__PURE__ */ jsxs("div", {
|
|
23181
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"}`,
|
|
23182
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]
|
|
23183
23886
|
}),
|
|
23184
23887
|
/* @__PURE__ */ jsxs("form", {
|
|
23888
|
+
id: "seo-settings-form",
|
|
23185
23889
|
onSubmit: handleSubmit,
|
|
23186
23890
|
className: "space-y-6",
|
|
23187
23891
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -23340,7 +24044,7 @@ function SocialSettings() {
|
|
|
23340
24044
|
id: "9aZHfH",
|
|
23341
24045
|
message: "Back to settings"
|
|
23342
24046
|
}),
|
|
23343
|
-
children: /* @__PURE__ */ jsx(
|
|
24047
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
23344
24048
|
})
|
|
23345
24049
|
}), /* @__PURE__ */ jsx("h1", {
|
|
23346
24050
|
className: "text-2xl font-bold",
|
|
@@ -23363,9 +24067,8 @@ function SocialSettings() {
|
|
|
23363
24067
|
return /* @__PURE__ */ jsxs("div", {
|
|
23364
24068
|
className: "space-y-6",
|
|
23365
24069
|
children: [
|
|
23366
|
-
/* @__PURE__ */
|
|
23367
|
-
|
|
23368
|
-
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
24070
|
+
/* @__PURE__ */ jsx(EditorHeader, {
|
|
24071
|
+
leading: /* @__PURE__ */ jsx(Link$1, {
|
|
23369
24072
|
to: "/settings",
|
|
23370
24073
|
children: /* @__PURE__ */ jsx(Button, {
|
|
23371
24074
|
variant: "ghost",
|
|
@@ -23374,21 +24077,36 @@ function SocialSettings() {
|
|
|
23374
24077
|
id: "9aZHfH",
|
|
23375
24078
|
message: "Back to settings"
|
|
23376
24079
|
}),
|
|
23377
|
-
children: /* @__PURE__ */ jsx(
|
|
24080
|
+
children: /* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" })
|
|
23378
24081
|
})
|
|
23379
|
-
}),
|
|
23380
|
-
|
|
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",
|
|
23381
24098
|
children: _t({
|
|
23382
24099
|
id: "d0rUsW",
|
|
23383
24100
|
message: "Social Links"
|
|
23384
24101
|
})
|
|
23385
|
-
})
|
|
24102
|
+
})
|
|
23386
24103
|
}),
|
|
23387
24104
|
saveStatus && /* @__PURE__ */ jsxs("div", {
|
|
23388
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"}`,
|
|
23389
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]
|
|
23390
24107
|
}),
|
|
23391
24108
|
/* @__PURE__ */ jsxs("form", {
|
|
24109
|
+
id: "social-settings-form",
|
|
23392
24110
|
onSubmit: handleSubmit,
|
|
23393
24111
|
className: "space-y-6",
|
|
23394
24112
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -23516,8 +24234,9 @@ function SocialSettings() {
|
|
|
23516
24234
|
*
|
|
23517
24235
|
* Steps:
|
|
23518
24236
|
* 1. Site Configuration (title, tagline, sample content)
|
|
23519
|
-
* 2.
|
|
23520
|
-
*
|
|
24237
|
+
* 2. Create admin account — user picks any available auth method:
|
|
24238
|
+
* - Passkey (always available)
|
|
24239
|
+
* - Any configured auth provider (AT Protocol, GitHub, Google, etc.)
|
|
23521
24240
|
*/
|
|
23522
24241
|
async function fetchSetupStatus() {
|
|
23523
24242
|
return parseApiResponse(await apiFetch("/_emdash/api/setup/status"), "Failed to fetch setup status");
|
|
@@ -23538,8 +24257,8 @@ async function executeAdminSetup(data) {
|
|
|
23538
24257
|
}
|
|
23539
24258
|
function SiteStep({ seedInfo, onNext, isLoading, error }) {
|
|
23540
24259
|
const { _: _t } = useLingui();
|
|
23541
|
-
const [title, setTitle] = React.useState("");
|
|
23542
|
-
const [tagline, setTagline] = React.useState("");
|
|
24260
|
+
const [title, setTitle] = React.useState(seedInfo?.title ?? "");
|
|
24261
|
+
const [tagline, setTagline] = React.useState(seedInfo?.tagline ?? "");
|
|
23543
24262
|
const [includeContent, setIncludeContent] = React.useState(true);
|
|
23544
24263
|
const [errors, setErrors] = React.useState({});
|
|
23545
24264
|
const validate = () => {
|
|
@@ -23751,58 +24470,97 @@ function AdminStep({ onNext, onBack, isLoading, error }) {
|
|
|
23751
24470
|
]
|
|
23752
24471
|
});
|
|
23753
24472
|
}
|
|
23754
|
-
function
|
|
24473
|
+
function handleSetupSuccess() {
|
|
23755
24474
|
window.location.href = "/_emdash/admin";
|
|
23756
24475
|
}
|
|
23757
|
-
function
|
|
24476
|
+
function AuthMethodStep({ adminData, providers, onBack }) {
|
|
23758
24477
|
const { _: _t3 } = useLingui();
|
|
24478
|
+
const [activeProvider, setActiveProvider] = React.useState(null);
|
|
24479
|
+
const buttonProviders = providers.filter((p) => p.LoginButton);
|
|
24480
|
+
const hasProviders = buttonProviders.length > 0;
|
|
24481
|
+
if (activeProvider) {
|
|
24482
|
+
const provider = providers.find((p) => p.id === activeProvider);
|
|
24483
|
+
if (provider && (provider.SetupStep || provider.LoginForm)) return /* @__PURE__ */ jsxs("div", {
|
|
24484
|
+
className: "space-y-4",
|
|
24485
|
+
children: [
|
|
24486
|
+
/* @__PURE__ */ jsx("div", {
|
|
24487
|
+
className: "text-center mb-2",
|
|
24488
|
+
children: /* @__PURE__ */ jsx("h3", {
|
|
24489
|
+
className: "text-lg font-medium",
|
|
24490
|
+
children: _t3({
|
|
24491
|
+
id: "e/ToF5",
|
|
24492
|
+
message: "Sign in with {0}",
|
|
24493
|
+
values: { 0: provider.label }
|
|
24494
|
+
})
|
|
24495
|
+
})
|
|
24496
|
+
}),
|
|
24497
|
+
provider.SetupStep ? /* @__PURE__ */ jsx(provider.SetupStep, { onComplete: handleSetupSuccess }) : provider.LoginForm ? /* @__PURE__ */ jsx(provider.LoginForm, {}) : null,
|
|
24498
|
+
/* @__PURE__ */ jsx(Button, {
|
|
24499
|
+
type: "button",
|
|
24500
|
+
variant: "ghost",
|
|
24501
|
+
className: "w-full justify-center",
|
|
24502
|
+
onClick: () => setActiveProvider(null),
|
|
24503
|
+
children: _t3({
|
|
24504
|
+
id: "2ygf/L",
|
|
24505
|
+
message: "← Back"
|
|
24506
|
+
})
|
|
24507
|
+
})
|
|
24508
|
+
]
|
|
24509
|
+
});
|
|
24510
|
+
}
|
|
23759
24511
|
return /* @__PURE__ */ jsxs("div", {
|
|
23760
24512
|
className: "space-y-6",
|
|
23761
24513
|
children: [
|
|
23762
24514
|
/* @__PURE__ */ jsxs("div", {
|
|
23763
24515
|
className: "text-center",
|
|
23764
|
-
children: [
|
|
23765
|
-
|
|
23766
|
-
|
|
23767
|
-
|
|
23768
|
-
|
|
23769
|
-
fill: "none",
|
|
23770
|
-
stroke: "currentColor",
|
|
23771
|
-
viewBox: "0 0 24 24",
|
|
23772
|
-
children: /* @__PURE__ */ jsx("path", {
|
|
23773
|
-
strokeLinecap: "round",
|
|
23774
|
-
strokeLinejoin: "round",
|
|
23775
|
-
strokeWidth: 2,
|
|
23776
|
-
d: "M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.39-2.823 1.07-4"
|
|
23777
|
-
})
|
|
23778
|
-
})
|
|
23779
|
-
}),
|
|
23780
|
-
/* @__PURE__ */ jsx("h3", {
|
|
23781
|
-
className: "text-lg font-medium",
|
|
23782
|
-
children: _t3({
|
|
23783
|
-
id: "fifQ8k",
|
|
23784
|
-
message: "Set up your passkey"
|
|
23785
|
-
})
|
|
23786
|
-
}),
|
|
23787
|
-
/* @__PURE__ */ jsx("p", {
|
|
23788
|
-
className: "text-sm text-kumo-subtle mt-1",
|
|
23789
|
-
children: _t3({
|
|
23790
|
-
id: "YdZ7D8",
|
|
23791
|
-
message: "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
|
|
23792
|
-
})
|
|
24516
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
24517
|
+
className: "text-lg font-medium",
|
|
24518
|
+
children: _t3({
|
|
24519
|
+
id: "HAV363",
|
|
24520
|
+
message: "Choose how to sign in"
|
|
23793
24521
|
})
|
|
23794
|
-
|
|
24522
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
24523
|
+
className: "text-sm text-kumo-subtle mt-1",
|
|
24524
|
+
children: _t3({
|
|
24525
|
+
id: "NH33Oi",
|
|
24526
|
+
message: "Pick any method to create your admin account."
|
|
24527
|
+
})
|
|
24528
|
+
})]
|
|
23795
24529
|
}),
|
|
23796
24530
|
/* @__PURE__ */ jsx(PasskeyRegistration, {
|
|
23797
24531
|
optionsEndpoint: "/_emdash/api/setup/admin",
|
|
23798
24532
|
verifyEndpoint: "/_emdash/api/setup/admin/verify",
|
|
23799
|
-
onSuccess:
|
|
24533
|
+
onSuccess: handleSetupSuccess,
|
|
23800
24534
|
buttonText: _t3({
|
|
23801
24535
|
id: "AKqzR1",
|
|
23802
24536
|
message: "Create Passkey"
|
|
23803
24537
|
}),
|
|
23804
24538
|
additionalData: { ...adminData }
|
|
23805
24539
|
}),
|
|
24540
|
+
hasProviders && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
24541
|
+
className: "relative",
|
|
24542
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
24543
|
+
className: "absolute inset-0 flex items-center",
|
|
24544
|
+
children: /* @__PURE__ */ jsx("span", { className: "w-full border-t" })
|
|
24545
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
24546
|
+
className: "relative flex justify-center text-xs uppercase",
|
|
24547
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
24548
|
+
className: "bg-kumo-base px-2 text-kumo-subtle",
|
|
24549
|
+
children: "Or continue with"
|
|
24550
|
+
})
|
|
24551
|
+
})]
|
|
24552
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
24553
|
+
className: `grid gap-3 ${buttonProviders.length === 1 ? "grid-cols-1" : "grid-cols-2"}`,
|
|
24554
|
+
children: buttonProviders.map((provider) => {
|
|
24555
|
+
const Btn = provider.LoginButton;
|
|
24556
|
+
const hasForm = !!provider.LoginForm || !!provider.SetupStep;
|
|
24557
|
+
const selectProvider = () => setActiveProvider(provider.id);
|
|
24558
|
+
return /* @__PURE__ */ jsx("div", {
|
|
24559
|
+
onClick: hasForm ? selectProvider : void 0,
|
|
24560
|
+
children: /* @__PURE__ */ jsx(Btn, {})
|
|
24561
|
+
}, provider.id);
|
|
24562
|
+
})
|
|
24563
|
+
})] }),
|
|
23806
24564
|
/* @__PURE__ */ jsx(Button, {
|
|
23807
24565
|
type: "button",
|
|
23808
24566
|
variant: "ghost",
|
|
@@ -23842,8 +24600,8 @@ function StepIndicator$1({ currentStep, useAccessAuth }) {
|
|
|
23842
24600
|
{
|
|
23843
24601
|
key: "passkey",
|
|
23844
24602
|
label: _t4({
|
|
23845
|
-
id: "
|
|
23846
|
-
message: "
|
|
24603
|
+
id: "n1ekoW",
|
|
24604
|
+
message: "Sign In"
|
|
23847
24605
|
})
|
|
23848
24606
|
}
|
|
23849
24607
|
];
|
|
@@ -23881,6 +24639,17 @@ function SetupWizard() {
|
|
|
23881
24639
|
const [_siteData, setSiteData] = React.useState(null);
|
|
23882
24640
|
const [adminData, setAdminData] = React.useState(null);
|
|
23883
24641
|
const [error, setError] = React.useState();
|
|
24642
|
+
const [urlError, setUrlError] = React.useState(null);
|
|
24643
|
+
const authProviderList = useAuthProviderList();
|
|
24644
|
+
React.useEffect(() => {
|
|
24645
|
+
const params = new URLSearchParams(window.location.search);
|
|
24646
|
+
const errorParam = params.get("error");
|
|
24647
|
+
const message = params.get("message");
|
|
24648
|
+
if (errorParam) {
|
|
24649
|
+
setUrlError(message || `Authentication error: ${errorParam}`);
|
|
24650
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
24651
|
+
}
|
|
24652
|
+
}, []);
|
|
23884
24653
|
const { data: status, isLoading: statusLoading, error: statusError } = useQuery({
|
|
23885
24654
|
queryKey: ["setup", "status"],
|
|
23886
24655
|
queryFn: fetchSetupStatus,
|
|
@@ -23999,6 +24768,10 @@ function SetupWizard() {
|
|
|
23999
24768
|
})
|
|
24000
24769
|
]
|
|
24001
24770
|
}),
|
|
24771
|
+
urlError && /* @__PURE__ */ jsx("div", {
|
|
24772
|
+
className: "mb-6 rounded-lg bg-kumo-danger/10 border border-kumo-danger/20 p-4 text-sm text-kumo-danger",
|
|
24773
|
+
children: urlError
|
|
24774
|
+
}),
|
|
24002
24775
|
/* @__PURE__ */ jsx(StepIndicator$1, {
|
|
24003
24776
|
currentStep,
|
|
24004
24777
|
useAccessAuth
|
|
@@ -24021,8 +24794,9 @@ function SetupWizard() {
|
|
|
24021
24794
|
isLoading: adminMutation.isPending,
|
|
24022
24795
|
error
|
|
24023
24796
|
}),
|
|
24024
|
-
currentStep === "passkey" && adminData && /* @__PURE__ */ jsx(
|
|
24797
|
+
currentStep === "passkey" && adminData && /* @__PURE__ */ jsx(AuthMethodStep, {
|
|
24025
24798
|
adminData,
|
|
24799
|
+
providers: authProviderList,
|
|
24026
24800
|
onBack: () => {
|
|
24027
24801
|
setError(void 0);
|
|
24028
24802
|
setCurrentStep("admin");
|
|
@@ -24783,7 +25557,7 @@ function SidebarNav({ manifest }) {
|
|
|
24783
25557
|
collapsible: true,
|
|
24784
25558
|
defaultOpen: true,
|
|
24785
25559
|
children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
24786
|
-
className: "[&>span]:text-start",
|
|
25560
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
24787
25561
|
children: _t({
|
|
24788
25562
|
id: "4b3oEV",
|
|
24789
25563
|
message: "Content"
|
|
@@ -24795,7 +25569,7 @@ function SidebarNav({ manifest }) {
|
|
|
24795
25569
|
collapsible: true,
|
|
24796
25570
|
defaultOpen: true,
|
|
24797
25571
|
children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
24798
|
-
className: "[&>span]:text-start",
|
|
25572
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
24799
25573
|
children: _t({
|
|
24800
25574
|
id: "wckWOP",
|
|
24801
25575
|
message: "Manage"
|
|
@@ -24807,7 +25581,7 @@ function SidebarNav({ manifest }) {
|
|
|
24807
25581
|
collapsible: true,
|
|
24808
25582
|
defaultOpen: true,
|
|
24809
25583
|
children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
24810
|
-
className: "[&>span]:text-start",
|
|
25584
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
24811
25585
|
children: _t({
|
|
24812
25586
|
id: "U3pytU",
|
|
24813
25587
|
message: "Admin"
|
|
@@ -24818,7 +25592,7 @@ function SidebarNav({ manifest }) {
|
|
|
24818
25592
|
collapsible: true,
|
|
24819
25593
|
defaultOpen: true,
|
|
24820
25594
|
children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
24821
|
-
className: "[&>span]:text-start",
|
|
25595
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
24822
25596
|
children: _t({
|
|
24823
25597
|
id: "ohUJJM",
|
|
24824
25598
|
message: "Plugins"
|
|
@@ -25788,7 +26562,7 @@ function TermRow({ term, level = 0, onEdit, onDelete }) {
|
|
|
25788
26562
|
className: "flex items-center gap-4 py-2 px-4 border-b hover:bg-kumo-tint/50",
|
|
25789
26563
|
children: [
|
|
25790
26564
|
/* @__PURE__ */ jsxs("div", {
|
|
25791
|
-
style: {
|
|
26565
|
+
style: { marginInlineStart: `${level * 1.5}rem` },
|
|
25792
26566
|
className: "flex-1",
|
|
25793
26567
|
children: [/* @__PURE__ */ jsx("span", {
|
|
25794
26568
|
className: "font-medium",
|
|
@@ -26826,7 +27600,7 @@ function ThemeMarketplaceDetail({ themeId }) {
|
|
|
26826
27600
|
children: [/* @__PURE__ */ jsxs(Link$1, {
|
|
26827
27601
|
to: "/themes/marketplace",
|
|
26828
27602
|
className: "inline-flex items-center gap-1 text-sm text-kumo-subtle hover:text-kumo-default",
|
|
26829
|
-
children: [/* @__PURE__ */ jsx(
|
|
27603
|
+
children: [/* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" }), _t({
|
|
26830
27604
|
id: "6wOzc6",
|
|
26831
27605
|
message: "Back to Themes"
|
|
26832
27606
|
})]
|
|
@@ -26854,7 +27628,7 @@ function ThemeMarketplaceDetail({ themeId }) {
|
|
|
26854
27628
|
/* @__PURE__ */ jsxs(Link$1, {
|
|
26855
27629
|
to: "/themes/marketplace",
|
|
26856
27630
|
className: "inline-flex items-center gap-1 text-sm text-kumo-subtle hover:text-kumo-default",
|
|
26857
|
-
children: [/* @__PURE__ */ jsx(
|
|
27631
|
+
children: [/* @__PURE__ */ jsx(ArrowPrev, { className: "h-4 w-4" }), _t({
|
|
26858
27632
|
id: "6wOzc6",
|
|
26859
27633
|
message: "Back to Themes"
|
|
26860
27634
|
})]
|
|
@@ -27052,7 +27826,7 @@ function Lightbox({ urls, index, onClose, onPrev, onNext }) {
|
|
|
27052
27826
|
id: "DHhJ7s",
|
|
27053
27827
|
message: "Previous"
|
|
27054
27828
|
}),
|
|
27055
|
-
children: /* @__PURE__ */ jsx(
|
|
27829
|
+
children: /* @__PURE__ */ jsx(CaretPrev, { className: "h-5 w-5" })
|
|
27056
27830
|
}), /* @__PURE__ */ jsx("button", {
|
|
27057
27831
|
onClick: onNext,
|
|
27058
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",
|
|
@@ -27060,7 +27834,7 @@ function Lightbox({ urls, index, onClose, onPrev, onNext }) {
|
|
|
27060
27834
|
id: "hXzOVo",
|
|
27061
27835
|
message: "Next"
|
|
27062
27836
|
}),
|
|
27063
|
-
children: /* @__PURE__ */ jsx(
|
|
27837
|
+
children: /* @__PURE__ */ jsx(CaretNext, { className: "h-5 w-5" })
|
|
27064
27838
|
})] }),
|
|
27065
27839
|
/* @__PURE__ */ jsxs("div", {
|
|
27066
27840
|
className: "absolute bottom-2 start-1/2 -translate-x-1/2 rounded-full bg-kumo-base/80 px-3 py-1 text-xs",
|
|
@@ -27132,7 +27906,20 @@ function Widgets() {
|
|
|
27132
27906
|
const [activeId, setActiveId] = React.useState(null);
|
|
27133
27907
|
const [activeDragData, setActiveDragData] = React.useState(null);
|
|
27134
27908
|
const [expandedWidgets, setExpandedWidgets] = React.useState(/* @__PURE__ */ new Set());
|
|
27909
|
+
const [blockSidebarPanel, setBlockSidebarPanel] = React.useState(null);
|
|
27135
27910
|
const draggingFromPaletteRef = React.useRef(false);
|
|
27911
|
+
const handleBlockSidebarOpen = React.useCallback((panel) => {
|
|
27912
|
+
setBlockSidebarPanel((prev) => {
|
|
27913
|
+
prev?.onClose();
|
|
27914
|
+
return panel;
|
|
27915
|
+
});
|
|
27916
|
+
}, []);
|
|
27917
|
+
const handleBlockSidebarClose = React.useCallback(() => {
|
|
27918
|
+
setBlockSidebarPanel((prev) => {
|
|
27919
|
+
prev?.onClose();
|
|
27920
|
+
return null;
|
|
27921
|
+
});
|
|
27922
|
+
}, []);
|
|
27136
27923
|
const { data: areas = [], isLoading } = useQuery({
|
|
27137
27924
|
queryKey: ["widget-areas"],
|
|
27138
27925
|
queryFn: fetchWidgetAreas
|
|
@@ -27270,174 +28057,189 @@ function Widgets() {
|
|
|
27270
28057
|
collisionDetection,
|
|
27271
28058
|
onDragStart: handleDragStart,
|
|
27272
28059
|
onDragEnd: handleDragEnd,
|
|
27273
|
-
children: [
|
|
27274
|
-
|
|
27275
|
-
|
|
27276
|
-
|
|
27277
|
-
|
|
27278
|
-
|
|
27279
|
-
|
|
27280
|
-
|
|
27281
|
-
|
|
27282
|
-
|
|
27283
|
-
|
|
27284
|
-
|
|
27285
|
-
|
|
27286
|
-
|
|
27287
|
-
|
|
27288
|
-
|
|
27289
|
-
|
|
27290
|
-
|
|
27291
|
-
|
|
27292
|
-
|
|
27293
|
-
|
|
27294
|
-
|
|
27295
|
-
|
|
27296
|
-
|
|
27297
|
-
|
|
27298
|
-
|
|
27299
|
-
|
|
27300
|
-
|
|
27301
|
-
|
|
27302
|
-
|
|
27303
|
-
render: (props) => /* @__PURE__ */ jsxs(Button, {
|
|
27304
|
-
...props,
|
|
27305
|
-
variant: "ghost",
|
|
27306
|
-
shape: "square",
|
|
28060
|
+
children: [
|
|
28061
|
+
/* @__PURE__ */ jsxs("div", {
|
|
28062
|
+
className: "space-y-6",
|
|
28063
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
28064
|
+
className: "flex items-center justify-between",
|
|
28065
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
28066
|
+
className: "text-3xl font-bold",
|
|
28067
|
+
children: "Widgets"
|
|
28068
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
28069
|
+
className: "text-kumo-subtle",
|
|
28070
|
+
children: "Manage content widgets in your widget areas"
|
|
28071
|
+
})] }), /* @__PURE__ */ jsxs(Dialog.Root, {
|
|
28072
|
+
open: isCreateAreaOpen,
|
|
28073
|
+
onOpenChange: (open) => {
|
|
28074
|
+
setIsCreateAreaOpen(open);
|
|
28075
|
+
if (!open) setCreateAreaError(null);
|
|
28076
|
+
},
|
|
28077
|
+
children: [/* @__PURE__ */ jsx(Dialog.Trigger, { render: (props) => /* @__PURE__ */ jsx(Button, {
|
|
28078
|
+
...props,
|
|
28079
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
28080
|
+
children: "Add Widget Area"
|
|
28081
|
+
}) }), /* @__PURE__ */ jsxs(Dialog, {
|
|
28082
|
+
className: "p-6",
|
|
28083
|
+
size: "lg",
|
|
28084
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
28085
|
+
className: "flex items-start justify-between gap-4 mb-4",
|
|
28086
|
+
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
28087
|
+
className: "text-lg font-semibold leading-none tracking-tight",
|
|
28088
|
+
children: "Create Widget Area"
|
|
28089
|
+
}), /* @__PURE__ */ jsx(Dialog.Close, {
|
|
27307
28090
|
"aria-label": "Close",
|
|
27308
|
-
|
|
27309
|
-
|
|
27310
|
-
|
|
27311
|
-
|
|
27312
|
-
|
|
27313
|
-
|
|
28091
|
+
render: (props) => /* @__PURE__ */ jsxs(Button, {
|
|
28092
|
+
...props,
|
|
28093
|
+
variant: "ghost",
|
|
28094
|
+
shape: "square",
|
|
28095
|
+
"aria-label": "Close",
|
|
28096
|
+
className: "absolute end-4 top-4",
|
|
28097
|
+
children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
|
|
28098
|
+
className: "sr-only",
|
|
28099
|
+
children: "Close"
|
|
28100
|
+
})]
|
|
28101
|
+
})
|
|
28102
|
+
})]
|
|
28103
|
+
}), /* @__PURE__ */ jsxs("form", {
|
|
28104
|
+
onSubmit: handleCreateArea,
|
|
28105
|
+
className: "space-y-4",
|
|
28106
|
+
children: [
|
|
28107
|
+
/* @__PURE__ */ jsx(Input, {
|
|
28108
|
+
label: "Name",
|
|
28109
|
+
name: "name",
|
|
28110
|
+
required: true,
|
|
28111
|
+
placeholder: "sidebar",
|
|
28112
|
+
pattern: "[a-z0-9\\-]+"
|
|
28113
|
+
}),
|
|
28114
|
+
/* @__PURE__ */ jsx(Input, {
|
|
28115
|
+
label: "Label",
|
|
28116
|
+
name: "label",
|
|
28117
|
+
required: true,
|
|
28118
|
+
placeholder: "Main Sidebar"
|
|
28119
|
+
}),
|
|
28120
|
+
/* @__PURE__ */ jsx(Input, {
|
|
28121
|
+
label: "Description",
|
|
28122
|
+
name: "description",
|
|
28123
|
+
placeholder: "Appears on posts and pages"
|
|
28124
|
+
}),
|
|
28125
|
+
/* @__PURE__ */ jsx(DialogError, { message: createAreaError || getMutationError(createAreaMutation.error) }),
|
|
28126
|
+
/* @__PURE__ */ jsxs("div", {
|
|
28127
|
+
className: "flex justify-end gap-2",
|
|
28128
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
28129
|
+
type: "button",
|
|
28130
|
+
variant: "outline",
|
|
28131
|
+
onClick: () => setIsCreateAreaOpen(false),
|
|
28132
|
+
children: "Cancel"
|
|
28133
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
28134
|
+
type: "submit",
|
|
28135
|
+
disabled: createAreaMutation.isPending,
|
|
28136
|
+
children: "Create"
|
|
28137
|
+
})]
|
|
28138
|
+
})
|
|
28139
|
+
]
|
|
27314
28140
|
})]
|
|
27315
|
-
})
|
|
27316
|
-
|
|
27317
|
-
|
|
28141
|
+
})]
|
|
28142
|
+
})]
|
|
28143
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
28144
|
+
className: "grid grid-cols-12 gap-6",
|
|
28145
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
28146
|
+
className: "col-span-4",
|
|
28147
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
28148
|
+
className: "rounded-lg border bg-kumo-base p-6 space-y-4",
|
|
27318
28149
|
children: [
|
|
27319
|
-
/* @__PURE__ */ jsx(
|
|
27320
|
-
|
|
27321
|
-
|
|
27322
|
-
required: true,
|
|
27323
|
-
placeholder: "sidebar",
|
|
27324
|
-
pattern: "[a-z0-9-]+"
|
|
28150
|
+
/* @__PURE__ */ jsx("h2", {
|
|
28151
|
+
className: "text-xl font-semibold",
|
|
28152
|
+
children: "Available Widgets"
|
|
27325
28153
|
}),
|
|
27326
|
-
/* @__PURE__ */ jsx(
|
|
27327
|
-
|
|
27328
|
-
|
|
27329
|
-
required: true,
|
|
27330
|
-
placeholder: "Main Sidebar"
|
|
27331
|
-
}),
|
|
27332
|
-
/* @__PURE__ */ jsx(Input, {
|
|
27333
|
-
label: "Description",
|
|
27334
|
-
name: "description",
|
|
27335
|
-
placeholder: "Appears on posts and pages"
|
|
28154
|
+
/* @__PURE__ */ jsx("p", {
|
|
28155
|
+
className: "text-sm text-kumo-subtle",
|
|
28156
|
+
children: "Drag widgets into an area to add them"
|
|
27336
28157
|
}),
|
|
27337
|
-
/* @__PURE__ */ jsx(DialogError, { message: createAreaError || getMutationError(createAreaMutation.error) }),
|
|
27338
28158
|
/* @__PURE__ */ jsxs("div", {
|
|
27339
|
-
className: "
|
|
27340
|
-
children: [/* @__PURE__ */ jsx(
|
|
27341
|
-
|
|
27342
|
-
|
|
27343
|
-
|
|
27344
|
-
|
|
27345
|
-
|
|
27346
|
-
|
|
27347
|
-
|
|
27348
|
-
|
|
27349
|
-
|
|
28159
|
+
className: "space-y-2",
|
|
28160
|
+
children: [BUILTIN_WIDGETS.map((item) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
|
|
28161
|
+
id: item.id,
|
|
28162
|
+
label: _t(item.label),
|
|
28163
|
+
description: _t(item.description),
|
|
28164
|
+
widgetInput: {
|
|
28165
|
+
...item.input,
|
|
28166
|
+
title: _t(item.label)
|
|
28167
|
+
}
|
|
28168
|
+
}, item.id)), components.map((comp) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
|
|
28169
|
+
id: `palette-comp-${comp.id}`,
|
|
28170
|
+
label: comp.label,
|
|
28171
|
+
description: comp.description,
|
|
28172
|
+
widgetInput: {
|
|
28173
|
+
type: "component",
|
|
28174
|
+
title: comp.label,
|
|
28175
|
+
componentId: comp.id
|
|
28176
|
+
}
|
|
28177
|
+
}, `palette-comp-${comp.id}`))]
|
|
27350
28178
|
})
|
|
27351
28179
|
]
|
|
27352
|
-
})
|
|
28180
|
+
})
|
|
28181
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
28182
|
+
className: "col-span-8 space-y-4",
|
|
28183
|
+
children: areas.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
28184
|
+
className: "rounded-lg border bg-kumo-base p-12 text-center",
|
|
28185
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
28186
|
+
className: "text-kumo-subtle",
|
|
28187
|
+
children: "No widget areas yet. Create one to get started."
|
|
28188
|
+
})
|
|
28189
|
+
}) : areas.map((area) => /* @__PURE__ */ jsx(WidgetAreaPanel, {
|
|
28190
|
+
area,
|
|
28191
|
+
expandedWidgets,
|
|
28192
|
+
onToggleWidget: toggleWidget,
|
|
28193
|
+
isDraggingPalette: activeDragData !== null && isPaletteItem(activeDragData),
|
|
28194
|
+
components,
|
|
28195
|
+
pluginBlocks,
|
|
28196
|
+
onBlockSidebarOpen: handleBlockSidebarOpen,
|
|
28197
|
+
onBlockSidebarClose: handleBlockSidebarClose
|
|
28198
|
+
}, area.id))
|
|
27353
28199
|
})]
|
|
27354
28200
|
})]
|
|
27355
|
-
}),
|
|
27356
|
-
|
|
27357
|
-
|
|
27358
|
-
|
|
28201
|
+
}),
|
|
28202
|
+
/* @__PURE__ */ jsx(DragOverlay, {
|
|
28203
|
+
dropAnimation: draggingFromPaletteRef.current ? null : void 0,
|
|
28204
|
+
children: activePaletteLabel ? /* @__PURE__ */ jsx("div", {
|
|
28205
|
+
className: "rounded border bg-kumo-base p-3 shadow-lg opacity-90",
|
|
28206
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
28207
|
+
className: "font-medium",
|
|
28208
|
+
children: activePaletteLabel
|
|
28209
|
+
})
|
|
28210
|
+
}) : activeWidget ? /* @__PURE__ */ jsx("div", {
|
|
28211
|
+
className: "rounded border bg-kumo-base p-3 shadow-lg opacity-90",
|
|
27359
28212
|
children: /* @__PURE__ */ jsxs("div", {
|
|
27360
|
-
className: "
|
|
28213
|
+
className: "flex items-center gap-2",
|
|
27361
28214
|
children: [
|
|
27362
|
-
/* @__PURE__ */ jsx("
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
/* @__PURE__ */ jsx("p", {
|
|
27367
|
-
className: "text-sm text-kumo-subtle",
|
|
27368
|
-
children: "Drag widgets into an area to add them"
|
|
28215
|
+
/* @__PURE__ */ jsx(DotsSixVertical, { className: "h-4 w-4 text-kumo-subtle" }),
|
|
28216
|
+
/* @__PURE__ */ jsx("span", {
|
|
28217
|
+
className: "font-medium",
|
|
28218
|
+
children: activeWidget.title || "Untitled Widget"
|
|
27369
28219
|
}),
|
|
27370
|
-
/* @__PURE__ */ jsxs("
|
|
27371
|
-
className: "
|
|
27372
|
-
children: [
|
|
27373
|
-
|
|
27374
|
-
|
|
27375
|
-
|
|
27376
|
-
|
|
27377
|
-
...item.input,
|
|
27378
|
-
title: _t(item.label)
|
|
27379
|
-
}
|
|
27380
|
-
}, item.id)), components.map((comp) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
|
|
27381
|
-
id: `palette-comp-${comp.id}`,
|
|
27382
|
-
label: comp.label,
|
|
27383
|
-
description: comp.description,
|
|
27384
|
-
widgetInput: {
|
|
27385
|
-
type: "component",
|
|
27386
|
-
title: comp.label,
|
|
27387
|
-
componentId: comp.id
|
|
27388
|
-
}
|
|
27389
|
-
}, `palette-comp-${comp.id}`))]
|
|
28220
|
+
/* @__PURE__ */ jsxs("span", {
|
|
28221
|
+
className: "text-xs text-kumo-subtle",
|
|
28222
|
+
children: [
|
|
28223
|
+
"(",
|
|
28224
|
+
activeWidget.type,
|
|
28225
|
+
")"
|
|
28226
|
+
]
|
|
27390
28227
|
})
|
|
27391
28228
|
]
|
|
27392
28229
|
})
|
|
27393
|
-
})
|
|
27394
|
-
|
|
27395
|
-
|
|
27396
|
-
|
|
27397
|
-
|
|
27398
|
-
|
|
27399
|
-
|
|
27400
|
-
|
|
27401
|
-
|
|
27402
|
-
|
|
27403
|
-
|
|
27404
|
-
|
|
27405
|
-
|
|
27406
|
-
components,
|
|
27407
|
-
pluginBlocks
|
|
27408
|
-
}, area.id))
|
|
27409
|
-
})]
|
|
27410
|
-
})]
|
|
27411
|
-
}), /* @__PURE__ */ jsx(DragOverlay, {
|
|
27412
|
-
dropAnimation: draggingFromPaletteRef.current ? null : void 0,
|
|
27413
|
-
children: activePaletteLabel ? /* @__PURE__ */ jsx("div", {
|
|
27414
|
-
className: "rounded border bg-kumo-base p-3 shadow-lg opacity-90",
|
|
27415
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
27416
|
-
className: "font-medium",
|
|
27417
|
-
children: activePaletteLabel
|
|
27418
|
-
})
|
|
27419
|
-
}) : activeWidget ? /* @__PURE__ */ jsx("div", {
|
|
27420
|
-
className: "rounded border bg-kumo-base p-3 shadow-lg opacity-90",
|
|
27421
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
27422
|
-
className: "flex items-center gap-2",
|
|
27423
|
-
children: [
|
|
27424
|
-
/* @__PURE__ */ jsx(DotsSixVertical, { className: "h-4 w-4 text-kumo-subtle" }),
|
|
27425
|
-
/* @__PURE__ */ jsx("span", {
|
|
27426
|
-
className: "font-medium",
|
|
27427
|
-
children: activeWidget.title || "Untitled Widget"
|
|
27428
|
-
}),
|
|
27429
|
-
/* @__PURE__ */ jsxs("span", {
|
|
27430
|
-
className: "text-xs text-kumo-subtle",
|
|
27431
|
-
children: [
|
|
27432
|
-
"(",
|
|
27433
|
-
activeWidget.type,
|
|
27434
|
-
")"
|
|
27435
|
-
]
|
|
27436
|
-
})
|
|
27437
|
-
]
|
|
27438
|
-
})
|
|
27439
|
-
}) : null
|
|
27440
|
-
})]
|
|
28230
|
+
}) : null
|
|
28231
|
+
}),
|
|
28232
|
+
blockSidebarPanel?.type === "image" && /* @__PURE__ */ jsx(ImageDetailPanel, {
|
|
28233
|
+
attributes: blockSidebarPanel.attrs,
|
|
28234
|
+
onUpdate: (attrs) => blockSidebarPanel.onUpdate(attrs),
|
|
28235
|
+
onReplace: (attrs) => blockSidebarPanel.onReplace(attrs),
|
|
28236
|
+
onDelete: () => {
|
|
28237
|
+
blockSidebarPanel.onDelete();
|
|
28238
|
+
setBlockSidebarPanel(null);
|
|
28239
|
+
},
|
|
28240
|
+
onClose: handleBlockSidebarClose
|
|
28241
|
+
})
|
|
28242
|
+
]
|
|
27441
28243
|
});
|
|
27442
28244
|
}
|
|
27443
28245
|
/** A draggable item in the available widgets palette */
|
|
@@ -27464,7 +28266,7 @@ function DraggablePaletteItem({ id, label, description, widgetInput }) {
|
|
|
27464
28266
|
})]
|
|
27465
28267
|
});
|
|
27466
28268
|
}
|
|
27467
|
-
function WidgetAreaPanel({ area, expandedWidgets, onToggleWidget, isDraggingPalette, components, pluginBlocks }) {
|
|
28269
|
+
function WidgetAreaPanel({ area, expandedWidgets, onToggleWidget, isDraggingPalette, components, pluginBlocks, onBlockSidebarOpen, onBlockSidebarClose }) {
|
|
27468
28270
|
const queryClient = useQueryClient();
|
|
27469
28271
|
const toastManager = Toast.useToastManager();
|
|
27470
28272
|
const [deleteAreaName, setDeleteAreaName] = React.useState(null);
|
|
@@ -27510,7 +28312,9 @@ function WidgetAreaPanel({ area, expandedWidgets, onToggleWidget, isDraggingPale
|
|
|
27510
28312
|
isExpanded: expandedWidgets.has(widget.id),
|
|
27511
28313
|
onToggle: () => onToggleWidget(widget.id),
|
|
27512
28314
|
components,
|
|
27513
|
-
pluginBlocks
|
|
28315
|
+
pluginBlocks,
|
|
28316
|
+
onBlockSidebarOpen,
|
|
28317
|
+
onBlockSidebarClose
|
|
27514
28318
|
}, widget.id))
|
|
27515
28319
|
}) : null,
|
|
27516
28320
|
isDraggingPalette && /* @__PURE__ */ jsx("div", {
|
|
@@ -27540,7 +28344,7 @@ function WidgetAreaPanel({ area, expandedWidgets, onToggleWidget, isDraggingPale
|
|
|
27540
28344
|
]
|
|
27541
28345
|
});
|
|
27542
28346
|
}
|
|
27543
|
-
function WidgetItem({ widget, areaName, isExpanded, onToggle, components, pluginBlocks }) {
|
|
28347
|
+
function WidgetItem({ widget, areaName, isExpanded, onToggle, components, pluginBlocks, onBlockSidebarOpen, onBlockSidebarClose }) {
|
|
27544
28348
|
const queryClient = useQueryClient();
|
|
27545
28349
|
const toastManager = Toast.useToastManager();
|
|
27546
28350
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
@@ -27603,7 +28407,7 @@ function WidgetItem({ widget, areaName, isExpanded, onToggle, components, plugin
|
|
|
27603
28407
|
children: /* @__PURE__ */ jsxs("div", {
|
|
27604
28408
|
className: "flex items-center gap-2",
|
|
27605
28409
|
children: [
|
|
27606
|
-
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" }),
|
|
27607
28411
|
/* @__PURE__ */ jsx("span", {
|
|
27608
28412
|
className: "font-medium",
|
|
27609
28413
|
children: widget.title || "Untitled Widget"
|
|
@@ -27632,12 +28436,14 @@ function WidgetItem({ widget, areaName, isExpanded, onToggle, components, plugin
|
|
|
27632
28436
|
components,
|
|
27633
28437
|
pluginBlocks,
|
|
27634
28438
|
onSave: (input) => updateMutation.mutate(input),
|
|
27635
|
-
isSaving: updateMutation.isPending
|
|
28439
|
+
isSaving: updateMutation.isPending,
|
|
28440
|
+
onBlockSidebarOpen,
|
|
28441
|
+
onBlockSidebarClose
|
|
27636
28442
|
})]
|
|
27637
28443
|
});
|
|
27638
28444
|
}
|
|
27639
28445
|
/** Inline editor form for a widget, rendered when the widget is expanded */
|
|
27640
|
-
function WidgetEditor({ widget, components, pluginBlocks, onSave, isSaving }) {
|
|
28446
|
+
function WidgetEditor({ widget, components, pluginBlocks, onSave, isSaving, onBlockSidebarOpen, onBlockSidebarClose }) {
|
|
27641
28447
|
const [title, setTitle] = React.useState(widget.title ?? "");
|
|
27642
28448
|
const [content, setContent] = React.useState(Array.isArray(widget.content) ? widget.content : []);
|
|
27643
28449
|
const [menuName, setMenuName] = React.useState(widget.menuName ?? "");
|
|
@@ -27676,7 +28482,9 @@ function WidgetEditor({ widget, components, pluginBlocks, onSave, isSaving }) {
|
|
|
27676
28482
|
onChange: (value) => setContent(value),
|
|
27677
28483
|
minimal: true,
|
|
27678
28484
|
placeholder: "Write widget content...",
|
|
27679
|
-
pluginBlocks
|
|
28485
|
+
pluginBlocks,
|
|
28486
|
+
onBlockSidebarOpen,
|
|
28487
|
+
onBlockSidebarClose
|
|
27680
28488
|
})] }),
|
|
27681
28489
|
widget.type === "menu" && /* @__PURE__ */ jsxs(Select, {
|
|
27682
28490
|
label: "Menu",
|
|
@@ -29616,7 +30424,7 @@ function PostTypeRow({ postType, selection, expanded, onToggleExpand, onToggleEn
|
|
|
29616
30424
|
onClick: onToggleExpand,
|
|
29617
30425
|
className: "flex items-center gap-1 text-start",
|
|
29618
30426
|
"aria-expanded": expanded,
|
|
29619
|
-
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", {
|
|
29620
30428
|
className: "font-medium",
|
|
29621
30429
|
children: postType.name
|
|
29622
30430
|
}), /* @__PURE__ */ jsx("p", {
|
|
@@ -31894,16 +32702,25 @@ function ContentListPage() {
|
|
|
31894
32702
|
});
|
|
31895
32703
|
const i18n = manifest?.i18n;
|
|
31896
32704
|
const activeLocale = i18n ? localeParam ?? i18n.defaultLocale : void 0;
|
|
32705
|
+
const [sort, setSort] = React.useState({
|
|
32706
|
+
field: "updatedAt",
|
|
32707
|
+
direction: "desc"
|
|
32708
|
+
});
|
|
31897
32709
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, error } = useInfiniteQuery({
|
|
31898
32710
|
queryKey: [
|
|
31899
32711
|
"content",
|
|
31900
32712
|
collection,
|
|
31901
|
-
{
|
|
32713
|
+
{
|
|
32714
|
+
locale: activeLocale,
|
|
32715
|
+
sort
|
|
32716
|
+
}
|
|
31902
32717
|
],
|
|
31903
32718
|
queryFn: ({ pageParam }) => fetchContentList(collection, {
|
|
31904
32719
|
locale: activeLocale,
|
|
31905
32720
|
cursor: pageParam,
|
|
31906
|
-
limit: 100
|
|
32721
|
+
limit: 100,
|
|
32722
|
+
orderBy: sort.field,
|
|
32723
|
+
order: sort.direction
|
|
31907
32724
|
}),
|
|
31908
32725
|
initialPageParam: void 0,
|
|
31909
32726
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
@@ -32014,7 +32831,9 @@ function ContentListPage() {
|
|
|
32014
32831
|
i18n,
|
|
32015
32832
|
activeLocale,
|
|
32016
32833
|
onLocaleChange: handleLocaleChange,
|
|
32017
|
-
urlPattern: collectionConfig.urlPattern
|
|
32834
|
+
urlPattern: collectionConfig.urlPattern,
|
|
32835
|
+
sort,
|
|
32836
|
+
onSortChange: setSort
|
|
32018
32837
|
});
|
|
32019
32838
|
}
|
|
32020
32839
|
const contentNewRoute = createRoute({
|
|
@@ -32734,7 +33553,7 @@ function MarketplaceBrowsePage() {
|
|
|
32734
33553
|
const { data: plugins } = useQuery({
|
|
32735
33554
|
queryKey: ["plugins"],
|
|
32736
33555
|
queryFn: async () => {
|
|
32737
|
-
const { fetchPlugins } = await import("./plugins-
|
|
33556
|
+
const { fetchPlugins } = await import("./plugins-DUJY_8HN.js").then((n) => n.a);
|
|
32738
33557
|
return fetchPlugins();
|
|
32739
33558
|
}
|
|
32740
33559
|
});
|
|
@@ -32753,7 +33572,7 @@ function MarketplaceDetailPage() {
|
|
|
32753
33572
|
const { data: plugins } = useQuery({
|
|
32754
33573
|
queryKey: ["plugins"],
|
|
32755
33574
|
queryFn: async () => {
|
|
32756
|
-
const { fetchPlugins } = await import("./plugins-
|
|
33575
|
+
const { fetchPlugins } = await import("./plugins-DUJY_8HN.js").then((n) => n.a);
|
|
32757
33576
|
return fetchPlugins();
|
|
32758
33577
|
}
|
|
32759
33578
|
});
|
|
@@ -33166,7 +33985,8 @@ const router = createAdminRouter(queryClient);
|
|
|
33166
33985
|
* Main Admin Application
|
|
33167
33986
|
*/
|
|
33168
33987
|
const EMPTY_PLUGINS = {};
|
|
33169
|
-
|
|
33988
|
+
const EMPTY_AUTH_PROVIDERS = {};
|
|
33989
|
+
function AdminApp({ pluginAdmins = EMPTY_PLUGINS, authProviders = EMPTY_AUTH_PROVIDERS, locale = "en", messages = {} }) {
|
|
33170
33990
|
React.useEffect(() => {
|
|
33171
33991
|
document.getElementById("emdash-boot-loader")?.remove();
|
|
33172
33992
|
}, []);
|
|
@@ -33180,16 +34000,19 @@ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, locale = "en", messages = {} }
|
|
|
33180
34000
|
}
|
|
33181
34001
|
return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(I18nProvider, {
|
|
33182
34002
|
i18n,
|
|
33183
|
-
children: /* @__PURE__ */ jsx(LocaleDirectionProvider, { children: /* @__PURE__ */ jsx(Toasty, { children: /* @__PURE__ */ jsx(
|
|
33184
|
-
|
|
33185
|
-
children: /* @__PURE__ */ jsx(
|
|
33186
|
-
|
|
33187
|
-
children: /* @__PURE__ */ jsx(
|
|
34003
|
+
children: /* @__PURE__ */ jsx(LocaleDirectionProvider, { children: /* @__PURE__ */ jsx(Toasty, { children: /* @__PURE__ */ jsx(AuthProviderProvider, {
|
|
34004
|
+
authProviders,
|
|
34005
|
+
children: /* @__PURE__ */ jsx(PluginAdminProvider, {
|
|
34006
|
+
pluginAdmins,
|
|
34007
|
+
children: /* @__PURE__ */ jsx(QueryClientProvider, {
|
|
34008
|
+
client: queryClient,
|
|
34009
|
+
children: /* @__PURE__ */ jsx(RouterProvider, { router })
|
|
34010
|
+
})
|
|
33188
34011
|
})
|
|
33189
34012
|
}) }) })
|
|
33190
34013
|
}) });
|
|
33191
34014
|
}
|
|
33192
34015
|
|
|
33193
34016
|
//#endregion
|
|
33194
|
-
export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, 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, 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, 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 };
|
|
33195
34018
|
//# sourceMappingURL=index.js.map
|