@emdash-cms/admin 0.0.2 → 0.1.1
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/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1173 -244
- package/dist/index.js.map +1 -1
- package/dist/plugins-XhZqfegd.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,9 +5,12 @@ import { Link, Link as Link$1, Outlet, RouterProvider, createRootRouteWithContex
|
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
7
7
|
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";
|
|
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, 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
9
|
import { clsx } from "clsx";
|
|
10
10
|
import { twMerge } from "tailwind-merge";
|
|
11
|
+
import { DndContext, DragOverlay, KeyboardSensor, PointerSensor, closestCenter, rectIntersection, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
|
|
12
|
+
import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
13
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
11
14
|
import { autoUpdate, flip, offset, shift, useFloating } from "@floating-ui/react";
|
|
12
15
|
import { Extension, InputRule, Node as Node$1, PasteRule, escapeForRegEx, mergeAttributes } from "@tiptap/core";
|
|
13
16
|
import CharacterCount from "@tiptap/extension-character-count";
|
|
@@ -27,9 +30,6 @@ import { BlockRenderer } from "@emdash-cms/blocks";
|
|
|
27
30
|
import DOMPurify from "dompurify";
|
|
28
31
|
import { Marked, Renderer } from "marked";
|
|
29
32
|
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
33
|
|
|
34
34
|
//#region src/components/ThemeProvider.tsx
|
|
35
35
|
const ThemeContext = React.createContext(void 0);
|
|
@@ -54,15 +54,8 @@ function ThemeProvider({ children, defaultTheme = "system" }) {
|
|
|
54
54
|
return theme;
|
|
55
55
|
});
|
|
56
56
|
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
|
-
}
|
|
57
|
+
if (theme === "system") setResolvedTheme(getSystemTheme());
|
|
58
|
+
else setResolvedTheme(theme);
|
|
66
59
|
}, [theme]);
|
|
67
60
|
React.useEffect(() => {
|
|
68
61
|
if (theme !== "system") return;
|
|
@@ -73,6 +66,11 @@ function ThemeProvider({ children, defaultTheme = "system" }) {
|
|
|
73
66
|
mediaQuery.addEventListener("change", handler);
|
|
74
67
|
return () => mediaQuery.removeEventListener("change", handler);
|
|
75
68
|
}, [theme]);
|
|
69
|
+
React.useEffect(() => {
|
|
70
|
+
const root = document.documentElement;
|
|
71
|
+
root.setAttribute("data-theme", "classic");
|
|
72
|
+
root.setAttribute("data-mode", resolvedTheme);
|
|
73
|
+
}, [resolvedTheme]);
|
|
76
74
|
const setTheme = React.useCallback((newTheme) => {
|
|
77
75
|
setThemeState(newTheme);
|
|
78
76
|
localStorage.setItem(STORAGE_KEY, newTheme);
|
|
@@ -2416,6 +2414,58 @@ function useCurrentUser() {
|
|
|
2416
2414
|
});
|
|
2417
2415
|
}
|
|
2418
2416
|
|
|
2417
|
+
//#endregion
|
|
2418
|
+
//#region src/lib/url.ts
|
|
2419
|
+
/**
|
|
2420
|
+
* Shared URL validation and transformation utilities
|
|
2421
|
+
*/
|
|
2422
|
+
const DEFAULT_REDIRECT = "/_emdash/admin";
|
|
2423
|
+
const LEADING_SLASHES = /^\/+/;
|
|
2424
|
+
/**
|
|
2425
|
+
* Sanitize a redirect URL to prevent open-redirect and javascript: XSS attacks.
|
|
2426
|
+
*
|
|
2427
|
+
* Only allows relative paths starting with `/`. Rejects protocol-relative
|
|
2428
|
+
* URLs (`//evil.com`), backslash tricks (`/\evil.com`), and non-path schemes
|
|
2429
|
+
* like `javascript:`.
|
|
2430
|
+
*
|
|
2431
|
+
* Returns the default admin URL when the input is unsafe.
|
|
2432
|
+
*/
|
|
2433
|
+
function sanitizeRedirectUrl(raw) {
|
|
2434
|
+
if (raw.startsWith("/") && !raw.startsWith("//") && !raw.includes("\\")) return raw;
|
|
2435
|
+
return DEFAULT_REDIRECT;
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Build a public content URL from collection metadata and slug.
|
|
2439
|
+
*
|
|
2440
|
+
* Uses the collection's `urlPattern` when available (e.g. `/blog/{slug}`),
|
|
2441
|
+
* otherwise falls back to `/{collection}/{slug}`. Leading slashes are
|
|
2442
|
+
* stripped from the slug to prevent protocol-relative URLs.
|
|
2443
|
+
*/
|
|
2444
|
+
function contentUrl(collection, slug, urlPattern) {
|
|
2445
|
+
const safe = slug.replace(LEADING_SLASHES, "");
|
|
2446
|
+
return urlPattern ? urlPattern.replace("{slug}", safe) : `/${collection}/${safe}`;
|
|
2447
|
+
}
|
|
2448
|
+
/** Matches http:// or https:// URLs */
|
|
2449
|
+
const SAFE_URL_RE = /^https?:\/\//i;
|
|
2450
|
+
/** Returns true if the URL uses a safe scheme (http/https) */
|
|
2451
|
+
function isSafeUrl$1(url) {
|
|
2452
|
+
return SAFE_URL_RE.test(url);
|
|
2453
|
+
}
|
|
2454
|
+
/**
|
|
2455
|
+
* Build an icon URL with a width query param, or return null for unsafe URLs.
|
|
2456
|
+
* Validates the URL scheme and appends `?w=<width>` for image resizing.
|
|
2457
|
+
*/
|
|
2458
|
+
function safeIconUrl(url, width) {
|
|
2459
|
+
if (!SAFE_URL_RE.test(url)) return null;
|
|
2460
|
+
try {
|
|
2461
|
+
const u = new URL(url);
|
|
2462
|
+
u.searchParams.set("w", String(width));
|
|
2463
|
+
return u.href;
|
|
2464
|
+
} catch {
|
|
2465
|
+
return null;
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2419
2469
|
//#endregion
|
|
2420
2470
|
//#region src/components/BlockKitFieldWidget.tsx
|
|
2421
2471
|
/**
|
|
@@ -2664,6 +2714,242 @@ var PluginFieldErrorBoundary = class extends React.Component {
|
|
|
2664
2714
|
}
|
|
2665
2715
|
};
|
|
2666
2716
|
|
|
2717
|
+
//#endregion
|
|
2718
|
+
//#region src/components/RepeaterField.tsx
|
|
2719
|
+
/**
|
|
2720
|
+
* RepeaterField — renders a list of repeating sub-field groups in the content editor.
|
|
2721
|
+
*
|
|
2722
|
+
* Each item is a collapsible card containing the defined sub-fields.
|
|
2723
|
+
* Items can be added, removed, and reordered via drag-and-drop.
|
|
2724
|
+
*/
|
|
2725
|
+
function ensureKeys(items) {
|
|
2726
|
+
return items.map((item, i) => {
|
|
2727
|
+
const obj = typeof item === "object" && item !== null ? item : {};
|
|
2728
|
+
return {
|
|
2729
|
+
...obj,
|
|
2730
|
+
_key: obj._key || `item-${i}-${Date.now()}`
|
|
2731
|
+
};
|
|
2732
|
+
});
|
|
2733
|
+
}
|
|
2734
|
+
function stripKeys(items) {
|
|
2735
|
+
return items.map(({ _key, ...rest }) => rest);
|
|
2736
|
+
}
|
|
2737
|
+
function RepeaterField({ label, id, value, onChange, subFields, minItems = 0, maxItems }) {
|
|
2738
|
+
const rawItems = Array.isArray(value) ? value : [];
|
|
2739
|
+
const [items, setItems] = React.useState(() => ensureKeys(rawItems));
|
|
2740
|
+
const [collapsedItems, setCollapsedItems] = React.useState(/* @__PURE__ */ new Set());
|
|
2741
|
+
React.useEffect(() => {
|
|
2742
|
+
setItems(ensureKeys(Array.isArray(value) ? value : []));
|
|
2743
|
+
}, [value]);
|
|
2744
|
+
const emitChange = (updated) => {
|
|
2745
|
+
setItems(updated);
|
|
2746
|
+
onChange(stripKeys(updated));
|
|
2747
|
+
};
|
|
2748
|
+
const handleAdd = () => {
|
|
2749
|
+
if (maxItems && items.length >= maxItems) return;
|
|
2750
|
+
const newItem = { _key: `item-${Date.now()}` };
|
|
2751
|
+
for (const sf of subFields) newItem[sf.slug] = sf.type === "boolean" ? false : sf.type === "number" || sf.type === "integer" ? null : "";
|
|
2752
|
+
emitChange([...items, newItem]);
|
|
2753
|
+
};
|
|
2754
|
+
const handleRemove = (key) => {
|
|
2755
|
+
if (items.length <= minItems) return;
|
|
2756
|
+
emitChange(items.filter((item) => item._key !== key));
|
|
2757
|
+
};
|
|
2758
|
+
const handleItemChange = (key, fieldSlug, fieldValue) => {
|
|
2759
|
+
emitChange(items.map((item) => item._key === key ? {
|
|
2760
|
+
...item,
|
|
2761
|
+
[fieldSlug]: fieldValue
|
|
2762
|
+
} : item));
|
|
2763
|
+
};
|
|
2764
|
+
const handleDragEnd = (event) => {
|
|
2765
|
+
const { active, over } = event;
|
|
2766
|
+
if (!over || active.id === over.id) return;
|
|
2767
|
+
const oldIndex = items.findIndex((item) => item._key === active.id);
|
|
2768
|
+
const newIndex = items.findIndex((item) => item._key === over.id);
|
|
2769
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
2770
|
+
emitChange(arrayMove(items, oldIndex, newIndex));
|
|
2771
|
+
};
|
|
2772
|
+
const toggleCollapse = (key) => {
|
|
2773
|
+
setCollapsedItems((prev) => {
|
|
2774
|
+
const next = new Set(prev);
|
|
2775
|
+
if (next.has(key)) next.delete(key);
|
|
2776
|
+
else next.add(key);
|
|
2777
|
+
return next;
|
|
2778
|
+
});
|
|
2779
|
+
};
|
|
2780
|
+
const canAdd = !maxItems || items.length < maxItems;
|
|
2781
|
+
const canRemove = items.length > minItems;
|
|
2782
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2783
|
+
className: "space-y-2",
|
|
2784
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2785
|
+
className: "flex items-center justify-between",
|
|
2786
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
2787
|
+
htmlFor: id,
|
|
2788
|
+
className: "text-sm font-medium",
|
|
2789
|
+
children: [label, items.length > 0 && /* @__PURE__ */ jsxs("span", {
|
|
2790
|
+
className: "ml-2 text-kumo-subtle font-normal",
|
|
2791
|
+
children: [
|
|
2792
|
+
"(",
|
|
2793
|
+
items.length,
|
|
2794
|
+
" items)"
|
|
2795
|
+
]
|
|
2796
|
+
})]
|
|
2797
|
+
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
2798
|
+
variant: "outline",
|
|
2799
|
+
size: "sm",
|
|
2800
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
2801
|
+
onClick: handleAdd,
|
|
2802
|
+
children: "Add Item"
|
|
2803
|
+
})]
|
|
2804
|
+
}), items.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
2805
|
+
className: "border-2 border-dashed rounded-lg p-6 text-center text-kumo-subtle",
|
|
2806
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
2807
|
+
className: "text-sm",
|
|
2808
|
+
children: "No items yet"
|
|
2809
|
+
}), canAdd && /* @__PURE__ */ jsx(Button, {
|
|
2810
|
+
variant: "outline",
|
|
2811
|
+
size: "sm",
|
|
2812
|
+
className: "mt-2",
|
|
2813
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
2814
|
+
onClick: handleAdd,
|
|
2815
|
+
children: "Add First Item"
|
|
2816
|
+
})]
|
|
2817
|
+
}) : /* @__PURE__ */ jsx(DndContext, {
|
|
2818
|
+
collisionDetection: closestCenter,
|
|
2819
|
+
onDragEnd: handleDragEnd,
|
|
2820
|
+
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
2821
|
+
items: items.map((item) => item._key),
|
|
2822
|
+
strategy: verticalListSortingStrategy,
|
|
2823
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2824
|
+
className: "space-y-2",
|
|
2825
|
+
children: items.map((item, index) => /* @__PURE__ */ jsx(SortableRepeaterItem, {
|
|
2826
|
+
item,
|
|
2827
|
+
index,
|
|
2828
|
+
subFields,
|
|
2829
|
+
isCollapsed: collapsedItems.has(item._key),
|
|
2830
|
+
onToggleCollapse: () => toggleCollapse(item._key),
|
|
2831
|
+
onRemove: canRemove ? () => handleRemove(item._key) : void 0,
|
|
2832
|
+
onChange: (fieldSlug, fieldValue) => handleItemChange(item._key, fieldSlug, fieldValue)
|
|
2833
|
+
}, item._key))
|
|
2834
|
+
})
|
|
2835
|
+
})
|
|
2836
|
+
})]
|
|
2837
|
+
});
|
|
2838
|
+
}
|
|
2839
|
+
function SortableRepeaterItem({ item, index, subFields, isCollapsed, onToggleCollapse, onRemove, onChange }) {
|
|
2840
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: item._key });
|
|
2841
|
+
const style = {
|
|
2842
|
+
transform: CSS.Transform.toString(transform),
|
|
2843
|
+
transition
|
|
2844
|
+
};
|
|
2845
|
+
const summaryField = subFields.find((sf) => sf.type === "string" || sf.type === "text");
|
|
2846
|
+
const summaryLabel = (summaryField ? item[summaryField.slug] || "" : "") || `Item ${index + 1}`;
|
|
2847
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2848
|
+
ref: setNodeRef,
|
|
2849
|
+
style,
|
|
2850
|
+
className: cn("border rounded-lg bg-kumo-base", isDragging && "opacity-50 ring-2 ring-kumo-brand"),
|
|
2851
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2852
|
+
className: "flex items-center gap-2 px-3 py-2 border-b cursor-pointer",
|
|
2853
|
+
onClick: onToggleCollapse,
|
|
2854
|
+
children: [
|
|
2855
|
+
/* @__PURE__ */ jsx(DotsSixVertical, {
|
|
2856
|
+
className: "h-4 w-4 text-kumo-subtle cursor-grab shrink-0",
|
|
2857
|
+
...attributes,
|
|
2858
|
+
...listeners,
|
|
2859
|
+
onClick: (e) => e.stopPropagation()
|
|
2860
|
+
}),
|
|
2861
|
+
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" }),
|
|
2862
|
+
/* @__PURE__ */ jsx("span", {
|
|
2863
|
+
className: "text-sm font-medium flex-1 truncate",
|
|
2864
|
+
children: summaryLabel
|
|
2865
|
+
}),
|
|
2866
|
+
onRemove && /* @__PURE__ */ jsx(Button, {
|
|
2867
|
+
variant: "ghost",
|
|
2868
|
+
shape: "square",
|
|
2869
|
+
onClick: (e) => {
|
|
2870
|
+
e.stopPropagation();
|
|
2871
|
+
onRemove();
|
|
2872
|
+
},
|
|
2873
|
+
"aria-label": `Remove item ${index + 1}`,
|
|
2874
|
+
children: /* @__PURE__ */ jsx(Trash, { className: "h-3.5 w-3.5 text-kumo-danger" })
|
|
2875
|
+
})
|
|
2876
|
+
]
|
|
2877
|
+
}), !isCollapsed && /* @__PURE__ */ jsx("div", {
|
|
2878
|
+
className: "p-3 space-y-3",
|
|
2879
|
+
children: subFields.map((sf) => /* @__PURE__ */ jsx(SubFieldInput, {
|
|
2880
|
+
subField: sf,
|
|
2881
|
+
value: item[sf.slug],
|
|
2882
|
+
onChange: (v) => onChange(sf.slug, v)
|
|
2883
|
+
}, sf.slug))
|
|
2884
|
+
})]
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
function SubFieldInput({ subField, value, onChange }) {
|
|
2888
|
+
switch (subField.type) {
|
|
2889
|
+
case "string": return /* @__PURE__ */ jsx(Input, {
|
|
2890
|
+
label: subField.label,
|
|
2891
|
+
value: typeof value === "string" ? value : "",
|
|
2892
|
+
onChange: (e) => onChange(e.target.value),
|
|
2893
|
+
required: subField.required
|
|
2894
|
+
});
|
|
2895
|
+
case "text": return /* @__PURE__ */ jsx(InputArea, {
|
|
2896
|
+
label: subField.label,
|
|
2897
|
+
value: typeof value === "string" ? value : "",
|
|
2898
|
+
onChange: (e) => onChange(e.target.value),
|
|
2899
|
+
required: subField.required,
|
|
2900
|
+
rows: 3
|
|
2901
|
+
});
|
|
2902
|
+
case "number":
|
|
2903
|
+
case "integer": return /* @__PURE__ */ jsx(Input, {
|
|
2904
|
+
label: subField.label,
|
|
2905
|
+
type: "number",
|
|
2906
|
+
value: typeof value === "number" ? String(value) : "",
|
|
2907
|
+
onChange: (e) => onChange(e.target.value ? Number(e.target.value) : null),
|
|
2908
|
+
required: subField.required,
|
|
2909
|
+
step: subField.type === "integer" ? "1" : "any"
|
|
2910
|
+
});
|
|
2911
|
+
case "boolean": return /* @__PURE__ */ jsxs("label", {
|
|
2912
|
+
className: "flex items-center gap-2",
|
|
2913
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
2914
|
+
type: "checkbox",
|
|
2915
|
+
checked: Boolean(value),
|
|
2916
|
+
onChange: (e) => onChange(e.target.checked)
|
|
2917
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2918
|
+
className: "text-sm",
|
|
2919
|
+
children: subField.label
|
|
2920
|
+
})]
|
|
2921
|
+
});
|
|
2922
|
+
case "datetime": return /* @__PURE__ */ jsx(Input, {
|
|
2923
|
+
label: subField.label,
|
|
2924
|
+
type: "datetime-local",
|
|
2925
|
+
value: typeof value === "string" ? value : "",
|
|
2926
|
+
onChange: (e) => onChange(e.target.value),
|
|
2927
|
+
required: subField.required
|
|
2928
|
+
});
|
|
2929
|
+
case "select": return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
2930
|
+
className: "text-sm font-medium",
|
|
2931
|
+
children: subField.label
|
|
2932
|
+
}), /* @__PURE__ */ jsxs("select", {
|
|
2933
|
+
className: "w-full mt-1 rounded-md border px-3 py-2 text-sm",
|
|
2934
|
+
value: typeof value === "string" ? value : "",
|
|
2935
|
+
onChange: (e) => onChange(e.target.value),
|
|
2936
|
+
required: subField.required,
|
|
2937
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
2938
|
+
value: "",
|
|
2939
|
+
children: "Select..."
|
|
2940
|
+
}), subField.options?.map((opt) => /* @__PURE__ */ jsx("option", {
|
|
2941
|
+
value: opt,
|
|
2942
|
+
children: opt
|
|
2943
|
+
}, opt))]
|
|
2944
|
+
})] });
|
|
2945
|
+
default: return /* @__PURE__ */ jsx(Input, {
|
|
2946
|
+
label: subField.label,
|
|
2947
|
+
value: typeof value === "string" ? value : "",
|
|
2948
|
+
onChange: (e) => onChange(e.target.value)
|
|
2949
|
+
});
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2667
2953
|
//#endregion
|
|
2668
2954
|
//#region src/lib/hooks.ts
|
|
2669
2955
|
/**
|
|
@@ -7185,6 +7471,74 @@ function SaveButton({ isDirty, isSaving, className, disabled, ...props }) {
|
|
|
7185
7471
|
});
|
|
7186
7472
|
}
|
|
7187
7473
|
|
|
7474
|
+
//#endregion
|
|
7475
|
+
//#region src/components/SeoImageField.tsx
|
|
7476
|
+
/**
|
|
7477
|
+
* SEO OG Image field for the content editor.
|
|
7478
|
+
*
|
|
7479
|
+
* Renders an image picker (reusing MediaPickerModal) that stores the
|
|
7480
|
+
* selected image URL in `seo.image`. Designed to sit next to the
|
|
7481
|
+
* Featured Image field in a two-column grid.
|
|
7482
|
+
*/
|
|
7483
|
+
function SeoImageField({ seo, onChange }) {
|
|
7484
|
+
const [pickerOpen, setPickerOpen] = React.useState(false);
|
|
7485
|
+
const imageUrl = seo?.image || null;
|
|
7486
|
+
const handleSelect = (item) => {
|
|
7487
|
+
onChange({ image: !item.provider || item.provider === "local" ? `/_emdash/api/media/file/${item.storageKey || item.id}` : item.url });
|
|
7488
|
+
};
|
|
7489
|
+
const handleRemove = () => {
|
|
7490
|
+
onChange({ image: null });
|
|
7491
|
+
};
|
|
7492
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
7493
|
+
/* @__PURE__ */ jsx(Label, { children: "OG Image" }),
|
|
7494
|
+
imageUrl ? /* @__PURE__ */ jsxs("div", {
|
|
7495
|
+
className: "mt-2 relative group",
|
|
7496
|
+
children: [/* @__PURE__ */ jsx("img", {
|
|
7497
|
+
src: imageUrl,
|
|
7498
|
+
alt: "",
|
|
7499
|
+
className: "max-h-48 rounded-lg border object-cover"
|
|
7500
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
7501
|
+
className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
|
|
7502
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
7503
|
+
type: "button",
|
|
7504
|
+
size: "sm",
|
|
7505
|
+
variant: "secondary",
|
|
7506
|
+
onClick: () => setPickerOpen(true),
|
|
7507
|
+
children: "Change"
|
|
7508
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
7509
|
+
type: "button",
|
|
7510
|
+
shape: "square",
|
|
7511
|
+
variant: "destructive",
|
|
7512
|
+
className: "h-8 w-8",
|
|
7513
|
+
onClick: handleRemove,
|
|
7514
|
+
"aria-label": "Remove image",
|
|
7515
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
7516
|
+
})]
|
|
7517
|
+
})]
|
|
7518
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
7519
|
+
type: "button",
|
|
7520
|
+
variant: "outline",
|
|
7521
|
+
className: "mt-2 w-full h-32 border-dashed",
|
|
7522
|
+
onClick: () => setPickerOpen(true),
|
|
7523
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
7524
|
+
className: "flex flex-col items-center gap-2 text-kumo-subtle",
|
|
7525
|
+
children: [/* @__PURE__ */ jsx(Image$1, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: "Select OG image" })]
|
|
7526
|
+
})
|
|
7527
|
+
}),
|
|
7528
|
+
/* @__PURE__ */ jsx("p", {
|
|
7529
|
+
className: "text-xs text-kumo-subtle mt-1",
|
|
7530
|
+
children: "Image shown when this page is shared on social media"
|
|
7531
|
+
}),
|
|
7532
|
+
/* @__PURE__ */ jsx(MediaPickerModal, {
|
|
7533
|
+
open: pickerOpen,
|
|
7534
|
+
onOpenChange: setPickerOpen,
|
|
7535
|
+
onSelect: handleSelect,
|
|
7536
|
+
mimeTypeFilter: "image/",
|
|
7537
|
+
title: "Select OG Image"
|
|
7538
|
+
})
|
|
7539
|
+
] });
|
|
7540
|
+
}
|
|
7541
|
+
|
|
7188
7542
|
//#endregion
|
|
7189
7543
|
//#region src/components/SeoPanel.tsx
|
|
7190
7544
|
/**
|
|
@@ -7509,7 +7863,7 @@ function formatScheduledDate(dateStr) {
|
|
|
7509
7863
|
/**
|
|
7510
7864
|
* Content editor with dynamic field rendering
|
|
7511
7865
|
*/
|
|
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 }) {
|
|
7866
|
+
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
7867
|
const [formData, setFormData] = React.useState(item?.data || {});
|
|
7514
7868
|
const [slug, setSlug] = React.useState(item?.slug || "");
|
|
7515
7869
|
const [slugTouched, setSlugTouched] = React.useState(!!item?.slug);
|
|
@@ -7624,15 +7978,16 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
7624
7978
|
});
|
|
7625
7979
|
};
|
|
7626
7980
|
const [isLoadingPreview, setIsLoadingPreview] = React.useState(false);
|
|
7981
|
+
const urlPattern = manifest?.collections[collection]?.urlPattern;
|
|
7627
7982
|
const handlePreview = async () => {
|
|
7628
7983
|
if (!item?.id) return;
|
|
7629
7984
|
setIsLoadingPreview(true);
|
|
7630
7985
|
try {
|
|
7631
7986
|
const result = await getPreviewUrl(collection, item.id);
|
|
7632
7987
|
if (result?.url) window.open(result.url, "_blank", "noopener,noreferrer");
|
|
7633
|
-
else window.open(
|
|
7988
|
+
else window.open(contentUrl(collection, slug || item.id, urlPattern), "_blank", "noopener,noreferrer");
|
|
7634
7989
|
} catch {
|
|
7635
|
-
window.open(
|
|
7990
|
+
window.open(contentUrl(collection, slug || item?.id || "", urlPattern), "_blank", "noopener,noreferrer");
|
|
7636
7991
|
} finally {
|
|
7637
7992
|
setIsLoadingPreview(false);
|
|
7638
7993
|
}
|
|
@@ -7743,7 +8098,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
7743
8098
|
"aria-hidden": "true"
|
|
7744
8099
|
})
|
|
7745
8100
|
}),
|
|
7746
|
-
!isNew && /* @__PURE__ */ jsx(Button, {
|
|
8101
|
+
!isNew && supportsPreview && /* @__PURE__ */ jsx(Button, {
|
|
7747
8102
|
variant: "outline",
|
|
7748
8103
|
type: "button",
|
|
7749
8104
|
onClick: handlePreview,
|
|
@@ -7756,58 +8111,71 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
7756
8111
|
isDirty,
|
|
7757
8112
|
isSaving: isSaving || false
|
|
7758
8113
|
}),
|
|
7759
|
-
!isNew && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
8114
|
+
!isNew && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
8115
|
+
supportsDrafts && hasPendingChanges && onDiscardDraft && /* @__PURE__ */ jsxs(Dialog.Root, {
|
|
8116
|
+
disablePointerDismissal: true,
|
|
8117
|
+
children: [/* @__PURE__ */ jsx(Dialog.Trigger, { render: (p) => /* @__PURE__ */ jsx(Button, {
|
|
8118
|
+
...p,
|
|
8119
|
+
type: "button",
|
|
8120
|
+
variant: "outline",
|
|
8121
|
+
size: "sm",
|
|
8122
|
+
icon: /* @__PURE__ */ jsx(X, {}),
|
|
8123
|
+
children: "Discard changes"
|
|
8124
|
+
}) }), /* @__PURE__ */ jsxs(Dialog, {
|
|
8125
|
+
className: "p-6",
|
|
8126
|
+
size: "sm",
|
|
8127
|
+
children: [
|
|
8128
|
+
/* @__PURE__ */ jsx(Dialog.Title, {
|
|
8129
|
+
className: "text-lg font-semibold",
|
|
8130
|
+
children: "Discard draft changes?"
|
|
8131
|
+
}),
|
|
8132
|
+
/* @__PURE__ */ jsx(Dialog.Description, {
|
|
8133
|
+
className: "text-kumo-subtle",
|
|
8134
|
+
children: "This will revert to the published version. Your draft changes will be lost."
|
|
8135
|
+
}),
|
|
8136
|
+
/* @__PURE__ */ jsxs("div", {
|
|
8137
|
+
className: "mt-6 flex justify-end gap-2",
|
|
8138
|
+
children: [/* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
|
|
8139
|
+
...p,
|
|
8140
|
+
variant: "secondary",
|
|
8141
|
+
children: "Cancel"
|
|
8142
|
+
}) }), /* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
|
|
8143
|
+
...p,
|
|
8144
|
+
variant: "destructive",
|
|
8145
|
+
onClick: onDiscardDraft,
|
|
8146
|
+
children: "Discard changes"
|
|
8147
|
+
}) })]
|
|
8148
|
+
})
|
|
8149
|
+
]
|
|
8150
|
+
})]
|
|
8151
|
+
}),
|
|
8152
|
+
isLive ? /* @__PURE__ */ jsx(Fragment, { children: hasPendingChanges ? /* @__PURE__ */ jsx(Button, {
|
|
8153
|
+
type: "button",
|
|
8154
|
+
variant: "primary",
|
|
8155
|
+
onClick: onPublish,
|
|
8156
|
+
children: "Publish changes"
|
|
8157
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
7763
8158
|
type: "button",
|
|
7764
8159
|
variant: "outline",
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
children:
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
variant: "secondary",
|
|
7785
|
-
children: "Cancel"
|
|
7786
|
-
}) }), /* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
|
|
7787
|
-
...p,
|
|
7788
|
-
variant: "destructive",
|
|
7789
|
-
onClick: onDiscardDraft,
|
|
7790
|
-
children: "Discard changes"
|
|
7791
|
-
}) })]
|
|
7792
|
-
})
|
|
7793
|
-
]
|
|
7794
|
-
})]
|
|
7795
|
-
}), isLive ? /* @__PURE__ */ jsx(Fragment, { children: hasPendingChanges ? /* @__PURE__ */ jsx(Button, {
|
|
7796
|
-
type: "button",
|
|
7797
|
-
variant: "primary",
|
|
7798
|
-
onClick: onPublish,
|
|
7799
|
-
children: "Publish changes"
|
|
7800
|
-
}) : /* @__PURE__ */ jsx(Button, {
|
|
7801
|
-
type: "button",
|
|
7802
|
-
variant: "outline",
|
|
7803
|
-
onClick: onUnpublish,
|
|
7804
|
-
children: "Unpublish"
|
|
7805
|
-
}) }) : /* @__PURE__ */ jsx(Button, {
|
|
7806
|
-
type: "button",
|
|
7807
|
-
variant: "secondary",
|
|
7808
|
-
onClick: onPublish,
|
|
7809
|
-
children: "Publish"
|
|
7810
|
-
})] })
|
|
8160
|
+
onClick: onUnpublish,
|
|
8161
|
+
children: "Unpublish"
|
|
8162
|
+
}) }) : /* @__PURE__ */ jsx(Button, {
|
|
8163
|
+
type: "button",
|
|
8164
|
+
variant: "secondary",
|
|
8165
|
+
onClick: onPublish,
|
|
8166
|
+
children: "Publish"
|
|
8167
|
+
}),
|
|
8168
|
+
isLive && item?.slug && /* @__PURE__ */ jsxs("a", {
|
|
8169
|
+
href: contentUrl(collection, item.slug, urlPattern),
|
|
8170
|
+
target: "_blank",
|
|
8171
|
+
rel: "noopener noreferrer",
|
|
8172
|
+
className: buttonVariants({ variant: "outline" }),
|
|
8173
|
+
children: [/* @__PURE__ */ jsx(ArrowSquareOut, {
|
|
8174
|
+
className: "mr-2 h-4 w-4",
|
|
8175
|
+
"aria-hidden": "true"
|
|
8176
|
+
}), "Live View"]
|
|
8177
|
+
})
|
|
8178
|
+
] })
|
|
7811
8179
|
]
|
|
7812
8180
|
})]
|
|
7813
8181
|
}), /* @__PURE__ */ jsxs("div", {
|
|
@@ -7818,18 +8186,28 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
7818
8186
|
className: cn("rounded-lg border bg-kumo-base p-6", isDistractionFree && "border-0 bg-transparent p-0"),
|
|
7819
8187
|
children: /* @__PURE__ */ jsx("div", {
|
|
7820
8188
|
className: "space-y-4",
|
|
7821
|
-
children: Object.entries(fields).map(([name, field]) =>
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
8189
|
+
children: Object.entries(fields).map(([name, field]) => {
|
|
8190
|
+
const fieldEl = /* @__PURE__ */ jsx(FieldRenderer, {
|
|
8191
|
+
name,
|
|
8192
|
+
field,
|
|
8193
|
+
value: formData[name],
|
|
8194
|
+
onChange: handleFieldChange,
|
|
8195
|
+
onEditorReady: field.kind === "portableText" ? setPortableTextEditor : void 0,
|
|
8196
|
+
minimal: isDistractionFree,
|
|
8197
|
+
pluginBlocks,
|
|
8198
|
+
onBlockSidebarOpen: field.kind === "portableText" ? handleBlockSidebarOpen : void 0,
|
|
8199
|
+
onBlockSidebarClose: field.kind === "portableText" ? handleBlockSidebarClose : void 0,
|
|
8200
|
+
manifest
|
|
8201
|
+
}, name);
|
|
8202
|
+
if (name === "featured_image" && field.kind === "image" && hasSeo && !isNew && onSeoChange) return /* @__PURE__ */ jsxs("div", {
|
|
8203
|
+
className: "grid grid-cols-1 gap-6 md:grid-cols-2",
|
|
8204
|
+
children: [/* @__PURE__ */ jsx("div", { children: fieldEl }), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(SeoImageField, {
|
|
8205
|
+
seo: item?.seo,
|
|
8206
|
+
onChange: onSeoChange
|
|
8207
|
+
}) })]
|
|
8208
|
+
}, `${name}-with-seo`);
|
|
8209
|
+
return fieldEl;
|
|
8210
|
+
})
|
|
7833
8211
|
})
|
|
7834
8212
|
})
|
|
7835
8213
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -8204,6 +8582,25 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
8204
8582
|
}, opt.value))
|
|
8205
8583
|
});
|
|
8206
8584
|
}
|
|
8585
|
+
case "multiSelect": {
|
|
8586
|
+
const selected = Array.isArray(value) ? value : [];
|
|
8587
|
+
return /* @__PURE__ */ jsxs("fieldset", { children: [/* @__PURE__ */ jsx(Label, {
|
|
8588
|
+
className: labelClass,
|
|
8589
|
+
children: label
|
|
8590
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
8591
|
+
className: "mt-2 flex flex-wrap gap-x-4 gap-y-2",
|
|
8592
|
+
children: field.options?.map((opt) => {
|
|
8593
|
+
const isChecked = selected.includes(opt.value);
|
|
8594
|
+
return /* @__PURE__ */ jsx(Checkbox, {
|
|
8595
|
+
label: opt.label,
|
|
8596
|
+
checked: isChecked,
|
|
8597
|
+
onCheckedChange: (checked) => {
|
|
8598
|
+
handleChange(checked ? [...selected, opt.value] : selected.filter((v) => v !== opt.value));
|
|
8599
|
+
}
|
|
8600
|
+
}, opt.value);
|
|
8601
|
+
})
|
|
8602
|
+
})] });
|
|
8603
|
+
}
|
|
8207
8604
|
case "datetime": return /* @__PURE__ */ jsx(Input, {
|
|
8208
8605
|
label,
|
|
8209
8606
|
id,
|
|
@@ -8214,10 +8611,25 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
8214
8611
|
});
|
|
8215
8612
|
case "image": return /* @__PURE__ */ jsx(ImageFieldRenderer, {
|
|
8216
8613
|
label,
|
|
8614
|
+
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
8615
|
value: value != null && typeof value === "object" ? value : void 0,
|
|
8218
8616
|
onChange: handleChange,
|
|
8219
8617
|
required: field.required
|
|
8220
8618
|
});
|
|
8619
|
+
case "repeater": {
|
|
8620
|
+
const validation = field.validation;
|
|
8621
|
+
const subFields = validation?.subFields ?? [];
|
|
8622
|
+
return /* @__PURE__ */ jsx(RepeaterField, {
|
|
8623
|
+
label,
|
|
8624
|
+
id,
|
|
8625
|
+
value,
|
|
8626
|
+
onChange: handleChange,
|
|
8627
|
+
required: field.required,
|
|
8628
|
+
subFields,
|
|
8629
|
+
minItems: typeof validation?.minItems === "number" ? validation.minItems : void 0,
|
|
8630
|
+
maxItems: typeof validation?.maxItems === "number" ? validation.maxItems : void 0
|
|
8631
|
+
});
|
|
8632
|
+
}
|
|
8221
8633
|
default: return /* @__PURE__ */ jsx(Input, {
|
|
8222
8634
|
label,
|
|
8223
8635
|
id,
|
|
@@ -8227,7 +8639,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
|
|
|
8227
8639
|
});
|
|
8228
8640
|
}
|
|
8229
8641
|
}
|
|
8230
|
-
function ImageFieldRenderer({ label, value, onChange, required }) {
|
|
8642
|
+
function ImageFieldRenderer({ label, description, value, onChange, required }) {
|
|
8231
8643
|
const [pickerOpen, setPickerOpen] = React.useState(false);
|
|
8232
8644
|
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
8645
|
const handleSelect = (item) => {
|
|
@@ -8291,6 +8703,10 @@ function ImageFieldRenderer({ label, value, onChange, required }) {
|
|
|
8291
8703
|
mimeTypeFilter: "image/",
|
|
8292
8704
|
title: `Select ${label}`
|
|
8293
8705
|
}),
|
|
8706
|
+
description && /* @__PURE__ */ jsx("p", {
|
|
8707
|
+
className: "text-xs text-kumo-subtle mt-1",
|
|
8708
|
+
children: description
|
|
8709
|
+
}),
|
|
8294
8710
|
required && !displayUrl && /* @__PURE__ */ jsx("p", {
|
|
8295
8711
|
className: "text-sm text-kumo-danger mt-1",
|
|
8296
8712
|
children: "This field is required"
|
|
@@ -8651,7 +9067,7 @@ function getItemTitle$1(item) {
|
|
|
8651
9067
|
/**
|
|
8652
9068
|
* Content list view with table display and trash tab
|
|
8653
9069
|
*/
|
|
8654
|
-
function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n, activeLocale, onLocaleChange }) {
|
|
9070
|
+
function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n, activeLocale, onLocaleChange, urlPattern }) {
|
|
8655
9071
|
const [activeTab, setActiveTab] = React.useState("all");
|
|
8656
9072
|
const [searchQuery, setSearchQuery] = React.useState("");
|
|
8657
9073
|
const [page, setPage] = React.useState(0);
|
|
@@ -8793,7 +9209,8 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
8793
9209
|
collection,
|
|
8794
9210
|
onDelete,
|
|
8795
9211
|
onDuplicate,
|
|
8796
|
-
showLocale: !!i18n
|
|
9212
|
+
showLocale: !!i18n,
|
|
9213
|
+
urlPattern
|
|
8797
9214
|
}, item.id)) })]
|
|
8798
9215
|
})
|
|
8799
9216
|
}),
|
|
@@ -8897,7 +9314,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
8897
9314
|
]
|
|
8898
9315
|
});
|
|
8899
9316
|
}
|
|
8900
|
-
function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale }) {
|
|
9317
|
+
function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale, urlPattern }) {
|
|
8901
9318
|
const title = getItemTitle$1(item);
|
|
8902
9319
|
const date = new Date(item.updatedAt || item.createdAt);
|
|
8903
9320
|
return /* @__PURE__ */ jsxs("tr", {
|
|
@@ -8938,6 +9355,20 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale }
|
|
|
8938
9355
|
children: /* @__PURE__ */ jsxs("div", {
|
|
8939
9356
|
className: "flex items-center justify-end space-x-1",
|
|
8940
9357
|
children: [
|
|
9358
|
+
item.status === "published" && item.slug && /* @__PURE__ */ jsx("a", {
|
|
9359
|
+
href: contentUrl(collection, item.slug, urlPattern),
|
|
9360
|
+
target: "_blank",
|
|
9361
|
+
rel: "noopener noreferrer",
|
|
9362
|
+
"aria-label": `View published ${title}`,
|
|
9363
|
+
className: buttonVariants({
|
|
9364
|
+
variant: "ghost",
|
|
9365
|
+
shape: "square"
|
|
9366
|
+
}),
|
|
9367
|
+
children: /* @__PURE__ */ jsx(ArrowSquareOut, {
|
|
9368
|
+
className: "h-4 w-4",
|
|
9369
|
+
"aria-hidden": "true"
|
|
9370
|
+
})
|
|
9371
|
+
}),
|
|
8941
9372
|
/* @__PURE__ */ jsx(Link$1, {
|
|
8942
9373
|
to: "/content/$collection/$id",
|
|
8943
9374
|
params: {
|
|
@@ -9192,6 +9623,12 @@ const FIELD_TYPES = [
|
|
|
9192
9623
|
label: "Slug",
|
|
9193
9624
|
description: "URL-friendly identifier",
|
|
9194
9625
|
icon: Link$2
|
|
9626
|
+
},
|
|
9627
|
+
{
|
|
9628
|
+
type: "repeater",
|
|
9629
|
+
label: "Repeater",
|
|
9630
|
+
description: "Repeating group of fields",
|
|
9631
|
+
icon: Rows
|
|
9195
9632
|
}
|
|
9196
9633
|
];
|
|
9197
9634
|
function getInitialFormState(field) {
|
|
@@ -9208,7 +9645,10 @@ function getInitialFormState(field) {
|
|
|
9208
9645
|
min: field.validation?.min?.toString() ?? "",
|
|
9209
9646
|
max: field.validation?.max?.toString() ?? "",
|
|
9210
9647
|
pattern: field.validation?.pattern ?? "",
|
|
9211
|
-
options: field.validation?.options?.join("\n") ?? ""
|
|
9648
|
+
options: field.validation?.options?.join("\n") ?? "",
|
|
9649
|
+
subFields: field.validation?.subFields ? field.validation.subFields : [],
|
|
9650
|
+
minItems: field.validation?.minItems?.toString() ?? "",
|
|
9651
|
+
maxItems: field.validation?.maxItems?.toString() ?? ""
|
|
9212
9652
|
};
|
|
9213
9653
|
return {
|
|
9214
9654
|
step: "type",
|
|
@@ -9223,7 +9663,10 @@ function getInitialFormState(field) {
|
|
|
9223
9663
|
min: "",
|
|
9224
9664
|
max: "",
|
|
9225
9665
|
pattern: "",
|
|
9226
|
-
options: ""
|
|
9666
|
+
options: "",
|
|
9667
|
+
subFields: [],
|
|
9668
|
+
minItems: "",
|
|
9669
|
+
maxItems: ""
|
|
9227
9670
|
};
|
|
9228
9671
|
}
|
|
9229
9672
|
/**
|
|
@@ -9267,6 +9710,16 @@ function FieldEditor({ open, onOpenChange, field, onSave, isSaving }) {
|
|
|
9267
9710
|
const optionList = options.split("\n").map((o) => o.trim()).filter(Boolean);
|
|
9268
9711
|
if (optionList.length > 0) validation.options = optionList;
|
|
9269
9712
|
}
|
|
9713
|
+
if (selectedType === "repeater") {
|
|
9714
|
+
if (formState.subFields.length > 0) validation.subFields = formState.subFields.map((sf) => ({
|
|
9715
|
+
slug: sf.slug,
|
|
9716
|
+
type: sf.type,
|
|
9717
|
+
label: sf.label,
|
|
9718
|
+
required: sf.required || void 0
|
|
9719
|
+
}));
|
|
9720
|
+
if (formState.minItems) validation.minItems = parseInt(formState.minItems, 10);
|
|
9721
|
+
if (formState.maxItems) validation.maxItems = parseInt(formState.maxItems, 10);
|
|
9722
|
+
}
|
|
9270
9723
|
onSave({
|
|
9271
9724
|
slug,
|
|
9272
9725
|
label,
|
|
@@ -9466,11 +9919,156 @@ function FieldEditor({ open, onOpenChange, field, onSave, isSaving }) {
|
|
|
9466
9919
|
onChange: (e) => setField("options", e.target.value),
|
|
9467
9920
|
placeholder: "Option 1\nOption 2\nOption 3",
|
|
9468
9921
|
rows: 5
|
|
9922
|
+
}),
|
|
9923
|
+
selectedType === "repeater" && /* @__PURE__ */ jsxs("div", {
|
|
9924
|
+
className: "space-y-4",
|
|
9925
|
+
children: [
|
|
9926
|
+
/* @__PURE__ */ jsxs("div", {
|
|
9927
|
+
className: "flex items-center justify-between",
|
|
9928
|
+
children: [/* @__PURE__ */ jsx("h4", {
|
|
9929
|
+
className: "font-medium text-sm",
|
|
9930
|
+
children: "Sub-Fields"
|
|
9931
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
9932
|
+
variant: "outline",
|
|
9933
|
+
size: "sm",
|
|
9934
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
9935
|
+
onClick: () => setFormState((prev) => ({
|
|
9936
|
+
...prev,
|
|
9937
|
+
subFields: [...prev.subFields, {
|
|
9938
|
+
slug: "",
|
|
9939
|
+
type: "string",
|
|
9940
|
+
label: "",
|
|
9941
|
+
required: false
|
|
9942
|
+
}]
|
|
9943
|
+
})),
|
|
9944
|
+
children: "Add Sub-Field"
|
|
9945
|
+
})]
|
|
9946
|
+
}),
|
|
9947
|
+
formState.subFields.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
9948
|
+
className: "text-sm text-kumo-subtle text-center py-4",
|
|
9949
|
+
children: "Add at least one sub-field to define the repeater structure."
|
|
9950
|
+
}),
|
|
9951
|
+
formState.subFields.map((sf, i) => /* @__PURE__ */ jsxs("div", {
|
|
9952
|
+
className: "flex gap-2 items-start border rounded-lg p-3",
|
|
9953
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
9954
|
+
className: "flex-1 space-y-2",
|
|
9955
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
9956
|
+
className: "grid grid-cols-2 gap-2",
|
|
9957
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
9958
|
+
label: "Label",
|
|
9959
|
+
value: sf.label,
|
|
9960
|
+
onChange: (e) => {
|
|
9961
|
+
const updated = [...formState.subFields];
|
|
9962
|
+
updated[i] = {
|
|
9963
|
+
...sf,
|
|
9964
|
+
label: e.target.value,
|
|
9965
|
+
slug: e.target.value.toLowerCase().replace(SLUG_INVALID_CHARS_REGEX, "_").replace(SLUG_LEADING_TRAILING_REGEX, "")
|
|
9966
|
+
};
|
|
9967
|
+
setFormState((prev) => ({
|
|
9968
|
+
...prev,
|
|
9969
|
+
subFields: updated
|
|
9970
|
+
}));
|
|
9971
|
+
},
|
|
9972
|
+
placeholder: "Field label"
|
|
9973
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
9974
|
+
className: "text-sm font-medium",
|
|
9975
|
+
children: "Type"
|
|
9976
|
+
}), /* @__PURE__ */ jsxs("select", {
|
|
9977
|
+
className: "w-full mt-1 rounded-md border px-3 py-2 text-sm",
|
|
9978
|
+
value: sf.type,
|
|
9979
|
+
onChange: (e) => {
|
|
9980
|
+
const updated = [...formState.subFields];
|
|
9981
|
+
updated[i] = {
|
|
9982
|
+
...sf,
|
|
9983
|
+
type: e.target.value
|
|
9984
|
+
};
|
|
9985
|
+
setFormState((prev) => ({
|
|
9986
|
+
...prev,
|
|
9987
|
+
subFields: updated
|
|
9988
|
+
}));
|
|
9989
|
+
},
|
|
9990
|
+
children: [
|
|
9991
|
+
/* @__PURE__ */ jsx("option", {
|
|
9992
|
+
value: "string",
|
|
9993
|
+
children: "Short Text"
|
|
9994
|
+
}),
|
|
9995
|
+
/* @__PURE__ */ jsx("option", {
|
|
9996
|
+
value: "text",
|
|
9997
|
+
children: "Long Text"
|
|
9998
|
+
}),
|
|
9999
|
+
/* @__PURE__ */ jsx("option", {
|
|
10000
|
+
value: "number",
|
|
10001
|
+
children: "Number"
|
|
10002
|
+
}),
|
|
10003
|
+
/* @__PURE__ */ jsx("option", {
|
|
10004
|
+
value: "integer",
|
|
10005
|
+
children: "Integer"
|
|
10006
|
+
}),
|
|
10007
|
+
/* @__PURE__ */ jsx("option", {
|
|
10008
|
+
value: "boolean",
|
|
10009
|
+
children: "Boolean"
|
|
10010
|
+
}),
|
|
10011
|
+
/* @__PURE__ */ jsx("option", {
|
|
10012
|
+
value: "datetime",
|
|
10013
|
+
children: "Date & Time"
|
|
10014
|
+
}),
|
|
10015
|
+
/* @__PURE__ */ jsx("option", {
|
|
10016
|
+
value: "select",
|
|
10017
|
+
children: "Select"
|
|
10018
|
+
})
|
|
10019
|
+
]
|
|
10020
|
+
})] })]
|
|
10021
|
+
}), /* @__PURE__ */ jsxs("label", {
|
|
10022
|
+
className: "flex items-center gap-2 text-sm",
|
|
10023
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
10024
|
+
type: "checkbox",
|
|
10025
|
+
checked: sf.required,
|
|
10026
|
+
onChange: (e) => {
|
|
10027
|
+
const updated = [...formState.subFields];
|
|
10028
|
+
updated[i] = {
|
|
10029
|
+
...sf,
|
|
10030
|
+
required: e.target.checked
|
|
10031
|
+
};
|
|
10032
|
+
setFormState((prev) => ({
|
|
10033
|
+
...prev,
|
|
10034
|
+
subFields: updated
|
|
10035
|
+
}));
|
|
10036
|
+
}
|
|
10037
|
+
}), "Required"]
|
|
10038
|
+
})]
|
|
10039
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
10040
|
+
variant: "ghost",
|
|
10041
|
+
shape: "square",
|
|
10042
|
+
onClick: () => setFormState((prev) => ({
|
|
10043
|
+
...prev,
|
|
10044
|
+
subFields: prev.subFields.filter((_, j) => j !== i)
|
|
10045
|
+
})),
|
|
10046
|
+
"aria-label": "Remove sub-field",
|
|
10047
|
+
children: /* @__PURE__ */ jsx(Trash, { className: "h-4 w-4 text-kumo-danger" })
|
|
10048
|
+
})]
|
|
10049
|
+
}, i)),
|
|
10050
|
+
/* @__PURE__ */ jsxs("div", {
|
|
10051
|
+
className: "grid grid-cols-2 gap-4",
|
|
10052
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
10053
|
+
label: "Min Items",
|
|
10054
|
+
type: "number",
|
|
10055
|
+
value: formState.minItems,
|
|
10056
|
+
onChange: (e) => setField("minItems", e.target.value),
|
|
10057
|
+
placeholder: "0"
|
|
10058
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
10059
|
+
label: "Max Items",
|
|
10060
|
+
type: "number",
|
|
10061
|
+
value: formState.maxItems,
|
|
10062
|
+
onChange: (e) => setField("maxItems", e.target.value),
|
|
10063
|
+
placeholder: "No limit"
|
|
10064
|
+
})]
|
|
10065
|
+
})
|
|
10066
|
+
]
|
|
9469
10067
|
})
|
|
9470
10068
|
]
|
|
9471
10069
|
}),
|
|
9472
10070
|
step === "config" && /* @__PURE__ */ jsxs("div", {
|
|
9473
|
-
className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
10071
|
+
className: "flex flex-col-reverse gap-2 py-2 sm:flex-row sm:justify-end sm:space-x-2",
|
|
9474
10072
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
9475
10073
|
variant: "outline",
|
|
9476
10074
|
onClick: () => onOpenChange(false),
|
|
@@ -9478,7 +10076,7 @@ function FieldEditor({ open, onOpenChange, field, onSave, isSaving }) {
|
|
|
9478
10076
|
children: "Cancel"
|
|
9479
10077
|
}), /* @__PURE__ */ jsx(Button, {
|
|
9480
10078
|
onClick: handleSave,
|
|
9481
|
-
disabled: !slug || !label || isSaving,
|
|
10079
|
+
disabled: !slug || !label || isSaving || selectedType === "repeater" && formState.subFields.length === 0,
|
|
9482
10080
|
children: isSaving ? "Saving..." : field ? "Update Field" : "Add Field"
|
|
9483
10081
|
})]
|
|
9484
10082
|
})
|
|
@@ -9558,7 +10156,7 @@ const SYSTEM_FIELDS = [
|
|
|
9558
10156
|
/**
|
|
9559
10157
|
* Content Type editor for creating/editing collections
|
|
9560
10158
|
*/
|
|
9561
|
-
function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, onUpdateField, onDeleteField, onReorderFields
|
|
10159
|
+
function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, onUpdateField, onDeleteField, onReorderFields }) {
|
|
9562
10160
|
useNavigate$1();
|
|
9563
10161
|
const [slug, setSlug] = React.useState(collection?.slug ?? "");
|
|
9564
10162
|
const [label, setLabel] = React.useState(collection?.label ?? "");
|
|
@@ -9651,6 +10249,16 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
9651
10249
|
};
|
|
9652
10250
|
const isFromCode = collection?.source === "code";
|
|
9653
10251
|
const fields = collection?.fields ?? [];
|
|
10252
|
+
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
10253
|
+
const handleDragEnd = (event) => {
|
|
10254
|
+
const { active, over } = event;
|
|
10255
|
+
if (!over || active.id === over.id) return;
|
|
10256
|
+
const oldIndex = fields.findIndex((f) => f.id === active.id);
|
|
10257
|
+
const newIndex = fields.findIndex((f) => f.id === over.id);
|
|
10258
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
10259
|
+
const reordered = arrayMove(fields, oldIndex, newIndex);
|
|
10260
|
+
onReorderFields?.(reordered.map((f) => f.slug));
|
|
10261
|
+
};
|
|
9654
10262
|
return /* @__PURE__ */ jsxs("div", {
|
|
9655
10263
|
className: "space-y-6",
|
|
9656
10264
|
children: [
|
|
@@ -9933,14 +10541,23 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
9933
10541
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
9934
10542
|
className: "px-4 py-2 text-xs font-medium text-kumo-subtle uppercase tracking-wider border-b",
|
|
9935
10543
|
children: "Custom Fields"
|
|
9936
|
-
}), /* @__PURE__ */ jsx(
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
10544
|
+
}), /* @__PURE__ */ jsx(DndContext, {
|
|
10545
|
+
sensors,
|
|
10546
|
+
collisionDetection: closestCenter,
|
|
10547
|
+
onDragEnd: handleDragEnd,
|
|
10548
|
+
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
10549
|
+
items: fields.map((f) => f.id),
|
|
10550
|
+
strategy: verticalListSortingStrategy,
|
|
10551
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10552
|
+
className: "divide-y",
|
|
10553
|
+
children: fields.map((field) => /* @__PURE__ */ jsx(FieldRow, {
|
|
10554
|
+
field,
|
|
10555
|
+
isFromCode,
|
|
10556
|
+
onEdit: () => handleEditField(field),
|
|
10557
|
+
onDelete: () => setDeleteFieldTarget(field)
|
|
10558
|
+
}, field.id))
|
|
10559
|
+
})
|
|
10560
|
+
})
|
|
9944
10561
|
})] })
|
|
9945
10562
|
]
|
|
9946
10563
|
})
|
|
@@ -9973,10 +10590,25 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
9973
10590
|
});
|
|
9974
10591
|
}
|
|
9975
10592
|
function FieldRow({ field, isFromCode, onEdit, onDelete }) {
|
|
10593
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
10594
|
+
id: field.id,
|
|
10595
|
+
disabled: isFromCode
|
|
10596
|
+
});
|
|
9976
10597
|
return /* @__PURE__ */ jsxs("div", {
|
|
9977
|
-
|
|
10598
|
+
ref: setNodeRef,
|
|
10599
|
+
style: {
|
|
10600
|
+
transform: CSS.Transform.toString(transform),
|
|
10601
|
+
transition
|
|
10602
|
+
},
|
|
10603
|
+
className: cn("flex items-center px-4 py-3 hover:bg-kumo-tint/25", isDragging && "opacity-50"),
|
|
9978
10604
|
children: [
|
|
9979
|
-
!isFromCode && /* @__PURE__ */ jsx(
|
|
10605
|
+
!isFromCode && /* @__PURE__ */ jsx("button", {
|
|
10606
|
+
...attributes,
|
|
10607
|
+
...listeners,
|
|
10608
|
+
className: "cursor-grab active:cursor-grabbing mr-3",
|
|
10609
|
+
"aria-label": `Drag to reorder ${field.label}`,
|
|
10610
|
+
children: /* @__PURE__ */ jsx(DotsSixVertical, { className: "h-5 w-5 text-kumo-subtle" })
|
|
10611
|
+
}),
|
|
9980
10612
|
/* @__PURE__ */ jsxs("div", {
|
|
9981
10613
|
className: "flex-1 min-w-0",
|
|
9982
10614
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -10875,43 +11507,24 @@ function CheckIcon({ className }) {
|
|
|
10875
11507
|
}
|
|
10876
11508
|
|
|
10877
11509
|
//#endregion
|
|
10878
|
-
//#region src/lib/
|
|
11510
|
+
//#region src/lib/webauthn-environment.ts
|
|
10879
11511
|
/**
|
|
10880
|
-
*
|
|
10881
|
-
|
|
10882
|
-
const DEFAULT_REDIRECT = "/_emdash/admin";
|
|
10883
|
-
/**
|
|
10884
|
-
* Sanitize a redirect URL to prevent open-redirect and javascript: XSS attacks.
|
|
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:`.
|
|
11512
|
+
* WebAuthn is only available in a browser "secure context": HTTPS, or special-cased
|
|
11513
|
+
* loopback hosts such as `http://localhost` / `http://127.0.0.1`.
|
|
10889
11514
|
*
|
|
10890
|
-
*
|
|
11515
|
+
* An origin like `http://emdash.local:8081` resolves to 127.0.0.1 but is still
|
|
11516
|
+
* **not** a secure context, so `PublicKeyCredential` is hidden — the same symptom
|
|
11517
|
+
* as an unsupported browser.
|
|
10891
11518
|
*/
|
|
10892
|
-
function
|
|
10893
|
-
|
|
10894
|
-
return DEFAULT_REDIRECT;
|
|
11519
|
+
function isWebAuthnSecureContext() {
|
|
11520
|
+
return typeof window !== "undefined" && window.isSecureContext;
|
|
10895
11521
|
}
|
|
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);
|
|
11522
|
+
function isPublicKeyCredentialConstructorAvailable() {
|
|
11523
|
+
return typeof window !== "undefined" && window.PublicKeyCredential !== void 0 && typeof window.PublicKeyCredential === "function";
|
|
10901
11524
|
}
|
|
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
|
-
}
|
|
11525
|
+
/** True when the page can use `navigator.credentials` for passkeys. */
|
|
11526
|
+
function isPasskeyEnvironmentUsable() {
|
|
11527
|
+
return isWebAuthnSecureContext() && isPublicKeyCredentialConstructorAvailable();
|
|
10915
11528
|
}
|
|
10916
11529
|
|
|
10917
11530
|
//#endregion
|
|
@@ -10933,16 +11546,10 @@ const BASE64URL_UNDERSCORE_REGEX$1 = /_/g;
|
|
|
10933
11546
|
const BASE64_PLUS_REGEX$1 = /\+/g;
|
|
10934
11547
|
const BASE64_SLASH_REGEX$1 = /\//g;
|
|
10935
11548
|
/**
|
|
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
11549
|
* Check if conditional mediation (autofill) is supported
|
|
10943
11550
|
*/
|
|
10944
11551
|
async function isConditionalMediationSupported() {
|
|
10945
|
-
if (!
|
|
11552
|
+
if (!isPasskeyEnvironmentUsable()) return false;
|
|
10946
11553
|
try {
|
|
10947
11554
|
return await PublicKeyCredential.isConditionalMediationAvailable?.() ?? false;
|
|
10948
11555
|
} catch {
|
|
@@ -10976,7 +11583,8 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
10976
11583
|
const [state, setState] = React.useState({ status: "idle" });
|
|
10977
11584
|
const [email, setEmail] = React.useState("");
|
|
10978
11585
|
const [supportsConditional, setSupportsConditional] = React.useState(false);
|
|
10979
|
-
const isSupported = React.useMemo(() =>
|
|
11586
|
+
const isSupported = React.useMemo(() => isPasskeyEnvironmentUsable(), []);
|
|
11587
|
+
const insecureContext = React.useMemo(() => typeof window !== "undefined" && !isWebAuthnSecureContext(), []);
|
|
10980
11588
|
React.useEffect(() => {
|
|
10981
11589
|
isConditionalMediationSupported().then(setSupportsConditional);
|
|
10982
11590
|
}, []);
|
|
@@ -10984,7 +11592,7 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
10984
11592
|
if (!isSupported) {
|
|
10985
11593
|
setState({
|
|
10986
11594
|
status: "error",
|
|
10987
|
-
message: "WebAuthn is not supported in this browser"
|
|
11595
|
+
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
11596
|
});
|
|
10989
11597
|
return;
|
|
10990
11598
|
}
|
|
@@ -11017,7 +11625,15 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
11017
11625
|
...useConditional && supportsConditional ? { mediation: "conditional" } : {}
|
|
11018
11626
|
};
|
|
11019
11627
|
const rawCredential = await navigator.credentials.get(credentialOptions);
|
|
11020
|
-
if (!rawCredential)
|
|
11628
|
+
if (!rawCredential) {
|
|
11629
|
+
const message = "No credential returned from authenticator";
|
|
11630
|
+
setState({
|
|
11631
|
+
status: "error",
|
|
11632
|
+
message
|
|
11633
|
+
});
|
|
11634
|
+
onError?.(new Error(message));
|
|
11635
|
+
return;
|
|
11636
|
+
}
|
|
11021
11637
|
setState({
|
|
11022
11638
|
status: "loading",
|
|
11023
11639
|
message: "Verifying..."
|
|
@@ -11073,6 +11689,7 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
11073
11689
|
}
|
|
11074
11690
|
}, [
|
|
11075
11691
|
isSupported,
|
|
11692
|
+
insecureContext,
|
|
11076
11693
|
optionsEndpoint,
|
|
11077
11694
|
verifyEndpoint,
|
|
11078
11695
|
email,
|
|
@@ -11084,10 +11701,34 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
11084
11701
|
className: "rounded-lg border border-kumo-danger/50 bg-kumo-danger/10 p-4",
|
|
11085
11702
|
children: [/* @__PURE__ */ jsx("h3", {
|
|
11086
11703
|
className: "font-medium text-kumo-danger",
|
|
11087
|
-
children: "Passkeys Not
|
|
11704
|
+
children: "Passkeys Not Available Here"
|
|
11088
11705
|
}), /* @__PURE__ */ jsx("p", {
|
|
11089
11706
|
className: "mt-1 text-sm text-kumo-subtle",
|
|
11090
|
-
children:
|
|
11707
|
+
children: insecureContext ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11708
|
+
"Passkeys require a ",
|
|
11709
|
+
/* @__PURE__ */ jsx("strong", {
|
|
11710
|
+
className: "text-kumo-default",
|
|
11711
|
+
children: "secure context"
|
|
11712
|
+
}),
|
|
11713
|
+
": use",
|
|
11714
|
+
" ",
|
|
11715
|
+
/* @__PURE__ */ jsx("strong", {
|
|
11716
|
+
className: "text-kumo-default",
|
|
11717
|
+
children: "HTTPS"
|
|
11718
|
+
}),
|
|
11719
|
+
", or open the admin at",
|
|
11720
|
+
" ",
|
|
11721
|
+
/* @__PURE__ */ jsx("strong", {
|
|
11722
|
+
className: "text-kumo-default",
|
|
11723
|
+
children: "http://localhost"
|
|
11724
|
+
}),
|
|
11725
|
+
" (with your dev port). Plain ",
|
|
11726
|
+
/* @__PURE__ */ jsx("code", {
|
|
11727
|
+
className: "text-xs",
|
|
11728
|
+
children: "http://"
|
|
11729
|
+
}),
|
|
11730
|
+
" on a custom hostname is not treated as secure, even on loopback."
|
|
11731
|
+
] }) : /* @__PURE__ */ jsx(Fragment, { children: "Your browser doesn't support passkeys. Please use a modern browser like Chrome, Safari, Firefox, or Edge." })
|
|
11091
11732
|
})]
|
|
11092
11733
|
});
|
|
11093
11734
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -11125,6 +11766,258 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
|
|
|
11125
11766
|
});
|
|
11126
11767
|
}
|
|
11127
11768
|
|
|
11769
|
+
//#endregion
|
|
11770
|
+
//#region src/components/Logo.tsx
|
|
11771
|
+
/**
|
|
11772
|
+
* EmDash icon mark — the rounded-rect em dash symbol.
|
|
11773
|
+
* Used in the sidebar brand and as favicon.
|
|
11774
|
+
*/
|
|
11775
|
+
function LogoIcon(props) {
|
|
11776
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
11777
|
+
viewBox: "0 0 75 75",
|
|
11778
|
+
fill: "none",
|
|
11779
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
11780
|
+
...props,
|
|
11781
|
+
children: [
|
|
11782
|
+
/* @__PURE__ */ jsx("rect", {
|
|
11783
|
+
x: "3",
|
|
11784
|
+
y: "3",
|
|
11785
|
+
width: "69",
|
|
11786
|
+
height: "69",
|
|
11787
|
+
rx: "10.518",
|
|
11788
|
+
stroke: "url(#emdash-icon-border)",
|
|
11789
|
+
strokeWidth: "6"
|
|
11790
|
+
}),
|
|
11791
|
+
/* @__PURE__ */ jsx("rect", {
|
|
11792
|
+
x: "18",
|
|
11793
|
+
y: "34",
|
|
11794
|
+
width: "39.3661",
|
|
11795
|
+
height: "6.56101",
|
|
11796
|
+
fill: "url(#emdash-icon-dash)"
|
|
11797
|
+
}),
|
|
11798
|
+
/* @__PURE__ */ jsxs("defs", { children: [/* @__PURE__ */ jsxs("linearGradient", {
|
|
11799
|
+
id: "emdash-icon-border",
|
|
11800
|
+
x1: "-42.9996",
|
|
11801
|
+
y1: "124",
|
|
11802
|
+
x2: "92.4233",
|
|
11803
|
+
y2: "-41.7456",
|
|
11804
|
+
gradientUnits: "userSpaceOnUse",
|
|
11805
|
+
children: [
|
|
11806
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "#0F006B" }),
|
|
11807
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11808
|
+
offset: "0.0833",
|
|
11809
|
+
stopColor: "#281A81"
|
|
11810
|
+
}),
|
|
11811
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11812
|
+
offset: "0.1667",
|
|
11813
|
+
stopColor: "#5D0C83"
|
|
11814
|
+
}),
|
|
11815
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11816
|
+
offset: "0.25",
|
|
11817
|
+
stopColor: "#911475"
|
|
11818
|
+
}),
|
|
11819
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11820
|
+
offset: "0.3333",
|
|
11821
|
+
stopColor: "#CE2F55"
|
|
11822
|
+
}),
|
|
11823
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11824
|
+
offset: "0.4167",
|
|
11825
|
+
stopColor: "#FF6633"
|
|
11826
|
+
}),
|
|
11827
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11828
|
+
offset: "0.5",
|
|
11829
|
+
stopColor: "#F6821F"
|
|
11830
|
+
}),
|
|
11831
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11832
|
+
offset: "0.5833",
|
|
11833
|
+
stopColor: "#FBAD41"
|
|
11834
|
+
}),
|
|
11835
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11836
|
+
offset: "0.6667",
|
|
11837
|
+
stopColor: "#FFCD89"
|
|
11838
|
+
}),
|
|
11839
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11840
|
+
offset: "0.75",
|
|
11841
|
+
stopColor: "#FFE9CB"
|
|
11842
|
+
}),
|
|
11843
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11844
|
+
offset: "0.8333",
|
|
11845
|
+
stopColor: "#FFF7EC"
|
|
11846
|
+
}),
|
|
11847
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11848
|
+
offset: "0.9167",
|
|
11849
|
+
stopColor: "#FFF8EE"
|
|
11850
|
+
}),
|
|
11851
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11852
|
+
offset: "1",
|
|
11853
|
+
stopColor: "white"
|
|
11854
|
+
})
|
|
11855
|
+
]
|
|
11856
|
+
}), /* @__PURE__ */ jsxs("linearGradient", {
|
|
11857
|
+
id: "emdash-icon-dash",
|
|
11858
|
+
x1: "91.4992",
|
|
11859
|
+
y1: "27.4982",
|
|
11860
|
+
x2: "28.1217",
|
|
11861
|
+
y2: "54.1775",
|
|
11862
|
+
gradientUnits: "userSpaceOnUse",
|
|
11863
|
+
children: [
|
|
11864
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "white" }),
|
|
11865
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11866
|
+
offset: "0.1293",
|
|
11867
|
+
stopColor: "#FFF8EE"
|
|
11868
|
+
}),
|
|
11869
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11870
|
+
offset: "0.6171",
|
|
11871
|
+
stopColor: "#FBAD41"
|
|
11872
|
+
}),
|
|
11873
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11874
|
+
offset: "0.848",
|
|
11875
|
+
stopColor: "#F6821F"
|
|
11876
|
+
}),
|
|
11877
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11878
|
+
offset: "1",
|
|
11879
|
+
stopColor: "#FF6633"
|
|
11880
|
+
})
|
|
11881
|
+
]
|
|
11882
|
+
})] })
|
|
11883
|
+
]
|
|
11884
|
+
});
|
|
11885
|
+
}
|
|
11886
|
+
/**
|
|
11887
|
+
* Full logo lockup — icon + "EmDash" wordmark.
|
|
11888
|
+
* Renders both dark-text and light-text variants, switching via CSS `light-dark()`.
|
|
11889
|
+
*/
|
|
11890
|
+
function LogoLockup({ className, ...props }) {
|
|
11891
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
11892
|
+
viewBox: "0 0 471 118",
|
|
11893
|
+
fill: "none",
|
|
11894
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
11895
|
+
className,
|
|
11896
|
+
role: "img",
|
|
11897
|
+
"aria-label": "EmDash",
|
|
11898
|
+
...props,
|
|
11899
|
+
children: [
|
|
11900
|
+
/* @__PURE__ */ jsx("path", {
|
|
11901
|
+
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",
|
|
11902
|
+
fill: "url(#emdash-lockup-icon)"
|
|
11903
|
+
}),
|
|
11904
|
+
/* @__PURE__ */ jsx("path", {
|
|
11905
|
+
d: "M28.6699 53.366H90.4746V63.6668H28.6699V53.366Z",
|
|
11906
|
+
fill: "url(#emdash-lockup-dash)"
|
|
11907
|
+
}),
|
|
11908
|
+
/* @__PURE__ */ jsx("path", {
|
|
11909
|
+
d: "M154.762 90V27.4834H194.447V35.8449H164.467V54.0844H192.844V62.2293H164.467V81.6385H194.447V90H154.762Z",
|
|
11910
|
+
fill: "currentColor"
|
|
11911
|
+
}),
|
|
11912
|
+
/* @__PURE__ */ jsx("path", {
|
|
11913
|
+
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",
|
|
11914
|
+
fill: "currentColor"
|
|
11915
|
+
}),
|
|
11916
|
+
/* @__PURE__ */ jsx("path", {
|
|
11917
|
+
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",
|
|
11918
|
+
fill: "currentColor"
|
|
11919
|
+
}),
|
|
11920
|
+
/* @__PURE__ */ jsx("path", {
|
|
11921
|
+
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",
|
|
11922
|
+
fill: "currentColor"
|
|
11923
|
+
}),
|
|
11924
|
+
/* @__PURE__ */ jsx("path", {
|
|
11925
|
+
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",
|
|
11926
|
+
fill: "currentColor"
|
|
11927
|
+
}),
|
|
11928
|
+
/* @__PURE__ */ jsx("path", {
|
|
11929
|
+
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",
|
|
11930
|
+
fill: "currentColor"
|
|
11931
|
+
}),
|
|
11932
|
+
/* @__PURE__ */ jsxs("defs", { children: [/* @__PURE__ */ jsxs("linearGradient", {
|
|
11933
|
+
id: "emdash-lockup-icon",
|
|
11934
|
+
x1: "-67.1002",
|
|
11935
|
+
y1: "194.666",
|
|
11936
|
+
x2: "145.514",
|
|
11937
|
+
y2: "-65.5554",
|
|
11938
|
+
gradientUnits: "userSpaceOnUse",
|
|
11939
|
+
children: [
|
|
11940
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "#0F006B" }),
|
|
11941
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11942
|
+
offset: "0.0833",
|
|
11943
|
+
stopColor: "#281A81"
|
|
11944
|
+
}),
|
|
11945
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11946
|
+
offset: "0.1667",
|
|
11947
|
+
stopColor: "#5D0C83"
|
|
11948
|
+
}),
|
|
11949
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11950
|
+
offset: "0.25",
|
|
11951
|
+
stopColor: "#911475"
|
|
11952
|
+
}),
|
|
11953
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11954
|
+
offset: "0.3333",
|
|
11955
|
+
stopColor: "#CE2F55"
|
|
11956
|
+
}),
|
|
11957
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11958
|
+
offset: "0.4167",
|
|
11959
|
+
stopColor: "#FF6633"
|
|
11960
|
+
}),
|
|
11961
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11962
|
+
offset: "0.5",
|
|
11963
|
+
stopColor: "#F6821F"
|
|
11964
|
+
}),
|
|
11965
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11966
|
+
offset: "0.5833",
|
|
11967
|
+
stopColor: "#FBAD41"
|
|
11968
|
+
}),
|
|
11969
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11970
|
+
offset: "0.6667",
|
|
11971
|
+
stopColor: "#FFCD89"
|
|
11972
|
+
}),
|
|
11973
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11974
|
+
offset: "0.75",
|
|
11975
|
+
stopColor: "#FFE9CB"
|
|
11976
|
+
}),
|
|
11977
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11978
|
+
offset: "0.8333",
|
|
11979
|
+
stopColor: "#FFF7EC"
|
|
11980
|
+
}),
|
|
11981
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11982
|
+
offset: "0.9167",
|
|
11983
|
+
stopColor: "#FFF8EE"
|
|
11984
|
+
}),
|
|
11985
|
+
/* @__PURE__ */ jsx("stop", {
|
|
11986
|
+
offset: "1",
|
|
11987
|
+
stopColor: "white"
|
|
11988
|
+
})
|
|
11989
|
+
]
|
|
11990
|
+
}), /* @__PURE__ */ jsxs("linearGradient", {
|
|
11991
|
+
id: "emdash-lockup-dash",
|
|
11992
|
+
x1: "144.064",
|
|
11993
|
+
y1: "43.1581",
|
|
11994
|
+
x2: "44.5609",
|
|
11995
|
+
y2: "85.0447",
|
|
11996
|
+
gradientUnits: "userSpaceOnUse",
|
|
11997
|
+
children: [
|
|
11998
|
+
/* @__PURE__ */ jsx("stop", { stopColor: "white" }),
|
|
11999
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12000
|
+
offset: "0.1293",
|
|
12001
|
+
stopColor: "#FFF8EE"
|
|
12002
|
+
}),
|
|
12003
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12004
|
+
offset: "0.6171",
|
|
12005
|
+
stopColor: "#FBAD41"
|
|
12006
|
+
}),
|
|
12007
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12008
|
+
offset: "0.848",
|
|
12009
|
+
stopColor: "#F6821F"
|
|
12010
|
+
}),
|
|
12011
|
+
/* @__PURE__ */ jsx("stop", {
|
|
12012
|
+
offset: "1",
|
|
12013
|
+
stopColor: "#FF6633"
|
|
12014
|
+
})
|
|
12015
|
+
]
|
|
12016
|
+
})] })
|
|
12017
|
+
]
|
|
12018
|
+
});
|
|
12019
|
+
}
|
|
12020
|
+
|
|
11128
12021
|
//#endregion
|
|
11129
12022
|
//#region src/components/LoginPage.tsx
|
|
11130
12023
|
/**
|
|
@@ -11323,10 +12216,7 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
11323
12216
|
className: "min-h-screen flex items-center justify-center bg-kumo-base p-4",
|
|
11324
12217
|
children: /* @__PURE__ */ jsxs("div", {
|
|
11325
12218
|
className: "text-center",
|
|
11326
|
-
children: [/* @__PURE__ */ jsx("
|
|
11327
|
-
className: "text-4xl font-bold mb-4",
|
|
11328
|
-
children: "— EmDash"
|
|
11329
|
-
}), /* @__PURE__ */ jsx(Loader, {})]
|
|
12219
|
+
children: [/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-4" }), /* @__PURE__ */ jsx(Loader, {})]
|
|
11330
12220
|
})
|
|
11331
12221
|
});
|
|
11332
12222
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -11336,10 +12226,7 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
|
|
|
11336
12226
|
children: [
|
|
11337
12227
|
/* @__PURE__ */ jsxs("div", {
|
|
11338
12228
|
className: "text-center mb-8",
|
|
11339
|
-
children: [/* @__PURE__ */ jsx("
|
|
11340
|
-
className: "text-4xl font-bold mb-2",
|
|
11341
|
-
children: "— EmDash"
|
|
11342
|
-
}), /* @__PURE__ */ jsxs("h1", {
|
|
12229
|
+
children: [/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-2" }), /* @__PURE__ */ jsxs("h1", {
|
|
11343
12230
|
className: "text-2xl font-semibold text-kumo-default",
|
|
11344
12231
|
children: [method === "passkey" && "Sign in to your site", method === "magic-link" && "Sign in with email"]
|
|
11345
12232
|
})]
|
|
@@ -15768,12 +16655,24 @@ function AllowedDomainsSettings() {
|
|
|
15768
16655
|
const handleDelete = () => {
|
|
15769
16656
|
if (deletingDomain) deleteMutation.mutate(deletingDomain);
|
|
15770
16657
|
};
|
|
15771
|
-
|
|
15772
|
-
className: "
|
|
15773
|
-
children: [/* @__PURE__ */ jsx(
|
|
16658
|
+
const settingsHeader = /* @__PURE__ */ jsxs("div", {
|
|
16659
|
+
className: "flex items-center gap-3",
|
|
16660
|
+
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
16661
|
+
to: "/settings",
|
|
16662
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
16663
|
+
variant: "ghost",
|
|
16664
|
+
shape: "square",
|
|
16665
|
+
"aria-label": "Back to settings",
|
|
16666
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
|
|
16667
|
+
})
|
|
16668
|
+
}), /* @__PURE__ */ jsx("h1", {
|
|
15774
16669
|
className: "text-2xl font-bold",
|
|
15775
16670
|
children: "Self-Signup Domains"
|
|
15776
|
-
})
|
|
16671
|
+
})]
|
|
16672
|
+
});
|
|
16673
|
+
if (manifestLoading || isLoading) return /* @__PURE__ */ jsxs("div", {
|
|
16674
|
+
className: "space-y-6",
|
|
16675
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
15777
16676
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
15778
16677
|
children: /* @__PURE__ */ jsx("p", {
|
|
15779
16678
|
className: "text-kumo-subtle",
|
|
@@ -15783,42 +16682,28 @@ function AllowedDomainsSettings() {
|
|
|
15783
16682
|
});
|
|
15784
16683
|
if (isExternalAuth) return /* @__PURE__ */ jsxs("div", {
|
|
15785
16684
|
className: "space-y-6",
|
|
15786
|
-
children: [/* @__PURE__ */ jsx("
|
|
15787
|
-
className: "text-2xl font-bold",
|
|
15788
|
-
children: "Self-Signup Domains"
|
|
15789
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
16685
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
15790
16686
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
15791
16687
|
children: /* @__PURE__ */ jsxs("div", {
|
|
15792
16688
|
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__ */
|
|
16689
|
+
children: [/* @__PURE__ */ jsx(Info, { className: "h-5 w-5 text-kumo-subtle mt-0.5 flex-shrink-0" }), /* @__PURE__ */ jsx("div", {
|
|
15794
16690
|
className: "space-y-2",
|
|
15795
|
-
children:
|
|
16691
|
+
children: /* @__PURE__ */ jsxs("p", {
|
|
15796
16692
|
className: "text-kumo-subtle",
|
|
15797
16693
|
children: [
|
|
15798
16694
|
"User access is managed by an external provider (",
|
|
15799
16695
|
manifest?.authMode,
|
|
15800
16696
|
"). Self-signup domain settings are not available when using external authentication."
|
|
15801
16697
|
]
|
|
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
|
-
})]
|
|
16698
|
+
})
|
|
15811
16699
|
})]
|
|
15812
16700
|
})
|
|
15813
16701
|
})]
|
|
15814
16702
|
});
|
|
15815
16703
|
if (error) return /* @__PURE__ */ jsxs("div", {
|
|
15816
16704
|
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",
|
|
16705
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
16706
|
+
className: "rounded-lg border bg-kumo-base p-6",
|
|
15822
16707
|
children: /* @__PURE__ */ jsx("p", {
|
|
15823
16708
|
className: "text-kumo-danger",
|
|
15824
16709
|
children: error instanceof Error ? error.message : "Failed to load allowed domains"
|
|
@@ -15828,13 +16713,10 @@ function AllowedDomainsSettings() {
|
|
|
15828
16713
|
return /* @__PURE__ */ jsxs("div", {
|
|
15829
16714
|
className: "space-y-6",
|
|
15830
16715
|
children: [
|
|
15831
|
-
|
|
15832
|
-
className: "text-2xl font-bold",
|
|
15833
|
-
children: "Self-Signup Domains"
|
|
15834
|
-
}),
|
|
16716
|
+
settingsHeader,
|
|
15835
16717
|
saveStatus && /* @__PURE__ */ jsxs("div", {
|
|
15836
|
-
className: `rounded-lg border p-
|
|
15837
|
-
children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-
|
|
16718
|
+
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"}`,
|
|
16719
|
+
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
16720
|
}),
|
|
15839
16721
|
/* @__PURE__ */ jsxs("div", {
|
|
15840
16722
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
@@ -16123,8 +17005,12 @@ function ApiTokenSettings() {
|
|
|
16123
17005
|
className: "flex items-center gap-3",
|
|
16124
17006
|
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
16125
17007
|
to: "/settings",
|
|
16126
|
-
|
|
16127
|
-
|
|
17008
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
17009
|
+
variant: "ghost",
|
|
17010
|
+
shape: "square",
|
|
17011
|
+
"aria-label": "Back to settings",
|
|
17012
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
|
|
17013
|
+
})
|
|
16128
17014
|
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
16129
17015
|
className: "text-2xl font-bold",
|
|
16130
17016
|
children: "API Tokens"
|
|
@@ -16877,12 +17763,6 @@ const BASE64_PLUS_REGEX = /\+/g;
|
|
|
16877
17763
|
const BASE64_SLASH_REGEX = /\//g;
|
|
16878
17764
|
const EMPTY_DATA = {};
|
|
16879
17765
|
/**
|
|
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
17766
|
* Convert base64url to ArrayBuffer
|
|
16887
17767
|
*/
|
|
16888
17768
|
function base64urlToBuffer(base64url) {
|
|
@@ -16908,7 +17788,8 @@ function bufferToBase64url(buffer) {
|
|
|
16908
17788
|
function PasskeyRegistration({ optionsEndpoint, verifyEndpoint, onSuccess, onError, buttonText = "Register Passkey", showNameInput = false, additionalData = EMPTY_DATA }) {
|
|
16909
17789
|
const [state, setState] = React.useState({ status: "idle" });
|
|
16910
17790
|
const [passkeyName, setPasskeyName] = React.useState("");
|
|
16911
|
-
const isSupported = React.useMemo(() =>
|
|
17791
|
+
const isSupported = React.useMemo(() => isPasskeyEnvironmentUsable(), []);
|
|
17792
|
+
const insecureContext = React.useMemo(() => typeof window !== "undefined" && !isWebAuthnSecureContext(), []);
|
|
16912
17793
|
const handleRegister = React.useCallback(async () => {
|
|
16913
17794
|
if (!isSupported) {
|
|
16914
17795
|
setState({
|
|
@@ -17017,10 +17898,34 @@ function PasskeyRegistration({ optionsEndpoint, verifyEndpoint, onSuccess, onErr
|
|
|
17017
17898
|
className: "rounded-lg border border-kumo-danger/50 bg-kumo-danger/10 p-4",
|
|
17018
17899
|
children: [/* @__PURE__ */ jsx("h3", {
|
|
17019
17900
|
className: "font-medium text-kumo-danger",
|
|
17020
|
-
children: "Passkeys Not
|
|
17901
|
+
children: "Passkeys Not Available Here"
|
|
17021
17902
|
}), /* @__PURE__ */ jsx("p", {
|
|
17022
17903
|
className: "mt-1 text-sm text-kumo-subtle",
|
|
17023
|
-
children:
|
|
17904
|
+
children: insecureContext ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17905
|
+
"Passkeys require a ",
|
|
17906
|
+
/* @__PURE__ */ jsx("strong", {
|
|
17907
|
+
className: "text-kumo-default",
|
|
17908
|
+
children: "secure context"
|
|
17909
|
+
}),
|
|
17910
|
+
": use",
|
|
17911
|
+
" ",
|
|
17912
|
+
/* @__PURE__ */ jsx("strong", {
|
|
17913
|
+
className: "text-kumo-default",
|
|
17914
|
+
children: "HTTPS"
|
|
17915
|
+
}),
|
|
17916
|
+
", or open the admin at",
|
|
17917
|
+
" ",
|
|
17918
|
+
/* @__PURE__ */ jsx("strong", {
|
|
17919
|
+
className: "text-kumo-default",
|
|
17920
|
+
children: "http://localhost"
|
|
17921
|
+
}),
|
|
17922
|
+
" (with your dev port). Plain ",
|
|
17923
|
+
/* @__PURE__ */ jsx("code", {
|
|
17924
|
+
className: "text-xs",
|
|
17925
|
+
children: "http://"
|
|
17926
|
+
}),
|
|
17927
|
+
" on a custom hostname is not treated as secure, even on loopback."
|
|
17928
|
+
] }) : /* @__PURE__ */ jsx(Fragment, { children: "Your browser doesn't support passkeys. Please use a modern browser like Chrome, Safari, Firefox, or Edge." })
|
|
17024
17929
|
})]
|
|
17025
17930
|
});
|
|
17026
17931
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -17311,12 +18216,24 @@ function SecuritySettings() {
|
|
|
17311
18216
|
message: "Passkey added successfully"
|
|
17312
18217
|
});
|
|
17313
18218
|
};
|
|
17314
|
-
|
|
17315
|
-
className: "
|
|
17316
|
-
children: [/* @__PURE__ */ jsx(
|
|
18219
|
+
const settingsHeader = /* @__PURE__ */ jsxs("div", {
|
|
18220
|
+
className: "flex items-center gap-3",
|
|
18221
|
+
children: [/* @__PURE__ */ jsx(Link$1, {
|
|
18222
|
+
to: "/settings",
|
|
18223
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
18224
|
+
variant: "ghost",
|
|
18225
|
+
shape: "square",
|
|
18226
|
+
"aria-label": "Back to settings",
|
|
18227
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
|
|
18228
|
+
})
|
|
18229
|
+
}), /* @__PURE__ */ jsx("h1", {
|
|
17317
18230
|
className: "text-2xl font-bold",
|
|
17318
18231
|
children: "Security Settings"
|
|
17319
|
-
})
|
|
18232
|
+
})]
|
|
18233
|
+
});
|
|
18234
|
+
if (manifestLoading || isLoading) return /* @__PURE__ */ jsxs("div", {
|
|
18235
|
+
className: "space-y-6",
|
|
18236
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
17320
18237
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
17321
18238
|
children: /* @__PURE__ */ jsx("p", {
|
|
17322
18239
|
className: "text-kumo-subtle",
|
|
@@ -17326,42 +18243,28 @@ function SecuritySettings() {
|
|
|
17326
18243
|
});
|
|
17327
18244
|
if (isExternalAuth) return /* @__PURE__ */ jsxs("div", {
|
|
17328
18245
|
className: "space-y-6",
|
|
17329
|
-
children: [/* @__PURE__ */ jsx("
|
|
17330
|
-
className: "text-2xl font-bold",
|
|
17331
|
-
children: "Security Settings"
|
|
17332
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
18246
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
17333
18247
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
17334
18248
|
children: /* @__PURE__ */ jsxs("div", {
|
|
17335
18249
|
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__ */
|
|
18250
|
+
children: [/* @__PURE__ */ jsx(Info, { className: "h-5 w-5 text-kumo-subtle mt-0.5 flex-shrink-0" }), /* @__PURE__ */ jsx("div", {
|
|
17337
18251
|
className: "space-y-2",
|
|
17338
|
-
children:
|
|
18252
|
+
children: /* @__PURE__ */ jsxs("p", {
|
|
17339
18253
|
className: "text-kumo-subtle",
|
|
17340
18254
|
children: [
|
|
17341
18255
|
"Authentication is managed by an external provider (",
|
|
17342
18256
|
manifest?.authMode,
|
|
17343
18257
|
"). Passkey settings are not available when using external authentication."
|
|
17344
18258
|
]
|
|
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
|
-
})]
|
|
18259
|
+
})
|
|
17354
18260
|
})]
|
|
17355
18261
|
})
|
|
17356
18262
|
})]
|
|
17357
18263
|
});
|
|
17358
18264
|
if (error) return /* @__PURE__ */ jsxs("div", {
|
|
17359
18265
|
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",
|
|
18266
|
+
children: [settingsHeader, /* @__PURE__ */ jsx("div", {
|
|
18267
|
+
className: "rounded-lg border bg-kumo-base p-6",
|
|
17365
18268
|
children: /* @__PURE__ */ jsx("p", {
|
|
17366
18269
|
className: "text-kumo-danger",
|
|
17367
18270
|
children: error instanceof Error ? error.message : "Failed to load passkeys"
|
|
@@ -17371,13 +18274,10 @@ function SecuritySettings() {
|
|
|
17371
18274
|
return /* @__PURE__ */ jsxs("div", {
|
|
17372
18275
|
className: "space-y-6",
|
|
17373
18276
|
children: [
|
|
17374
|
-
|
|
17375
|
-
className: "text-2xl font-bold",
|
|
17376
|
-
children: "Security Settings"
|
|
17377
|
-
}),
|
|
18277
|
+
settingsHeader,
|
|
17378
18278
|
saveStatus && /* @__PURE__ */ jsxs("div", {
|
|
17379
|
-
className: `rounded-lg border p-
|
|
17380
|
-
children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-
|
|
18279
|
+
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"}`,
|
|
18280
|
+
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
18281
|
}),
|
|
17382
18282
|
/* @__PURE__ */ jsxs("div", {
|
|
17383
18283
|
className: "rounded-lg border bg-kumo-base p-6",
|
|
@@ -18129,10 +19029,7 @@ function SetupWizard() {
|
|
|
18129
19029
|
/* @__PURE__ */ jsxs("div", {
|
|
18130
19030
|
className: "text-center mb-6",
|
|
18131
19031
|
children: [
|
|
18132
|
-
/* @__PURE__ */ jsx("
|
|
18133
|
-
className: "text-4xl font-bold mb-2",
|
|
18134
|
-
children: "— EmDash"
|
|
18135
|
-
}),
|
|
19032
|
+
/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-2" }),
|
|
18136
19033
|
/* @__PURE__ */ jsxs("h1", {
|
|
18137
19034
|
className: "text-2xl font-semibold text-kumo-default",
|
|
18138
19035
|
children: [
|
|
@@ -18770,10 +19667,9 @@ function SidebarNav({ manifest }) {
|
|
|
18770
19667
|
/* @__PURE__ */ jsx(KumoSidebar.Header, { children: /* @__PURE__ */ jsxs(Link$1, {
|
|
18771
19668
|
to: "/",
|
|
18772
19669
|
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: "—"
|
|
19670
|
+
children: [/* @__PURE__ */ jsx(LogoIcon, {
|
|
19671
|
+
className: "size-5 shrink-0",
|
|
19672
|
+
"aria-hidden": "true"
|
|
18777
19673
|
}), /* @__PURE__ */ jsx("span", {
|
|
18778
19674
|
className: "emdash-brand-text font-semibold truncate",
|
|
18779
19675
|
children: "EmDash"
|
|
@@ -18874,8 +19770,8 @@ function Header() {
|
|
|
18874
19770
|
const initials = ((user?.name || user?.email || "U")[0] ?? "U").toUpperCase();
|
|
18875
19771
|
return /* @__PURE__ */ jsxs("header", {
|
|
18876
19772
|
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
|
|
19773
|
+
children: [/* @__PURE__ */ jsx(KumoSidebar.Trigger, { className: "cursor-pointer" }), /* @__PURE__ */ jsxs("div", {
|
|
19774
|
+
className: "flex items-center gap-2",
|
|
18879
19775
|
children: [
|
|
18880
19776
|
/* @__PURE__ */ jsxs(LinkButton, {
|
|
18881
19777
|
variant: "ghost",
|
|
@@ -18893,7 +19789,7 @@ function Header() {
|
|
|
18893
19789
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
18894
19790
|
variant: "ghost",
|
|
18895
19791
|
size: "sm",
|
|
18896
|
-
className: "gap-2",
|
|
19792
|
+
className: "gap-2 py-1 h-auto",
|
|
18897
19793
|
children: [user?.avatarUrl ? /* @__PURE__ */ jsx("img", {
|
|
18898
19794
|
src: user.avatarUrl,
|
|
18899
19795
|
alt: "",
|
|
@@ -19021,8 +19917,8 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
|
|
|
19021
19917
|
className: "flex flex-col space-y-1.5 text-center sm:text-center",
|
|
19022
19918
|
children: [
|
|
19023
19919
|
/* @__PURE__ */ jsx("div", {
|
|
19024
|
-
className: "mx-auto mb-4
|
|
19025
|
-
children: /* @__PURE__ */ jsx(
|
|
19920
|
+
className: "mx-auto mb-4",
|
|
19921
|
+
children: /* @__PURE__ */ jsx(LogoIcon, { className: "h-16 w-16" })
|
|
19026
19922
|
}),
|
|
19027
19923
|
/* @__PURE__ */ jsxs(Dialog.Title, {
|
|
19028
19924
|
className: "text-2xl font-semibold leading-none tracking-tight",
|
|
@@ -19467,10 +20363,7 @@ function SignupPage() {
|
|
|
19467
20363
|
children: [
|
|
19468
20364
|
/* @__PURE__ */ jsxs("div", {
|
|
19469
20365
|
className: "text-center mb-8",
|
|
19470
|
-
children: [/* @__PURE__ */ jsx("
|
|
19471
|
-
className: "text-4xl font-bold mb-2",
|
|
19472
|
-
children: "— EmDash"
|
|
19473
|
-
}), /* @__PURE__ */ jsxs("h1", {
|
|
20366
|
+
children: [/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-2" }), /* @__PURE__ */ jsxs("h1", {
|
|
19474
20367
|
className: "text-2xl font-semibold text-kumo-default",
|
|
19475
20368
|
children: [
|
|
19476
20369
|
step === "email" && "Create an account",
|
|
@@ -19601,6 +20494,14 @@ function TermFormDialog({ open, onClose, taxonomyName, taxonomyDef, term, allTer
|
|
|
19601
20494
|
const [description, setDescription] = React.useState(term?.description || "");
|
|
19602
20495
|
const [autoSlug, setAutoSlug] = React.useState(!term);
|
|
19603
20496
|
const [error, setError] = React.useState(null);
|
|
20497
|
+
React.useEffect(() => {
|
|
20498
|
+
setLabel(term?.label || "");
|
|
20499
|
+
setSlug(term?.slug || "");
|
|
20500
|
+
setParentId(term?.parentId || "");
|
|
20501
|
+
setDescription(term?.description || "");
|
|
20502
|
+
setAutoSlug(!term);
|
|
20503
|
+
setError(null);
|
|
20504
|
+
}, [term]);
|
|
19604
20505
|
React.useEffect(() => {
|
|
19605
20506
|
if (autoSlug && label) setSlug(slugify(label));
|
|
19606
20507
|
}, [label, autoSlug]);
|
|
@@ -24693,13 +25594,20 @@ function ContentListPage() {
|
|
|
24693
25594
|
});
|
|
24694
25595
|
const i18n = manifest?.i18n;
|
|
24695
25596
|
const activeLocale = i18n ? localeParam ?? i18n.defaultLocale : void 0;
|
|
24696
|
-
const { data, isLoading, error } =
|
|
25597
|
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, error } = useInfiniteQuery({
|
|
24697
25598
|
queryKey: [
|
|
24698
25599
|
"content",
|
|
24699
25600
|
collection,
|
|
24700
25601
|
{ locale: activeLocale }
|
|
24701
25602
|
],
|
|
24702
|
-
queryFn: () => fetchContentList(collection, {
|
|
25603
|
+
queryFn: ({ pageParam }) => fetchContentList(collection, {
|
|
25604
|
+
locale: activeLocale,
|
|
25605
|
+
cursor: pageParam,
|
|
25606
|
+
limit: 100
|
|
25607
|
+
}),
|
|
25608
|
+
initialPageParam: void 0,
|
|
25609
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
25610
|
+
enabled: !!manifest
|
|
24703
25611
|
});
|
|
24704
25612
|
const { data: trashedData, isLoading: isTrashedLoading } = useQuery({
|
|
24705
25613
|
queryKey: [
|
|
@@ -24786,13 +25694,18 @@ function ContentListPage() {
|
|
|
24786
25694
|
search: { locale: locale || void 0 }
|
|
24787
25695
|
});
|
|
24788
25696
|
};
|
|
25697
|
+
const items = React.useMemo(() => {
|
|
25698
|
+
return data?.pages.flatMap((page) => page.items) || [];
|
|
25699
|
+
}, [data]);
|
|
24789
25700
|
return /* @__PURE__ */ jsx(ContentList, {
|
|
24790
25701
|
collection,
|
|
24791
25702
|
collectionLabel: collectionConfig.label,
|
|
24792
|
-
items
|
|
25703
|
+
items,
|
|
24793
25704
|
trashedItems: trashedData?.items || [],
|
|
24794
|
-
isLoading,
|
|
25705
|
+
isLoading: isLoading || isFetchingNextPage,
|
|
24795
25706
|
isTrashedLoading,
|
|
25707
|
+
hasMore: !!hasNextPage,
|
|
25708
|
+
onLoadMore: () => void fetchNextPage(),
|
|
24796
25709
|
trashedCount: trashedData?.items?.length || 0,
|
|
24797
25710
|
onDelete: (id) => deleteMutation.mutate(id),
|
|
24798
25711
|
onRestore: (id) => restoreMutation.mutate(id),
|
|
@@ -24800,7 +25713,8 @@ function ContentListPage() {
|
|
|
24800
25713
|
onDuplicate: (id) => duplicateMutation.mutate(id),
|
|
24801
25714
|
i18n,
|
|
24802
25715
|
activeLocale,
|
|
24803
|
-
onLocaleChange: handleLocaleChange
|
|
25716
|
+
onLocaleChange: handleLocaleChange,
|
|
25717
|
+
urlPattern: collectionConfig.urlPattern
|
|
24804
25718
|
});
|
|
24805
25719
|
}
|
|
24806
25720
|
/** Extract plugin block definitions from the manifest for Portable Text editor */
|
|
@@ -24992,6 +25906,7 @@ function ContentEditPage() {
|
|
|
24992
25906
|
collection,
|
|
24993
25907
|
id
|
|
24994
25908
|
] });
|
|
25909
|
+
if (rawItem?.draftRevisionId) queryClient.invalidateQueries({ queryKey: ["revision", rawItem.draftRevisionId] });
|
|
24995
25910
|
},
|
|
24996
25911
|
onError: (error) => {
|
|
24997
25912
|
toastManager.add({
|
|
@@ -25014,6 +25929,7 @@ function ContentEditPage() {
|
|
|
25014
25929
|
collection,
|
|
25015
25930
|
id
|
|
25016
25931
|
] });
|
|
25932
|
+
if (rawItem?.draftRevisionId) queryClient.invalidateQueries({ queryKey: ["revision", rawItem.draftRevisionId] });
|
|
25017
25933
|
},
|
|
25018
25934
|
onError: (err) => {
|
|
25019
25935
|
toastManager.add({
|
|
@@ -25237,6 +26153,7 @@ function ContentEditPage() {
|
|
|
25237
26153
|
isDeleting: deleteMutation.isPending,
|
|
25238
26154
|
supportsDrafts: collectionConfig.supports.includes("drafts"),
|
|
25239
26155
|
supportsRevisions: collectionConfig.supports.includes("revisions"),
|
|
26156
|
+
supportsPreview: collectionConfig.supports.includes("preview"),
|
|
25240
26157
|
currentUser,
|
|
25241
26158
|
users: usersData?.items,
|
|
25242
26159
|
onAuthorChange: handleAuthorChange,
|
|
@@ -25732,6 +26649,17 @@ function ContentTypesEditPage() {
|
|
|
25732
26649
|
queryClient.invalidateQueries({ queryKey: ["manifest"] });
|
|
25733
26650
|
}
|
|
25734
26651
|
});
|
|
26652
|
+
const reorderFieldsMutation = useMutation({
|
|
26653
|
+
mutationFn: (fieldSlugs) => reorderFields(slug, fieldSlugs),
|
|
26654
|
+
onSuccess: () => {
|
|
26655
|
+
queryClient.invalidateQueries({ queryKey: [
|
|
26656
|
+
"schema",
|
|
26657
|
+
"collections",
|
|
26658
|
+
slug
|
|
26659
|
+
] });
|
|
26660
|
+
queryClient.invalidateQueries({ queryKey: ["manifest"] });
|
|
26661
|
+
}
|
|
26662
|
+
});
|
|
25735
26663
|
if (error) return /* @__PURE__ */ jsx(ErrorScreen, { error: error.message });
|
|
25736
26664
|
if (isLoading) return /* @__PURE__ */ jsx(LoadingScreen, {});
|
|
25737
26665
|
return /* @__PURE__ */ jsx(ContentTypeEditor, {
|
|
@@ -25743,7 +26671,8 @@ function ContentTypesEditPage() {
|
|
|
25743
26671
|
fieldSlug,
|
|
25744
26672
|
input
|
|
25745
26673
|
}),
|
|
25746
|
-
onDeleteField: (fieldSlug) => deleteFieldMutation.mutate(fieldSlug)
|
|
26674
|
+
onDeleteField: (fieldSlug) => deleteFieldMutation.mutate(fieldSlug),
|
|
26675
|
+
onReorderFields: (fieldSlugs) => reorderFieldsMutation.mutate(fieldSlugs)
|
|
25747
26676
|
});
|
|
25748
26677
|
}
|
|
25749
26678
|
const pluginRoute = createRoute({
|