@emdash-cms/admin 0.1.0 → 0.2.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/LICENSE +9 -0
- package/dist/config-BHC21FmY.d.ts +34 -0
- package/dist/config-BHC21FmY.d.ts.map +1 -0
- package/dist/index.d.ts +44 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1538 -369
- package/dist/index.js.map +1 -1
- package/dist/locales/de/messages.mjs +1 -0
- package/dist/locales/en/messages.mjs +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.js +3 -0
- package/dist/plugins-XhZqfegd.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/useLocale-CXsoFCFt.js +80 -0
- package/dist/useLocale-CXsoFCFt.js.map +1 -0
- package/package.json +19 -5
package/dist/index.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { c as fetchManifest, i as fetchPlugins, l as parseApiResponse, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as throwResponseError } from "./plugins-XhZqfegd.js";
|
|
2
|
+
import { i as SUPPORTED_LOCALE_CODES, n as DEFAULT_LOCALE, o as resolveLocale, r as SUPPORTED_LOCALES, t as useLocale } from "./useLocale-CXsoFCFt.js";
|
|
3
|
+
import "./locales/index.js";
|
|
2
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
|
+
import { i18n } from "@lingui/core";
|
|
6
|
+
import { I18nProvider, Trans, useLingui } from "@lingui/react";
|
|
3
7
|
import { QueryClient, QueryClientProvider, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
4
8
|
import { Link, Link as Link$1, Outlet, RouterProvider, createRootRouteWithContext, createRoute, createRouter, useLocation, useNavigate, useNavigate as useNavigate$1, useParams, useParams as useParams$1, useSearch } from "@tanstack/react-router";
|
|
5
9
|
import * as React from "react";
|
|
6
10
|
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
7
11
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
import { ArrowCounterClockwise, ArrowLeft, ArrowRight, ArrowSquareOut, ArrowUUpLeft, ArrowUUpRight, ArrowsClockwise, ArrowsInSimple, ArrowsLeftRight, ArrowsOutSimple, BracketsCurly, Calendar, CalendarBlank, CaretDown, CaretLeft, CaretRight, CaretUp, 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, 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";
|
|
12
|
+
import { ArrowCounterClockwise, ArrowLeft, ArrowRight, ArrowSquareOut, ArrowUUpLeft, ArrowUUpRight, ArrowsClockwise, ArrowsInSimple, ArrowsLeftRight, ArrowsOutSimple, BracketsCurly, Calendar, CalendarBlank, CaretDown, CaretLeft, CaretRight, CaretUp, 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";
|
|
9
13
|
import { clsx } from "clsx";
|
|
10
14
|
import { twMerge } from "tailwind-merge";
|
|
15
|
+
import { DndContext, DragOverlay, KeyboardSensor, PointerSensor, closestCenter, rectIntersection, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
|
|
16
|
+
import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
17
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
11
18
|
import { autoUpdate, flip, offset, shift, useFloating } from "@floating-ui/react";
|
|
12
19
|
import { Extension, InputRule, Node as Node$1, PasteRule, escapeForRegEx, mergeAttributes } from "@tiptap/core";
|
|
13
20
|
import CharacterCount from "@tiptap/extension-character-count";
|
|
@@ -27,9 +34,6 @@ import { BlockRenderer } from "@emdash-cms/blocks";
|
|
|
27
34
|
import DOMPurify from "dompurify";
|
|
28
35
|
import { Marked, Renderer } from "marked";
|
|
29
36
|
import { useHotkeys } from "react-hotkeys-hook";
|
|
30
|
-
import { DndContext, DragOverlay, KeyboardSensor, PointerSensor, closestCenter, rectIntersection, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
|
|
31
|
-
import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
32
|
-
import { CSS } from "@dnd-kit/utilities";
|
|
33
37
|
|
|
34
38
|
//#region src/components/ThemeProvider.tsx
|
|
35
39
|
const ThemeContext = React.createContext(void 0);
|
|
@@ -54,15 +58,8 @@ function ThemeProvider({ children, defaultTheme = "system" }) {
|
|
|
54
58
|
return theme;
|
|
55
59
|
});
|
|
56
60
|
React.useEffect(() => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (theme === "system") {
|
|
60
|
-
root.removeAttribute("data-mode");
|
|
61
|
-
setResolvedTheme(getSystemTheme());
|
|
62
|
-
} else {
|
|
63
|
-
root.setAttribute("data-mode", theme);
|
|
64
|
-
setResolvedTheme(theme);
|
|
65
|
-
}
|
|
61
|
+
if (theme === "system") setResolvedTheme(getSystemTheme());
|
|
62
|
+
else setResolvedTheme(theme);
|
|
66
63
|
}, [theme]);
|
|
67
64
|
React.useEffect(() => {
|
|
68
65
|
if (theme !== "system") return;
|
|
@@ -73,6 +70,11 @@ function ThemeProvider({ children, defaultTheme = "system" }) {
|
|
|
73
70
|
mediaQuery.addEventListener("change", handler);
|
|
74
71
|
return () => mediaQuery.removeEventListener("change", handler);
|
|
75
72
|
}, [theme]);
|
|
73
|
+
React.useEffect(() => {
|
|
74
|
+
const root = document.documentElement;
|
|
75
|
+
root.setAttribute("data-theme", "classic");
|
|
76
|
+
root.setAttribute("data-mode", resolvedTheme);
|
|
77
|
+
}, [resolvedTheme]);
|
|
76
78
|
const setTheme = React.useCallback((newTheme) => {
|
|
77
79
|
setThemeState(newTheme);
|
|
78
80
|
localStorage.setItem(STORAGE_KEY, newTheme);
|
|
@@ -2416,6 +2418,58 @@ function useCurrentUser() {
|
|
|
2416
2418
|
});
|
|
2417
2419
|
}
|
|
2418
2420
|
|
|
2421
|
+
//#endregion
|
|
2422
|
+
//#region src/lib/url.ts
|
|
2423
|
+
/**
|
|
2424
|
+
* Shared URL validation and transformation utilities
|
|
2425
|
+
*/
|
|
2426
|
+
const DEFAULT_REDIRECT = "/_emdash/admin";
|
|
2427
|
+
const LEADING_SLASHES = /^\/+/;
|
|
2428
|
+
/**
|
|
2429
|
+
* Sanitize a redirect URL to prevent open-redirect and javascript: XSS attacks.
|
|
2430
|
+
*
|
|
2431
|
+
* Only allows relative paths starting with `/`. Rejects protocol-relative
|
|
2432
|
+
* URLs (`//evil.com`), backslash tricks (`/\evil.com`), and non-path schemes
|
|
2433
|
+
* like `javascript:`.
|
|
2434
|
+
*
|
|
2435
|
+
* Returns the default admin URL when the input is unsafe.
|
|
2436
|
+
*/
|
|
2437
|
+
function sanitizeRedirectUrl(raw) {
|
|
2438
|
+
if (raw.startsWith("/") && !raw.startsWith("//") && !raw.includes("\\")) return raw;
|
|
2439
|
+
return DEFAULT_REDIRECT;
|
|
2440
|
+
}
|
|
2441
|
+
/**
|
|
2442
|
+
* Build a public content URL from collection metadata and slug.
|
|
2443
|
+
*
|
|
2444
|
+
* Uses the collection's `urlPattern` when available (e.g. `/blog/{slug}`),
|
|
2445
|
+
* otherwise falls back to `/{collection}/{slug}`. Leading slashes are
|
|
2446
|
+
* stripped from the slug to prevent protocol-relative URLs.
|
|
2447
|
+
*/
|
|
2448
|
+
function contentUrl(collection, slug, urlPattern) {
|
|
2449
|
+
const safe = slug.replace(LEADING_SLASHES, "");
|
|
2450
|
+
return urlPattern ? urlPattern.replace("{slug}", safe) : `/${collection}/${safe}`;
|
|
2451
|
+
}
|
|
2452
|
+
/** Matches http:// or https:// URLs */
|
|
2453
|
+
const SAFE_URL_RE = /^https?:\/\//i;
|
|
2454
|
+
/** Returns true if the URL uses a safe scheme (http/https) */
|
|
2455
|
+
function isSafeUrl$1(url) {
|
|
2456
|
+
return SAFE_URL_RE.test(url);
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Build an icon URL with a width query param, or return null for unsafe URLs.
|
|
2460
|
+
* Validates the URL scheme and appends `?w=<width>` for image resizing.
|
|
2461
|
+
*/
|
|
2462
|
+
function safeIconUrl(url, width) {
|
|
2463
|
+
if (!SAFE_URL_RE.test(url)) return null;
|
|
2464
|
+
try {
|
|
2465
|
+
const u = new URL(url);
|
|
2466
|
+
u.searchParams.set("w", String(width));
|
|
2467
|
+
return u.href;
|
|
2468
|
+
} catch {
|
|
2469
|
+
return null;
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2419
2473
|
//#endregion
|
|
2420
2474
|
//#region src/components/BlockKitFieldWidget.tsx
|
|
2421
2475
|
/**
|
|
@@ -2664,6 +2718,242 @@ var PluginFieldErrorBoundary = class extends React.Component {
|
|
|
2664
2718
|
}
|
|
2665
2719
|
};
|
|
2666
2720
|
|
|
2721
|
+
//#endregion
|
|
2722
|
+
//#region src/components/RepeaterField.tsx
|
|
2723
|
+
/**
|
|
2724
|
+
* RepeaterField — renders a list of repeating sub-field groups in the content editor.
|
|
2725
|
+
*
|
|
2726
|
+
* Each item is a collapsible card containing the defined sub-fields.
|
|
2727
|
+
* Items can be added, removed, and reordered via drag-and-drop.
|
|
2728
|
+
*/
|
|
2729
|
+
function ensureKeys(items) {
|
|
2730
|
+
return items.map((item, i) => {
|
|
2731
|
+
const obj = typeof item === "object" && item !== null ? item : {};
|
|
2732
|
+
return {
|
|
2733
|
+
...obj,
|
|
2734
|
+
_key: obj._key || `item-${i}-${Date.now()}`
|
|
2735
|
+
};
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
function stripKeys(items) {
|
|
2739
|
+
return items.map(({ _key, ...rest }) => rest);
|
|
2740
|
+
}
|
|
2741
|
+
function RepeaterField({ label, id, value, onChange, subFields, minItems = 0, maxItems }) {
|
|
2742
|
+
const rawItems = Array.isArray(value) ? value : [];
|
|
2743
|
+
const [items, setItems] = React.useState(() => ensureKeys(rawItems));
|
|
2744
|
+
const [collapsedItems, setCollapsedItems] = React.useState(/* @__PURE__ */ new Set());
|
|
2745
|
+
React.useEffect(() => {
|
|
2746
|
+
setItems(ensureKeys(Array.isArray(value) ? value : []));
|
|
2747
|
+
}, [value]);
|
|
2748
|
+
const emitChange = (updated) => {
|
|
2749
|
+
setItems(updated);
|
|
2750
|
+
onChange(stripKeys(updated));
|
|
2751
|
+
};
|
|
2752
|
+
const handleAdd = () => {
|
|
2753
|
+
if (maxItems && items.length >= maxItems) return;
|
|
2754
|
+
const newItem = { _key: `item-${Date.now()}` };
|
|
2755
|
+
for (const sf of subFields) newItem[sf.slug] = sf.type === "boolean" ? false : sf.type === "number" || sf.type === "integer" ? null : "";
|
|
2756
|
+
emitChange([...items, newItem]);
|
|
2757
|
+
};
|
|
2758
|
+
const handleRemove = (key) => {
|
|
2759
|
+
if (items.length <= minItems) return;
|
|
2760
|
+
emitChange(items.filter((item) => item._key !== key));
|
|
2761
|
+
};
|
|
2762
|
+
const handleItemChange = (key, fieldSlug, fieldValue) => {
|
|
2763
|
+
emitChange(items.map((item) => item._key === key ? {
|
|
2764
|
+
...item,
|
|
2765
|
+
[fieldSlug]: fieldValue
|
|
2766
|
+
} : item));
|
|
2767
|
+
};
|
|
2768
|
+
const handleDragEnd = (event) => {
|
|
2769
|
+
const { active, over } = event;
|
|
2770
|
+
if (!over || active.id === over.id) return;
|
|
2771
|
+
const oldIndex = items.findIndex((item) => item._key === active.id);
|
|
2772
|
+
const newIndex = items.findIndex((item) => item._key === over.id);
|
|
2773
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
2774
|
+
emitChange(arrayMove(items, oldIndex, newIndex));
|
|
2775
|
+
};
|
|
2776
|
+
const toggleCollapse = (key) => {
|
|
2777
|
+
setCollapsedItems((prev) => {
|
|
2778
|
+
const next = new Set(prev);
|
|
2779
|
+
if (next.has(key)) next.delete(key);
|
|
2780
|
+
else next.add(key);
|
|
2781
|
+
return next;
|
|
2782
|
+
});
|
|
2783
|
+
};
|
|
2784
|
+
const canAdd = !maxItems || items.length < maxItems;
|
|
2785
|
+
const canRemove = items.length > minItems;
|
|
2786
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2787
|
+
className: "space-y-2",
|
|
2788
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2789
|
+
className: "flex items-center justify-between",
|
|
2790
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
2791
|
+
htmlFor: id,
|
|
2792
|
+
className: "text-sm font-medium",
|
|
2793
|
+
children: [label, items.length > 0 && /* @__PURE__ */ jsxs("span", {
|
|
2794
|
+
className: "ml-2 text-kumo-subtle font-normal",
|
|
2795
|
+
children: [
|
|
2796
|
+
"(",
|
|
2797
|
+
items.length,
|
|
2798
|
+
" items)"
|
|
2799
|
+
]
|
|
2800
|
+
})]
|
|
2801
|
+
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
2802
|
+
variant: "outline",
|
|
2803
|
+
size: "sm",
|
|
2804
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
2805
|
+
onClick: handleAdd,
|
|
2806
|
+
children: "Add Item"
|
|
2807
|
+
})]
|
|
2808
|
+
}), items.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
2809
|
+
className: "border-2 border-dashed rounded-lg p-6 text-center text-kumo-subtle",
|
|
2810
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
2811
|
+
className: "text-sm",
|
|
2812
|
+
children: "No items yet"
|
|
2813
|
+
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
2814
|
+
variant: "outline",
|
|
2815
|
+
size: "sm",
|
|
2816
|
+
className: "mt-2",
|
|
2817
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
2818
|
+
onClick: handleAdd,
|
|
2819
|
+
children: "Add First Item"
|
|
2820
|
+
})]
|
|
2821
|
+
}) : /* @__PURE__ */ jsx(DndContext, {
|
|
2822
|
+
collisionDetection: closestCenter,
|
|
2823
|
+
onDragEnd: handleDragEnd,
|
|
2824
|
+
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
2825
|
+
items: items.map((item) => item._key),
|
|
2826
|
+
strategy: verticalListSortingStrategy,
|
|
2827
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2828
|
+
className: "space-y-2",
|
|
2829
|
+
children: items.map((item, index) => /* @__PURE__ */ jsx(SortableRepeaterItem, {
|
|
2830
|
+
item,
|
|
2831
|
+
index,
|
|
2832
|
+
subFields,
|
|
2833
|
+
isCollapsed: collapsedItems.has(item._key),
|
|
2834
|
+
onToggleCollapse: () => toggleCollapse(item._key),
|
|
2835
|
+
onRemove: canRemove ? () => handleRemove(item._key) : void 0,
|
|
2836
|
+
onChange: (fieldSlug, fieldValue) => handleItemChange(item._key, fieldSlug, fieldValue)
|
|
2837
|
+
}, item._key))
|
|
2838
|
+
})
|
|
2839
|
+
})
|
|
2840
|
+
})]
|
|
2841
|
+
});
|
|
2842
|
+
}
|
|
2843
|
+
function SortableRepeaterItem({ item, index, subFields, isCollapsed, onToggleCollapse, onRemove, onChange }) {
|
|
2844
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: item._key });
|
|
2845
|
+
const style = {
|
|
2846
|
+
transform: CSS.Transform.toString(transform),
|
|
2847
|
+
transition
|
|
2848
|
+
};
|
|
2849
|
+
const summaryField = subFields.find((sf) => sf.type === "string" || sf.type === "text");
|
|
2850
|
+
const summaryLabel = (summaryField ? item[summaryField.slug] || "" : "") || `Item ${index + 1}`;
|
|
2851
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2852
|
+
ref: setNodeRef,
|
|
2853
|
+
style,
|
|
2854
|
+
className: cn("border rounded-lg bg-kumo-base", isDragging && "opacity-50 ring-2 ring-kumo-brand"),
|
|
2855
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2856
|
+
className: "flex items-center gap-2 px-3 py-2 border-b cursor-pointer",
|
|
2857
|
+
onClick: onToggleCollapse,
|
|
2858
|
+
children: [
|
|
2859
|
+
/* @__PURE__ */ jsx(DotsSixVertical, {
|
|
2860
|
+
className: "h-4 w-4 text-kumo-subtle cursor-grab shrink-0",
|
|
2861
|
+
...attributes,
|
|
2862
|
+
...listeners,
|
|
2863
|
+
onClick: (e) => e.stopPropagation()
|
|
2864
|
+
}),
|
|
2865
|
+
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" }),
|
|
2866
|
+
/* @__PURE__ */ jsx("span", {
|
|
2867
|
+
className: "text-sm font-medium flex-1 truncate",
|
|
2868
|
+
children: summaryLabel
|
|
2869
|
+
}),
|
|
2870
|
+
onRemove && /* @__PURE__ */ jsx(Button, {
|
|
2871
|
+
variant: "ghost",
|
|
2872
|
+
shape: "square",
|
|
2873
|
+
onClick: (e) => {
|
|
2874
|
+
e.stopPropagation();
|
|
2875
|
+
onRemove();
|
|
2876
|
+
},
|
|
2877
|
+
"aria-label": `Remove item ${index + 1}`,
|
|
2878
|
+
children: /* @__PURE__ */ jsx(Trash, { className: "h-3.5 w-3.5 text-kumo-danger" })
|
|
2879
|
+
})
|
|
2880
|
+
]
|
|
2881
|
+
}), !isCollapsed && /* @__PURE__ */ jsx("div", {
|
|
2882
|
+
className: "p-3 space-y-3",
|
|
2883
|
+
children: subFields.map((sf) => /* @__PURE__ */ jsx(SubFieldInput, {
|
|
2884
|
+
subField: sf,
|
|
2885
|
+
value: item[sf.slug],
|
|
2886
|
+
onChange: (v) => onChange(sf.slug, v)
|
|
2887
|
+
}, sf.slug))
|
|
2888
|
+
})]
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
function SubFieldInput({ subField, value, onChange }) {
|
|
2892
|
+
switch (subField.type) {
|
|
2893
|
+
case "string": return /* @__PURE__ */ jsx(Input, {
|
|
2894
|
+
label: subField.label,
|
|
2895
|
+
value: typeof value === "string" ? value : "",
|
|
2896
|
+
onChange: (e) => onChange(e.target.value),
|
|
2897
|
+
required: subField.required
|
|
2898
|
+
});
|
|
2899
|
+
case "text": return /* @__PURE__ */ jsx(InputArea, {
|
|
2900
|
+
label: subField.label,
|
|
2901
|
+
value: typeof value === "string" ? value : "",
|
|
2902
|
+
onChange: (e) => onChange(e.target.value),
|
|
2903
|
+
required: subField.required,
|
|
2904
|
+
rows: 3
|
|
2905
|
+
});
|
|
2906
|
+
case "number":
|
|
2907
|
+
case "integer": return /* @__PURE__ */ jsx(Input, {
|
|
2908
|
+
label: subField.label,
|
|
2909
|
+
type: "number",
|
|
2910
|
+
value: typeof value === "number" ? String(value) : "",
|
|
2911
|
+
onChange: (e) => onChange(e.target.value ? Number(e.target.value) : null),
|
|
2912
|
+
required: subField.required,
|
|
2913
|
+
step: subField.type === "integer" ? "1" : "any"
|
|
2914
|
+
});
|
|
2915
|
+
case "boolean": return /* @__PURE__ */ jsxs("label", {
|
|
2916
|
+
className: "flex items-center gap-2",
|
|
2917
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
2918
|
+
type: "checkbox",
|
|
2919
|
+
checked: Boolean(value),
|
|
2920
|
+
onChange: (e) => onChange(e.target.checked)
|
|
2921
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2922
|
+
className: "text-sm",
|
|
2923
|
+
children: subField.label
|
|
2924
|
+
})]
|
|
2925
|
+
});
|
|
2926
|
+
case "datetime": return /* @__PURE__ */ jsx(Input, {
|
|
2927
|
+
label: subField.label,
|
|
2928
|
+
type: "datetime-local",
|
|
2929
|
+
value: typeof value === "string" ? value : "",
|
|
2930
|
+
onChange: (e) => onChange(e.target.value),
|
|
2931
|
+
required: subField.required
|
|
2932
|
+
});
|
|
2933
|
+
case "select": return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2934
|
+
className: "text-sm font-medium",
|
|
2935
|
+
children: subField.label
|
|
2936
|
+
}), /* @__PURE__ */ jsxs("select", {
|
|
2937
|
+
className: "w-full mt-1 rounded-md border px-3 py-2 text-sm",
|
|
2938
|
+
value: typeof value === "string" ? value : "",
|
|
2939
|
+
onChange: (e) => onChange(e.target.value),
|
|
2940
|
+
required: subField.required,
|
|
2941
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
2942
|
+
value: "",
|
|
2943
|
+
children: "Select..."
|
|
2944
|
+
}), subField.options?.map((opt) => /* @__PURE__ */ jsx("option", {
|
|
2945
|
+
value: opt,
|
|
2946
|
+
children: opt
|
|
2947
|
+
}, opt))]
|
|
2948
|
+
})] });
|
|
2949
|
+
default: return /* @__PURE__ */ jsx(Input, {
|
|
2950
|
+
label: subField.label,
|
|
2951
|
+
value: typeof value === "string" ? value : "",
|
|
2952
|
+
onChange: (e) => onChange(e.target.value)
|
|
2953
|
+
});
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2667
2957
|
//#endregion
|
|
2668
2958
|
//#region src/lib/hooks.ts
|
|
2669
2959
|
/**
|
|
@@ -2998,7 +3288,7 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
|
|
|
2998
3288
|
open,
|
|
2999
3289
|
onOpenChange: handleClose,
|
|
3000
3290
|
children: /* @__PURE__ */ jsxs(Dialog, {
|
|
3001
|
-
className: "p-6 max-w-4xl max-h-[80vh] flex flex-col",
|
|
3291
|
+
className: "p-6 max-w-4xl max-h-[80vh] flex flex-col overflow-hidden",
|
|
3002
3292
|
size: "xl",
|
|
3003
3293
|
children: [
|
|
3004
3294
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -3126,7 +3416,7 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
|
|
|
3126
3416
|
className: "mb-3"
|
|
3127
3417
|
}),
|
|
3128
3418
|
/* @__PURE__ */ jsx("div", {
|
|
3129
|
-
className: "flex-1 overflow-y-auto min-h-
|
|
3419
|
+
className: "flex-1 overflow-y-auto min-h-0",
|
|
3130
3420
|
children: isLoading ? /* @__PURE__ */ jsx("div", {
|
|
3131
3421
|
className: "flex items-center justify-center h-full",
|
|
3132
3422
|
children: /* @__PURE__ */ jsx(Loader, {})
|
|
@@ -7185,6 +7475,74 @@ function SaveButton({ isDirty, isSaving, className, disabled, ...props }) {
|
|
|
7185
7475
|
});
|
|
7186
7476
|
}
|
|
7187
7477
|
|
|
7478
|
+
//#endregion
|
|
7479
|
+
//#region src/components/SeoImageField.tsx
|
|
7480
|
+
/**
|
|
7481
|
+
* SEO OG Image field for the content editor.
|
|
7482
|
+
*
|
|
7483
|
+
* Renders an image picker (reusing MediaPickerModal) that stores the
|
|
7484
|
+
* selected image URL in `seo.image`. Designed to sit next to the
|
|
7485
|
+
* Featured Image field in a two-column grid.
|
|
7486
|
+
*/
|
|
7487
|
+
function SeoImageField({ seo, onChange }) {
|
|
7488
|
+
const [pickerOpen, setPickerOpen] = React.useState(false);
|
|
7489
|
+
const imageUrl = seo?.image || null;
|
|
7490
|
+
const handleSelect = (item) => {
|
|
7491
|
+
onChange({ image: !item.provider || item.provider === "local" ? `/_emdash/api/media/file/${item.storageKey || item.id}` : item.url });
|
|
7492
|
+
};
|
|
7493
|
+
const handleRemove = () => {
|
|
7494
|
+
onChange({ image: null });
|
|
7495
|
+
};
|
|
7496
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
7497
|
+
/* @__PURE__ */ jsx(Label, { children: "OG Image" }),
|
|
7498
|
+
imageUrl ? /* @__PURE__ */ jsxs("div", {
|
|
7499
|
+
className: "mt-2 relative group",
|
|
7500
|
+
children: [/* @__PURE__ */ jsx("img", {
|
|
7501
|
+
src: imageUrl,
|
|
7502
|
+
alt: "",
|
|
7503
|
+
className: "max-h-48 rounded-lg border object-cover"
|
|
7504
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
7505
|
+
className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
|
|
7506
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
7507
|
+
type: "button",
|
|
7508
|
+
size: "sm",
|
|
7509
|
+
variant: "secondary",
|
|
7510
|
+
onClick: () => setPickerOpen(true),
|
|
7511
|
+
children: "Change"
|
|
7512
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
7513
|
+
type: "button",
|
|
7514
|
+
shape: "square",
|
|
7515
|
+
variant: "destructive",
|
|
7516
|
+
className: "h-8 w-8",
|
|
7517
|
+
onClick: handleRemove,
|
|
7518
|
+
"aria-label": "Remove image",
|
|
7519
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
7520
|
+
})]
|
|
7521
|
+
})]
|
|
7522
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
7523
|
+
type: "button",
|
|
7524
|
+
variant: "outline",
|
|
7525
|
+
className: "mt-2 w-full h-32 border-dashed",
|
|
7526
|
+
onClick: () => setPickerOpen(true),
|
|
7527
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
7528
|
+
className: "flex flex-col items-center gap-2 text-kumo-subtle",
|
|
7529
|
+
children: [/* @__PURE__ */ jsx(Image$1, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: "Select OG image" })]
|
|
7530
|
+
})
|
|
7531
|
+
}),
|
|
7532
|
+
/* @__PURE__ */ jsx("p", {
|
|
7533
|
+
className: "text-xs text-kumo-subtle mt-1",
|
|
7534
|
+
children: "Image shown when this page is shared on social media"
|
|
7535
|
+
}),
|
|
7536
|
+
/* @__PURE__ */ jsx(MediaPickerModal, {
|
|
7537
|
+
open: pickerOpen,
|
|
7538
|
+
onOpenChange: setPickerOpen,
|
|
7539
|
+
onSelect: handleSelect,
|
|
7540
|
+
mimeTypeFilter: "image/",
|
|
7541
|
+
title: "Select OG Image"
|
|
7542
|
+
})
|
|
7543
|
+
] });
|
|
7544
|
+
}
|
|
7545
|
+
|
|
7188
7546
|
//#endregion
|
|
7189
7547
|
//#region src/components/SeoPanel.tsx
|
|
7190
7548
|
/**
|
|
@@ -7509,7 +7867,7 @@ function formatScheduledDate(dateStr) {
|
|
|
7509
7867
|
/**
|
|
7510
7868
|
* Content editor with dynamic field rendering
|
|
7511
7869
|
*/
|
|
7512
|
-
function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSaving, onSave, onAutosave, isAutosaving, lastAutosaveAt, onPublish, onUnpublish, onDiscardDraft, onSchedule, onUnschedule, isScheduling, supportsDrafts = false, supportsRevisions = false, currentUser, users, onAuthorChange, availableBylines, selectedBylines, onBylinesChange, onQuickCreateByline, onQuickEditByline, onDelete, isDeleting, i18n, translations, onTranslate, pluginBlocks, hasSeo = false, onSeoChange, manifest }) {
|
|
7870
|
+
function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSaving, onSave, onAutosave, isAutosaving, lastAutosaveAt, onPublish, onUnpublish, onDiscardDraft, onSchedule, onUnschedule, isScheduling, supportsDrafts = false, supportsRevisions = false, supportsPreview = false, currentUser, users, onAuthorChange, availableBylines, selectedBylines, onBylinesChange, onQuickCreateByline, onQuickEditByline, onDelete, isDeleting, i18n, translations, onTranslate, pluginBlocks, hasSeo = false, onSeoChange, manifest }) {
|
|
7513
7871
|
const [formData, setFormData] = React.useState(item?.data || {});
|
|
7514
7872
|
const [slug, setSlug] = React.useState(item?.slug || "");
|
|
7515
7873
|
const [slugTouched, setSlugTouched] = React.useState(!!item?.slug);
|
|
@@ -7624,15 +7982,16 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
7624
7982
|
});
|
|
7625
7983
|
};
|
|
7626
7984
|
const [isLoadingPreview, setIsLoadingPreview] = React.useState(false);
|
|
7985
|
+
const urlPattern = manifest?.collections[collection]?.urlPattern;
|
|
7627
7986
|
const handlePreview = async () => {
|
|
7628
7987
|
if (!item?.id) return;
|
|
7629
7988
|
setIsLoadingPreview(true);
|
|
7630
7989
|
try {
|
|
7631
7990
|
const result = await getPreviewUrl(collection, item.id);
|
|
7632
7991
|
if (result?.url) window.open(result.url, "_blank", "noopener,noreferrer");
|
|
7633
|
-
else window.open(
|
|
7992
|
+
else window.open(contentUrl(collection, slug || item.id, urlPattern), "_blank", "noopener,noreferrer");
|
|
7634
7993
|
} catch {
|
|
7635
|
-
window.open(
|
|
7994
|
+
window.open(contentUrl(collection, slug || item?.id || "", urlPattern), "_blank", "noopener,noreferrer");
|
|
7636
7995
|
} finally {
|
|
7637
7996
|
setIsLoadingPreview(false);
|
|
7638
7997
|
}
|
|
@@ -7743,7 +8102,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
7743
8102
|
"aria-hidden": "true"
|
|
7744
8103
|
})
|
|
7745
8104
|
}),
|
|
7746
|
-
!isNew && /* @__PURE__ */ jsx(Button, {
|
|
8105
|
+
!isNew && supportsPreview && /* @__PURE__ */ jsx(Button, {
|
|
7747
8106
|
variant: "outline",
|
|
7748
8107
|
type: "button",
|
|
7749
8108
|
onClick: handlePreview,
|
|
@@ -7756,9 +8115,8 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
7756
8115
|
isDirty,
|
|
7757
8116
|
isSaving: isSaving || false
|
|
7758
8117
|
}),
|
|
7759
|
-
!isNew && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
7760
|
-
|
|
7761
|
-
children: [/* @__PURE__ */ jsx(Dialog.Trigger, { render: (p) => /* @__PURE__ */ jsx(Button, {
|
|
8118
|
+
!isNew && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
8119
|
+
supportsDrafts && hasPendingChanges && onDiscardDraft && /* @__PURE__ */ jsxs(Dialog.Root, { children: [/* @__PURE__ */ jsx(Dialog.Trigger, { render: (p) => /* @__PURE__ */ jsx(Button, {
|
|
7762
8120
|
...p,
|
|
7763
8121
|
type: "button",
|
|
7764
8122
|
variant: "outline",
|
|
@@ -7791,23 +8149,34 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
7791
8149
|
}) })]
|
|
7792
8150
|
})
|
|
7793
8151
|
]
|
|
7794
|
-
})]
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
8152
|
+
})] }),
|
|
8153
|
+
isLive ? /* @__PURE__ */ jsx(Fragment, { children: hasPendingChanges ? /* @__PURE__ */ jsx(Button, {
|
|
8154
|
+
type: "button",
|
|
8155
|
+
variant: "primary",
|
|
8156
|
+
onClick: onPublish,
|
|
8157
|
+
children: "Publish changes"
|
|
8158
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
8159
|
+
type: "button",
|
|
8160
|
+
variant: "outline",
|
|
8161
|
+
onClick: onUnpublish,
|
|
8162
|
+
children: "Unpublish"
|
|
8163
|
+
}) }) : /* @__PURE__ */ jsx(Button, {
|
|
8164
|
+
type: "button",
|
|
8165
|
+
variant: "secondary",
|
|
8166
|
+
onClick: onPublish,
|
|
8167
|
+
children: "Publish"
|
|
8168
|
+
}),
|
|
8169
|
+
isLive && item?.slug && /* @__PURE__ */ jsxs("a", {
|
|
8170
|
+
href: contentUrl(collection, item.slug, urlPattern),
|
|
8171
|
+
target: "_blank",
|
|
8172
|
+
rel: "noopener noreferrer",
|
|
8173
|
+
className: buttonVariants({ variant: "outline" }),
|
|
8174
|
+
children: [/* @__PURE__ */ jsx(ArrowSquareOut, {
|
|
8175
|
+
className: "mr-2 h-4 w-4",
|
|
8176
|
+
"aria-hidden": "true"
|
|
8177
|
+
}), "Live View"]
|
|
8178
|
+
})
|
|
8179
|
+
] })
|
|
7811
8180
|
]
|
|
7812
8181
|
})]
|
|
7813
8182
|
}), /* @__PURE__ */ jsxs("div", {
|
|
@@ -7818,18 +8187,28 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
7818
8187
|
className: cn("rounded-lg border bg-kumo-base p-6", isDistractionFree && "border-0 bg-transparent p-0"),
|
|
7819
8188
|
children: /* @__PURE__ */ jsx("div", {
|
|
7820
8189
|
className: "space-y-4",
|
|
7821
|
-
children: Object.entries(fields).map(([name, field]) =>
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
8190
|
+
children: Object.entries(fields).map(([name, field]) => {
|
|
8191
|
+
const fieldEl = /* @__PURE__ */ jsx(FieldRenderer, {
|
|
8192
|
+
name,
|
|
8193
|
+
field,
|
|
8194
|
+
value: formData[name],
|
|
8195
|
+
onChange: handleFieldChange,
|
|
8196
|
+
onEditorReady: field.kind === "portableText" ? setPortableTextEditor : void 0,
|
|
8197
|
+
minimal: isDistractionFree,
|
|
8198
|
+
pluginBlocks,
|
|
8199
|
+
onBlockSidebarOpen: field.kind === "portableText" ? handleBlockSidebarOpen : void 0,
|
|
8200
|
+
onBlockSidebarClose: field.kind === "portableText" ? handleBlockSidebarClose : void 0,
|
|
8201
|
+
manifest
|
|
8202
|
+
}, name);
|
|
8203
|
+
if (name === "featured_image" && field.kind === "image" && hasSeo && !isNew && onSeoChange) return /* @__PURE__ */ jsxs("div", {
|
|
8204
|
+
className: "grid grid-cols-1 gap-6 md:grid-cols-2",
|
|
8205
|
+
children: [/* @__PURE__ */ jsx("div", { children: fieldEl }), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(SeoImageField, {
|
|
8206
|
+
seo: item?.seo,
|
|
8207
|
+
onChange: onSeoChange
|
|
8208
|
+
}) })]
|
|
8209
|
+
}, `${name}-with-seo`);
|
|
8210
|
+
return fieldEl;
|
|
8211
|
+
})
|
|
7833
8212
|
})
|
|
7834
8213
|
})
|
|
7835
8214
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -7868,6 +8247,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
7868
8247
|
children: supportsDrafts ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
7869
8248
|
isLive && /* @__PURE__ */ jsx(Badge, {
|
|
7870
8249
|
variant: "primary",
|
|
8250
|
+
className: "text-white",
|
|
7871
8251
|
children: "Published"
|
|
7872
8252
|
}),
|
|
7873
8253
|
hasPendingChanges && /* @__PURE__ */ jsx(Badge, {
|
|
@@ -8160,27 +8540,31 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
8160
8540
|
required: field.required
|
|
8161
8541
|
});
|
|
8162
8542
|
case "boolean": return /* @__PURE__ */ jsx(Switch, {
|
|
8543
|
+
id,
|
|
8163
8544
|
label,
|
|
8164
8545
|
checked: typeof value === "boolean" ? value : false,
|
|
8165
8546
|
onCheckedChange: handleChange
|
|
8166
8547
|
});
|
|
8167
8548
|
case "portableText": {
|
|
8168
8549
|
const labelId = `${id}-label`;
|
|
8169
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8170
|
-
id
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8550
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
8551
|
+
id,
|
|
8552
|
+
children: [!minimal && /* @__PURE__ */ jsx("span", {
|
|
8553
|
+
id: labelId,
|
|
8554
|
+
className: cn("text-sm font-medium leading-none text-kumo-default", labelClass),
|
|
8555
|
+
children: label
|
|
8556
|
+
}), /* @__PURE__ */ jsx(PortableTextEditor, {
|
|
8557
|
+
value: Array.isArray(value) ? value : [],
|
|
8558
|
+
onChange: handleChange,
|
|
8559
|
+
placeholder: `Enter ${label.toLowerCase()}...`,
|
|
8560
|
+
"aria-labelledby": labelId,
|
|
8561
|
+
pluginBlocks,
|
|
8562
|
+
onEditorReady,
|
|
8563
|
+
minimal,
|
|
8564
|
+
onBlockSidebarOpen,
|
|
8565
|
+
onBlockSidebarClose
|
|
8566
|
+
})]
|
|
8567
|
+
});
|
|
8184
8568
|
}
|
|
8185
8569
|
case "richText": return /* @__PURE__ */ jsx(InputArea, {
|
|
8186
8570
|
label,
|
|
@@ -8194,6 +8578,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
8194
8578
|
const selectItems = {};
|
|
8195
8579
|
for (const opt of field.options ?? []) selectItems[opt.value] = opt.label;
|
|
8196
8580
|
return /* @__PURE__ */ jsx(Select, {
|
|
8581
|
+
id,
|
|
8197
8582
|
label,
|
|
8198
8583
|
value: typeof value === "string" ? value : "",
|
|
8199
8584
|
onValueChange: (v) => handleChange(v ?? ""),
|
|
@@ -8204,6 +8589,25 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
8204
8589
|
}, opt.value))
|
|
8205
8590
|
});
|
|
8206
8591
|
}
|
|
8592
|
+
case "multiSelect": {
|
|
8593
|
+
const selected = Array.isArray(value) ? value : [];
|
|
8594
|
+
return /* @__PURE__ */ jsxs("fieldset", { children: [/* @__PURE__ */ jsx(Label, {
|
|
8595
|
+
className: labelClass,
|
|
8596
|
+
children: label
|
|
8597
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
8598
|
+
className: "mt-2 flex flex-wrap gap-x-4 gap-y-2",
|
|
8599
|
+
children: field.options?.map((opt) => {
|
|
8600
|
+
const isChecked = selected.includes(opt.value);
|
|
8601
|
+
return /* @__PURE__ */ jsx(Checkbox, {
|
|
8602
|
+
label: opt.label,
|
|
8603
|
+
checked: isChecked,
|
|
8604
|
+
onCheckedChange: (checked) => {
|
|
8605
|
+
handleChange(checked ? [...selected, opt.value] : selected.filter((v) => v !== opt.value));
|
|
8606
|
+
}
|
|
8607
|
+
}, opt.value);
|
|
8608
|
+
})
|
|
8609
|
+
})] });
|
|
8610
|
+
}
|
|
8207
8611
|
case "datetime": return /* @__PURE__ */ jsx(Input, {
|
|
8208
8612
|
label,
|
|
8209
8613
|
id,
|
|
@@ -8213,11 +8617,27 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
8213
8617
|
required: field.required
|
|
8214
8618
|
});
|
|
8215
8619
|
case "image": return /* @__PURE__ */ jsx(ImageFieldRenderer, {
|
|
8620
|
+
id,
|
|
8216
8621
|
label,
|
|
8622
|
+
description: name === "featured_image" ? "Used as the main visual for this post on listing pages and at the top of the post" : void 0,
|
|
8217
8623
|
value: value != null && typeof value === "object" ? value : void 0,
|
|
8218
8624
|
onChange: handleChange,
|
|
8219
8625
|
required: field.required
|
|
8220
8626
|
});
|
|
8627
|
+
case "repeater": {
|
|
8628
|
+
const validation = field.validation;
|
|
8629
|
+
const subFields = validation?.subFields ?? [];
|
|
8630
|
+
return /* @__PURE__ */ jsx(RepeaterField, {
|
|
8631
|
+
label,
|
|
8632
|
+
id,
|
|
8633
|
+
value,
|
|
8634
|
+
onChange: handleChange,
|
|
8635
|
+
required: field.required,
|
|
8636
|
+
subFields,
|
|
8637
|
+
minItems: typeof validation?.minItems === "number" ? validation.minItems : void 0,
|
|
8638
|
+
maxItems: typeof validation?.maxItems === "number" ? validation.maxItems : void 0
|
|
8639
|
+
});
|
|
8640
|
+
}
|
|
8221
8641
|
default: return /* @__PURE__ */ jsx(Input, {
|
|
8222
8642
|
label,
|
|
8223
8643
|
id,
|
|
@@ -8227,7 +8647,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
8227
8647
|
});
|
|
8228
8648
|
}
|
|
8229
8649
|
}
|
|
8230
|
-
function ImageFieldRenderer({ label, value, onChange, required }) {
|
|
8650
|
+
function ImageFieldRenderer({ id, label, description, value, onChange, required }) {
|
|
8231
8651
|
const [pickerOpen, setPickerOpen] = React.useState(false);
|
|
8232
8652
|
const displayUrl = typeof value === "string" ? value : value?.previewUrl || value?.src || (value && (!value.provider || value.provider === "local") ? `/_emdash/api/media/file/${typeof value.meta?.storageKey === "string" ? value.meta.storageKey : value.id}` : void 0);
|
|
8233
8653
|
const handleSelect = (item) => {
|
|
@@ -8248,54 +8668,61 @@ function ImageFieldRenderer({ label, value, onChange, required }) {
|
|
|
8248
8668
|
const handleRemove = () => {
|
|
8249
8669
|
onChange(void 0);
|
|
8250
8670
|
};
|
|
8251
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8671
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
8672
|
+
id,
|
|
8673
|
+
children: [
|
|
8674
|
+
/* @__PURE__ */ jsx(Label, { children: label }),
|
|
8675
|
+
displayUrl ? /* @__PURE__ */ jsxs("div", {
|
|
8676
|
+
className: "mt-2 relative group",
|
|
8677
|
+
children: [/* @__PURE__ */ jsx("img", {
|
|
8678
|
+
src: displayUrl,
|
|
8679
|
+
alt: "",
|
|
8680
|
+
className: "max-h-48 rounded-lg border object-cover"
|
|
8681
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
8682
|
+
className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
|
|
8683
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
8684
|
+
type: "button",
|
|
8685
|
+
size: "sm",
|
|
8686
|
+
variant: "secondary",
|
|
8687
|
+
onClick: () => setPickerOpen(true),
|
|
8688
|
+
children: "Change"
|
|
8689
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
8690
|
+
type: "button",
|
|
8691
|
+
shape: "square",
|
|
8692
|
+
variant: "destructive",
|
|
8693
|
+
className: "h-8 w-8",
|
|
8694
|
+
onClick: handleRemove,
|
|
8695
|
+
"aria-label": "Remove image",
|
|
8696
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
8697
|
+
})]
|
|
8275
8698
|
})]
|
|
8276
|
-
})
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8699
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
8700
|
+
type: "button",
|
|
8701
|
+
variant: "outline",
|
|
8702
|
+
className: "mt-2 w-full h-32 border-dashed",
|
|
8703
|
+
onClick: () => setPickerOpen(true),
|
|
8704
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
8705
|
+
className: "flex flex-col items-center gap-2 text-kumo-subtle",
|
|
8706
|
+
children: [/* @__PURE__ */ jsx(Image$1, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: "Select image" })]
|
|
8707
|
+
})
|
|
8708
|
+
}),
|
|
8709
|
+
/* @__PURE__ */ jsx(MediaPickerModal, {
|
|
8710
|
+
open: pickerOpen,
|
|
8711
|
+
onOpenChange: setPickerOpen,
|
|
8712
|
+
onSelect: handleSelect,
|
|
8713
|
+
mimeTypeFilter: "image/",
|
|
8714
|
+
title: `Select ${label}`
|
|
8715
|
+
}),
|
|
8716
|
+
description && /* @__PURE__ */ jsx("p", {
|
|
8717
|
+
className: "text-xs text-kumo-subtle mt-1",
|
|
8718
|
+
children: description
|
|
8719
|
+
}),
|
|
8720
|
+
required && !displayUrl && /* @__PURE__ */ jsx("p", {
|
|
8721
|
+
className: "text-sm text-kumo-danger mt-1",
|
|
8722
|
+
children: "This field is required"
|
|
8285
8723
|
})
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
open: pickerOpen,
|
|
8289
|
-
onOpenChange: setPickerOpen,
|
|
8290
|
-
onSelect: handleSelect,
|
|
8291
|
-
mimeTypeFilter: "image/",
|
|
8292
|
-
title: `Select ${label}`
|
|
8293
|
-
}),
|
|
8294
|
-
required && !displayUrl && /* @__PURE__ */ jsx("p", {
|
|
8295
|
-
className: "text-sm text-kumo-danger mt-1",
|
|
8296
|
-
children: "This field is required"
|
|
8297
|
-
})
|
|
8298
|
-
] });
|
|
8724
|
+
]
|
|
8725
|
+
});
|
|
8299
8726
|
}
|
|
8300
8727
|
function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuickEdit }) {
|
|
8301
8728
|
const [selectedBylineId, setSelectedBylineId] = React.useState("");
|
|
@@ -8475,7 +8902,10 @@ function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuic
|
|
|
8475
8902
|
children: [/* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
|
|
8476
8903
|
...p,
|
|
8477
8904
|
variant: "secondary",
|
|
8478
|
-
onClick:
|
|
8905
|
+
onClick: (e) => {
|
|
8906
|
+
resetQuickCreate();
|
|
8907
|
+
p.onClick?.(e);
|
|
8908
|
+
},
|
|
8479
8909
|
children: "Cancel"
|
|
8480
8910
|
}) }), /* @__PURE__ */ jsx(Button, {
|
|
8481
8911
|
type: "button",
|
|
@@ -8619,6 +9049,7 @@ function AuthorSelector({ authorId, users, onChange }) {
|
|
|
8619
9049
|
* Only renders when i18n is configured (manifest.i18n is present).
|
|
8620
9050
|
*/
|
|
8621
9051
|
function LocaleSwitcher({ locales, defaultLocale, value, onChange, showAll = false, className, size = "md" }) {
|
|
9052
|
+
const { _: _t } = useLingui();
|
|
8622
9053
|
return /* @__PURE__ */ jsxs("div", {
|
|
8623
9054
|
className: cn("flex items-center gap-1.5", className),
|
|
8624
9055
|
children: [/* @__PURE__ */ jsx(GlobeSimple, {
|
|
@@ -8627,14 +9058,23 @@ function LocaleSwitcher({ locales, defaultLocale, value, onChange, showAll = fal
|
|
|
8627
9058
|
}), /* @__PURE__ */ jsxs("select", {
|
|
8628
9059
|
value,
|
|
8629
9060
|
onChange: (e) => onChange(e.target.value),
|
|
8630
|
-
"aria-label":
|
|
9061
|
+
"aria-label": _t({
|
|
9062
|
+
id: "8NbHF7",
|
|
9063
|
+
message: "Locale"
|
|
9064
|
+
}),
|
|
8631
9065
|
className: cn("rounded-md border bg-transparent font-medium transition-colors", "focus:ring-kumo-ring focus:outline-none focus:ring-2 focus:ring-offset-1", "hover:bg-kumo-tint/50 cursor-pointer", size === "sm" ? "px-1.5 py-0.5 text-xs" : "px-2 py-1 text-sm"),
|
|
8632
9066
|
children: [showAll && /* @__PURE__ */ jsx("option", {
|
|
8633
9067
|
value: "",
|
|
8634
|
-
children:
|
|
9068
|
+
children: _t({
|
|
9069
|
+
id: "JjOi48",
|
|
9070
|
+
message: "All locales"
|
|
9071
|
+
})
|
|
8635
9072
|
}), locales.map((locale) => /* @__PURE__ */ jsxs("option", {
|
|
8636
9073
|
value: locale,
|
|
8637
|
-
children: [locale.toUpperCase(), locale === defaultLocale ?
|
|
9074
|
+
children: [locale.toUpperCase(), locale === defaultLocale ? _t({
|
|
9075
|
+
id: "FozKV6",
|
|
9076
|
+
message: " (default)"
|
|
9077
|
+
}) : ""]
|
|
8638
9078
|
}, locale))]
|
|
8639
9079
|
})]
|
|
8640
9080
|
});
|
|
@@ -8651,7 +9091,7 @@ function getItemTitle$1(item) {
|
|
|
8651
9091
|
/**
|
|
8652
9092
|
* Content list view with table display and trash tab
|
|
8653
9093
|
*/
|
|
8654
|
-
function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n, activeLocale, onLocaleChange }) {
|
|
9094
|
+
function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n, activeLocale, onLocaleChange, urlPattern }) {
|
|
8655
9095
|
const [activeTab, setActiveTab] = React.useState("all");
|
|
8656
9096
|
const [searchQuery, setSearchQuery] = React.useState("");
|
|
8657
9097
|
const [page, setPage] = React.useState(0);
|
|
@@ -8666,6 +9106,15 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
8666
9106
|
}, [items, searchQuery]);
|
|
8667
9107
|
const totalPages = Math.max(1, Math.ceil(filteredItems.length / PAGE_SIZE));
|
|
8668
9108
|
const paginatedItems = filteredItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
|
|
9109
|
+
React.useEffect(() => {
|
|
9110
|
+
if (page >= totalPages - 1 && hasMore && onLoadMore && !searchQuery) onLoadMore();
|
|
9111
|
+
}, [
|
|
9112
|
+
page,
|
|
9113
|
+
totalPages,
|
|
9114
|
+
hasMore,
|
|
9115
|
+
onLoadMore,
|
|
9116
|
+
searchQuery
|
|
9117
|
+
]);
|
|
8669
9118
|
return /* @__PURE__ */ jsxs("div", {
|
|
8670
9119
|
className: "space-y-4",
|
|
8671
9120
|
children: [
|
|
@@ -8686,6 +9135,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
8686
9135
|
}), /* @__PURE__ */ jsxs(Link$1, {
|
|
8687
9136
|
to: "/content/$collection/new",
|
|
8688
9137
|
params: { collection },
|
|
9138
|
+
search: { locale: activeLocale },
|
|
8689
9139
|
className: buttonVariants(),
|
|
8690
9140
|
children: [/* @__PURE__ */ jsx(Plus, {
|
|
8691
9141
|
className: "mr-2 h-4 w-4",
|
|
@@ -8776,6 +9226,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
8776
9226
|
/* @__PURE__ */ jsx(Link$1, {
|
|
8777
9227
|
to: "/content/$collection/new",
|
|
8778
9228
|
params: { collection },
|
|
9229
|
+
search: { locale: activeLocale },
|
|
8779
9230
|
className: "text-kumo-brand underline",
|
|
8780
9231
|
children: "Create your first one"
|
|
8781
9232
|
})
|
|
@@ -8793,7 +9244,8 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
8793
9244
|
collection,
|
|
8794
9245
|
onDelete,
|
|
8795
9246
|
onDuplicate,
|
|
8796
|
-
showLocale: !!i18n
|
|
9247
|
+
showLocale: !!i18n,
|
|
9248
|
+
urlPattern
|
|
8797
9249
|
}, item.id)) })]
|
|
8798
9250
|
})
|
|
8799
9251
|
}),
|
|
@@ -8803,6 +9255,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
8803
9255
|
className: "text-sm text-kumo-subtle",
|
|
8804
9256
|
children: [
|
|
8805
9257
|
filteredItems.length,
|
|
9258
|
+
hasMore && !searchQuery ? "+" : "",
|
|
8806
9259
|
" ",
|
|
8807
9260
|
filteredItems.length === 1 ? "item" : "items",
|
|
8808
9261
|
searchQuery && ` matching "${searchQuery}"`
|
|
@@ -8897,7 +9350,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
8897
9350
|
]
|
|
8898
9351
|
});
|
|
8899
9352
|
}
|
|
8900
|
-
function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale }) {
|
|
9353
|
+
function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale, urlPattern }) {
|
|
8901
9354
|
const title = getItemTitle$1(item);
|
|
8902
9355
|
const date = new Date(item.updatedAt || item.createdAt);
|
|
8903
9356
|
return /* @__PURE__ */ jsxs("tr", {
|
|
@@ -8938,6 +9391,20 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale }
|
|
|
8938
9391
|
children: /* @__PURE__ */ jsxs("div", {
|
|
8939
9392
|
className: "flex items-center justify-end space-x-1",
|
|
8940
9393
|
children: [
|
|
9394
|
+
item.status === "published" && item.slug && /* @__PURE__ */ jsx("a", {
|
|
9395
|
+
href: contentUrl(collection, item.slug, urlPattern),
|
|
9396
|
+
target: "_blank",
|
|
9397
|
+
rel: "noopener noreferrer",
|
|
9398
|
+
"aria-label": `View published ${title}`,
|
|
9399
|
+
className: buttonVariants({
|
|
9400
|
+
variant: "ghost",
|
|
9401
|
+
shape: "square"
|
|
9402
|
+
}),
|
|
9403
|
+
children: /* @__PURE__ */ jsx(ArrowSquareOut, {
|
|
9404
|
+
className: "h-4 w-4",
|
|
9405
|
+
"aria-hidden": "true"
|
|
9406
|
+
})
|
|
9407
|
+
}),
|
|
8941
9408
|
/* @__PURE__ */ jsx(Link$1, {
|
|
8942
9409
|
to: "/content/$collection/$id",
|
|
8943
9410
|
params: {
|
|
@@ -9192,6 +9659,12 @@ const FIELD_TYPES = [
|
|
|
9192
9659
|
label: "Slug",
|
|
9193
9660
|
description: "URL-friendly identifier",
|
|
9194
9661
|
icon: Link$2
|
|
9662
|
+
},
|
|
9663
|
+
{
|
|
9664
|
+
type: "repeater",
|
|
9665
|
+
label: "Repeater",
|
|
9666
|
+
description: "Repeating group of fields",
|
|
9667
|
+
icon: Rows
|
|
9195
9668
|
}
|
|
9196
9669
|
];
|
|
9197
9670
|
function getInitialFormState(field) {
|
|
@@ -9208,7 +9681,10 @@ function getInitialFormState(field) {
|
|
|
9208
9681
|
min: field.validation?.min?.toString() ?? "",
|
|
9209
9682
|
max: field.validation?.max?.toString() ?? "",
|
|
9210
9683
|
pattern: field.validation?.pattern ?? "",
|
|
9211
|
-
options: field.validation?.options?.join("\n") ?? ""
|
|
9684
|
+
options: field.validation?.options?.join("\n") ?? "",
|
|
9685
|
+
subFields: field.validation?.subFields ? field.validation.subFields : [],
|
|
9686
|
+
minItems: field.validation?.minItems?.toString() ?? "",
|
|
9687
|
+
maxItems: field.validation?.maxItems?.toString() ?? ""
|
|
9212
9688
|
};
|
|
9213
9689
|
return {
|
|
9214
9690
|
step: "type",
|
|
@@ -9223,7 +9699,10 @@ function getInitialFormState(field) {
|
|
|
9223
9699
|
min: "",
|
|
9224
9700
|
max: "",
|
|
9225
9701
|
pattern: "",
|
|
9226
|
-
options: ""
|
|
9702
|
+
options: "",
|
|
9703
|
+
subFields: [],
|
|
9704
|
+
minItems: "",
|
|
9705
|
+
maxItems: ""
|
|
9227
9706
|
};
|
|
9228
9707
|
}
|
|
9229
9708
|
/**
|
|
@@ -9267,6 +9746,16 @@ function FieldEditor({ open, onOpenChange, field, onSave, isSaving }) {
|
|
|
9267
9746
|
const optionList = options.split("\n").map((o) => o.trim()).filter(Boolean);
|
|
9268
9747
|
if (optionList.length > 0) validation.options = optionList;
|
|
9269
9748
|
}
|
|
9749
|
+
if (selectedType === "repeater") {
|
|
9750
|
+
if (formState.subFields.length > 0) validation.subFields = formState.subFields.map((sf) => ({
|
|
9751
|
+
slug: sf.slug,
|
|
9752
|
+
type: sf.type,
|
|
9753
|
+
label: sf.label,
|
|
9754
|
+
required: sf.required || void 0
|
|
9755
|
+
}));
|
|
9756
|
+
if (formState.minItems) validation.minItems = parseInt(formState.minItems, 10);
|
|
9757
|
+
if (formState.maxItems) validation.maxItems = parseInt(formState.maxItems, 10);
|
|
9758
|
+
}
|
|
9270
9759
|
onSave({
|
|
9271
9760
|
slug,
|
|
9272
9761
|
label,
|
|
@@ -9466,11 +9955,156 @@ function FieldEditor({ open, onOpenChange, field, onSave, isSaving }) {
|
|
|
9466
9955
|
onChange: (e) => setField("options", e.target.value),
|
|
9467
9956
|
placeholder: "Option 1\nOption 2\nOption 3",
|
|
9468
9957
|
rows: 5
|
|
9958
|
+
}),
|
|
9959
|
+
selectedType === "repeater" && /* @__PURE__ */ jsxs("div", {
|
|
9960
|
+
className: "space-y-4",
|
|
9961
|
+
children: [
|
|
9962
|
+
/* @__PURE__ */ jsxs("div", {
|
|
9963
|
+
className: "flex items-center justify-between",
|
|
9964
|
+
children: [/* @__PURE__ */ jsx("h4", {
|
|
9965
|
+
className: "font-medium text-sm",
|
|
9966
|
+
children: "Sub-Fields"
|
|
9967
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
9968
|
+
variant: "outline",
|
|
9969
|
+
size: "sm",
|
|
9970
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
9971
|
+
onClick: () => setFormState((prev) => ({
|
|
9972
|
+
...prev,
|
|
9973
|
+
subFields: [...prev.subFields, {
|
|
9974
|
+
slug: "",
|
|
9975
|
+
type: "string",
|
|
9976
|
+
label: "",
|
|
9977
|
+
required: false
|
|
9978
|
+
}]
|
|
9979
|
+
})),
|
|
9980
|
+
children: "Add Sub-Field"
|
|
9981
|
+
})]
|
|
9982
|
+
}),
|
|
9983
|
+
formState.subFields.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
9984
|
+
className: "text-sm text-kumo-subtle text-center py-4",
|
|
9985
|
+
children: "Add at least one sub-field to define the repeater structure."
|
|
9986
|
+
}),
|
|
9987
|
+
formState.subFields.map((sf, i) => /* @__PURE__ */ jsxs("div", {
|
|
9988
|
+
className: "flex gap-2 items-start border rounded-lg p-3",
|
|
9989
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
9990
|
+
className: "flex-1 space-y-2",
|
|
9991
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
9992
|
+
className: "grid grid-cols-2 gap-2",
|
|
9993
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
9994
|
+
label: "Label",
|
|
9995
|
+
value: sf.label,
|
|
9996
|
+
onChange: (e) => {
|
|
9997
|
+
const updated = [...formState.subFields];
|
|
9998
|
+
updated[i] = {
|
|
9999
|
+
...sf,
|
|
10000
|
+
label: e.target.value,
|
|
10001
|
+
slug: e.target.value.toLowerCase().replace(SLUG_INVALID_CHARS_REGEX, "_").replace(SLUG_LEADING_TRAILING_REGEX, "")
|
|
10002
|
+
};
|
|
10003
|
+
setFormState((prev) => ({
|
|
10004
|
+
...prev,
|
|
10005
|
+
subFields: updated
|
|
10006
|
+
}));
|
|
10007
|
+
},
|
|
10008
|
+
placeholder: "Field label"
|
|
10009
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
10010
|
+
className: "text-sm font-medium",
|
|
10011
|
+
children: "Type"
|
|
10012
|
+
}), /* @__PURE__ */ jsxs("select", {
|
|
10013
|
+
className: "w-full mt-1 rounded-md border px-3 py-2 text-sm",
|
|
10014
|
+
value: sf.type,
|
|
10015
|
+
onChange: (e) => {
|
|
10016
|
+
const updated = [...formState.subFields];
|
|
10017
|
+
updated[i] = {
|
|
10018
|
+
...sf,
|
|
10019
|
+
type: e.target.value
|
|
10020
|
+
};
|
|
10021
|
+
setFormState((prev) => ({
|
|
10022
|
+
...prev,
|
|
10023
|
+
subFields: updated
|
|
10024
|
+
}));
|
|
10025
|
+
},
|
|
10026
|
+
children: [
|
|
10027
|
+
/* @__PURE__ */ jsx("option", {
|
|
10028
|
+
value: "string",
|
|
10029
|
+
children: "Short Text"
|
|
10030
|
+
}),
|
|
10031
|
+
/* @__PURE__ */ jsx("option", {
|
|
10032
|
+
value: "text",
|
|
10033
|
+
children: "Long Text"
|
|
10034
|
+
}),
|
|
10035
|
+
/* @__PURE__ */ jsx("option", {
|
|
10036
|
+
value: "number",
|
|
10037
|
+
children: "Number"
|
|
10038
|
+
}),
|
|
10039
|
+
/* @__PURE__ */ jsx("option", {
|
|
10040
|
+
value: "integer",
|
|
10041
|
+
children: "Integer"
|
|
10042
|
+
}),
|
|
10043
|
+
/* @__PURE__ */ jsx("option", {
|
|
10044
|
+
value: "boolean",
|
|
10045
|
+
children: "Boolean"
|
|
10046
|
+
}),
|
|
10047
|
+
/* @__PURE__ */ jsx("option", {
|
|
10048
|
+
value: "datetime",
|
|
10049
|
+
children: "Date & Time"
|
|
10050
|
+
}),
|
|
10051
|
+
/* @__PURE__ */ jsx("option", {
|
|
10052
|
+
value: "select",
|
|
10053
|
+
children: "Select"
|
|
10054
|
+
})
|
|
10055
|
+
]
|
|
10056
|
+
})] })]
|
|
10057
|
+
}), /* @__PURE__ */ jsxs("label", {
|
|
10058
|
+
className: "flex items-center gap-2 text-sm",
|
|
10059
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
10060
|
+
type: "checkbox",
|
|
10061
|
+
checked: sf.required,
|
|
10062
|
+
onChange: (e) => {
|
|
10063
|
+
const updated = [...formState.subFields];
|
|
10064
|
+
updated[i] = {
|
|
10065
|
+
...sf,
|
|
10066
|
+
required: e.target.checked
|
|
10067
|
+
};
|
|
10068
|
+
setFormState((prev) => ({
|
|
10069
|
+
...prev,
|
|
10070
|
+
subFields: updated
|
|
10071
|
+
}));
|
|
10072
|
+
}
|
|
10073
|
+
}), "Required"]
|
|
10074
|
+
})]
|
|
10075
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
10076
|
+
variant: "ghost",
|
|
10077
|
+
shape: "square",
|
|
10078
|
+
onClick: () => setFormState((prev) => ({
|
|
10079
|
+
...prev,
|
|
10080
|
+
subFields: prev.subFields.filter((_, j) => j !== i)
|
|
10081
|
+
})),
|
|
10082
|
+
"aria-label": "Remove sub-field",
|
|
10083
|
+
children: /* @__PURE__ */ jsx(Trash, { className: "h-4 w-4 text-kumo-danger" })
|
|
10084
|
+
})]
|
|
10085
|
+
}, i)),
|
|
10086
|
+
/* @__PURE__ */ jsxs("div", {
|
|
10087
|
+
className: "grid grid-cols-2 gap-4",
|
|
10088
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
10089
|
+
label: "Min Items",
|
|
10090
|
+
type: "number",
|
|
10091
|
+
value: formState.minItems,
|
|
10092
|
+
onChange: (e) => setField("minItems", e.target.value),
|
|
10093
|
+
placeholder: "0"
|
|
10094
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
10095
|
+
label: "Max Items",
|
|
10096
|
+
type: "number",
|
|
10097
|
+
value: formState.maxItems,
|
|
10098
|
+
onChange: (e) => setField("maxItems", e.target.value),
|
|
10099
|
+
placeholder: "No limit"
|
|
10100
|
+
})]
|
|
10101
|
+
})
|
|
10102
|
+
]
|
|
9469
10103
|
})
|
|
9470
10104
|
]
|
|
9471
10105
|
}),
|
|
9472
10106
|
step === "config" && /* @__PURE__ */ jsxs("div", {
|
|
9473
|
-
className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
10107
|
+
className: "flex flex-col-reverse gap-2 py-2 sm:flex-row sm:justify-end sm:space-x-2",
|
|
9474
10108
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
9475
10109
|
variant: "outline",
|
|
9476
10110
|
onClick: () => onOpenChange(false),
|
|
@@ -9478,7 +10112,7 @@ function FieldEditor({ open, onOpenChange, field, onSave, isSaving }) {
|
|
|
9478
10112
|
children: "Cancel"
|
|
9479
10113
|
}), /* @__PURE__ */ jsx(Button, {
|
|
9480
10114
|
onClick: handleSave,
|
|
9481
|
-
disabled: !slug || !label || isSaving,
|
|
10115
|
+
disabled: !slug || !label || isSaving || selectedType === "repeater" && formState.subFields.length === 0,
|
|
9482
10116
|
children: isSaving ? "Saving..." : field ? "Update Field" : "Add Field"
|
|
9483
10117
|
})]
|
|
9484
10118
|
})
|
|
@@ -9558,7 +10192,7 @@ const SYSTEM_FIELDS = [
|
|
|
9558
10192
|
/**
|
|
9559
10193
|
* Content Type editor for creating/editing collections
|
|
9560
10194
|
*/
|
|
9561
|
-
function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, onUpdateField, onDeleteField, onReorderFields
|
|
10195
|
+
function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, onUpdateField, onDeleteField, onReorderFields }) {
|
|
9562
10196
|
useNavigate$1();
|
|
9563
10197
|
const [slug, setSlug] = React.useState(collection?.slug ?? "");
|
|
9564
10198
|
const [label, setLabel] = React.useState(collection?.label ?? "");
|
|
@@ -9651,6 +10285,16 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
9651
10285
|
};
|
|
9652
10286
|
const isFromCode = collection?.source === "code";
|
|
9653
10287
|
const fields = collection?.fields ?? [];
|
|
10288
|
+
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
10289
|
+
const handleDragEnd = (event) => {
|
|
10290
|
+
const { active, over } = event;
|
|
10291
|
+
if (!over || active.id === over.id) return;
|
|
10292
|
+
const oldIndex = fields.findIndex((f) => f.id === active.id);
|
|
10293
|
+
const newIndex = fields.findIndex((f) => f.id === over.id);
|
|
10294
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
10295
|
+
const reordered = arrayMove(fields, oldIndex, newIndex);
|
|
10296
|
+
onReorderFields?.(reordered.map((f) => f.slug));
|
|
10297
|
+
};
|
|
9654
10298
|
return /* @__PURE__ */ jsxs("div", {
|
|
9655
10299
|
className: "space-y-6",
|
|
9656
10300
|
children: [
|
|
@@ -9933,14 +10577,23 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
9933
10577
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
9934
10578
|
className: "px-4 py-2 text-xs font-medium text-kumo-subtle uppercase tracking-wider border-b",
|
|
9935
10579
|
children: "Custom Fields"
|
|
9936
|
-
}), /* @__PURE__ */ jsx(
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
10580
|
+
}), /* @__PURE__ */ jsx(DndContext, {
|
|
10581
|
+
sensors,
|
|
10582
|
+
collisionDetection: closestCenter,
|
|
10583
|
+
onDragEnd: handleDragEnd,
|
|
10584
|
+
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
10585
|
+
items: fields.map((f) => f.id),
|
|
10586
|
+
strategy: verticalListSortingStrategy,
|
|
10587
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10588
|
+
className: "divide-y",
|
|
10589
|
+
children: fields.map((field) => /* @__PURE__ */ jsx(FieldRow, {
|
|
10590
|
+
field,
|
|
10591
|
+
isFromCode,
|
|
10592
|
+
onEdit: () => handleEditField(field),
|
|
10593
|
+
onDelete: () => setDeleteFieldTarget(field)
|
|
10594
|
+
}, field.id))
|
|
10595
|
+
})
|
|
10596
|
+
})
|
|
9944
10597
|
})] })
|
|
9945
10598
|
]
|
|
9946
10599
|
})
|
|
@@ -9973,10 +10626,25 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
9973
10626
|
});
|
|
9974
10627
|
}
|
|
9975
10628
|
function FieldRow({ field, isFromCode, onEdit, onDelete }) {
|
|
10629
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
10630
|
+
id: field.id,
|
|
10631
|
+
disabled: isFromCode
|
|
10632
|
+
});
|
|
9976
10633
|
return /* @__PURE__ */ jsxs("div", {
|
|
9977
|
-
|
|
10634
|
+
ref: setNodeRef,
|
|
10635
|
+
style: {
|
|
10636
|
+
transform: CSS.Transform.toString(transform),
|
|
10637
|
+
transition
|
|
10638
|
+
},
|
|
10639
|
+
className: cn("flex items-center px-4 py-3 hover:bg-kumo-tint/25", isDragging && "opacity-50"),
|
|
9978
10640
|
children: [
|
|
9979
|
-
!isFromCode && /* @__PURE__ */ jsx(
|
|
10641
|
+
!isFromCode && /* @__PURE__ */ jsx("button", {
|
|
10642
|
+
...attributes,
|
|
10643
|
+
...listeners,
|
|
10644
|
+
className: "cursor-grab active:cursor-grabbing mr-3",
|
|
10645
|
+
"aria-label": `Drag to reorder ${field.label}`,
|
|
10646
|
+
children: /* @__PURE__ */ jsx(DotsSixVertical, { className: "h-5 w-5 text-kumo-subtle" })
|
|
10647
|
+
}),
|
|
9980
10648
|
/* @__PURE__ */ jsxs("div", {
|
|
9981
10649
|
className: "flex-1 min-w-0",
|
|
9982
10650
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -10875,43 +11543,24 @@ function CheckIcon({ className }) {
|
|
|
10875
11543
|
}
|
|
10876
11544
|
|
|
10877
11545
|
//#endregion
|
|
10878
|
-
//#region src/lib/
|
|
10879
|
-
/**
|
|
10880
|
-
* Shared URL validation and transformation utilities
|
|
10881
|
-
*/
|
|
10882
|
-
const DEFAULT_REDIRECT = "/_emdash/admin";
|
|
11546
|
+
//#region src/lib/webauthn-environment.ts
|
|
10883
11547
|
/**
|
|
10884
|
-
*
|
|
10885
|
-
*
|
|
10886
|
-
* Only allows relative paths starting with `/`. Rejects protocol-relative
|
|
10887
|
-
* URLs (`//evil.com`), backslash tricks (`/\evil.com`), and non-path schemes
|
|
10888
|
-
* like `javascript:`.
|
|
11548
|
+
* WebAuthn is only available in a browser "secure context": HTTPS, or special-cased
|
|
11549
|
+
* loopback hosts such as `http://localhost` / `http://127.0.0.1`.
|
|
10889
11550
|
*
|
|
10890
|
-
*
|
|
11551
|
+
* An origin like `http://emdash.local:8081` resolves to 127.0.0.1 but is still
|
|
11552
|
+
* **not** a secure context, so `PublicKeyCredential` is hidden — the same symptom
|
|
11553
|
+
* as an unsupported browser.
|
|
10891
11554
|
*/
|
|
10892
|
-
function
|
|
10893
|
-
|
|
10894
|
-
return DEFAULT_REDIRECT;
|
|
11555
|
+
function isWebAuthnSecureContext() {
|
|
11556
|
+
return typeof window !== "undefined" && window.isSecureContext;
|
|
10895
11557
|
}
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
/** Returns true if the URL uses a safe scheme (http/https) */
|
|
10899
|
-
function isSafeUrl$1(url) {
|
|
10900
|
-
return SAFE_URL_RE.test(url);
|
|
11558
|
+
function isPublicKeyCredentialConstructorAvailable() {
|
|
11559
|
+
return typeof window !== "undefined" && window.PublicKeyCredential !== void 0 && typeof window.PublicKeyCredential === "function";
|
|
10901
11560
|
}
|
|
10902
|
-
/**
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
*/
|
|
10906
|
-
function safeIconUrl(url, width) {
|
|
10907
|
-
if (!SAFE_URL_RE.test(url)) return null;
|
|
10908
|
-
try {
|
|
10909
|
-
const u = new URL(url);
|
|
10910
|
-
u.searchParams.set("w", String(width));
|
|
10911
|
-
return u.href;
|
|
10912
|
-
} catch {
|
|
10913
|
-
return null;
|
|
10914
|
-
}
|
|
11561
|
+
/** True when the page can use `navigator.credentials` for passkeys. */
|
|
11562
|
+
function isPasskeyEnvironmentUsable() {
|
|
11563
|
+
return isWebAuthnSecureContext() && isPublicKeyCredentialConstructorAvailable();
|
|
10915
11564
|
}
|
|
10916
11565
|
|
|
10917
11566
|
//#endregion
|
|
@@ -10933,16 +11582,10 @@ const BASE64URL_UNDERSCORE_REGEX$1 = /_/g;
|
|
|
10933
11582
|
const BASE64_PLUS_REGEX$1 = /\+/g;
|
|
10934
11583
|
const BASE64_SLASH_REGEX$1 = /\//g;
|
|
10935
11584
|
/**
|
|
10936
|
-
* Check if WebAuthn is supported in the current browser
|
|
10937
|
-
*/
|
|
10938
|
-
function isWebAuthnSupported$1() {
|
|
10939
|
-
return typeof window !== "undefined" && window.PublicKeyCredential !== void 0 && typeof window.PublicKeyCredential === "function";
|
|
10940
|
-
}
|
|
10941
|
-
/**
|
|
10942
11585
|
* Check if conditional mediation (autofill) is supported
|
|
10943
11586
|
*/
|
|
10944
11587
|
async function isConditionalMediationSupported() {
|
|
10945
|
-
if (!
|
|
11588
|
+
if (!isPasskeyEnvironmentUsable()) return false;
|
|
10946
11589
|
try {
|
|
10947
11590
|
return await PublicKeyCredential.isConditionalMediationAvailable?.() ?? false;
|
|
10948
11591
|
} catch {
|
|
@@ -10976,7 +11619,8 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
10976
11619
|
const [state, setState] = React.useState({ status: "idle" });
|
|
10977
11620
|
const [email, setEmail] = React.useState("");
|
|
10978
11621
|
const [supportsConditional, setSupportsConditional] = React.useState(false);
|
|
10979
|
-
const isSupported = React.useMemo(() =>
|
|
11622
|
+
const isSupported = React.useMemo(() => isPasskeyEnvironmentUsable(), []);
|
|
11623
|
+
const insecureContext = React.useMemo(() => typeof window !== "undefined" && !isWebAuthnSecureContext(), []);
|
|
10980
11624
|
React.useEffect(() => {
|
|
10981
11625
|
isConditionalMediationSupported().then(setSupportsConditional);
|
|
10982
11626
|
}, []);
|
|
@@ -10984,7 +11628,7 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
10984
11628
|
if (!isSupported) {
|
|
10985
11629
|
setState({
|
|
10986
11630
|
status: "error",
|
|
10987
|
-
message: "WebAuthn is not supported in this browser"
|
|
11631
|
+
message: insecureContext ? "Passkeys require HTTPS or http://localhost (with your port); this hostname is not a secure browser context." : "WebAuthn is not supported in this browser"
|
|
10988
11632
|
});
|
|
10989
11633
|
return;
|
|
10990
11634
|
}
|
|
@@ -11017,7 +11661,15 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
11017
11661
|
...useConditional && supportsConditional ? { mediation: "conditional" } : {}
|
|
11018
11662
|
};
|
|
11019
11663
|
const rawCredential = await navigator.credentials.get(credentialOptions);
|
|
11020
|
-
if (!rawCredential)
|
|
11664
|
+
if (!rawCredential) {
|
|
11665
|
+
const message = "No credential returned from authenticator";
|
|
11666
|
+
setState({
|
|
11667
|
+
status: "error",
|
|
11668
|
+
message
|
|
11669
|
+
});
|
|
11670
|
+
onError?.(new Error(message));
|
|
11671
|
+
return;
|
|
11672
|
+
}
|
|
11021
11673
|
setState({
|
|
11022
11674
|
status: "loading",
|
|
11023
11675
|
message: "Verifying..."
|
|
@@ -11073,6 +11725,7 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
11073
11725
|
}
|
|
11074
11726
|
}, [
|
|
11075
11727
|
isSupported,
|
|
11728
|
+
insecureContext,
|
|
11076
11729
|
optionsEndpoint,
|
|
11077
11730
|
verifyEndpoint,
|
|
11078
11731
|
email,
|
|
@@ -11084,10 +11737,34 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
11084
11737
|
className: "rounded-lg border border-kumo-danger/50 bg-kumo-danger/10 p-4",
|
|
11085
11738
|
children: [/* @__PURE__ */ jsx("h3", {
|
|
11086
11739
|
className: "font-medium text-kumo-danger",
|
|
11087
|
-
children: "Passkeys Not
|
|
11740
|
+
children: "Passkeys Not Available Here"
|
|
11088
11741
|
}), /* @__PURE__ */ jsx("p", {
|
|
11089
11742
|
className: "mt-1 text-sm text-kumo-subtle",
|
|
11090
|
-
children:
|
|
11743
|
+
children: insecureContext ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11744
|
+
"Passkeys require a ",
|
|
11745
|
+
/* @__PURE__ */ jsx("strong", {
|
|
11746
|
+
className: "text-kumo-default",
|
|
11747
|
+
children: "secure context"
|
|
11748
|
+
}),
|
|
11749
|
+
": use",
|
|
11750
|
+
" ",
|
|
11751
|
+
/* @__PURE__ */ jsx("strong", {
|
|
11752
|
+
className: "text-kumo-default",
|
|
11753
|
+
children: "HTTPS"
|
|
11754
|
+
}),
|
|
11755
|
+
", or open the admin at",
|
|
11756
|
+
" ",
|
|
11757
|
+
/* @__PURE__ */ jsx("strong", {
|
|
11758
|
+
className: "text-kumo-default",
|
|
11759
|
+
children: "http://localhost"
|
|
11760
|
+
}),
|
|
11761
|
+
" (with your dev port). Plain ",
|
|
11762
|
+
/* @__PURE__ */ jsx("code", {
|
|
11763
|
+
className: "text-xs",
|
|
11764
|
+
children: "http://"
|
|
11765
|
+
}),
|
|
11766
|
+
" on a custom hostname is not treated as secure, even on loopback."
|
|
11767
|
+
] }) : /* @__PURE__ */ jsx(Fragment, { children: "Your browser doesn't support passkeys. Please use a modern browser like Chrome, Safari, Firefox, or Edge." })
|
|
11091
11768
|
})]
|
|
11092
11769
|
});
|
|
11093
11770
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -11109,18 +11786,270 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
11109
11786
|
className: "rounded-lg bg-kumo-danger/10 p-4 text-sm text-kumo-danger",
|
|
11110
11787
|
children: state.message
|
|
11111
11788
|
}),
|
|
11112
|
-
/* @__PURE__ */ jsx(Button, {
|
|
11113
|
-
type: "button",
|
|
11114
|
-
onClick: () => handleLogin(false),
|
|
11115
|
-
loading: state.status === "loading",
|
|
11116
|
-
className: "w-full justify-center",
|
|
11117
|
-
variant: "primary",
|
|
11118
|
-
children: state.status === "loading" ? /* @__PURE__ */ jsx(Fragment, { children: state.message }) : buttonText
|
|
11789
|
+
/* @__PURE__ */ jsx(Button, {
|
|
11790
|
+
type: "button",
|
|
11791
|
+
onClick: () => handleLogin(false),
|
|
11792
|
+
loading: state.status === "loading",
|
|
11793
|
+
className: "w-full justify-center",
|
|
11794
|
+
variant: "primary",
|
|
11795
|
+
children: state.status === "loading" ? /* @__PURE__ */ jsx(Fragment, { children: state.message }) : buttonText
|
|
11796
|
+
}),
|
|
11797
|
+
/* @__PURE__ */ jsx("p", {
|
|
11798
|
+
className: "text-xs text-kumo-subtle text-center",
|
|
11799
|
+
children: "Use your device's biometric authentication, security key, or PIN to sign in."
|
|
11800
|
+
})
|
|
11801
|
+
]
|
|
11802
|
+
});
|
|
11803
|
+
}
|
|
11804
|
+
|
|
11805
|
+
//#endregion
|
|
11806
|
+
//#region src/components/Logo.tsx
|
|
11807
|
+
/**
|
|
11808
|
+
* EmDash icon mark — the rounded-rect em dash symbol.
|
|
11809
|
+
* Used in the sidebar brand and as favicon.
|
|
11810
|
+
*/
|
|
11811
|
+
function LogoIcon(props) {
|
|
11812
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
11813
|
+
viewBox: "0 0 75 75",
|
|
11814
|
+
fill: "none",
|
|
11815
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
11816
|
+
...props,
|
|
11817
|
+
children: [
|
|
11818
|
+
/* @__PURE__ */ jsx("rect", {
|
|
11819
|
+
x: "3",
|
|
11820
|
+
y: "3",
|
|
11821
|
+
width: "69",
|
|
11822
|
+
height: "69",
|
|
11823
|
+
rx: "10.518",
|
|
11824
|
+
stroke: "url(#emdash-icon-border)",
|
|
11825
|
+
strokeWidth: "6"
|
|
11826
|
+
}),
|
|
11827
|
+
/* @__PURE__ */ jsx("rect", {
|
|
11828
|
+
x: "18",
|
|
11829
|
+
y: "34",
|
|
11830
|
+
width: "39.3661",
|
|
11831
|
+
height: "6.56101",
|
|
11832
|
+
fill: "url(#emdash-icon-dash)"
|
|
11833
|
+
}),
|
|
11834
|
+
/* @__PURE__ */ jsxs("defs", { children: [/* @__PURE__ */ jsxs("linearGradient", {
|
|
11835
|
+
id: "emdash-icon-border",
|
|
11836
|
+
x1: "-42.9996",
|
|
11837
|
+
y1: "124",
|
|
11838
|
+
x2: "92.4233",
|
|
11839
|
+
y2: "-41.7456",
|
|
11840
|
+
gradientUnits: "userSpaceOnUse",
|
|
11841
|
+
children: [
|
|
11842
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "#0F006B" }),
|
|
11843
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11844
|
+
offset: "0.0833",
|
|
11845
|
+
stopColor: "#281A81"
|
|
11846
|
+
}),
|
|
11847
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11848
|
+
offset: "0.1667",
|
|
11849
|
+
stopColor: "#5D0C83"
|
|
11850
|
+
}),
|
|
11851
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11852
|
+
offset: "0.25",
|
|
11853
|
+
stopColor: "#911475"
|
|
11854
|
+
}),
|
|
11855
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11856
|
+
offset: "0.3333",
|
|
11857
|
+
stopColor: "#CE2F55"
|
|
11858
|
+
}),
|
|
11859
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11860
|
+
offset: "0.4167",
|
|
11861
|
+
stopColor: "#FF6633"
|
|
11862
|
+
}),
|
|
11863
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11864
|
+
offset: "0.5",
|
|
11865
|
+
stopColor: "#F6821F"
|
|
11866
|
+
}),
|
|
11867
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11868
|
+
offset: "0.5833",
|
|
11869
|
+
stopColor: "#FBAD41"
|
|
11870
|
+
}),
|
|
11871
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11872
|
+
offset: "0.6667",
|
|
11873
|
+
stopColor: "#FFCD89"
|
|
11874
|
+
}),
|
|
11875
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11876
|
+
offset: "0.75",
|
|
11877
|
+
stopColor: "#FFE9CB"
|
|
11878
|
+
}),
|
|
11879
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11880
|
+
offset: "0.8333",
|
|
11881
|
+
stopColor: "#FFF7EC"
|
|
11882
|
+
}),
|
|
11883
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11884
|
+
offset: "0.9167",
|
|
11885
|
+
stopColor: "#FFF8EE"
|
|
11886
|
+
}),
|
|
11887
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11888
|
+
offset: "1",
|
|
11889
|
+
stopColor: "white"
|
|
11890
|
+
})
|
|
11891
|
+
]
|
|
11892
|
+
}), /* @__PURE__ */ jsxs("linearGradient", {
|
|
11893
|
+
id: "emdash-icon-dash",
|
|
11894
|
+
x1: "91.4992",
|
|
11895
|
+
y1: "27.4982",
|
|
11896
|
+
x2: "28.1217",
|
|
11897
|
+
y2: "54.1775",
|
|
11898
|
+
gradientUnits: "userSpaceOnUse",
|
|
11899
|
+
children: [
|
|
11900
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "white" }),
|
|
11901
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11902
|
+
offset: "0.1293",
|
|
11903
|
+
stopColor: "#FFF8EE"
|
|
11904
|
+
}),
|
|
11905
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11906
|
+
offset: "0.6171",
|
|
11907
|
+
stopColor: "#FBAD41"
|
|
11908
|
+
}),
|
|
11909
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11910
|
+
offset: "0.848",
|
|
11911
|
+
stopColor: "#F6821F"
|
|
11912
|
+
}),
|
|
11913
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11914
|
+
offset: "1",
|
|
11915
|
+
stopColor: "#FF6633"
|
|
11916
|
+
})
|
|
11917
|
+
]
|
|
11918
|
+
})] })
|
|
11919
|
+
]
|
|
11920
|
+
});
|
|
11921
|
+
}
|
|
11922
|
+
/**
|
|
11923
|
+
* Full logo lockup — icon + "EmDash" wordmark.
|
|
11924
|
+
* Renders both dark-text and light-text variants, switching via CSS `light-dark()`.
|
|
11925
|
+
*/
|
|
11926
|
+
function LogoLockup({ className, ...props }) {
|
|
11927
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
11928
|
+
viewBox: "0 0 471 118",
|
|
11929
|
+
fill: "none",
|
|
11930
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
11931
|
+
className,
|
|
11932
|
+
role: "img",
|
|
11933
|
+
"aria-label": "EmDash",
|
|
11934
|
+
...props,
|
|
11935
|
+
children: [
|
|
11936
|
+
/* @__PURE__ */ jsx("path", {
|
|
11937
|
+
d: "M0.410156 96.5125V21.2097C0.410156 9.48841 9.91245 -0.013916 21.6338 -0.013916V9.40601L21.3291 9.40991C14.9509 9.57133 9.83008 14.7927 9.83008 21.2097V96.5125C9.83008 102.93 14.9509 108.151 21.3291 108.312L21.6338 108.316H96.9365L97.2412 108.312C103.518 108.153 108.577 103.094 108.736 96.8171L108.74 96.5125V21.2097C108.74 14.6909 103.455 9.40601 96.9365 9.40601V-0.013916C108.658 -0.013916 118.16 9.48838 118.16 21.2097V96.5125C118.16 108.234 108.658 117.736 96.9365 117.736H21.6338C9.91248 117.736 0.410156 108.234 0.410156 96.5125ZM96.9365 -0.013916V9.40601H21.6338V-0.013916H96.9365Z",
|
|
11938
|
+
fill: "url(#emdash-lockup-icon)"
|
|
11939
|
+
}),
|
|
11940
|
+
/* @__PURE__ */ jsx("path", {
|
|
11941
|
+
d: "M28.6699 53.366H90.4746V63.6668H28.6699V53.366Z",
|
|
11942
|
+
fill: "url(#emdash-lockup-dash)"
|
|
11943
|
+
}),
|
|
11944
|
+
/* @__PURE__ */ jsx("path", {
|
|
11945
|
+
d: "M154.762 90V27.4834H194.447V35.8449H164.467V54.0844H192.844V62.2293H164.467V81.6385H194.447V90H154.762Z",
|
|
11946
|
+
fill: "currentColor"
|
|
11947
|
+
}),
|
|
11948
|
+
/* @__PURE__ */ jsx("path", {
|
|
11949
|
+
d: "M204.172 90V44.4231H213.53V51.4849H213.747C215.697 46.7193 220.332 43.5566 226.311 43.5566C232.593 43.5566 237.185 46.8059 239.005 52.5247H239.222C241.561 46.9792 246.933 43.5566 253.432 43.5566C262.443 43.5566 268.335 49.5353 268.335 58.6767V90H258.934V60.9296C258.934 54.9942 255.771 51.5716 250.226 51.5716C244.68 51.5716 240.825 55.7307 240.825 61.4928V90H231.64V60.2364C231.64 54.9508 228.304 51.5716 223.018 51.5716C217.473 51.5716 213.53 55.9473 213.53 61.8394V90H204.172Z",
|
|
11950
|
+
fill: "currentColor"
|
|
11951
|
+
}),
|
|
11952
|
+
/* @__PURE__ */ jsx("path", {
|
|
11953
|
+
d: "M279.404 90V27.4834H301.456C319.998 27.4834 331.046 38.8776 331.046 58.5467V58.6334C331.046 78.3892 320.085 90 301.456 90H279.404ZM289.108 81.5951H300.546C313.803 81.5951 321.125 73.4935 321.125 58.72V58.6334C321.125 43.9465 313.716 35.8449 300.546 35.8449H289.108V81.5951Z",
|
|
11954
|
+
fill: "currentColor"
|
|
11955
|
+
}),
|
|
11956
|
+
/* @__PURE__ */ jsx("path", {
|
|
11957
|
+
d: "M353.379 90.8232C344.281 90.8232 338.172 85.2344 338.172 77.0461V76.9595C338.172 69.0312 344.324 64.1789 355.112 63.529L367.502 62.7925V59.3699C367.502 54.3443 364.253 51.3116 358.448 51.3116C353.032 51.3116 349.696 53.8677 348.916 57.507L348.83 57.8969H339.992L340.035 57.4203C340.685 49.5787 347.487 43.5566 358.708 43.5566C369.842 43.5566 376.904 49.4487 376.904 58.5901V90H367.502V82.8082H367.329C364.686 87.7038 359.401 90.8232 353.379 90.8232ZM347.617 76.8295C347.617 80.8153 350.909 83.3281 355.935 83.3281C362.52 83.3281 367.502 78.8657 367.502 72.9303V69.3778L356.368 70.0709C350.736 70.4175 347.617 72.887 347.617 76.7428V76.8295Z",
|
|
11958
|
+
fill: "currentColor"
|
|
11959
|
+
}),
|
|
11960
|
+
/* @__PURE__ */ jsx("path", {
|
|
11961
|
+
d: "M403.959 90.9098C392.564 90.9098 385.893 85.2777 384.939 76.9595L384.896 76.5695H394.167L394.254 77.0028C395.121 81.2052 398.24 83.6747 404.002 83.6747C409.634 83.6747 413.013 81.3352 413.013 77.6527V77.6093C413.013 74.6633 411.367 72.9737 406.471 71.8039L399.02 70.1143C390.355 68.1214 386.066 63.9623 386.066 57.3337V57.2903C386.066 49.1454 393.171 43.5566 403.655 43.5566C414.443 43.5566 420.942 49.5787 421.418 57.3337L421.462 57.8536H412.667L412.624 57.5503C412.06 53.5645 408.941 50.7917 403.655 50.7917C398.63 50.7917 395.467 53.1746 395.467 56.8138V56.8571C395.467 59.6732 397.33 61.5794 402.226 62.7492L409.634 64.4388C418.949 66.605 422.501 70.2876 422.501 76.8295V76.8728C422.501 85.191 414.703 90.9098 403.959 90.9098Z",
|
|
11962
|
+
fill: "currentColor"
|
|
11119
11963
|
}),
|
|
11120
|
-
/* @__PURE__ */ jsx("
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
})
|
|
11964
|
+
/* @__PURE__ */ jsx("path", {
|
|
11965
|
+
d: "M431.014 90V27.4834H440.372V51.9182H440.588C443.014 46.6326 447.91 43.5566 454.712 43.5566C464.46 43.5566 470.872 50.8351 470.872 61.8394V90H461.514V63.6157C461.514 56.0773 457.701 51.5716 451.116 51.5716C444.661 51.5716 440.372 56.5105 440.372 63.6157V90H431.014Z",
|
|
11966
|
+
fill: "currentColor"
|
|
11967
|
+
}),
|
|
11968
|
+
/* @__PURE__ */ jsxs("defs", { children: [/* @__PURE__ */ jsxs("linearGradient", {
|
|
11969
|
+
id: "emdash-lockup-icon",
|
|
11970
|
+
x1: "-67.1002",
|
|
11971
|
+
y1: "194.666",
|
|
11972
|
+
x2: "145.514",
|
|
11973
|
+
y2: "-65.5554",
|
|
11974
|
+
gradientUnits: "userSpaceOnUse",
|
|
11975
|
+
children: [
|
|
11976
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "#0F006B" }),
|
|
11977
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11978
|
+
offset: "0.0833",
|
|
11979
|
+
stopColor: "#281A81"
|
|
11980
|
+
}),
|
|
11981
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11982
|
+
offset: "0.1667",
|
|
11983
|
+
stopColor: "#5D0C83"
|
|
11984
|
+
}),
|
|
11985
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11986
|
+
offset: "0.25",
|
|
11987
|
+
stopColor: "#911475"
|
|
11988
|
+
}),
|
|
11989
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11990
|
+
offset: "0.3333",
|
|
11991
|
+
stopColor: "#CE2F55"
|
|
11992
|
+
}),
|
|
11993
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11994
|
+
offset: "0.4167",
|
|
11995
|
+
stopColor: "#FF6633"
|
|
11996
|
+
}),
|
|
11997
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11998
|
+
offset: "0.5",
|
|
11999
|
+
stopColor: "#F6821F"
|
|
12000
|
+
}),
|
|
12001
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12002
|
+
offset: "0.5833",
|
|
12003
|
+
stopColor: "#FBAD41"
|
|
12004
|
+
}),
|
|
12005
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12006
|
+
offset: "0.6667",
|
|
12007
|
+
stopColor: "#FFCD89"
|
|
12008
|
+
}),
|
|
12009
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12010
|
+
offset: "0.75",
|
|
12011
|
+
stopColor: "#FFE9CB"
|
|
12012
|
+
}),
|
|
12013
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12014
|
+
offset: "0.8333",
|
|
12015
|
+
stopColor: "#FFF7EC"
|
|
12016
|
+
}),
|
|
12017
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12018
|
+
offset: "0.9167",
|
|
12019
|
+
stopColor: "#FFF8EE"
|
|
12020
|
+
}),
|
|
12021
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12022
|
+
offset: "1",
|
|
12023
|
+
stopColor: "white"
|
|
12024
|
+
})
|
|
12025
|
+
]
|
|
12026
|
+
}), /* @__PURE__ */ jsxs("linearGradient", {
|
|
12027
|
+
id: "emdash-lockup-dash",
|
|
12028
|
+
x1: "144.064",
|
|
12029
|
+
y1: "43.1581",
|
|
12030
|
+
x2: "44.5609",
|
|
12031
|
+
y2: "85.0447",
|
|
12032
|
+
gradientUnits: "userSpaceOnUse",
|
|
12033
|
+
children: [
|
|
12034
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "white" }),
|
|
12035
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12036
|
+
offset: "0.1293",
|
|
12037
|
+
stopColor: "#FFF8EE"
|
|
12038
|
+
}),
|
|
12039
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12040
|
+
offset: "0.6171",
|
|
12041
|
+
stopColor: "#FBAD41"
|
|
12042
|
+
}),
|
|
12043
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12044
|
+
offset: "0.848",
|
|
12045
|
+
stopColor: "#F6821F"
|
|
12046
|
+
}),
|
|
12047
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12048
|
+
offset: "1",
|
|
12049
|
+
stopColor: "#FF6633"
|
|
12050
|
+
})
|
|
12051
|
+
]
|
|
12052
|
+
})] })
|
|
11124
12053
|
]
|
|
11125
12054
|
});
|
|
11126
12055
|
}
|
|
@@ -11183,6 +12112,7 @@ const OAUTH_PROVIDERS = [{
|
|
|
11183
12112
|
icon: /* @__PURE__ */ jsx(GoogleIcon, { className: "h-5 w-5" })
|
|
11184
12113
|
}];
|
|
11185
12114
|
function MagicLinkForm({ onBack }) {
|
|
12115
|
+
const { _: _t } = useLingui();
|
|
11186
12116
|
const [email, setEmail] = React.useState("");
|
|
11187
12117
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
11188
12118
|
const [error, setError] = React.useState(null);
|
|
@@ -11199,11 +12129,17 @@ function MagicLinkForm({ onBack }) {
|
|
|
11199
12129
|
});
|
|
11200
12130
|
if (!response.ok) {
|
|
11201
12131
|
const body = await response.json().catch(() => ({}));
|
|
11202
|
-
throw new Error(body?.error?.message ||
|
|
12132
|
+
throw new Error(body?.error?.message || _t({
|
|
12133
|
+
id: "dsPiA2",
|
|
12134
|
+
message: "Failed to send magic link"
|
|
12135
|
+
}));
|
|
11203
12136
|
}
|
|
11204
12137
|
setSent(true);
|
|
11205
12138
|
} catch (err) {
|
|
11206
|
-
setError(err instanceof Error ? err.message :
|
|
12139
|
+
setError(err instanceof Error ? err.message : _t({
|
|
12140
|
+
id: "dsPiA2",
|
|
12141
|
+
message: "Failed to send magic link"
|
|
12142
|
+
}));
|
|
11207
12143
|
} finally {
|
|
11208
12144
|
setIsLoading(false);
|
|
11209
12145
|
}
|
|
@@ -11228,30 +12164,40 @@ function MagicLinkForm({ onBack }) {
|
|
|
11228
12164
|
}),
|
|
11229
12165
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
|
|
11230
12166
|
className: "text-xl font-semibold",
|
|
11231
|
-
children:
|
|
11232
|
-
|
|
12167
|
+
children: _t({
|
|
12168
|
+
id: "v4fiSg",
|
|
12169
|
+
message: "Check your email"
|
|
12170
|
+
})
|
|
12171
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
11233
12172
|
className: "text-kumo-subtle mt-2",
|
|
11234
|
-
children:
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
11240
|
-
", we've sent a sign-in link."
|
|
11241
|
-
]
|
|
12173
|
+
children: /* @__PURE__ */ jsx(Trans, {
|
|
12174
|
+
id: "HvU4EW",
|
|
12175
|
+
message: "If an account exists for <0>{email}</0>, we've sent a sign-in link.",
|
|
12176
|
+
values: { email },
|
|
12177
|
+
components: { 0: /* @__PURE__ */ jsx("span", { className: "font-medium text-kumo-default" }) }
|
|
12178
|
+
})
|
|
11242
12179
|
})] }),
|
|
11243
12180
|
/* @__PURE__ */ jsxs("div", {
|
|
11244
12181
|
className: "text-sm text-kumo-subtle",
|
|
11245
|
-
children: [/* @__PURE__ */ jsx("p", { children:
|
|
12182
|
+
children: [/* @__PURE__ */ jsx("p", { children: _t({
|
|
12183
|
+
id: "tfQcxX",
|
|
12184
|
+
message: "Click the link in the email to sign in."
|
|
12185
|
+
}) }), /* @__PURE__ */ jsx("p", {
|
|
11246
12186
|
className: "mt-2",
|
|
11247
|
-
children:
|
|
12187
|
+
children: _t({
|
|
12188
|
+
id: "Cw9Xfg",
|
|
12189
|
+
message: "The link will expire in 15 minutes."
|
|
12190
|
+
})
|
|
11248
12191
|
})]
|
|
11249
12192
|
}),
|
|
11250
12193
|
/* @__PURE__ */ jsx(Button, {
|
|
11251
12194
|
variant: "outline",
|
|
11252
12195
|
onClick: onBack,
|
|
11253
12196
|
className: "mt-4 w-full justify-center",
|
|
11254
|
-
children:
|
|
12197
|
+
children: _t({
|
|
12198
|
+
id: "VCoEm+",
|
|
12199
|
+
message: "Back to login"
|
|
12200
|
+
})
|
|
11255
12201
|
})
|
|
11256
12202
|
]
|
|
11257
12203
|
});
|
|
@@ -11260,7 +12206,10 @@ function MagicLinkForm({ onBack }) {
|
|
|
11260
12206
|
className: "space-y-4",
|
|
11261
12207
|
children: [
|
|
11262
12208
|
/* @__PURE__ */ jsx(Input, {
|
|
11263
|
-
label:
|
|
12209
|
+
label: _t({
|
|
12210
|
+
id: "ATGYL1",
|
|
12211
|
+
message: "Email address"
|
|
12212
|
+
}),
|
|
11264
12213
|
type: "email",
|
|
11265
12214
|
value: email,
|
|
11266
12215
|
onChange: (e) => setEmail(e.target.value),
|
|
@@ -11281,14 +12230,23 @@ function MagicLinkForm({ onBack }) {
|
|
|
11281
12230
|
variant: "primary",
|
|
11282
12231
|
loading: isLoading,
|
|
11283
12232
|
disabled: !email,
|
|
11284
|
-
children: isLoading ?
|
|
12233
|
+
children: isLoading ? _t({
|
|
12234
|
+
id: "IoAuJG",
|
|
12235
|
+
message: "Sending..."
|
|
12236
|
+
}) : _t({
|
|
12237
|
+
id: "+Ni3Gv",
|
|
12238
|
+
message: "Send magic link"
|
|
12239
|
+
})
|
|
11285
12240
|
}),
|
|
11286
12241
|
/* @__PURE__ */ jsx(Button, {
|
|
11287
12242
|
type: "button",
|
|
11288
12243
|
variant: "ghost",
|
|
11289
12244
|
className: "w-full justify-center",
|
|
11290
12245
|
onClick: onBack,
|
|
11291
|
-
children:
|
|
12246
|
+
children: _t({
|
|
12247
|
+
id: "VCoEm+",
|
|
12248
|
+
message: "Back to login"
|
|
12249
|
+
})
|
|
11292
12250
|
})
|
|
11293
12251
|
]
|
|
11294
12252
|
});
|
|
@@ -11298,6 +12256,8 @@ function handleOAuthClick(providerId) {
|
|
|
11298
12256
|
}
|
|
11299
12257
|
function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
11300
12258
|
const safeRedirectUrl = sanitizeRedirectUrl(redirectUrl);
|
|
12259
|
+
const { _: _t2 } = useLingui();
|
|
12260
|
+
const { locale, setLocale } = useLocale();
|
|
11301
12261
|
const [method, setMethod] = React.useState("passkey");
|
|
11302
12262
|
const [urlError, setUrlError] = React.useState(null);
|
|
11303
12263
|
const { data: manifest, isLoading: manifestLoading } = useQuery({
|
|
@@ -11312,7 +12272,11 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
11312
12272
|
const error = params.get("error");
|
|
11313
12273
|
const message = params.get("message");
|
|
11314
12274
|
if (error) {
|
|
11315
|
-
setUrlError(message ||
|
|
12275
|
+
setUrlError(message || _t2({
|
|
12276
|
+
id: "Xeb2Gt",
|
|
12277
|
+
message: "Authentication error: {error}",
|
|
12278
|
+
values: { error }
|
|
12279
|
+
}));
|
|
11316
12280
|
window.history.replaceState({}, "", window.location.pathname);
|
|
11317
12281
|
}
|
|
11318
12282
|
}, []);
|
|
@@ -11323,10 +12287,7 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
11323
12287
|
className: "min-h-screen flex items-center justify-center bg-kumo-base p-4",
|
|
11324
12288
|
children: /* @__PURE__ */ jsxs("div", {
|
|
11325
12289
|
className: "text-center",
|
|
11326
|
-
children: [/* @__PURE__ */ jsx("
|
|
11327
|
-
className: "text-4xl font-bold mb-4",
|
|
11328
|
-
children: "— EmDash"
|
|
11329
|
-
}), /* @__PURE__ */ jsx(Loader, {})]
|
|
12290
|
+
children: [/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-4" }), /* @__PURE__ */ jsx(Loader, {})]
|
|
11330
12291
|
})
|
|
11331
12292
|
});
|
|
11332
12293
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -11336,12 +12297,15 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
11336
12297
|
children: [
|
|
11337
12298
|
/* @__PURE__ */ jsxs("div", {
|
|
11338
12299
|
className: "text-center mb-8",
|
|
11339
|
-
children: [/* @__PURE__ */ jsx("
|
|
11340
|
-
className: "text-4xl font-bold mb-2",
|
|
11341
|
-
children: "— EmDash"
|
|
11342
|
-
}), /* @__PURE__ */ jsxs("h1", {
|
|
12300
|
+
children: [/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-2" }), /* @__PURE__ */ jsxs("h1", {
|
|
11343
12301
|
className: "text-2xl font-semibold text-kumo-default",
|
|
11344
|
-
children: [method === "passkey" &&
|
|
12302
|
+
children: [method === "passkey" && _t2({
|
|
12303
|
+
id: "lE0wHD",
|
|
12304
|
+
message: "Sign in to your site"
|
|
12305
|
+
}), method === "magic-link" && _t2({
|
|
12306
|
+
id: "me9L29",
|
|
12307
|
+
message: "Sign in with email"
|
|
12308
|
+
})]
|
|
11345
12309
|
})]
|
|
11346
12310
|
}),
|
|
11347
12311
|
urlError && /* @__PURE__ */ jsx("div", {
|
|
@@ -11357,7 +12321,10 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
11357
12321
|
optionsEndpoint: "/_emdash/api/auth/passkey/options",
|
|
11358
12322
|
verifyEndpoint: "/_emdash/api/auth/passkey/verify",
|
|
11359
12323
|
onSuccess: handleSuccess,
|
|
11360
|
-
buttonText:
|
|
12324
|
+
buttonText: _t2({
|
|
12325
|
+
id: "QjsOMP",
|
|
12326
|
+
message: "Sign in with Passkey"
|
|
12327
|
+
})
|
|
11361
12328
|
}),
|
|
11362
12329
|
/* @__PURE__ */ jsxs("div", {
|
|
11363
12330
|
className: "relative",
|
|
@@ -11368,7 +12335,10 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
11368
12335
|
className: "relative flex justify-center text-xs uppercase",
|
|
11369
12336
|
children: /* @__PURE__ */ jsx("span", {
|
|
11370
12337
|
className: "bg-kumo-base px-2 text-kumo-subtle",
|
|
11371
|
-
children:
|
|
12338
|
+
children: _t2({
|
|
12339
|
+
id: "zW+FpA",
|
|
12340
|
+
message: "Or continue with"
|
|
12341
|
+
})
|
|
11372
12342
|
})
|
|
11373
12343
|
})]
|
|
11374
12344
|
}),
|
|
@@ -11390,26 +12360,42 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
11390
12360
|
className: "w-full justify-center",
|
|
11391
12361
|
type: "button",
|
|
11392
12362
|
onClick: () => setMethod("magic-link"),
|
|
11393
|
-
children:
|
|
12363
|
+
children: _t2({
|
|
12364
|
+
id: "RxerEl",
|
|
12365
|
+
message: "Sign in with email link"
|
|
12366
|
+
})
|
|
11394
12367
|
})
|
|
11395
12368
|
]
|
|
11396
12369
|
}), method === "magic-link" && /* @__PURE__ */ jsx(MagicLinkForm, { onBack: () => setMethod("passkey") })]
|
|
11397
12370
|
}),
|
|
11398
12371
|
/* @__PURE__ */ jsx("p", {
|
|
11399
12372
|
className: "text-center mt-6 text-sm text-kumo-subtle",
|
|
11400
|
-
children: method === "passkey" ?
|
|
12373
|
+
children: method === "passkey" ? _t2({
|
|
12374
|
+
id: "+ET/av",
|
|
12375
|
+
message: "Use your registered passkey to sign in securely."
|
|
12376
|
+
}) : _t2({
|
|
12377
|
+
id: "pv+wH3",
|
|
12378
|
+
message: "We'll send you a link to sign in without a password."
|
|
12379
|
+
})
|
|
11401
12380
|
}),
|
|
11402
|
-
manifest?.signupEnabled && /* @__PURE__ */
|
|
12381
|
+
manifest?.signupEnabled && /* @__PURE__ */ jsx("p", {
|
|
11403
12382
|
className: "text-center mt-4 text-sm text-kumo-subtle",
|
|
11404
|
-
children:
|
|
11405
|
-
|
|
11406
|
-
" ",
|
|
11407
|
-
/* @__PURE__ */ jsx(Link$1, {
|
|
12383
|
+
children: /* @__PURE__ */ jsx(Trans, {
|
|
12384
|
+
id: "352VU2",
|
|
12385
|
+
message: "Don't have an account? <0>Sign up</0>",
|
|
12386
|
+
components: { 0: /* @__PURE__ */ jsx(Link$1, {
|
|
11408
12387
|
to: "/signup",
|
|
11409
|
-
className: "text-kumo-brand hover:underline font-medium"
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
12388
|
+
className: "text-kumo-brand hover:underline font-medium"
|
|
12389
|
+
}) }
|
|
12390
|
+
})
|
|
12391
|
+
}),
|
|
12392
|
+
SUPPORTED_LOCALES.length > 1 && /* @__PURE__ */ jsx("div", {
|
|
12393
|
+
className: "mt-6 flex justify-center gap-2 text-xs text-kumo-subtle",
|
|
12394
|
+
children: SUPPORTED_LOCALES.map((l, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [i > 0 && /* @__PURE__ */ jsx("span", { children: "·" }), /* @__PURE__ */ jsx("button", {
|
|
12395
|
+
onClick: () => setLocale(l.code),
|
|
12396
|
+
className: l.code === locale ? "font-medium text-kumo-default" : "hover:text-kumo-default transition-colors",
|
|
12397
|
+
children: l.label
|
|
12398
|
+
})] }, l.code))
|
|
11413
12399
|
})
|
|
11414
12400
|
]
|
|
11415
12401
|
})
|
|
@@ -13584,7 +14570,7 @@ function ContentPickerModal({ open, onOpenChange, onSelect }) {
|
|
|
13584
14570
|
open,
|
|
13585
14571
|
onOpenChange,
|
|
13586
14572
|
children: /* @__PURE__ */ jsxs(Dialog, {
|
|
13587
|
-
className: "p-6 w-2xl h-[80vh] flex flex-col",
|
|
14573
|
+
className: "p-6 max-w-2xl h-[80vh] flex flex-col",
|
|
13588
14574
|
size: "lg",
|
|
13589
14575
|
children: [
|
|
13590
14576
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -13934,9 +14920,11 @@ function MenuEditor() {
|
|
|
13934
14920
|
/* @__PURE__ */ jsx(Input, {
|
|
13935
14921
|
label: "URL",
|
|
13936
14922
|
name: "url",
|
|
13937
|
-
type: "
|
|
14923
|
+
type: "text",
|
|
13938
14924
|
required: true,
|
|
13939
|
-
|
|
14925
|
+
pattern: "(https?://.+|/.*)",
|
|
14926
|
+
title: "Enter a URL (https://…) or a relative path (/…)",
|
|
14927
|
+
placeholder: "https://example.com or /about"
|
|
13940
14928
|
}),
|
|
13941
14929
|
/* @__PURE__ */ jsxs(Select, {
|
|
13942
14930
|
label: "Target",
|
|
@@ -14100,8 +15088,10 @@ function MenuEditor() {
|
|
|
14100
15088
|
editingItem.type === "custom" && /* @__PURE__ */ jsx(Input, {
|
|
14101
15089
|
label: "URL",
|
|
14102
15090
|
name: "url",
|
|
14103
|
-
type: "
|
|
15091
|
+
type: "text",
|
|
14104
15092
|
required: true,
|
|
15093
|
+
pattern: "(https?://.+|/.*)",
|
|
15094
|
+
title: "Enter a URL (https://…) or a relative path (/…)",
|
|
14105
15095
|
defaultValue: editingItem.custom_url || ""
|
|
14106
15096
|
}),
|
|
14107
15097
|
/* @__PURE__ */ jsxs(Select, {
|
|
@@ -15576,13 +16566,18 @@ function Settings() {
|
|
|
15576
16566
|
queryKey: ["manifest"],
|
|
15577
16567
|
queryFn: fetchManifest
|
|
15578
16568
|
});
|
|
16569
|
+
const { _: _t } = useLingui();
|
|
16570
|
+
const { locale, setLocale } = useLocale();
|
|
15579
16571
|
const showSecuritySettings = manifest?.authMode === "passkey";
|
|
15580
16572
|
return /* @__PURE__ */ jsxs("div", {
|
|
15581
16573
|
className: "space-y-6",
|
|
15582
16574
|
children: [
|
|
15583
16575
|
/* @__PURE__ */ jsx("h1", {
|
|
15584
16576
|
className: "text-2xl font-bold",
|
|
15585
|
-
children:
|
|
16577
|
+
children: _t({
|
|
16578
|
+
id: "Tz0i8g",
|
|
16579
|
+
message: "Settings"
|
|
16580
|
+
})
|
|
15586
16581
|
}),
|
|
15587
16582
|
/* @__PURE__ */ jsxs("div", {
|
|
15588
16583
|
className: "space-y-2",
|
|
@@ -15590,20 +16585,38 @@ function Settings() {
|
|
|
15590
16585
|
/* @__PURE__ */ jsx(SettingsLink, {
|
|
15591
16586
|
to: "/settings/general",
|
|
15592
16587
|
icon: /* @__PURE__ */ jsx(Gear, { className: "h-5 w-5" }),
|
|
15593
|
-
title:
|
|
15594
|
-
|
|
16588
|
+
title: _t({
|
|
16589
|
+
id: "Weq9zb",
|
|
16590
|
+
message: "General"
|
|
16591
|
+
}),
|
|
16592
|
+
description: _t({
|
|
16593
|
+
id: "RR0ADZ",
|
|
16594
|
+
message: "Site identity, logo, favicon, and reading preferences"
|
|
16595
|
+
})
|
|
15595
16596
|
}),
|
|
15596
16597
|
/* @__PURE__ */ jsx(SettingsLink, {
|
|
15597
16598
|
to: "/settings/social",
|
|
15598
16599
|
icon: /* @__PURE__ */ jsx(ShareNetwork, { className: "h-5 w-5" }),
|
|
15599
|
-
title:
|
|
15600
|
-
|
|
16600
|
+
title: _t({
|
|
16601
|
+
id: "d0rUsW",
|
|
16602
|
+
message: "Social Links"
|
|
16603
|
+
}),
|
|
16604
|
+
description: _t({
|
|
16605
|
+
id: "qS3mgX",
|
|
16606
|
+
message: "Social media profile links"
|
|
16607
|
+
})
|
|
15601
16608
|
}),
|
|
15602
16609
|
/* @__PURE__ */ jsx(SettingsLink, {
|
|
15603
16610
|
to: "/settings/seo",
|
|
15604
16611
|
icon: /* @__PURE__ */ jsx(MagnifyingGlass, { className: "h-5 w-5" }),
|
|
15605
|
-
title:
|
|
15606
|
-
|
|
16612
|
+
title: _t({
|
|
16613
|
+
id: "4Ml90q",
|
|
16614
|
+
message: "SEO"
|
|
16615
|
+
}),
|
|
16616
|
+
description: _t({
|
|
16617
|
+
id: "zY0S+v",
|
|
16618
|
+
message: "Search engine optimization and verification"
|
|
16619
|
+
})
|
|
15607
16620
|
})
|
|
15608
16621
|
]
|
|
15609
16622
|
}),
|
|
@@ -15612,13 +16625,25 @@ function Settings() {
|
|
|
15612
16625
|
children: [/* @__PURE__ */ jsx(SettingsLink, {
|
|
15613
16626
|
to: "/settings/security",
|
|
15614
16627
|
icon: /* @__PURE__ */ jsx(Shield, { className: "h-5 w-5" }),
|
|
15615
|
-
title:
|
|
15616
|
-
|
|
16628
|
+
title: _t({
|
|
16629
|
+
id: "a3LDKx",
|
|
16630
|
+
message: "Security"
|
|
16631
|
+
}),
|
|
16632
|
+
description: _t({
|
|
16633
|
+
id: "OfnTKV",
|
|
16634
|
+
message: "Manage your passkeys and authentication"
|
|
16635
|
+
})
|
|
15617
16636
|
}), /* @__PURE__ */ jsx(SettingsLink, {
|
|
15618
16637
|
to: "/settings/allowed-domains",
|
|
15619
16638
|
icon: /* @__PURE__ */ jsx(Globe, { className: "h-5 w-5" }),
|
|
15620
|
-
title:
|
|
15621
|
-
|
|
16639
|
+
title: _t({
|
|
16640
|
+
id: "Tllxyd",
|
|
16641
|
+
message: "Self-Signup Domains"
|
|
16642
|
+
}),
|
|
16643
|
+
description: _t({
|
|
16644
|
+
id: "DsZc8w",
|
|
16645
|
+
message: "Allow users from specific domains to sign up"
|
|
16646
|
+
})
|
|
15622
16647
|
})]
|
|
15623
16648
|
}),
|
|
15624
16649
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -15626,14 +16651,58 @@ function Settings() {
|
|
|
15626
16651
|
children: [/* @__PURE__ */ jsx(SettingsLink, {
|
|
15627
16652
|
to: "/settings/api-tokens",
|
|
15628
16653
|
icon: /* @__PURE__ */ jsx(Key, { className: "h-5 w-5" }),
|
|
15629
|
-
title:
|
|
15630
|
-
|
|
16654
|
+
title: _t({
|
|
16655
|
+
id: "ZiooJI",
|
|
16656
|
+
message: "API Tokens"
|
|
16657
|
+
}),
|
|
16658
|
+
description: _t({
|
|
16659
|
+
id: "dhH+RW",
|
|
16660
|
+
message: "Create personal access tokens for programmatic API access"
|
|
16661
|
+
})
|
|
15631
16662
|
}), /* @__PURE__ */ jsx(SettingsLink, {
|
|
15632
16663
|
to: "/settings/email",
|
|
15633
16664
|
icon: /* @__PURE__ */ jsx(Envelope, { className: "h-5 w-5" }),
|
|
15634
|
-
title:
|
|
15635
|
-
|
|
16665
|
+
title: _t({
|
|
16666
|
+
id: "O3oNi5",
|
|
16667
|
+
message: "Email"
|
|
16668
|
+
}),
|
|
16669
|
+
description: _t({
|
|
16670
|
+
id: "/JPN+P",
|
|
16671
|
+
message: "View email provider status and send test emails"
|
|
16672
|
+
})
|
|
15636
16673
|
})]
|
|
16674
|
+
}),
|
|
16675
|
+
SUPPORTED_LOCALES.length > 1 && /* @__PURE__ */ jsx("div", {
|
|
16676
|
+
className: "space-y-2",
|
|
16677
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
16678
|
+
className: "flex items-center justify-between p-4 rounded-lg border bg-kumo-base",
|
|
16679
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
16680
|
+
className: "flex items-center gap-3",
|
|
16681
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
16682
|
+
className: "text-kumo-subtle",
|
|
16683
|
+
children: /* @__PURE__ */ jsx(GlobeSimple, { className: "h-5 w-5" })
|
|
16684
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
16685
|
+
className: "font-medium",
|
|
16686
|
+
children: _t({
|
|
16687
|
+
id: "vXIe7J",
|
|
16688
|
+
message: "Language"
|
|
16689
|
+
})
|
|
16690
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
16691
|
+
className: "text-sm text-kumo-subtle",
|
|
16692
|
+
children: _t({
|
|
16693
|
+
id: "Y8S9QC",
|
|
16694
|
+
message: "Choose your preferred admin language"
|
|
16695
|
+
})
|
|
16696
|
+
})] })]
|
|
16697
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
16698
|
+
className: "flex gap-1",
|
|
16699
|
+
children: SUPPORTED_LOCALES.map((l) => /* @__PURE__ */ jsx("button", {
|
|
16700
|
+
onClick: () => setLocale(l.code),
|
|
16701
|
+
className: cn("rounded-md px-3 py-1.5 text-sm transition-colors", l.code === locale ? "bg-kumo-brand/10 text-kumo-brand font-medium" : "hover:bg-kumo-tint"),
|
|
16702
|
+
children: l.label
|
|
16703
|
+
}, l.code))
|
|
16704
|
+
})]
|
|
16705
|
+
})
|
|
15637
16706
|
})
|
|
15638
16707
|
]
|
|
15639
16708
|
});
|
|
@@ -15768,12 +16837,24 @@ function AllowedDomainsSettings() {
|
|
|
15768
16837
|
const handleDelete = () => {
|
|
15769
16838
|
if (deletingDomain) deleteMutation.mutate(deletingDomain);
|
|
15770
16839
|
};
|
|
15771
|
-
|
|
15772
|
-
className: "
|
|
15773
|
-
children: [/* @__PURE__ */ jsx(
|
|
16840
|
+
const settingsHeader = /* @__PURE__ */ jsxs("div", {
|
|
16841
|
+
className: "flex items-center gap-3",
|
|
16842
|
+
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
16843
|
+
to: "/settings",
|
|
16844
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
16845
|
+
variant: "ghost",
|
|
16846
|
+
shape: "square",
|
|
16847
|
+
"aria-label": "Back to settings",
|
|
16848
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
|
|
16849
|
+
})
|
|
16850
|
+
}), /* @__PURE__ */ jsx("h1", {
|
|
15774
16851
|
className: "text-2xl font-bold",
|
|
15775
16852
|
children: "Self-Signup Domains"
|
|
15776
|
-
})
|
|
16853
|
+
})]
|
|
16854
|
+
});
|
|
16855
|
+
if (manifestLoading || isLoading) return /* @__PURE__ */ jsxs("div", {
|
|
16856
|
+
className: "space-y-6",
|
|
16857
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
15777
16858
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
15778
16859
|
children: /* @__PURE__ */ jsx("p", {
|
|
15779
16860
|
className: "text-kumo-subtle",
|
|
@@ -15783,42 +16864,28 @@ function AllowedDomainsSettings() {
|
|
|
15783
16864
|
});
|
|
15784
16865
|
if (isExternalAuth) return /* @__PURE__ */ jsxs("div", {
|
|
15785
16866
|
className: "space-y-6",
|
|
15786
|
-
children: [/* @__PURE__ */ jsx("
|
|
15787
|
-
className: "text-2xl font-bold",
|
|
15788
|
-
children: "Self-Signup Domains"
|
|
15789
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
16867
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
15790
16868
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
15791
16869
|
children: /* @__PURE__ */ jsxs("div", {
|
|
15792
16870
|
className: "flex items-start gap-3",
|
|
15793
|
-
children: [/* @__PURE__ */ jsx(Info, { className: "h-5 w-5 text-kumo-subtle mt-0.5 flex-shrink-0" }), /* @__PURE__ */
|
|
16871
|
+
children: [/* @__PURE__ */ jsx(Info, { className: "h-5 w-5 text-kumo-subtle mt-0.5 flex-shrink-0" }), /* @__PURE__ */ jsx("div", {
|
|
15794
16872
|
className: "space-y-2",
|
|
15795
|
-
children:
|
|
16873
|
+
children: /* @__PURE__ */ jsxs("p", {
|
|
15796
16874
|
className: "text-kumo-subtle",
|
|
15797
16875
|
children: [
|
|
15798
16876
|
"User access is managed by an external provider (",
|
|
15799
16877
|
manifest?.authMode,
|
|
15800
16878
|
"). Self-signup domain settings are not available when using external authentication."
|
|
15801
16879
|
]
|
|
15802
|
-
})
|
|
15803
|
-
to: "/settings",
|
|
15804
|
-
children: /* @__PURE__ */ jsx(Button, {
|
|
15805
|
-
variant: "outline",
|
|
15806
|
-
size: "sm",
|
|
15807
|
-
icon: /* @__PURE__ */ jsx(ArrowLeft, {}),
|
|
15808
|
-
children: "Back to Settings"
|
|
15809
|
-
})
|
|
15810
|
-
})]
|
|
16880
|
+
})
|
|
15811
16881
|
})]
|
|
15812
16882
|
})
|
|
15813
16883
|
})]
|
|
15814
16884
|
});
|
|
15815
16885
|
if (error) return /* @__PURE__ */ jsxs("div", {
|
|
15816
16886
|
className: "space-y-6",
|
|
15817
|
-
children: [/* @__PURE__ */ jsx("
|
|
15818
|
-
className: "
|
|
15819
|
-
children: "Self-Signup Domains"
|
|
15820
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
15821
|
-
className: "rounded-lg border border-kumo-danger/50 bg-kumo-danger/10 p-6",
|
|
16887
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
16888
|
+
className: "rounded-lg border bg-kumo-base p-6",
|
|
15822
16889
|
children: /* @__PURE__ */ jsx("p", {
|
|
15823
16890
|
className: "text-kumo-danger",
|
|
15824
16891
|
children: error instanceof Error ? error.message : "Failed to load allowed domains"
|
|
@@ -15828,13 +16895,10 @@ function AllowedDomainsSettings() {
|
|
|
15828
16895
|
return /* @__PURE__ */ jsxs("div", {
|
|
15829
16896
|
className: "space-y-6",
|
|
15830
16897
|
children: [
|
|
15831
|
-
|
|
15832
|
-
className: "text-2xl font-bold",
|
|
15833
|
-
children: "Self-Signup Domains"
|
|
15834
|
-
}),
|
|
16898
|
+
settingsHeader,
|
|
15835
16899
|
saveStatus && /* @__PURE__ */ jsxs("div", {
|
|
15836
|
-
className: `rounded-lg border p-
|
|
15837
|
-
children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-
|
|
16900
|
+
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"}`,
|
|
16901
|
+
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]
|
|
15838
16902
|
}),
|
|
15839
16903
|
/* @__PURE__ */ jsxs("div", {
|
|
15840
16904
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
@@ -16123,8 +17187,12 @@ function ApiTokenSettings() {
|
|
|
16123
17187
|
className: "flex items-center gap-3",
|
|
16124
17188
|
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
16125
17189
|
to: "/settings",
|
|
16126
|
-
|
|
16127
|
-
|
|
17190
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
17191
|
+
variant: "ghost",
|
|
17192
|
+
shape: "square",
|
|
17193
|
+
"aria-label": "Back to settings",
|
|
17194
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
|
|
17195
|
+
})
|
|
16128
17196
|
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
16129
17197
|
className: "text-2xl font-bold",
|
|
16130
17198
|
children: "API Tokens"
|
|
@@ -16390,7 +17458,7 @@ function EmailSettings() {
|
|
|
16390
17458
|
const timer = setTimeout(setStatus, 5e3, null);
|
|
16391
17459
|
return () => clearTimeout(timer);
|
|
16392
17460
|
}, [status]);
|
|
16393
|
-
const { data: settings, isLoading } = useQuery({
|
|
17461
|
+
const { data: settings, isLoading, error: fetchError } = useQuery({
|
|
16394
17462
|
queryKey: ["email-settings"],
|
|
16395
17463
|
queryFn: fetchEmailSettings
|
|
16396
17464
|
});
|
|
@@ -16419,6 +17487,27 @@ function EmailSettings() {
|
|
|
16419
17487
|
className: "flex items-center justify-center py-12",
|
|
16420
17488
|
children: /* @__PURE__ */ jsx(Loader, { size: "lg" })
|
|
16421
17489
|
});
|
|
17490
|
+
if (fetchError) return /* @__PURE__ */ jsxs("div", {
|
|
17491
|
+
className: "space-y-6",
|
|
17492
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
17493
|
+
className: "flex items-center gap-3",
|
|
17494
|
+
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
17495
|
+
to: "/settings",
|
|
17496
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
17497
|
+
variant: "ghost",
|
|
17498
|
+
shape: "square",
|
|
17499
|
+
"aria-label": "Back to settings",
|
|
17500
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
|
|
17501
|
+
})
|
|
17502
|
+
}), /* @__PURE__ */ jsx("h1", {
|
|
17503
|
+
className: "text-2xl font-bold",
|
|
17504
|
+
children: "Email Settings"
|
|
17505
|
+
})]
|
|
17506
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
17507
|
+
className: "flex items-center gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200",
|
|
17508
|
+
children: [/* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), getMutationError(fetchError) || "Failed to load email settings"]
|
|
17509
|
+
})]
|
|
17510
|
+
});
|
|
16422
17511
|
return /* @__PURE__ */ jsxs("div", {
|
|
16423
17512
|
className: "space-y-6",
|
|
16424
17513
|
children: [
|
|
@@ -16877,12 +17966,6 @@ const BASE64_PLUS_REGEX = /\+/g;
|
|
|
16877
17966
|
const BASE64_SLASH_REGEX = /\//g;
|
|
16878
17967
|
const EMPTY_DATA = {};
|
|
16879
17968
|
/**
|
|
16880
|
-
* Check if WebAuthn is supported in the current browser
|
|
16881
|
-
*/
|
|
16882
|
-
function isWebAuthnSupported() {
|
|
16883
|
-
return typeof window !== "undefined" && window.PublicKeyCredential !== void 0 && typeof window.PublicKeyCredential === "function";
|
|
16884
|
-
}
|
|
16885
|
-
/**
|
|
16886
17969
|
* Convert base64url to ArrayBuffer
|
|
16887
17970
|
*/
|
|
16888
17971
|
function base64urlToBuffer(base64url) {
|
|
@@ -16908,7 +17991,8 @@ function bufferToBase64url(buffer) {
|
|
|
16908
17991
|
function PasskeyRegistration({ optionsEndpoint, verifyEndpoint, onSuccess, onError, buttonText = "Register Passkey", showNameInput = false, additionalData = EMPTY_DATA }) {
|
|
16909
17992
|
const [state, setState] = React.useState({ status: "idle" });
|
|
16910
17993
|
const [passkeyName, setPasskeyName] = React.useState("");
|
|
16911
|
-
const isSupported = React.useMemo(() =>
|
|
17994
|
+
const isSupported = React.useMemo(() => isPasskeyEnvironmentUsable(), []);
|
|
17995
|
+
const insecureContext = React.useMemo(() => typeof window !== "undefined" && !isWebAuthnSecureContext(), []);
|
|
16912
17996
|
const handleRegister = React.useCallback(async () => {
|
|
16913
17997
|
if (!isSupported) {
|
|
16914
17998
|
setState({
|
|
@@ -17017,10 +18101,34 @@ function PasskeyRegistration({ optionsEndpoint, verifyEndpoint, onSuccess, onErr
|
|
|
17017
18101
|
className: "rounded-lg border border-kumo-danger/50 bg-kumo-danger/10 p-4",
|
|
17018
18102
|
children: [/* @__PURE__ */ jsx("h3", {
|
|
17019
18103
|
className: "font-medium text-kumo-danger",
|
|
17020
|
-
children: "Passkeys Not
|
|
18104
|
+
children: "Passkeys Not Available Here"
|
|
17021
18105
|
}), /* @__PURE__ */ jsx("p", {
|
|
17022
18106
|
className: "mt-1 text-sm text-kumo-subtle",
|
|
17023
|
-
children:
|
|
18107
|
+
children: insecureContext ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18108
|
+
"Passkeys require a ",
|
|
18109
|
+
/* @__PURE__ */ jsx("strong", {
|
|
18110
|
+
className: "text-kumo-default",
|
|
18111
|
+
children: "secure context"
|
|
18112
|
+
}),
|
|
18113
|
+
": use",
|
|
18114
|
+
" ",
|
|
18115
|
+
/* @__PURE__ */ jsx("strong", {
|
|
18116
|
+
className: "text-kumo-default",
|
|
18117
|
+
children: "HTTPS"
|
|
18118
|
+
}),
|
|
18119
|
+
", or open the admin at",
|
|
18120
|
+
" ",
|
|
18121
|
+
/* @__PURE__ */ jsx("strong", {
|
|
18122
|
+
className: "text-kumo-default",
|
|
18123
|
+
children: "http://localhost"
|
|
18124
|
+
}),
|
|
18125
|
+
" (with your dev port). Plain ",
|
|
18126
|
+
/* @__PURE__ */ jsx("code", {
|
|
18127
|
+
className: "text-xs",
|
|
18128
|
+
children: "http://"
|
|
18129
|
+
}),
|
|
18130
|
+
" on a custom hostname is not treated as secure, even on loopback."
|
|
18131
|
+
] }) : /* @__PURE__ */ jsx(Fragment, { children: "Your browser doesn't support passkeys. Please use a modern browser like Chrome, Safari, Firefox, or Edge." })
|
|
17024
18132
|
})]
|
|
17025
18133
|
});
|
|
17026
18134
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -17311,12 +18419,24 @@ function SecuritySettings() {
|
|
|
17311
18419
|
message: "Passkey added successfully"
|
|
17312
18420
|
});
|
|
17313
18421
|
};
|
|
17314
|
-
|
|
17315
|
-
className: "
|
|
17316
|
-
children: [/* @__PURE__ */ jsx(
|
|
18422
|
+
const settingsHeader = /* @__PURE__ */ jsxs("div", {
|
|
18423
|
+
className: "flex items-center gap-3",
|
|
18424
|
+
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
18425
|
+
to: "/settings",
|
|
18426
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
18427
|
+
variant: "ghost",
|
|
18428
|
+
shape: "square",
|
|
18429
|
+
"aria-label": "Back to settings",
|
|
18430
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
|
|
18431
|
+
})
|
|
18432
|
+
}), /* @__PURE__ */ jsx("h1", {
|
|
17317
18433
|
className: "text-2xl font-bold",
|
|
17318
18434
|
children: "Security Settings"
|
|
17319
|
-
})
|
|
18435
|
+
})]
|
|
18436
|
+
});
|
|
18437
|
+
if (manifestLoading || isLoading) return /* @__PURE__ */ jsxs("div", {
|
|
18438
|
+
className: "space-y-6",
|
|
18439
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
17320
18440
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
17321
18441
|
children: /* @__PURE__ */ jsx("p", {
|
|
17322
18442
|
className: "text-kumo-subtle",
|
|
@@ -17326,42 +18446,28 @@ function SecuritySettings() {
|
|
|
17326
18446
|
});
|
|
17327
18447
|
if (isExternalAuth) return /* @__PURE__ */ jsxs("div", {
|
|
17328
18448
|
className: "space-y-6",
|
|
17329
|
-
children: [/* @__PURE__ */ jsx("
|
|
17330
|
-
className: "text-2xl font-bold",
|
|
17331
|
-
children: "Security Settings"
|
|
17332
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
18449
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
17333
18450
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
17334
18451
|
children: /* @__PURE__ */ jsxs("div", {
|
|
17335
18452
|
className: "flex items-start gap-3",
|
|
17336
|
-
children: [/* @__PURE__ */ jsx(Info, { className: "h-5 w-5 text-kumo-subtle mt-0.5 flex-shrink-0" }), /* @__PURE__ */
|
|
18453
|
+
children: [/* @__PURE__ */ jsx(Info, { className: "h-5 w-5 text-kumo-subtle mt-0.5 flex-shrink-0" }), /* @__PURE__ */ jsx("div", {
|
|
17337
18454
|
className: "space-y-2",
|
|
17338
|
-
children:
|
|
18455
|
+
children: /* @__PURE__ */ jsxs("p", {
|
|
17339
18456
|
className: "text-kumo-subtle",
|
|
17340
18457
|
children: [
|
|
17341
18458
|
"Authentication is managed by an external provider (",
|
|
17342
18459
|
manifest?.authMode,
|
|
17343
18460
|
"). Passkey settings are not available when using external authentication."
|
|
17344
18461
|
]
|
|
17345
|
-
})
|
|
17346
|
-
to: "/settings",
|
|
17347
|
-
children: /* @__PURE__ */ jsx(Button, {
|
|
17348
|
-
variant: "outline",
|
|
17349
|
-
size: "sm",
|
|
17350
|
-
icon: /* @__PURE__ */ jsx(ArrowLeft, {}),
|
|
17351
|
-
children: "Back to Settings"
|
|
17352
|
-
})
|
|
17353
|
-
})]
|
|
18462
|
+
})
|
|
17354
18463
|
})]
|
|
17355
18464
|
})
|
|
17356
18465
|
})]
|
|
17357
18466
|
});
|
|
17358
18467
|
if (error) return /* @__PURE__ */ jsxs("div", {
|
|
17359
18468
|
className: "space-y-6",
|
|
17360
|
-
children: [/* @__PURE__ */ jsx("
|
|
17361
|
-
className: "
|
|
17362
|
-
children: "Security Settings"
|
|
17363
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
17364
|
-
className: "rounded-lg border border-kumo-danger/50 bg-kumo-danger/10 p-6",
|
|
18469
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
18470
|
+
className: "rounded-lg border bg-kumo-base p-6",
|
|
17365
18471
|
children: /* @__PURE__ */ jsx("p", {
|
|
17366
18472
|
className: "text-kumo-danger",
|
|
17367
18473
|
children: error instanceof Error ? error.message : "Failed to load passkeys"
|
|
@@ -17371,13 +18477,10 @@ function SecuritySettings() {
|
|
|
17371
18477
|
return /* @__PURE__ */ jsxs("div", {
|
|
17372
18478
|
className: "space-y-6",
|
|
17373
18479
|
children: [
|
|
17374
|
-
|
|
17375
|
-
className: "text-2xl font-bold",
|
|
17376
|
-
children: "Security Settings"
|
|
17377
|
-
}),
|
|
18480
|
+
settingsHeader,
|
|
17378
18481
|
saveStatus && /* @__PURE__ */ jsxs("div", {
|
|
17379
|
-
className: `rounded-lg border p-
|
|
17380
|
-
children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-
|
|
18482
|
+
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"}`,
|
|
18483
|
+
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]
|
|
17381
18484
|
}),
|
|
17382
18485
|
/* @__PURE__ */ jsxs("div", {
|
|
17383
18486
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
@@ -18129,10 +19232,7 @@ function SetupWizard() {
|
|
|
18129
19232
|
/* @__PURE__ */ jsxs("div", {
|
|
18130
19233
|
className: "text-center mb-6",
|
|
18131
19234
|
children: [
|
|
18132
|
-
/* @__PURE__ */ jsx("
|
|
18133
|
-
className: "text-4xl font-bold mb-2",
|
|
18134
|
-
children: "— EmDash"
|
|
18135
|
-
}),
|
|
19235
|
+
/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-2" }),
|
|
18136
19236
|
/* @__PURE__ */ jsxs("h1", {
|
|
18137
19237
|
className: "text-2xl font-semibold text-kumo-default",
|
|
18138
19238
|
children: [
|
|
@@ -18592,20 +19692,13 @@ function SidebarNav({ manifest }) {
|
|
|
18592
19692
|
icon: Stack,
|
|
18593
19693
|
minRole: ROLE_EDITOR$1
|
|
18594
19694
|
},
|
|
18595
|
-
{
|
|
18596
|
-
to: "/taxonomies/$taxonomy",
|
|
18597
|
-
label: "Categories",
|
|
18598
|
-
icon: FileText,
|
|
18599
|
-
params: { taxonomy: "category" },
|
|
18600
|
-
minRole: ROLE_EDITOR$1
|
|
18601
|
-
},
|
|
18602
|
-
{
|
|
19695
|
+
...manifest.taxonomies.map((tax) => ({
|
|
18603
19696
|
to: "/taxonomies/$taxonomy",
|
|
18604
|
-
label:
|
|
19697
|
+
label: tax.label,
|
|
18605
19698
|
icon: FileText,
|
|
18606
|
-
params: { taxonomy:
|
|
19699
|
+
params: { taxonomy: tax.name },
|
|
18607
19700
|
minRole: ROLE_EDITOR$1
|
|
18608
|
-
},
|
|
19701
|
+
})),
|
|
18609
19702
|
{
|
|
18610
19703
|
to: "/bylines",
|
|
18611
19704
|
label: "Bylines",
|
|
@@ -18770,10 +19863,9 @@ function SidebarNav({ manifest }) {
|
|
|
18770
19863
|
/* @__PURE__ */ jsx(KumoSidebar.Header, { children: /* @__PURE__ */ jsxs(Link$1, {
|
|
18771
19864
|
to: "/",
|
|
18772
19865
|
className: "emdash-brand-link flex w-full min-w-0 items-center gap-2 px-3 py-1",
|
|
18773
|
-
children: [/* @__PURE__ */ jsx(
|
|
18774
|
-
className: "
|
|
18775
|
-
"aria-hidden": "true"
|
|
18776
|
-
children: "—"
|
|
19866
|
+
children: [/* @__PURE__ */ jsx(LogoIcon, {
|
|
19867
|
+
className: "size-5 shrink-0",
|
|
19868
|
+
"aria-hidden": "true"
|
|
18777
19869
|
}), /* @__PURE__ */ jsx("span", {
|
|
18778
19870
|
className: "emdash-brand-text font-semibold truncate",
|
|
18779
19871
|
children: "EmDash"
|
|
@@ -18874,8 +19966,8 @@ function Header() {
|
|
|
18874
19966
|
const initials = ((user?.name || user?.email || "U")[0] ?? "U").toUpperCase();
|
|
18875
19967
|
return /* @__PURE__ */ jsxs("header", {
|
|
18876
19968
|
className: "sticky top-0 z-10 flex h-16 items-center justify-between border-b bg-kumo-base px-4",
|
|
18877
|
-
children: [/* @__PURE__ */ jsx(KumoSidebar.Trigger, {}), /* @__PURE__ */ jsxs("div", {
|
|
18878
|
-
className: "flex items-center
|
|
19969
|
+
children: [/* @__PURE__ */ jsx(KumoSidebar.Trigger, { className: "cursor-pointer" }), /* @__PURE__ */ jsxs("div", {
|
|
19970
|
+
className: "flex items-center gap-2",
|
|
18879
19971
|
children: [
|
|
18880
19972
|
/* @__PURE__ */ jsxs(LinkButton, {
|
|
18881
19973
|
variant: "ghost",
|
|
@@ -18893,7 +19985,7 @@ function Header() {
|
|
|
18893
19985
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
18894
19986
|
variant: "ghost",
|
|
18895
19987
|
size: "sm",
|
|
18896
|
-
className: "gap-2",
|
|
19988
|
+
className: "gap-2 py-1 h-auto",
|
|
18897
19989
|
children: [user?.avatarUrl ? /* @__PURE__ */ jsx("img", {
|
|
18898
19990
|
src: user.avatarUrl,
|
|
18899
19991
|
alt: "",
|
|
@@ -18998,7 +20090,6 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
|
|
|
18998
20090
|
onOpenChange: (isOpen) => !isOpen && handleGetStarted(),
|
|
18999
20091
|
children: /* @__PURE__ */ jsxs(Dialog, {
|
|
19000
20092
|
className: "p-6 sm:max-w-md",
|
|
19001
|
-
size: "lg",
|
|
19002
20093
|
children: [
|
|
19003
20094
|
/* @__PURE__ */ jsxs("div", {
|
|
19004
20095
|
className: "flex items-start justify-between gap-4",
|
|
@@ -19021,8 +20112,8 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
|
|
|
19021
20112
|
className: "flex flex-col space-y-1.5 text-center sm:text-center",
|
|
19022
20113
|
children: [
|
|
19023
20114
|
/* @__PURE__ */ jsx("div", {
|
|
19024
|
-
className: "mx-auto mb-4
|
|
19025
|
-
children: /* @__PURE__ */ jsx(
|
|
20115
|
+
className: "mx-auto mb-4",
|
|
20116
|
+
children: /* @__PURE__ */ jsx(LogoIcon, { className: "h-16 w-16" })
|
|
19026
20117
|
}),
|
|
19027
20118
|
/* @__PURE__ */ jsxs(Dialog.Title, {
|
|
19028
20119
|
className: "text-2xl font-semibold leading-none tracking-tight",
|
|
@@ -19467,10 +20558,7 @@ function SignupPage() {
|
|
|
19467
20558
|
children: [
|
|
19468
20559
|
/* @__PURE__ */ jsxs("div", {
|
|
19469
20560
|
className: "text-center mb-8",
|
|
19470
|
-
children: [/* @__PURE__ */ jsx("
|
|
19471
|
-
className: "text-4xl font-bold mb-2",
|
|
19472
|
-
children: "— EmDash"
|
|
19473
|
-
}), /* @__PURE__ */ jsxs("h1", {
|
|
20561
|
+
children: [/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-2" }), /* @__PURE__ */ jsxs("h1", {
|
|
19474
20562
|
className: "text-2xl font-semibold text-kumo-default",
|
|
19475
20563
|
children: [
|
|
19476
20564
|
step === "email" && "Create an account",
|
|
@@ -19601,6 +20689,14 @@ function TermFormDialog({ open, onClose, taxonomyName, taxonomyDef, term, allTer
|
|
|
19601
20689
|
const [description, setDescription] = React.useState(term?.description || "");
|
|
19602
20690
|
const [autoSlug, setAutoSlug] = React.useState(!term);
|
|
19603
20691
|
const [error, setError] = React.useState(null);
|
|
20692
|
+
React.useEffect(() => {
|
|
20693
|
+
setLabel(term?.label || "");
|
|
20694
|
+
setSlug(term?.slug || "");
|
|
20695
|
+
setParentId(term?.parentId || "");
|
|
20696
|
+
setDescription(term?.description || "");
|
|
20697
|
+
setAutoSlug(!term);
|
|
20698
|
+
setError(null);
|
|
20699
|
+
}, [term]);
|
|
19604
20700
|
React.useEffect(() => {
|
|
19605
20701
|
if (autoSlug && label) setSlug(slugify(label));
|
|
19606
20702
|
}, [label, autoSlug]);
|
|
@@ -24650,6 +25746,10 @@ const adminLayoutRoute = createRoute({
|
|
|
24650
25746
|
id: "_admin",
|
|
24651
25747
|
component: RootComponent
|
|
24652
25748
|
});
|
|
25749
|
+
if (typeof window !== "undefined" && typeof window.requestIdleCallback === "undefined") {
|
|
25750
|
+
window.requestIdleCallback = (cb) => setTimeout(cb, 50);
|
|
25751
|
+
window.cancelIdleCallback = (id) => clearTimeout(id);
|
|
25752
|
+
}
|
|
24653
25753
|
function RootComponent() {
|
|
24654
25754
|
const { data: manifest, isLoading, error } = useQuery({
|
|
24655
25755
|
queryKey: ["manifest"],
|
|
@@ -24693,13 +25793,20 @@ function ContentListPage() {
|
|
|
24693
25793
|
});
|
|
24694
25794
|
const i18n = manifest?.i18n;
|
|
24695
25795
|
const activeLocale = i18n ? localeParam ?? i18n.defaultLocale : void 0;
|
|
24696
|
-
const { data, isLoading, error } =
|
|
25796
|
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, error } = useInfiniteQuery({
|
|
24697
25797
|
queryKey: [
|
|
24698
25798
|
"content",
|
|
24699
25799
|
collection,
|
|
24700
25800
|
{ locale: activeLocale }
|
|
24701
25801
|
],
|
|
24702
|
-
queryFn: () => fetchContentList(collection, {
|
|
25802
|
+
queryFn: ({ pageParam }) => fetchContentList(collection, {
|
|
25803
|
+
locale: activeLocale,
|
|
25804
|
+
cursor: pageParam,
|
|
25805
|
+
limit: 100
|
|
25806
|
+
}),
|
|
25807
|
+
initialPageParam: void 0,
|
|
25808
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
25809
|
+
enabled: !!manifest
|
|
24703
25810
|
});
|
|
24704
25811
|
const { data: trashedData, isLoading: isTrashedLoading } = useQuery({
|
|
24705
25812
|
queryKey: [
|
|
@@ -24775,6 +25882,9 @@ function ContentListPage() {
|
|
|
24775
25882
|
});
|
|
24776
25883
|
}
|
|
24777
25884
|
});
|
|
25885
|
+
const items = React.useMemo(() => {
|
|
25886
|
+
return data?.pages.flatMap((page) => page.items) || [];
|
|
25887
|
+
}, [data]);
|
|
24778
25888
|
if (!manifest) return /* @__PURE__ */ jsx(LoadingScreen, {});
|
|
24779
25889
|
const collectionConfig = manifest.collections[collection];
|
|
24780
25890
|
if (!collectionConfig) return /* @__PURE__ */ jsx(NotFoundPage, { message: `Collection "${collection}" not found` });
|
|
@@ -24789,10 +25899,12 @@ function ContentListPage() {
|
|
|
24789
25899
|
return /* @__PURE__ */ jsx(ContentList, {
|
|
24790
25900
|
collection,
|
|
24791
25901
|
collectionLabel: collectionConfig.label,
|
|
24792
|
-
items
|
|
25902
|
+
items,
|
|
24793
25903
|
trashedItems: trashedData?.items || [],
|
|
24794
|
-
isLoading,
|
|
25904
|
+
isLoading: isLoading || isFetchingNextPage,
|
|
24795
25905
|
isTrashedLoading,
|
|
25906
|
+
hasMore: !!hasNextPage,
|
|
25907
|
+
onLoadMore: React.useCallback(() => void fetchNextPage(), [fetchNextPage]),
|
|
24796
25908
|
trashedCount: trashedData?.items?.length || 0,
|
|
24797
25909
|
onDelete: (id) => deleteMutation.mutate(id),
|
|
24798
25910
|
onRestore: (id) => restoreMutation.mutate(id),
|
|
@@ -24800,7 +25912,8 @@ function ContentListPage() {
|
|
|
24800
25912
|
onDuplicate: (id) => duplicateMutation.mutate(id),
|
|
24801
25913
|
i18n,
|
|
24802
25914
|
activeLocale,
|
|
24803
|
-
onLocaleChange: handleLocaleChange
|
|
25915
|
+
onLocaleChange: handleLocaleChange,
|
|
25916
|
+
urlPattern: collectionConfig.urlPattern
|
|
24804
25917
|
});
|
|
24805
25918
|
}
|
|
24806
25919
|
/** Extract plugin block definitions from the manifest for Portable Text editor */
|
|
@@ -24815,10 +25928,12 @@ function getPluginBlocks(manifest) {
|
|
|
24815
25928
|
const contentNewRoute = createRoute({
|
|
24816
25929
|
getParentRoute: () => adminLayoutRoute,
|
|
24817
25930
|
path: "/content/$collection/new",
|
|
24818
|
-
component: ContentNewPage
|
|
25931
|
+
component: ContentNewPage,
|
|
25932
|
+
validateSearch: (search) => ({ locale: typeof search.locale === "string" ? search.locale : void 0 })
|
|
24819
25933
|
});
|
|
24820
25934
|
function ContentNewPage() {
|
|
24821
25935
|
const { collection } = useParams({ from: "/_admin/content/$collection/new" });
|
|
25936
|
+
const { locale } = useSearch({ from: "/_admin/content/$collection/new" });
|
|
24822
25937
|
const navigate = useNavigate();
|
|
24823
25938
|
const queryClient = useQueryClient();
|
|
24824
25939
|
const [selectedBylines, setSelectedBylines] = React.useState([]);
|
|
@@ -24827,7 +25942,10 @@ function ContentNewPage() {
|
|
|
24827
25942
|
queryFn: fetchManifest
|
|
24828
25943
|
});
|
|
24829
25944
|
const createMutation = useMutation({
|
|
24830
|
-
mutationFn: (data) => createContent(collection,
|
|
25945
|
+
mutationFn: (data) => createContent(collection, {
|
|
25946
|
+
...data,
|
|
25947
|
+
locale
|
|
25948
|
+
}),
|
|
24831
25949
|
onSuccess: (result) => {
|
|
24832
25950
|
queryClient.invalidateQueries({ queryKey: ["content", collection] });
|
|
24833
25951
|
navigate({
|
|
@@ -24894,11 +26012,13 @@ function ContentNewPage() {
|
|
|
24894
26012
|
const contentEditRoute = createRoute({
|
|
24895
26013
|
getParentRoute: () => adminLayoutRoute,
|
|
24896
26014
|
path: "/content/$collection/$id",
|
|
24897
|
-
component: ContentEditPage
|
|
26015
|
+
component: ContentEditPage,
|
|
26016
|
+
validateSearch: (search) => ({ ...typeof search.field === "string" && { field: search.field } })
|
|
24898
26017
|
});
|
|
24899
26018
|
const ROLE_EDITOR = 40;
|
|
24900
26019
|
function ContentEditPage() {
|
|
24901
26020
|
const { collection, id } = useParams({ from: "/_admin/content/$collection/$id" });
|
|
26021
|
+
const searchParams = useSearch({ from: "/_admin/content/$collection/$id" });
|
|
24902
26022
|
const queryClient = useQueryClient();
|
|
24903
26023
|
const navigate = useNavigate();
|
|
24904
26024
|
const toastManager = Toast.useToastManager();
|
|
@@ -24915,6 +26035,29 @@ function ContentEditPage() {
|
|
|
24915
26035
|
],
|
|
24916
26036
|
queryFn: () => fetchContent(collection, id)
|
|
24917
26037
|
});
|
|
26038
|
+
React.useEffect(() => {
|
|
26039
|
+
if (typeof searchParams.field !== "string" || isLoading) return;
|
|
26040
|
+
const timeoutId = requestIdleCallback(() => {
|
|
26041
|
+
const el = document.getElementById(`field-${searchParams.field}`);
|
|
26042
|
+
if (el) {
|
|
26043
|
+
el.scrollIntoView({
|
|
26044
|
+
behavior: "smooth",
|
|
26045
|
+
block: "center"
|
|
26046
|
+
});
|
|
26047
|
+
el.focus();
|
|
26048
|
+
const { field: _, ...preservedSearch } = searchParams;
|
|
26049
|
+
navigate({
|
|
26050
|
+
search: preservedSearch,
|
|
26051
|
+
replace: true
|
|
26052
|
+
});
|
|
26053
|
+
}
|
|
26054
|
+
});
|
|
26055
|
+
return () => cancelIdleCallback(timeoutId);
|
|
26056
|
+
}, [
|
|
26057
|
+
searchParams,
|
|
26058
|
+
isLoading,
|
|
26059
|
+
navigate
|
|
26060
|
+
]);
|
|
24918
26061
|
const { data: translationsData } = useQuery({
|
|
24919
26062
|
queryKey: [
|
|
24920
26063
|
"translations",
|
|
@@ -24992,6 +26135,7 @@ function ContentEditPage() {
|
|
|
24992
26135
|
collection,
|
|
24993
26136
|
id
|
|
24994
26137
|
] });
|
|
26138
|
+
if (rawItem?.draftRevisionId) queryClient.invalidateQueries({ queryKey: ["revision", rawItem.draftRevisionId] });
|
|
24995
26139
|
},
|
|
24996
26140
|
onError: (error) => {
|
|
24997
26141
|
toastManager.add({
|
|
@@ -25014,6 +26158,7 @@ function ContentEditPage() {
|
|
|
25014
26158
|
collection,
|
|
25015
26159
|
id
|
|
25016
26160
|
] });
|
|
26161
|
+
if (rawItem?.draftRevisionId) queryClient.invalidateQueries({ queryKey: ["revision", rawItem.draftRevisionId] });
|
|
25017
26162
|
},
|
|
25018
26163
|
onError: (err) => {
|
|
25019
26164
|
toastManager.add({
|
|
@@ -25237,6 +26382,7 @@ function ContentEditPage() {
|
|
|
25237
26382
|
isDeleting: deleteMutation.isPending,
|
|
25238
26383
|
supportsDrafts: collectionConfig.supports.includes("drafts"),
|
|
25239
26384
|
supportsRevisions: collectionConfig.supports.includes("revisions"),
|
|
26385
|
+
supportsPreview: collectionConfig.supports.includes("preview"),
|
|
25240
26386
|
currentUser,
|
|
25241
26387
|
users: usersData?.items,
|
|
25242
26388
|
onAuthorChange: handleAuthorChange,
|
|
@@ -25732,6 +26878,17 @@ function ContentTypesEditPage() {
|
|
|
25732
26878
|
queryClient.invalidateQueries({ queryKey: ["manifest"] });
|
|
25733
26879
|
}
|
|
25734
26880
|
});
|
|
26881
|
+
const reorderFieldsMutation = useMutation({
|
|
26882
|
+
mutationFn: (fieldSlugs) => reorderFields(slug, fieldSlugs),
|
|
26883
|
+
onSuccess: () => {
|
|
26884
|
+
queryClient.invalidateQueries({ queryKey: [
|
|
26885
|
+
"schema",
|
|
26886
|
+
"collections",
|
|
26887
|
+
slug
|
|
26888
|
+
] });
|
|
26889
|
+
queryClient.invalidateQueries({ queryKey: ["manifest"] });
|
|
26890
|
+
}
|
|
26891
|
+
});
|
|
25735
26892
|
if (error) return /* @__PURE__ */ jsx(ErrorScreen, { error: error.message });
|
|
25736
26893
|
if (isLoading) return /* @__PURE__ */ jsx(LoadingScreen, {});
|
|
25737
26894
|
return /* @__PURE__ */ jsx(ContentTypeEditor, {
|
|
@@ -25743,7 +26900,8 @@ function ContentTypesEditPage() {
|
|
|
25743
26900
|
fieldSlug,
|
|
25744
26901
|
input
|
|
25745
26902
|
}),
|
|
25746
|
-
onDeleteField: (fieldSlug) => deleteFieldMutation.mutate(fieldSlug)
|
|
26903
|
+
onDeleteField: (fieldSlug) => deleteFieldMutation.mutate(fieldSlug),
|
|
26904
|
+
onReorderFields: (fieldSlugs) => reorderFieldsMutation.mutate(fieldSlugs)
|
|
25747
26905
|
});
|
|
25748
26906
|
}
|
|
25749
26907
|
const pluginRoute = createRoute({
|
|
@@ -25897,19 +27055,30 @@ const router = createAdminRouter(queryClient);
|
|
|
25897
27055
|
* Main Admin Application
|
|
25898
27056
|
*/
|
|
25899
27057
|
const EMPTY_PLUGINS = {};
|
|
25900
|
-
function AdminApp({ pluginAdmins = EMPTY_PLUGINS }) {
|
|
27058
|
+
function AdminApp({ pluginAdmins = EMPTY_PLUGINS, locale = "en", messages = {} }) {
|
|
25901
27059
|
React.useEffect(() => {
|
|
25902
27060
|
document.getElementById("emdash-boot-loader")?.remove();
|
|
25903
27061
|
}, []);
|
|
25904
|
-
|
|
25905
|
-
|
|
25906
|
-
|
|
25907
|
-
|
|
25908
|
-
|
|
25909
|
-
})
|
|
25910
|
-
|
|
27062
|
+
const i18nInitialized = React.useRef(false);
|
|
27063
|
+
if (!i18nInitialized.current) {
|
|
27064
|
+
i18n.loadAndActivate({
|
|
27065
|
+
locale,
|
|
27066
|
+
messages
|
|
27067
|
+
});
|
|
27068
|
+
i18nInitialized.current = true;
|
|
27069
|
+
}
|
|
27070
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(I18nProvider, {
|
|
27071
|
+
i18n,
|
|
27072
|
+
children: /* @__PURE__ */ jsx(Toasty, { children: /* @__PURE__ */ jsx(PluginAdminProvider, {
|
|
27073
|
+
pluginAdmins,
|
|
27074
|
+
children: /* @__PURE__ */ jsx(QueryClientProvider, {
|
|
27075
|
+
client: queryClient,
|
|
27076
|
+
children: /* @__PURE__ */ jsx(RouterProvider, { router })
|
|
27077
|
+
})
|
|
27078
|
+
}) })
|
|
27079
|
+
}) });
|
|
25911
27080
|
}
|
|
25912
27081
|
|
|
25913
27082
|
//#endregion
|
|
25914
|
-
export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, CAPABILITY_LABELS, ContentEditor, ContentList, Dashboard, Header, Link, LoginPage, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, 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, 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, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, verifySignupToken };
|
|
27083
|
+
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, verifySignupToken };
|
|
25915
27084
|
//# sourceMappingURL=index.js.map
|