@emdash-cms/admin 0.6.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/{LocaleDirectionProvider-BS3RseHk.js → LocaleDirectionProvider-B43-InGI.js} +26 -13
  2. package/dist/LocaleDirectionProvider-B43-InGI.js.map +1 -0
  3. package/dist/{config-NP8UHbds.d.ts → config-h7tWQRka.d.ts} +1 -1
  4. package/dist/{config-NP8UHbds.d.ts.map → config-h7tWQRka.d.ts.map} +1 -1
  5. package/dist/index.d.ts +96 -8
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +948 -381
  8. package/dist/index.js.map +1 -1
  9. package/dist/locales/ar/messages.mjs +1 -1
  10. package/dist/locales/de/messages.mjs +1 -1
  11. package/dist/locales/en/messages.mjs +1 -1
  12. package/dist/locales/es-419/messages.mjs +1 -1
  13. package/dist/locales/eu/messages.mjs +1 -1
  14. package/dist/locales/fa/messages.mjs +1 -0
  15. package/dist/locales/fr/messages.mjs +1 -1
  16. package/dist/locales/index.d.ts +1 -1
  17. package/dist/locales/index.js +1 -1
  18. package/dist/locales/ja/messages.mjs +1 -1
  19. package/dist/locales/ko/messages.mjs +1 -1
  20. package/dist/locales/pl/messages.mjs +1 -0
  21. package/dist/locales/pseudo/messages.mjs +1 -1
  22. package/dist/locales/pt-BR/messages.mjs +1 -1
  23. package/dist/locales/zh-CN/messages.mjs +1 -1
  24. package/dist/locales/zh-TW/messages.mjs +1 -1
  25. package/dist/messages-B6ZguVfS.js +6 -0
  26. package/dist/messages-B6ZguVfS.js.map +1 -0
  27. package/dist/messages-BOnMi1Vb.js +6 -0
  28. package/dist/messages-BOnMi1Vb.js.map +1 -0
  29. package/dist/messages-BZuRVH-D.js +6 -0
  30. package/dist/messages-BZuRVH-D.js.map +1 -0
  31. package/dist/messages-Bc_PDiKw.js +6 -0
  32. package/dist/messages-Bc_PDiKw.js.map +1 -0
  33. package/dist/messages-Bddg68eH.js +6 -0
  34. package/dist/messages-Bddg68eH.js.map +1 -0
  35. package/dist/messages-BoP-RHEG.js +6 -0
  36. package/dist/messages-BoP-RHEG.js.map +1 -0
  37. package/dist/messages-C4hNtyU5.js +6 -0
  38. package/dist/messages-C4hNtyU5.js.map +1 -0
  39. package/dist/messages-CMwt5Gv-.js +6 -0
  40. package/dist/messages-CMwt5Gv-.js.map +1 -0
  41. package/dist/messages-CnliZpid.js +6 -0
  42. package/dist/messages-CnliZpid.js.map +1 -0
  43. package/dist/messages-CseAZVO2.js +6 -0
  44. package/dist/messages-CseAZVO2.js.map +1 -0
  45. package/dist/messages-DRyNt1qC.js +6 -0
  46. package/dist/messages-DRyNt1qC.js.map +1 -0
  47. package/dist/messages-DTehFUkD.js +6 -0
  48. package/dist/messages-DTehFUkD.js.map +1 -0
  49. package/dist/messages-DeQMOYvT.js +6 -0
  50. package/dist/messages-DeQMOYvT.js.map +1 -0
  51. package/dist/messages-Do3BokvI.js +6 -0
  52. package/dist/messages-Do3BokvI.js.map +1 -0
  53. package/dist/{plugins-XhZqfegd.js → plugins-DUJY_8HN.js} +9 -2
  54. package/dist/plugins-DUJY_8HN.js.map +1 -0
  55. package/dist/styles.css +1 -1
  56. package/package.json +2 -2
  57. package/dist/LocaleDirectionProvider-BS3RseHk.js.map +0 -1
  58. package/dist/messages-57qx1FXa.js +0 -6
  59. package/dist/messages-57qx1FXa.js.map +0 -1
  60. package/dist/messages-8uPA5VDB.js +0 -6
  61. package/dist/messages-8uPA5VDB.js.map +0 -1
  62. package/dist/messages-B60-1lfB.js +0 -6
  63. package/dist/messages-B60-1lfB.js.map +0 -1
  64. package/dist/messages-Bb5acOxw.js +0 -6
  65. package/dist/messages-Bb5acOxw.js.map +0 -1
  66. package/dist/messages-Bc3iE3Xx.js +0 -6
  67. package/dist/messages-Bc3iE3Xx.js.map +0 -1
  68. package/dist/messages-BxPoHitx.js +0 -6
  69. package/dist/messages-BxPoHitx.js.map +0 -1
  70. package/dist/messages-CAhWq719.js +0 -6
  71. package/dist/messages-CAhWq719.js.map +0 -1
  72. package/dist/messages-CLJeZfc0.js +0 -6
  73. package/dist/messages-CLJeZfc0.js.map +0 -1
  74. package/dist/messages-CjWV7b4D.js +0 -6
  75. package/dist/messages-CjWV7b4D.js.map +0 -1
  76. package/dist/messages-DPxhCxJU.js +0 -6
  77. package/dist/messages-DPxhCxJU.js.map +0 -1
  78. package/dist/messages-DYWnQ-qc.js +0 -6
  79. package/dist/messages-DYWnQ-qc.js.map +0 -1
  80. package/dist/messages-J31OZzeY.js +0 -6
  81. package/dist/messages-J31OZzeY.js.map +0 -1
  82. package/dist/plugins-XhZqfegd.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { c as fetchManifest, i as fetchPlugins, l as parseApiResponse, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as throwResponseError } from "./plugins-XhZqfegd.js";
2
- import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-BS3RseHk.js";
1
+ import { c as fetchAuthMode, d as throwResponseError, i as fetchPlugins, l as fetchManifest, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as parseApiResponse } from "./plugins-DUJY_8HN.js";
2
+ import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-B43-InGI.js";
3
3
  import "./locales/index.js";
4
4
  import { Badge, Button, Checkbox, CommandPalette, Dialog, Input, InputArea, Label, LinkButton, Loader, Popover, Select, Sidebar as KumoSidebar, Switch, Tabs, Toast, Toasty, Tooltip, buttonVariants, useSidebar } from "@cloudflare/kumo";
5
5
  import { i18n } from "@lingui/core";
@@ -10,7 +10,7 @@ import * as React from "react";
10
10
  import { createContext, useCallback, useContext, useEffect, useState } from "react";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { Dialog as Dialog$1 } from "@cloudflare/kumo/primitives";
13
- import { ArrowCounterClockwise, 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";
13
+ import { ArrowCounterClockwise, ArrowLeft, ArrowRight, ArrowSquareOut, ArrowUUpLeft, ArrowUUpRight, ArrowsClockwise, ArrowsInSimple, ArrowsLeftRight, ArrowsOutSimple, BracketsCurly, Calendar, CalendarBlank, CaretDown, CaretLeft, CaretRight, CaretUp, CaretUpDown, ChatCircle, Check, CheckCircle, CircleDashed, CircleNotch, ClockCounterClockwise, Cloud, Code, CodeBlock, Copy, Cube, Database, DeviceMobile, DotsSixVertical, DownloadSimple, Envelope, EnvelopeSimple, Eye, EyeSlash, File, FileArrowDown, FileText, FloppyDisk, FolderOpen, Gear, GithubLogo, Globe, GlobeSimple, GridFour, HardDrive, Hash, Image as Image$1, ImageSquare, Info, Key, Link as Link$2, LinkBreak, LinkSimple, List, ListBullets, ListChecks, ListNumbers, MagnifyingGlass, Minus, Monitor, Moon, Palette, PaperPlaneTilt, Paragraph, Pencil, PencilSimple, PlugsConnected, Plus, Prohibit, PuzzlePiece, Quotes, Rows, Ruler, ShareNetwork, Shield, ShieldCheck, ShieldWarning, SignOut, SlidersHorizontal, Sparkle, SquaresFour, Stack, Storefront, Sun, TextAlignCenter, TextAlignLeft, TextAlignRight, TextB, TextHOne, TextHThree, TextHTwo, TextItalic, TextStrikethrough, TextT, TextUnderline, ToggleLeft, Trash, Upload, User, UserCircle, UserPlus, Users, Warning, WarningCircle, WebhooksLogo, X, YoutubeLogo } from "@phosphor-icons/react";
14
14
  import { clsx } from "clsx";
15
15
  import { twMerge } from "tailwind-merge";
16
16
  import { DndContext, DragOverlay, KeyboardSensor, PointerSensor, closestCenter, rectIntersection, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
@@ -100,6 +100,34 @@ function useTheme() {
100
100
  return context;
101
101
  }
102
102
 
103
+ //#endregion
104
+ //#region src/lib/auth-provider-context.tsx
105
+ const AuthProviderContext = createContext({});
106
+ /**
107
+ * Provider that makes auth provider components available to all descendants
108
+ */
109
+ function AuthProviderProvider({ children, authProviders }) {
110
+ return /* @__PURE__ */ jsx(AuthProviderContext.Provider, {
111
+ value: authProviders,
112
+ children
113
+ });
114
+ }
115
+ /**
116
+ * Get all auth provider modules
117
+ */
118
+ function useAuthProviders() {
119
+ return useContext(AuthProviderContext);
120
+ }
121
+ /**
122
+ * Get auth providers as an ordered array (buttons first, then forms)
123
+ */
124
+ function useAuthProviderList() {
125
+ const providers = useContext(AuthProviderContext);
126
+ return Object.values(providers).toSorted((a, b) => {
127
+ return (a.LoginForm ? 1 : 0) - (b.LoginForm ? 1 : 0);
128
+ });
129
+ }
130
+
103
131
  //#endregion
104
132
  //#region src/lib/plugin-context.tsx
105
133
  const PluginAdminContext = createContext({});
@@ -1105,6 +1133,8 @@ async function fetchContentList(collection, options) {
1105
1133
  if (options?.limit) params.set("limit", String(options.limit));
1106
1134
  if (options?.status) params.set("status", options.status);
1107
1135
  if (options?.locale) params.set("locale", options.locale);
1136
+ if (options?.orderBy) params.set("orderBy", options.orderBy);
1137
+ if (options?.order) params.set("order", options.order);
1108
1138
  return parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}${params.toString() ? `?${params}` : ""}`), "Failed to fetch content");
1109
1139
  }
1110
1140
  /**
@@ -2261,6 +2291,10 @@ const API_TOKEN_SCOPES = {
2261
2291
  MediaWrite: "media:write",
2262
2292
  SchemaRead: "schema:read",
2263
2293
  SchemaWrite: "schema:write",
2294
+ TaxonomiesManage: "taxonomies:manage",
2295
+ MenusManage: "menus:manage",
2296
+ SettingsRead: "settings:read",
2297
+ SettingsManage: "settings:manage",
2264
2298
  Admin: "admin"
2265
2299
  };
2266
2300
  /**
@@ -2921,7 +2955,7 @@ var PluginFieldErrorBoundary = class extends React.Component {
2921
2955
  * Each item is a collapsible card containing the defined sub-fields.
2922
2956
  * Items can be added, removed, and reordered via drag-and-drop.
2923
2957
  */
2924
- function ensureKeys(items) {
2958
+ function ensureKeys$1(items) {
2925
2959
  return items.map((item, i) => {
2926
2960
  const obj = typeof item === "object" && item !== null ? item : {};
2927
2961
  return {
@@ -2930,20 +2964,28 @@ function ensureKeys(items) {
2930
2964
  };
2931
2965
  });
2932
2966
  }
2933
- function stripKeys(items) {
2967
+ function stripKeys$1(items) {
2934
2968
  return items.map(({ _key, ...rest }) => rest);
2935
2969
  }
2936
2970
  function RepeaterField({ label, id, value, onChange, subFields, minItems = 0, maxItems }) {
2937
2971
  const { _: _t } = useLingui();
2938
2972
  const rawItems = Array.isArray(value) ? value : [];
2939
- const [items, setItems] = React.useState(() => ensureKeys(rawItems));
2973
+ const [items, setItems] = React.useState(() => ensureKeys$1(rawItems));
2940
2974
  const [collapsedItems, setCollapsedItems] = React.useState(/* @__PURE__ */ new Set());
2941
2975
  React.useEffect(() => {
2942
- setItems(ensureKeys(Array.isArray(value) ? value : []));
2976
+ const incoming = Array.isArray(value) ? value : [];
2977
+ setItems((prev) => incoming.map((item, i) => {
2978
+ const obj = typeof item === "object" && item !== null ? item : {};
2979
+ const existingKey = obj._key || prev[i]?._key;
2980
+ return {
2981
+ ...obj,
2982
+ _key: existingKey || `item-${i}-${Date.now()}`
2983
+ };
2984
+ }));
2943
2985
  }, [value]);
2944
2986
  const emitChange = (updated) => {
2945
2987
  setItems(updated);
2946
- onChange(stripKeys(updated));
2988
+ onChange(stripKeys$1(updated));
2947
2989
  };
2948
2990
  const handleAdd = () => {
2949
2991
  if (maxItems && items.length >= maxItems) return;
@@ -3109,14 +3151,16 @@ function SubFieldInput({ subField, value, onChange }) {
3109
3151
  label: subField.label,
3110
3152
  value: typeof value === "string" ? value : "",
3111
3153
  onChange: (e) => onChange(e.target.value),
3112
- required: subField.required
3154
+ required: subField.required,
3155
+ dir: "auto"
3113
3156
  });
3114
3157
  case "text": return /* @__PURE__ */ jsx(InputArea, {
3115
3158
  label: subField.label,
3116
3159
  value: typeof value === "string" ? value : "",
3117
3160
  onChange: (e) => onChange(e.target.value),
3118
3161
  required: subField.required,
3119
- rows: 3
3162
+ rows: 3,
3163
+ dir: "auto"
3120
3164
  });
3121
3165
  case "number":
3122
3166
  case "integer": return /* @__PURE__ */ jsx(Input, {
@@ -6630,6 +6674,24 @@ function SlashCommandMenu({ state, onCommand, onClose: _onClose, setSelectedInde
6630
6674
  }, item.id))
6631
6675
  }), document.body);
6632
6676
  }
6677
+ function getPluginBlockDefaultValues(fields) {
6678
+ const defaults = {};
6679
+ for (const field of fields ?? []) {
6680
+ const initialValue = "initial_value" in field ? field.initial_value : void 0;
6681
+ if (initialValue !== void 0) defaults[field.action_id] = initialValue;
6682
+ }
6683
+ return defaults;
6684
+ }
6685
+ function buildPluginBlockFormValues(block, initialValues) {
6686
+ const defaults = getPluginBlockDefaultValues(block?.fields);
6687
+ return initialValues ? {
6688
+ ...defaults,
6689
+ ...initialValues
6690
+ } : defaults;
6691
+ }
6692
+ function hasPluginBlockFormData(values) {
6693
+ return Object.values(values).some((value) => value !== void 0 && value !== null && value !== "");
6694
+ }
6633
6695
  /**
6634
6696
  * Plugin block insertion/editing modal.
6635
6697
  * When the block has `fields`, renders Block Kit elements.
@@ -6640,8 +6702,7 @@ function PluginBlockModal({ block, initialValues, onClose, onInsert }) {
6640
6702
  const inputRef = React.useRef(null);
6641
6703
  React.useEffect(() => {
6642
6704
  if (block) {
6643
- if (initialValues) setFormValues({ ...initialValues });
6644
- else setFormValues({});
6705
+ setFormValues(buildPluginBlockFormValues(block, initialValues));
6645
6706
  if (!block.fields || block.fields.length === 0) setTimeout(() => inputRef.current?.focus(), 0);
6646
6707
  }
6647
6708
  }, [block, initialValues]);
@@ -6661,7 +6722,7 @@ function PluginBlockModal({ block, initialValues, onClose, onInsert }) {
6661
6722
  };
6662
6723
  const isEditing = !!initialValues;
6663
6724
  const hasFields = block?.fields && block.fields.length > 0;
6664
- const canSubmit = hasFields ? Object.values(formValues).some((v) => v !== void 0 && v !== null && v !== "") : typeof formValues.id === "string" && formValues.id.trim().length > 0;
6725
+ const canSubmit = hasFields ? hasPluginBlockFormData(formValues) : typeof formValues.id === "string" && formValues.id.trim().length > 0;
6665
6726
  return /* @__PURE__ */ jsx(Dialog.Root, {
6666
6727
  open: !!block,
6667
6728
  onOpenChange: (open) => !open && onClose(),
@@ -6694,7 +6755,7 @@ function PluginBlockModal({ block, initialValues, onClose, onInsert }) {
6694
6755
  }), /* @__PURE__ */ jsxs("form", {
6695
6756
  onSubmit: handleSubmit,
6696
6757
  children: [/* @__PURE__ */ jsx("div", {
6697
- className: "py-4 space-y-4",
6758
+ className: "py-4 space-y-4 max-h-[70vh] overflow-y-auto -mx-1 px-1",
6698
6759
  children: hasFields ? block.fields.map((field) => /* @__PURE__ */ jsx(BlockKitField, {
6699
6760
  field,
6700
6761
  pluginId: block.pluginId,
@@ -6781,12 +6842,244 @@ function BlockKitField({ field, pluginId, value, onChange }) {
6781
6842
  children: field.label
6782
6843
  })]
6783
6844
  });
6845
+ case "repeater": return /* @__PURE__ */ jsx(BlockKitRepeater, {
6846
+ field,
6847
+ pluginId,
6848
+ value,
6849
+ onChange
6850
+ });
6784
6851
  default: return /* @__PURE__ */ jsxs("div", {
6785
6852
  className: "text-sm text-kumo-subtle",
6786
6853
  children: ["Unknown field type: ", field.type]
6787
6854
  });
6788
6855
  }
6789
6856
  }
6857
+ function ensureKeys(items) {
6858
+ return items.map((item, i) => {
6859
+ const obj = typeof item === "object" && item !== null ? item : {};
6860
+ return {
6861
+ ...obj,
6862
+ _key: obj._key || `item-${i}-${Date.now()}`
6863
+ };
6864
+ });
6865
+ }
6866
+ function stripKeys(items) {
6867
+ return items.map(({ _key, ...rest }) => rest);
6868
+ }
6869
+ function BlockKitRepeater({ field, pluginId, value, onChange }) {
6870
+ const { _: _t2 } = useLingui();
6871
+ const rawItems = React.useMemo(() => Array.isArray(value) ? value : [], [value]);
6872
+ const [items, setItems] = React.useState(() => ensureKeys(rawItems));
6873
+ const [expanded, setExpanded] = React.useState(/* @__PURE__ */ new Set());
6874
+ const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
6875
+ const lastEmittedRef = React.useRef(null);
6876
+ React.useEffect(() => {
6877
+ if (lastEmittedRef.current === rawItems) return;
6878
+ setItems((prev) => rawItems.map((item, i) => {
6879
+ const obj = typeof item === "object" && item !== null ? item : {};
6880
+ const existingKey = obj._key || prev[i]?._key;
6881
+ return {
6882
+ ...obj,
6883
+ _key: existingKey || `item-${i}-${Date.now()}`
6884
+ };
6885
+ }));
6886
+ }, [rawItems]);
6887
+ const minItems = field.min_items ?? 0;
6888
+ const maxItems = field.max_items;
6889
+ const canAdd = maxItems === void 0 || items.length < maxItems;
6890
+ const canRemove = items.length > minItems;
6891
+ const addButtonLabel = field.item_label ? _t2({
6892
+ id: "3nLdaX",
6893
+ message: "Add {0}",
6894
+ values: { 0: field.item_label }
6895
+ }) : _t2({
6896
+ id: "NQ8llq",
6897
+ message: "Add item"
6898
+ });
6899
+ const emit = (next) => {
6900
+ setItems(next);
6901
+ const stripped = stripKeys(next);
6902
+ lastEmittedRef.current = stripped;
6903
+ onChange(field.action_id, stripped);
6904
+ };
6905
+ const handleAdd = () => {
6906
+ if (!canAdd) return;
6907
+ const newItem = { _key: `item-${Date.now()}-${Math.random().toString(36).slice(2, 7)}` };
6908
+ for (const sf of field.fields) switch (sf.type) {
6909
+ case "toggle":
6910
+ newItem[sf.action_id] = false;
6911
+ break;
6912
+ case "number_input":
6913
+ newItem[sf.action_id] = void 0;
6914
+ break;
6915
+ default: newItem[sf.action_id] = "";
6916
+ }
6917
+ setExpanded((prev) => {
6918
+ const next = new Set(prev);
6919
+ next.add(newItem._key);
6920
+ return next;
6921
+ });
6922
+ emit([...items, newItem]);
6923
+ };
6924
+ const handleRemove = (key) => {
6925
+ if (!canRemove) return;
6926
+ setExpanded((prev) => {
6927
+ if (!prev.has(key)) return prev;
6928
+ const next = new Set(prev);
6929
+ next.delete(key);
6930
+ return next;
6931
+ });
6932
+ emit(items.filter((it) => it._key !== key));
6933
+ };
6934
+ const handleItemChange = (key, subActionId, subValue) => {
6935
+ emit(items.map((it) => it._key === key ? {
6936
+ ...it,
6937
+ [subActionId]: subValue
6938
+ } : it));
6939
+ };
6940
+ const handleDragEnd = (event) => {
6941
+ const { active, over } = event;
6942
+ if (!over || active.id === over.id) return;
6943
+ const oldIndex = items.findIndex((it) => it._key === active.id);
6944
+ const newIndex = items.findIndex((it) => it._key === over.id);
6945
+ if (oldIndex === -1 || newIndex === -1) return;
6946
+ emit(arrayMove(items, oldIndex, newIndex));
6947
+ };
6948
+ const toggleExpanded = (key) => {
6949
+ setExpanded((prev) => {
6950
+ const next = new Set(prev);
6951
+ if (next.has(key)) next.delete(key);
6952
+ else next.add(key);
6953
+ return next;
6954
+ });
6955
+ };
6956
+ return /* @__PURE__ */ jsxs("div", {
6957
+ className: "space-y-2",
6958
+ children: [/* @__PURE__ */ jsxs("div", {
6959
+ className: "flex items-center justify-between",
6960
+ children: [/* @__PURE__ */ jsxs("label", {
6961
+ className: "text-sm font-medium",
6962
+ children: [field.label, items.length > 0 && /* @__PURE__ */ jsxs("span", {
6963
+ className: "ms-2 text-kumo-subtle font-normal",
6964
+ children: [
6965
+ "(",
6966
+ items.length,
6967
+ ")"
6968
+ ]
6969
+ })]
6970
+ }), canAdd && /* @__PURE__ */ jsx(Button, {
6971
+ variant: "outline",
6972
+ size: "sm",
6973
+ icon: /* @__PURE__ */ jsx(Plus, {}),
6974
+ onClick: handleAdd,
6975
+ type: "button",
6976
+ children: addButtonLabel
6977
+ })]
6978
+ }), items.length === 0 ? /* @__PURE__ */ jsxs("div", {
6979
+ className: "border-2 border-dashed rounded-lg p-6 text-center text-kumo-subtle",
6980
+ children: [/* @__PURE__ */ jsx("p", {
6981
+ className: "text-sm",
6982
+ children: _t2({
6983
+ id: "bWmJ0A",
6984
+ message: "No items yet"
6985
+ })
6986
+ }), canAdd && /* @__PURE__ */ jsx(Button, {
6987
+ variant: "outline",
6988
+ size: "sm",
6989
+ className: "mt-2",
6990
+ icon: /* @__PURE__ */ jsx(Plus, {}),
6991
+ onClick: handleAdd,
6992
+ type: "button",
6993
+ children: addButtonLabel
6994
+ })]
6995
+ }) : /* @__PURE__ */ jsx(DndContext, {
6996
+ sensors,
6997
+ collisionDetection: closestCenter,
6998
+ onDragEnd: handleDragEnd,
6999
+ children: /* @__PURE__ */ jsx(SortableContext, {
7000
+ items: items.map((it) => it._key),
7001
+ strategy: verticalListSortingStrategy,
7002
+ children: /* @__PURE__ */ jsx("div", {
7003
+ className: "space-y-2",
7004
+ children: items.map((item, index) => /* @__PURE__ */ jsx(BlockKitRepeaterItem, {
7005
+ item,
7006
+ index,
7007
+ fields: field.fields,
7008
+ pluginId,
7009
+ isCollapsed: !expanded.has(item._key),
7010
+ onToggleCollapse: () => toggleExpanded(item._key),
7011
+ onRemove: canRemove ? () => handleRemove(item._key) : void 0,
7012
+ onChange: (subActionId, v) => handleItemChange(item._key, subActionId, v)
7013
+ }, item._key))
7014
+ })
7015
+ })
7016
+ })]
7017
+ });
7018
+ }
7019
+ function BlockKitRepeaterItem({ item, index, fields, pluginId, isCollapsed, onToggleCollapse, onRemove, onChange }) {
7020
+ const { _: _t3 } = useLingui();
7021
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: item._key });
7022
+ const style = {
7023
+ transform: CSS.Transform.toString(transform),
7024
+ transition
7025
+ };
7026
+ const summaryField = fields.find((f) => f.type === "text_input");
7027
+ const summaryLabel = (summaryField && typeof item[summaryField.action_id] === "string" ? item[summaryField.action_id] : "").trim() || _t3({
7028
+ id: "y/5CE8",
7029
+ message: "Item {0}",
7030
+ values: { 0: index + 1 }
7031
+ });
7032
+ return /* @__PURE__ */ jsxs("div", {
7033
+ ref: setNodeRef,
7034
+ style,
7035
+ className: cn("border border-kumo-line rounded-lg bg-kumo-base", isDragging && "opacity-50 ring-2 ring-kumo-brand"),
7036
+ children: [/* @__PURE__ */ jsxs("div", {
7037
+ className: "flex items-center gap-2 px-3 py-2 border-b border-kumo-line",
7038
+ children: [
7039
+ /* @__PURE__ */ jsx("span", {
7040
+ className: "inline-flex h-4 w-4 text-kumo-subtle cursor-grab shrink-0",
7041
+ "aria-label": _t3({
7042
+ id: "KGi3u9",
7043
+ message: "Drag to reorder"
7044
+ }),
7045
+ ...attributes,
7046
+ ...listeners,
7047
+ children: /* @__PURE__ */ jsx(DotsSixVertical, { className: "h-4 w-4" })
7048
+ }),
7049
+ /* @__PURE__ */ jsxs("button", {
7050
+ type: "button",
7051
+ className: "flex items-center gap-2 flex-1 min-w-0 text-left cursor-pointer",
7052
+ onClick: onToggleCollapse,
7053
+ "aria-expanded": !isCollapsed,
7054
+ children: [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" }), /* @__PURE__ */ jsx("span", {
7055
+ className: "text-sm font-medium flex-1 truncate",
7056
+ children: summaryLabel
7057
+ })]
7058
+ }),
7059
+ onRemove && /* @__PURE__ */ jsx(Button, {
7060
+ variant: "ghost",
7061
+ shape: "square",
7062
+ type: "button",
7063
+ onClick: onRemove,
7064
+ "aria-label": _t3({
7065
+ id: "mU3j1b",
7066
+ message: "Remove item {0}",
7067
+ values: { 0: index + 1 }
7068
+ }),
7069
+ children: /* @__PURE__ */ jsx(Trash, { className: "h-3.5 w-3.5 text-kumo-danger" })
7070
+ })
7071
+ ]
7072
+ }), !isCollapsed && /* @__PURE__ */ jsx("div", {
7073
+ className: "p-3 space-y-3",
7074
+ children: fields.map((sf) => /* @__PURE__ */ jsx(BlockKitField, {
7075
+ field: sf,
7076
+ pluginId,
7077
+ value: item[sf.action_id],
7078
+ onChange: (actionId, v) => onChange(actionId, v)
7079
+ }, sf.action_id))
7080
+ })]
7081
+ });
7082
+ }
6790
7083
  /**
6791
7084
  * Select field that supports loading options dynamically via `optionsRoute`.
6792
7085
  * When `optionsRoute` is set, fetches `{ items: [{ id, name }] }` from the plugin route.
@@ -6885,7 +7178,7 @@ function EditorFooter({ editor }) {
6885
7178
  * Portable Text Editor Component
6886
7179
  */
6887
7180
  function PortableTextEditor({ value, onChange, placeholder = "Start writing...", className, editable = true, "aria-labelledby": ariaLabelledby, pluginBlocks = [], focusMode: controlledFocusMode, onFocusModeChange, onEditorReady, minimal = false, onBlockSidebarOpen, onBlockSidebarClose }) {
6888
- const { _: _t2 } = useLingui();
7181
+ const { _: _t4 } = useLingui();
6889
7182
  const onChangeRef = React.useRef(onChange);
6890
7183
  React.useEffect(() => {
6891
7184
  onChangeRef.current = onChange;
@@ -6969,7 +7262,7 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6969
7262
  for (const block of pluginBlocks) cmds.push({
6970
7263
  id: `plugin-${block.pluginId}-${block.type}`,
6971
7264
  title: block.label,
6972
- description: block.description ?? _t2({
7265
+ description: block.description ?? _t4({
6973
7266
  id: "g0zqOw",
6974
7267
  message: "Embed a {0}",
6975
7268
  values: { 0: block.label }
@@ -6986,7 +7279,7 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6986
7279
  }
6987
7280
  });
6988
7281
  return cmds;
6989
- }, [pluginBlocks, _t2]);
7282
+ }, [pluginBlocks, _t4]);
6990
7283
  const filterCommandsRef = React.useRef((_q) => []);
6991
7284
  filterCommandsRef.current = (query) => {
6992
7285
  if (!query) return slashCommands;
@@ -6994,8 +7287,8 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
6994
7287
  const titleMatches = [];
6995
7288
  const otherMatches = [];
6996
7289
  for (const item of slashCommands) {
6997
- const titleStr = typeof item.title === "string" ? item.title : _t2(item.title);
6998
- const descStr = typeof item.description === "string" ? item.description : _t2(item.description);
7290
+ const titleStr = typeof item.title === "string" ? item.title : _t4(item.title);
7291
+ const descStr = typeof item.description === "string" ? item.description : _t4(item.description);
6999
7292
  if (titleStr.toLowerCase().includes(searchText)) titleMatches.push(item);
7000
7293
  else if (descStr.toLowerCase().includes(searchText) || item.aliases?.some((alias) => alias.toLowerCase().includes(searchText))) otherMatches.push(item);
7001
7294
  }
@@ -7047,7 +7340,10 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
7047
7340
  content: initialContent,
7048
7341
  editable,
7049
7342
  immediatelyRender: true,
7050
- editorProps: React.useMemo(() => ({ attributes: { class: "prose prose-sm sm:prose-base dark:prose-invert max-w-none focus:outline-none min-h-[200px] p-4" } }), []),
7343
+ editorProps: React.useMemo(() => ({ attributes: {
7344
+ class: "prose prose-sm sm:prose-base dark:prose-invert max-w-none focus:outline-none min-h-[200px] p-4",
7345
+ dir: "auto"
7346
+ } }), []),
7051
7347
  onUpdate: ({ editor: updatedEditor }) => {
7052
7348
  const cb = onChangeRef.current;
7053
7349
  if (cb) cb(prosemirrorToPortableText(updatedEditor.getJSON()));
@@ -8398,7 +8694,8 @@ function SeoPanel({ contentKey, seo, onChange }) {
8398
8694
  value: draft.title,
8399
8695
  onChange: (e) => {
8400
8696
  updateDraft({ title: e.target.value });
8401
- }
8697
+ },
8698
+ dir: "auto"
8402
8699
  }),
8403
8700
  /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(InputArea, {
8404
8701
  label: _t({
@@ -8417,7 +8714,8 @@ function SeoPanel({ contentKey, seo, onChange }) {
8417
8714
  onChange: (e) => {
8418
8715
  updateDraft({ description: e.target.value });
8419
8716
  },
8420
- rows: 3
8717
+ rows: 3,
8718
+ dir: "auto"
8421
8719
  }) }),
8422
8720
  /* @__PURE__ */ jsx(Input, {
8423
8721
  label: _t({
@@ -8455,6 +8753,65 @@ function SeoPanel({ contentKey, seo, onChange }) {
8455
8753
  });
8456
8754
  }
8457
8755
 
8756
+ //#endregion
8757
+ //#region src/lib/taxonomy-match.ts
8758
+ /**
8759
+ * Taxonomy term matching for the admin picker.
8760
+ *
8761
+ * The picker filters an editor's typed input against existing terms. A naive
8762
+ * `label.toLowerCase().includes(input.toLowerCase())` fails the accent case:
8763
+ * typing `"Mexico"` does not substring-match a term labeled `"México"`
8764
+ * because `"méxico".toLowerCase()` is still `"méxico"` and
8765
+ * `"méxico".includes("mexico")` is `false`. The editor then sees zero
8766
+ * suggestions and creates a duplicate `"Mexico"` term alongside the
8767
+ * canonical `"México"`, splitting the taxonomy.
8768
+ *
8769
+ * This module folds diacritics via NFD decomposition before substring
8770
+ * matching. No regexes are compiled from user input, so there is no ReDoS
8771
+ * surface.
8772
+ */
8773
+ const DIACRITIC_RANGE = /[\u0300-\u036f]/g;
8774
+ /**
8775
+ * Case-fold + diacritic-fold normalization for substring matching.
8776
+ *
8777
+ * `"México"`, `"mexico"`, `"MÉXICO"` all collapse to `"mexico"`.
8778
+ *
8779
+ * NFD decomposes accented characters into a base + combining-diacritic
8780
+ * sequence; the regex drops the combiners. Greek tonos, Vietnamese
8781
+ * stacked diacritics, and other Latin-adjacent scripts are covered.
8782
+ * Combining marks used meaningfully in non-Latin scripts (Arabic harakat
8783
+ * U+064B–U+0652, Japanese dakuten U+3099) fall outside the U+0300–036F
8784
+ * block and are left untouched — stripping them would change meaning.
8785
+ */
8786
+ function foldForMatch(value) {
8787
+ return value.normalize("NFD").replace(DIACRITIC_RANGE, "").toLowerCase();
8788
+ }
8789
+ /**
8790
+ * True if `input` is a substring of the term's label, ignoring case and
8791
+ * diacritics.
8792
+ *
8793
+ * Empty or whitespace-only input returns `false` — the caller decides
8794
+ * whether to show all terms or none in that state. The whitespace guard
8795
+ * matters: without it, a needle of `" "` would `.includes()`-match
8796
+ * every term whose label contains a space.
8797
+ */
8798
+ function termMatches(term, input) {
8799
+ const needle = foldForMatch(input).trim();
8800
+ if (!needle) return false;
8801
+ return foldForMatch(term.label).includes(needle);
8802
+ }
8803
+ /**
8804
+ * True if `input` is an exact (fold-equal) match for the term's label.
8805
+ * Used to decide whether to show the "Create new term" button — if an
8806
+ * editor types `"Mexico"` and a term labeled `"México"` already exists,
8807
+ * Create must not appear or they'll produce a duplicate.
8808
+ */
8809
+ function termExactMatches(term, input) {
8810
+ const needle = foldForMatch(input).trim();
8811
+ if (!needle) return false;
8812
+ return foldForMatch(term.label).trim() === needle;
8813
+ }
8814
+
8458
8815
  //#endregion
8459
8816
  //#region src/components/TaxonomySidebar.tsx
8460
8817
  /**
@@ -8500,7 +8857,7 @@ function CategoryCheckboxTree({ term, level = 0, selectedIds, onToggle }) {
8500
8857
  const isChecked = selectedIds.has(term.id);
8501
8858
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("label", {
8502
8859
  className: "flex items-center py-1 cursor-pointer hover:bg-kumo-tint/50 rounded px-2",
8503
- style: { marginLeft: `${level}rem` },
8860
+ style: { marginInlineStart: `${level}rem` },
8504
8861
  children: [/* @__PURE__ */ jsx("input", {
8505
8862
  type: "checkbox",
8506
8863
  checked: isChecked,
@@ -8527,7 +8884,7 @@ function TagInput({ terms, selectedIds, onAdd, onRemove, onCreate, isCreating, l
8527
8884
  const trimmedInput = input.trim();
8528
8885
  const suggestions = React.useMemo(() => {
8529
8886
  if (!trimmedInput) return [];
8530
- return terms.filter((term) => term.label.toLowerCase().includes(trimmedInput.toLowerCase()) && !selectedIds.has(term.id)).slice(0, 5);
8887
+ return terms.filter((term) => !selectedIds.has(term.id) && termMatches(term, trimmedInput)).slice(0, 5);
8531
8888
  }, [
8532
8889
  trimmedInput,
8533
8890
  terms,
@@ -8535,7 +8892,7 @@ function TagInput({ terms, selectedIds, onAdd, onRemove, onCreate, isCreating, l
8535
8892
  ]);
8536
8893
  const hasExactMatch = React.useMemo(() => {
8537
8894
  if (!trimmedInput) return false;
8538
- return terms.some((term) => term.label.toLowerCase() === trimmedInput.toLowerCase());
8895
+ return terms.some((term) => termExactMatches(term, trimmedInput));
8539
8896
  }, [trimmedInput, terms]);
8540
8897
  const showCreateOption = trimmedInput.length > 0 && !hasExactMatch;
8541
8898
  const handleSelect = (term) => {
@@ -8850,13 +9207,6 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
8850
9207
  roleLabel: entry.roleLabel
8851
9208
  })) ?? []);
8852
9209
  const [portableTextEditor, setPortableTextEditor] = React.useState(null);
8853
- const ptEditorClaimedRef = React.useRef(false);
8854
- React.useCallback((editor) => {
8855
- if (!ptEditorClaimedRef.current) {
8856
- ptEditorClaimedRef.current = true;
8857
- setPortableTextEditor(editor);
8858
- }
8859
- }, []);
8860
9210
  const [blockSidebarPanel, setBlockSidebarPanel] = React.useState(null);
8861
9211
  const handleBlockSidebarOpen = React.useCallback((panel) => {
8862
9212
  setBlockSidebarPanel(panel);
@@ -9684,6 +10034,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
9684
10034
  value: typeof value === "string" ? value : "",
9685
10035
  onChange: (e) => handleChange(e.target.value),
9686
10036
  required: field.required,
10037
+ dir: "auto",
9687
10038
  className: minimal ? "border-0 bg-transparent px-0 text-lg font-medium focus-visible:ring-0 focus-visible:ring-offset-0" : void 0
9688
10039
  });
9689
10040
  case "number": return /* @__PURE__ */ jsx(Input, {
@@ -9734,6 +10085,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
9734
10085
  value: typeof value === "string" ? value : "",
9735
10086
  onChange: (e) => handleChange(e.target.value),
9736
10087
  rows: 10,
10088
+ dir: "auto",
9737
10089
  placeholder: _t2({
9738
10090
  id: "dPFFy+",
9739
10091
  message: "Enter markdown content..."
@@ -9830,7 +10182,8 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
9830
10182
  id,
9831
10183
  value: typeof value === "string" ? value : "",
9832
10184
  onChange: (e) => handleChange(e.target.value),
9833
- required: field.required
10185
+ required: field.required,
10186
+ dir: "auto"
9834
10187
  });
9835
10188
  }
9836
10189
  }
@@ -10470,7 +10823,7 @@ function getItemTitle$1(item) {
10470
10823
  /**
10471
10824
  * Content list view with table display and trash tab
10472
10825
  */
10473
- function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n: i18n$1, activeLocale, onLocaleChange, urlPattern }) {
10826
+ function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n: i18n$1, activeLocale, onLocaleChange, urlPattern, sort, onSortChange }) {
10474
10827
  const { _: _t } = useLingui();
10475
10828
  const [activeTab, setActiveTab] = React.useState("all");
10476
10829
  const [searchQuery, setSearchQuery] = React.useState("");
@@ -10586,34 +10939,38 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
10586
10939
  children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
10587
10940
  className: "border-b bg-kumo-tint/50",
10588
10941
  children: [
10589
- /* @__PURE__ */ jsx("th", {
10590
- scope: "col",
10591
- className: "px-4 py-3 text-start text-sm font-medium",
10592
- children: _t({
10942
+ /* @__PURE__ */ jsx(SortableTh, {
10943
+ field: "title",
10944
+ sort,
10945
+ onSortChange,
10946
+ label: _t({
10593
10947
  id: "MHrjPM",
10594
10948
  message: "Title"
10595
10949
  })
10596
10950
  }),
10597
- /* @__PURE__ */ jsx("th", {
10598
- scope: "col",
10599
- className: "px-4 py-3 text-start text-sm font-medium",
10600
- children: _t({
10951
+ /* @__PURE__ */ jsx(SortableTh, {
10952
+ field: "status",
10953
+ sort,
10954
+ onSortChange,
10955
+ label: _t({
10601
10956
  id: "uAQUqI",
10602
10957
  message: "Status"
10603
10958
  })
10604
10959
  }),
10605
- i18n$1 && /* @__PURE__ */ jsx("th", {
10606
- scope: "col",
10607
- className: "px-4 py-3 text-start text-sm font-medium",
10608
- children: _t({
10960
+ i18n$1 && /* @__PURE__ */ jsx(SortableTh, {
10961
+ field: "locale",
10962
+ sort,
10963
+ onSortChange,
10964
+ label: _t({
10609
10965
  id: "8NbHF7",
10610
10966
  message: "Locale"
10611
10967
  })
10612
10968
  }),
10613
- /* @__PURE__ */ jsx("th", {
10614
- scope: "col",
10615
- className: "px-4 py-3 text-start text-sm font-medium",
10616
- children: _t({
10969
+ /* @__PURE__ */ jsx(SortableTh, {
10970
+ field: "updatedAt",
10971
+ sort,
10972
+ onSortChange,
10973
+ label: _t({
10617
10974
  id: "mYGY3B",
10618
10975
  message: "Date"
10619
10976
  })
@@ -10806,6 +11163,52 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
10806
11163
  ]
10807
11164
  });
10808
11165
  }
11166
+ /**
11167
+ * Table header that doubles as a sort control when the parent opted in by
11168
+ * passing `onSortChange`. When no callback is provided we fall back to a
11169
+ * plain `<th>` so legacy callers (and screen readers) see exactly the same
11170
+ * markup as before this change.
11171
+ *
11172
+ * The button's accessible name is just the column label — the sort state
11173
+ * is conveyed via `aria-sort` on the <th>, which screen readers announce
11174
+ * automatically. Adding a verbose aria-label would make each header re-read
11175
+ * the sort instruction on every focus, which is noisy.
11176
+ */
11177
+ function SortableTh({ field, sort, onSortChange, label }) {
11178
+ const isActive = sort?.field === field;
11179
+ const direction = isActive ? sort?.direction : void 0;
11180
+ if (!onSortChange) return /* @__PURE__ */ jsx("th", {
11181
+ scope: "col",
11182
+ className: "px-4 py-3 text-start text-sm font-medium",
11183
+ children: label
11184
+ });
11185
+ const ariaSort = isActive ? direction === "asc" ? "ascending" : "descending" : "none";
11186
+ const handleClick = () => {
11187
+ if (isActive) onSortChange({
11188
+ field,
11189
+ direction: direction === "asc" ? "desc" : "asc"
11190
+ });
11191
+ else onSortChange({
11192
+ field,
11193
+ direction: "desc"
11194
+ });
11195
+ };
11196
+ const Icon = isActive ? direction === "asc" ? CaretUp : CaretDown : CaretUpDown;
11197
+ return /* @__PURE__ */ jsx("th", {
11198
+ scope: "col",
11199
+ "aria-sort": ariaSort,
11200
+ className: "px-4 py-3 text-start text-sm font-medium",
11201
+ children: /* @__PURE__ */ jsxs("button", {
11202
+ type: "button",
11203
+ onClick: handleClick,
11204
+ className: cn("inline-flex items-center gap-1 rounded hover:text-kumo-brand", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-kumo-brand", isActive ? "text-kumo-fg" : "text-kumo-subtle"),
11205
+ children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx(Icon, {
11206
+ className: "h-3 w-3",
11207
+ "aria-hidden": "true"
11208
+ })]
11209
+ })
11210
+ });
11211
+ }
10809
11212
  function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale, urlPattern }) {
10810
11213
  const { _: _t2 } = useLingui();
10811
11214
  const title = getItemTitle$1(item);
@@ -12027,7 +12430,7 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
12027
12430
  const [labelSingular, setLabelSingular] = React.useState(collection?.labelSingular ?? "");
12028
12431
  const [description, setDescription] = React.useState(collection?.description ?? "");
12029
12432
  const [urlPattern, setUrlPattern] = React.useState(collection?.urlPattern ?? "");
12030
- const [supports, setSupports] = React.useState((collection?.supports ?? ["drafts"]).filter((s) => s !== "seo"));
12433
+ const [supports, setSupports] = React.useState((collection?.supports ?? ["drafts", "revisions"]).filter((s) => s !== "seo"));
12031
12434
  const [hasSeo, setHasSeo] = React.useState(collection?.hasSeo ?? false);
12032
12435
  const [commentsEnabled, setCommentsEnabled] = React.useState(collection?.commentsEnabled ?? false);
12033
12436
  const [commentsModeration, setCommentsModeration] = React.useState(collection?.commentsModeration ?? "first_time");
@@ -14119,6 +14522,24 @@ function LogoLockup({ className, ...props }) {
14119
14522
  ]
14120
14523
  });
14121
14524
  }
14525
+ function BrandLogo({ logoUrl, siteName, className }) {
14526
+ if (logoUrl) return /* @__PURE__ */ jsx("img", {
14527
+ src: logoUrl,
14528
+ alt: siteName || "",
14529
+ className,
14530
+ style: { objectFit: "contain" }
14531
+ });
14532
+ return /* @__PURE__ */ jsx(LogoLockup, { className });
14533
+ }
14534
+ function BrandIcon({ logoUrl, siteName, className }) {
14535
+ if (logoUrl) return /* @__PURE__ */ jsx("img", {
14536
+ src: logoUrl,
14537
+ alt: siteName || "",
14538
+ className,
14539
+ style: { objectFit: "contain" }
14540
+ });
14541
+ return /* @__PURE__ */ jsx(LogoIcon, { className });
14542
+ }
14122
14543
 
14123
14544
  //#endregion
14124
14545
  //#region src/components/InviteAcceptPage.tsx
@@ -14657,54 +15078,13 @@ function PasskeyLogin({ optionsEndpoint, verifyEndpoint, onSuccess, onError, sho
14657
15078
  * It's a standalone page for authentication.
14658
15079
  *
14659
15080
  * Supports:
14660
- * - Passkey authentication (primary)
14661
- * - OAuth (GitHub, Google) when configured
15081
+ * - Passkey authentication (always available)
15082
+ * - Pluggable auth providers (AT Protocol, GitHub, Google, etc.) when configured
14662
15083
  * - Magic link (email) when configured
14663
15084
  *
14664
15085
  * When external auth (e.g., Cloudflare Access) is configured, this page
14665
15086
  * redirects to the admin dashboard since authentication is handled externally.
14666
15087
  */
14667
- function GitHubIcon({ className }) {
14668
- return /* @__PURE__ */ jsx("svg", {
14669
- className,
14670
- viewBox: "0 0 24 24",
14671
- fill: "currentColor",
14672
- children: /* @__PURE__ */ jsx("path", { d: "M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" })
14673
- });
14674
- }
14675
- function GoogleIcon({ className }) {
14676
- return /* @__PURE__ */ jsxs("svg", {
14677
- className,
14678
- viewBox: "0 0 24 24",
14679
- children: [
14680
- /* @__PURE__ */ jsx("path", {
14681
- fill: "#4285F4",
14682
- d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
14683
- }),
14684
- /* @__PURE__ */ jsx("path", {
14685
- fill: "#34A853",
14686
- d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
14687
- }),
14688
- /* @__PURE__ */ jsx("path", {
14689
- fill: "#FBBC05",
14690
- d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
14691
- }),
14692
- /* @__PURE__ */ jsx("path", {
14693
- fill: "#EA4335",
14694
- d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
14695
- })
14696
- ]
14697
- });
14698
- }
14699
- const OAUTH_PROVIDERS = [{
14700
- id: "github",
14701
- name: "GitHub",
14702
- icon: /* @__PURE__ */ jsx(GitHubIcon, { className: "h-5 w-5" })
14703
- }, {
14704
- id: "google",
14705
- name: "Google",
14706
- icon: /* @__PURE__ */ jsx(GoogleIcon, { className: "h-5 w-5" })
14707
- }];
14708
15088
  function MagicLinkForm({ onBack }) {
14709
15089
  const { _: _t } = useLingui();
14710
15090
  const [email, setEmail] = React.useState("");
@@ -14845,22 +15225,21 @@ function MagicLinkForm({ onBack }) {
14845
15225
  ]
14846
15226
  });
14847
15227
  }
14848
- function handleOAuthClick(providerId) {
14849
- window.location.href = `/_emdash/api/auth/oauth/${providerId}`;
14850
- }
14851
15228
  function LoginPage({ redirectUrl = "/_emdash/admin" }) {
14852
15229
  const safeRedirectUrl = sanitizeRedirectUrl(redirectUrl);
14853
15230
  const { _: _t2 } = useLingui();
14854
15231
  const { locale, setLocale } = useLocale();
14855
15232
  const [method, setMethod] = React.useState("passkey");
14856
15233
  const [urlError, setUrlError] = React.useState(null);
14857
- const { data: manifest, isLoading: manifestLoading } = useQuery({
14858
- queryKey: ["manifest"],
14859
- queryFn: fetchManifest
15234
+ const [activeProvider, setActiveProvider] = React.useState(null);
15235
+ const authProviderList = useAuthProviderList();
15236
+ const { data: authInfo, isLoading: authModeLoading } = useQuery({
15237
+ queryKey: ["authMode"],
15238
+ queryFn: fetchAuthMode
14860
15239
  });
14861
15240
  React.useEffect(() => {
14862
- if (manifest?.authMode && manifest.authMode !== "passkey") window.location.href = safeRedirectUrl;
14863
- }, [manifest, safeRedirectUrl]);
15241
+ if (authInfo?.authMode && authInfo.authMode !== "passkey") window.location.href = safeRedirectUrl;
15242
+ }, [authInfo, safeRedirectUrl]);
14864
15243
  React.useEffect(() => {
14865
15244
  const params = new URLSearchParams(window.location.search);
14866
15245
  const error = params.get("error");
@@ -14877,11 +15256,12 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
14877
15256
  const handleSuccess = () => {
14878
15257
  window.location.href = safeRedirectUrl;
14879
15258
  };
14880
- if (manifestLoading || manifest?.authMode && manifest.authMode !== "passkey") return /* @__PURE__ */ jsx("div", {
15259
+ const buttonProviders = authProviderList.filter((p) => p.LoginButton);
15260
+ if (authModeLoading || authInfo?.authMode && authInfo.authMode !== "passkey") return /* @__PURE__ */ jsx("div", {
14881
15261
  className: "min-h-screen flex items-center justify-center bg-kumo-base p-4",
14882
15262
  children: /* @__PURE__ */ jsxs("div", {
14883
15263
  className: "flex flex-col items-center",
14884
- children: [/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mb-4" }), /* @__PURE__ */ jsx(Loader, {})]
15264
+ children: [/* @__PURE__ */ jsx(BrandLogo, { className: "h-10 mb-4" }), /* @__PURE__ */ jsx(Loader, {})]
14885
15265
  })
14886
15266
  });
14887
15267
  return /* @__PURE__ */ jsx("div", {
@@ -14891,15 +15271,19 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
14891
15271
  children: [
14892
15272
  /* @__PURE__ */ jsxs("div", {
14893
15273
  className: "text-center mb-8",
14894
- children: [/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-2" }), /* @__PURE__ */ jsxs("h1", {
15274
+ children: [/* @__PURE__ */ jsx(BrandLogo, { className: "h-10 mx-auto mb-2" }), /* @__PURE__ */ jsx("h1", {
14895
15275
  className: "text-2xl font-semibold text-kumo-default",
14896
- children: [method === "passkey" && _t2({
14897
- id: "lE0wHD",
14898
- message: "Sign in to your site"
14899
- }), method === "magic-link" && _t2({
15276
+ children: method === "magic-link" ? _t2({
14900
15277
  id: "me9L29",
14901
15278
  message: "Sign in with email"
14902
- })]
15279
+ }) : activeProvider ? _t2({
15280
+ id: "e/ToF5",
15281
+ message: "Sign in with {0}",
15282
+ values: { 0: authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider }
15283
+ }) : _t2({
15284
+ id: "lE0wHD",
15285
+ message: "Sign in to your site"
15286
+ })
14903
15287
  })]
14904
15288
  }),
14905
15289
  urlError && /* @__PURE__ */ jsx("div", {
@@ -14908,71 +15292,94 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
14908
15292
  }),
14909
15293
  /* @__PURE__ */ jsxs("div", {
14910
15294
  className: "bg-kumo-base border rounded-lg shadow-sm p-6",
14911
- children: [method === "passkey" && /* @__PURE__ */ jsxs("div", {
14912
- className: "space-y-6",
14913
- children: [
14914
- /* @__PURE__ */ jsx(PasskeyLogin, {
14915
- optionsEndpoint: "/_emdash/api/auth/passkey/options",
14916
- verifyEndpoint: "/_emdash/api/auth/passkey/verify",
14917
- onSuccess: handleSuccess,
14918
- buttonText: _t2({
14919
- id: "QjsOMP",
14920
- message: "Sign in with Passkey"
14921
- })
14922
- }),
14923
- /* @__PURE__ */ jsxs("div", {
14924
- className: "relative",
14925
- children: [/* @__PURE__ */ jsx("div", {
14926
- className: "absolute inset-0 flex items-center",
14927
- children: /* @__PURE__ */ jsx("span", { className: "w-full border-t" })
14928
- }), /* @__PURE__ */ jsx("div", {
14929
- className: "relative flex justify-center text-xs uppercase",
14930
- children: /* @__PURE__ */ jsx("span", {
14931
- className: "bg-kumo-base px-2 text-kumo-subtle",
14932
- children: _t2({
14933
- id: "zW+FpA",
14934
- message: "Or continue with"
15295
+ children: [
15296
+ method === "passkey" && !activeProvider && /* @__PURE__ */ jsxs("div", {
15297
+ className: "space-y-6",
15298
+ children: [
15299
+ /* @__PURE__ */ jsx(PasskeyLogin, {
15300
+ optionsEndpoint: "/_emdash/api/auth/passkey/options",
15301
+ verifyEndpoint: "/_emdash/api/auth/passkey/verify",
15302
+ onSuccess: handleSuccess,
15303
+ buttonText: _t2({
15304
+ id: "QjsOMP",
15305
+ message: "Sign in with Passkey"
15306
+ })
15307
+ }),
15308
+ /* @__PURE__ */ jsxs("div", {
15309
+ className: "relative",
15310
+ children: [/* @__PURE__ */ jsx("div", {
15311
+ className: "absolute inset-0 flex items-center",
15312
+ children: /* @__PURE__ */ jsx("span", { className: "w-full border-t" })
15313
+ }), /* @__PURE__ */ jsx("div", {
15314
+ className: "relative flex justify-center text-xs uppercase",
15315
+ children: /* @__PURE__ */ jsx("span", {
15316
+ className: "bg-kumo-base px-2 text-kumo-subtle",
15317
+ children: _t2({
15318
+ id: "zW+FpA",
15319
+ message: "Or continue with"
15320
+ })
14935
15321
  })
15322
+ })]
15323
+ }),
15324
+ buttonProviders.length > 0 && /* @__PURE__ */ jsx("div", {
15325
+ className: `grid gap-3 ${buttonProviders.length === 1 ? "grid-cols-1" : "grid-cols-2"}`,
15326
+ children: buttonProviders.map((provider) => {
15327
+ const Btn = provider.LoginButton;
15328
+ const hasForm = !!provider.LoginForm;
15329
+ const selectProvider = () => setActiveProvider(provider.id);
15330
+ return /* @__PURE__ */ jsx("div", {
15331
+ onClick: hasForm ? selectProvider : void 0,
15332
+ children: /* @__PURE__ */ jsx(Btn, {})
15333
+ }, provider.id);
14936
15334
  })
14937
- })]
14938
- }),
14939
- /* @__PURE__ */ jsx("div", {
14940
- className: "grid grid-cols-2 gap-3",
14941
- children: OAUTH_PROVIDERS.map((provider) => /* @__PURE__ */ jsxs(Button, {
14942
- variant: "outline",
14943
- type: "button",
14944
- onClick: () => handleOAuthClick(provider.id),
15335
+ }),
15336
+ /* @__PURE__ */ jsx(Button, {
15337
+ variant: "ghost",
14945
15338
  className: "w-full justify-center",
14946
- children: [provider.icon, /* @__PURE__ */ jsx("span", {
14947
- className: "ms-2",
14948
- children: provider.name
14949
- })]
14950
- }, provider.id))
14951
- }),
14952
- /* @__PURE__ */ jsx(Button, {
14953
- variant: "ghost",
14954
- className: "w-full justify-center",
14955
- type: "button",
14956
- onClick: () => setMethod("magic-link"),
14957
- children: _t2({
14958
- id: "RxerEl",
14959
- message: "Sign in with email link"
15339
+ type: "button",
15340
+ onClick: () => setMethod("magic-link"),
15341
+ children: _t2({
15342
+ id: "RxerEl",
15343
+ message: "Sign in with email link"
15344
+ })
14960
15345
  })
14961
- })
14962
- ]
14963
- }), method === "magic-link" && /* @__PURE__ */ jsx(MagicLinkForm, { onBack: () => setMethod("passkey") })]
15346
+ ]
15347
+ }),
15348
+ method === "passkey" && activeProvider && (() => {
15349
+ const provider = authProviderList.find((p) => p.id === activeProvider);
15350
+ if (!provider?.LoginForm) return null;
15351
+ const Form = provider.LoginForm;
15352
+ return /* @__PURE__ */ jsxs("div", {
15353
+ className: "space-y-4",
15354
+ children: [/* @__PURE__ */ jsx(Form, {}), /* @__PURE__ */ jsx(Button, {
15355
+ type: "button",
15356
+ variant: "ghost",
15357
+ className: "w-full justify-center",
15358
+ onClick: () => setActiveProvider(null),
15359
+ children: _t2({
15360
+ id: "VCoEm+",
15361
+ message: "Back to login"
15362
+ })
15363
+ })]
15364
+ });
15365
+ })(),
15366
+ method === "magic-link" && /* @__PURE__ */ jsx(MagicLinkForm, { onBack: () => setMethod("passkey") })
15367
+ ]
14964
15368
  }),
14965
15369
  /* @__PURE__ */ jsx("p", {
14966
15370
  className: "text-center mt-6 text-sm text-kumo-subtle",
14967
- children: method === "passkey" ? _t2({
14968
- id: "+ET/av",
14969
- message: "Use your registered passkey to sign in securely."
14970
- }) : _t2({
15371
+ children: method === "magic-link" ? _t2({
14971
15372
  id: "pv+wH3",
14972
15373
  message: "We'll send you a link to sign in without a password."
15374
+ }) : activeProvider ? _t2({
15375
+ id: "TZGT4o",
15376
+ message: "Enter your handle to sign in."
15377
+ }) : _t2({
15378
+ id: "+ET/av",
15379
+ message: "Use your registered passkey to sign in securely."
14973
15380
  })
14974
15381
  }),
14975
- manifest?.signupEnabled && /* @__PURE__ */ jsx("p", {
15382
+ authInfo?.signupEnabled && /* @__PURE__ */ jsx("p", {
14976
15383
  className: "text-center mt-4 text-sm text-kumo-subtle",
14977
15384
  children: /* @__PURE__ */ jsx(Trans, {
14978
15385
  id: "352VU2",
@@ -21366,6 +21773,50 @@ const API_TOKEN_SCOPE_VALUES = [
21366
21773
  message: "Modify collection schemas"
21367
21774
  }
21368
21775
  },
21776
+ {
21777
+ scope: API_TOKEN_SCOPES.TaxonomiesManage,
21778
+ label: {
21779
+ id: "cfmuSI",
21780
+ message: "Taxonomies Manage"
21781
+ },
21782
+ description: {
21783
+ id: "mmBufY",
21784
+ message: "Create, update, and delete taxonomy terms"
21785
+ }
21786
+ },
21787
+ {
21788
+ scope: API_TOKEN_SCOPES.MenusManage,
21789
+ label: {
21790
+ id: "Wosws9",
21791
+ message: "Menus Manage"
21792
+ },
21793
+ description: {
21794
+ id: "DGhfmD",
21795
+ message: "Create, update, and delete navigation menus"
21796
+ }
21797
+ },
21798
+ {
21799
+ scope: API_TOKEN_SCOPES.SettingsRead,
21800
+ label: {
21801
+ id: "tYQSLw",
21802
+ message: "Settings Read"
21803
+ },
21804
+ description: {
21805
+ id: "fHKrk0",
21806
+ message: "Read site settings"
21807
+ }
21808
+ },
21809
+ {
21810
+ scope: API_TOKEN_SCOPES.SettingsManage,
21811
+ label: {
21812
+ id: "+K5A49",
21813
+ message: "Settings Manage"
21814
+ },
21815
+ description: {
21816
+ id: "RAXQwS",
21817
+ message: "Update site settings"
21818
+ }
21819
+ },
21369
21820
  {
21370
21821
  scope: API_TOKEN_SCOPES.Admin,
21371
21822
  label: {
@@ -23423,8 +23874,9 @@ function SocialSettings() {
23423
23874
  *
23424
23875
  * Steps:
23425
23876
  * 1. Site Configuration (title, tagline, sample content)
23426
- * 2. Admin Account (email, name)
23427
- * 3. Passkey Registration
23877
+ * 2. Create admin account — user picks any available auth method:
23878
+ * - Passkey (always available)
23879
+ * - Any configured auth provider (AT Protocol, GitHub, Google, etc.)
23428
23880
  */
23429
23881
  async function fetchSetupStatus() {
23430
23882
  return parseApiResponse(await apiFetch("/_emdash/api/setup/status"), "Failed to fetch setup status");
@@ -23445,8 +23897,8 @@ async function executeAdminSetup(data) {
23445
23897
  }
23446
23898
  function SiteStep({ seedInfo, onNext, isLoading, error }) {
23447
23899
  const { _: _t } = useLingui();
23448
- const [title, setTitle] = React.useState("");
23449
- const [tagline, setTagline] = React.useState("");
23900
+ const [title, setTitle] = React.useState(seedInfo?.title ?? "");
23901
+ const [tagline, setTagline] = React.useState(seedInfo?.tagline ?? "");
23450
23902
  const [includeContent, setIncludeContent] = React.useState(true);
23451
23903
  const [errors, setErrors] = React.useState({});
23452
23904
  const validate = () => {
@@ -23658,58 +24110,97 @@ function AdminStep({ onNext, onBack, isLoading, error }) {
23658
24110
  ]
23659
24111
  });
23660
24112
  }
23661
- function handlePasskeySuccess() {
24113
+ function handleSetupSuccess() {
23662
24114
  window.location.href = "/_emdash/admin";
23663
24115
  }
23664
- function PasskeyStep({ adminData, onBack }) {
24116
+ function AuthMethodStep({ adminData, providers, onBack }) {
23665
24117
  const { _: _t3 } = useLingui();
24118
+ const [activeProvider, setActiveProvider] = React.useState(null);
24119
+ const buttonProviders = providers.filter((p) => p.LoginButton);
24120
+ const hasProviders = buttonProviders.length > 0;
24121
+ if (activeProvider) {
24122
+ const provider = providers.find((p) => p.id === activeProvider);
24123
+ if (provider && (provider.SetupStep || provider.LoginForm)) return /* @__PURE__ */ jsxs("div", {
24124
+ className: "space-y-4",
24125
+ children: [
24126
+ /* @__PURE__ */ jsx("div", {
24127
+ className: "text-center mb-2",
24128
+ children: /* @__PURE__ */ jsx("h3", {
24129
+ className: "text-lg font-medium",
24130
+ children: _t3({
24131
+ id: "e/ToF5",
24132
+ message: "Sign in with {0}",
24133
+ values: { 0: provider.label }
24134
+ })
24135
+ })
24136
+ }),
24137
+ provider.SetupStep ? /* @__PURE__ */ jsx(provider.SetupStep, { onComplete: handleSetupSuccess }) : provider.LoginForm ? /* @__PURE__ */ jsx(provider.LoginForm, {}) : null,
24138
+ /* @__PURE__ */ jsx(Button, {
24139
+ type: "button",
24140
+ variant: "ghost",
24141
+ className: "w-full justify-center",
24142
+ onClick: () => setActiveProvider(null),
24143
+ children: _t3({
24144
+ id: "2ygf/L",
24145
+ message: "← Back"
24146
+ })
24147
+ })
24148
+ ]
24149
+ });
24150
+ }
23666
24151
  return /* @__PURE__ */ jsxs("div", {
23667
24152
  className: "space-y-6",
23668
24153
  children: [
23669
24154
  /* @__PURE__ */ jsxs("div", {
23670
24155
  className: "text-center",
23671
- children: [
23672
- /* @__PURE__ */ jsx("div", {
23673
- className: "inline-flex items-center justify-center w-16 h-16 rounded-full bg-kumo-brand/10 mb-4",
23674
- children: /* @__PURE__ */ jsx("svg", {
23675
- className: "w-8 h-8 text-kumo-brand",
23676
- fill: "none",
23677
- stroke: "currentColor",
23678
- viewBox: "0 0 24 24",
23679
- children: /* @__PURE__ */ jsx("path", {
23680
- strokeLinecap: "round",
23681
- strokeLinejoin: "round",
23682
- strokeWidth: 2,
23683
- d: "M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.39-2.823 1.07-4"
23684
- })
23685
- })
23686
- }),
23687
- /* @__PURE__ */ jsx("h3", {
23688
- className: "text-lg font-medium",
23689
- children: _t3({
23690
- id: "fifQ8k",
23691
- message: "Set up your passkey"
23692
- })
23693
- }),
23694
- /* @__PURE__ */ jsx("p", {
23695
- className: "text-sm text-kumo-subtle mt-1",
23696
- children: _t3({
23697
- id: "YdZ7D8",
23698
- message: "Passkeys are more secure than passwords. You'll use your device's biometrics, PIN, or security key to sign in."
23699
- })
24156
+ children: [/* @__PURE__ */ jsx("h3", {
24157
+ className: "text-lg font-medium",
24158
+ children: _t3({
24159
+ id: "HAV363",
24160
+ message: "Choose how to sign in"
23700
24161
  })
23701
- ]
24162
+ }), /* @__PURE__ */ jsx("p", {
24163
+ className: "text-sm text-kumo-subtle mt-1",
24164
+ children: _t3({
24165
+ id: "NH33Oi",
24166
+ message: "Pick any method to create your admin account."
24167
+ })
24168
+ })]
23702
24169
  }),
23703
24170
  /* @__PURE__ */ jsx(PasskeyRegistration, {
23704
24171
  optionsEndpoint: "/_emdash/api/setup/admin",
23705
24172
  verifyEndpoint: "/_emdash/api/setup/admin/verify",
23706
- onSuccess: handlePasskeySuccess,
24173
+ onSuccess: handleSetupSuccess,
23707
24174
  buttonText: _t3({
23708
24175
  id: "AKqzR1",
23709
24176
  message: "Create Passkey"
23710
24177
  }),
23711
24178
  additionalData: { ...adminData }
23712
24179
  }),
24180
+ hasProviders && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
24181
+ className: "relative",
24182
+ children: [/* @__PURE__ */ jsx("div", {
24183
+ className: "absolute inset-0 flex items-center",
24184
+ children: /* @__PURE__ */ jsx("span", { className: "w-full border-t" })
24185
+ }), /* @__PURE__ */ jsx("div", {
24186
+ className: "relative flex justify-center text-xs uppercase",
24187
+ children: /* @__PURE__ */ jsx("span", {
24188
+ className: "bg-kumo-base px-2 text-kumo-subtle",
24189
+ children: "Or continue with"
24190
+ })
24191
+ })]
24192
+ }), /* @__PURE__ */ jsx("div", {
24193
+ className: `grid gap-3 ${buttonProviders.length === 1 ? "grid-cols-1" : "grid-cols-2"}`,
24194
+ children: buttonProviders.map((provider) => {
24195
+ const Btn = provider.LoginButton;
24196
+ const hasForm = !!provider.LoginForm || !!provider.SetupStep;
24197
+ const selectProvider = () => setActiveProvider(provider.id);
24198
+ return /* @__PURE__ */ jsx("div", {
24199
+ onClick: hasForm ? selectProvider : void 0,
24200
+ children: /* @__PURE__ */ jsx(Btn, {})
24201
+ }, provider.id);
24202
+ })
24203
+ })] }),
23713
24204
  /* @__PURE__ */ jsx(Button, {
23714
24205
  type: "button",
23715
24206
  variant: "ghost",
@@ -23749,8 +24240,8 @@ function StepIndicator$1({ currentStep, useAccessAuth }) {
23749
24240
  {
23750
24241
  key: "passkey",
23751
24242
  label: _t4({
23752
- id: "FGSN4s",
23753
- message: "Passkey"
24243
+ id: "n1ekoW",
24244
+ message: "Sign In"
23754
24245
  })
23755
24246
  }
23756
24247
  ];
@@ -23788,11 +24279,26 @@ function SetupWizard() {
23788
24279
  const [_siteData, setSiteData] = React.useState(null);
23789
24280
  const [adminData, setAdminData] = React.useState(null);
23790
24281
  const [error, setError] = React.useState();
24282
+ const [urlError, setUrlError] = React.useState(null);
24283
+ const authProviderList = useAuthProviderList();
24284
+ React.useEffect(() => {
24285
+ const params = new URLSearchParams(window.location.search);
24286
+ const errorParam = params.get("error");
24287
+ const message = params.get("message");
24288
+ if (errorParam) {
24289
+ setUrlError(message || `Authentication error: ${errorParam}`);
24290
+ window.history.replaceState({}, "", window.location.pathname);
24291
+ }
24292
+ }, []);
23791
24293
  const { data: status, isLoading: statusLoading, error: statusError } = useQuery({
23792
24294
  queryKey: ["setup", "status"],
23793
24295
  queryFn: fetchSetupStatus,
23794
24296
  retry: false
23795
24297
  });
24298
+ const { data: manifest } = useQuery({
24299
+ queryKey: ["manifest"],
24300
+ queryFn: fetchManifest
24301
+ });
23796
24302
  const useAccessAuth = status?.authMode === "cloudflare-access";
23797
24303
  const siteMutation = useMutation({
23798
24304
  mutationFn: executeSiteSetup,
@@ -23871,7 +24377,11 @@ function SetupWizard() {
23871
24377
  /* @__PURE__ */ jsxs("div", {
23872
24378
  className: "text-center mb-6",
23873
24379
  children: [
23874
- /* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-2" }),
24380
+ /* @__PURE__ */ jsx(BrandLogo, {
24381
+ logoUrl: manifest?.admin?.logo,
24382
+ siteName: manifest?.admin?.siteName,
24383
+ className: "h-10 mx-auto mb-2"
24384
+ }),
23875
24385
  /* @__PURE__ */ jsxs("h1", {
23876
24386
  className: "text-2xl font-semibold text-kumo-default",
23877
24387
  children: [
@@ -23898,6 +24408,10 @@ function SetupWizard() {
23898
24408
  })
23899
24409
  ]
23900
24410
  }),
24411
+ urlError && /* @__PURE__ */ jsx("div", {
24412
+ className: "mb-6 rounded-lg bg-kumo-danger/10 border border-kumo-danger/20 p-4 text-sm text-kumo-danger",
24413
+ children: urlError
24414
+ }),
23901
24415
  /* @__PURE__ */ jsx(StepIndicator$1, {
23902
24416
  currentStep,
23903
24417
  useAccessAuth
@@ -23920,8 +24434,9 @@ function SetupWizard() {
23920
24434
  isLoading: adminMutation.isPending,
23921
24435
  error
23922
24436
  }),
23923
- currentStep === "passkey" && adminData && /* @__PURE__ */ jsx(PasskeyStep, {
24437
+ currentStep === "passkey" && adminData && /* @__PURE__ */ jsx(AuthMethodStep, {
23924
24438
  adminData,
24439
+ providers: authProviderList,
23925
24440
  onBack: () => {
23926
24441
  setError(void 0);
23927
24442
  setCurrentStep("admin");
@@ -24655,12 +25170,14 @@ function SidebarNav({ manifest }) {
24655
25170
  /* @__PURE__ */ jsx(KumoSidebar.Header, { children: /* @__PURE__ */ jsxs(Link$1, {
24656
25171
  to: "/",
24657
25172
  className: "emdash-brand-link flex w-full min-w-0 items-center gap-2 px-3 py-1",
24658
- children: [/* @__PURE__ */ jsx(LogoIcon, {
25173
+ children: [/* @__PURE__ */ jsx(BrandIcon, {
25174
+ logoUrl: manifest.admin?.logo,
25175
+ siteName: manifest.admin?.siteName,
24659
25176
  className: "size-5 shrink-0",
24660
25177
  "aria-hidden": "true"
24661
25178
  }), /* @__PURE__ */ jsx("span", {
24662
25179
  className: "emdash-brand-text font-semibold truncate",
24663
- children: "EmDash"
25180
+ children: manifest.admin?.siteName || "EmDash"
24664
25181
  })]
24665
25182
  }) }),
24666
25183
  /* @__PURE__ */ jsxs(KumoSidebar.Content, { children: [
@@ -24726,7 +25243,8 @@ function SidebarNav({ manifest }) {
24726
25243
  /* @__PURE__ */ jsx(KumoSidebar.Footer, { children: /* @__PURE__ */ jsxs("p", {
24727
25244
  className: "emdash-nav-label px-3 py-2 text-[11px] text-white/30",
24728
25245
  children: [
24729
- "EmDash CMS v",
25246
+ manifest.admin?.siteName || "EmDash CMS",
25247
+ " v",
24730
25248
  manifest.version || "0.0.0",
24731
25249
  manifest.commit && ` (${manifest.commit})`
24732
25250
  ]
@@ -25684,7 +26202,7 @@ function TermRow({ term, level = 0, onEdit, onDelete }) {
25684
26202
  className: "flex items-center gap-4 py-2 px-4 border-b hover:bg-kumo-tint/50",
25685
26203
  children: [
25686
26204
  /* @__PURE__ */ jsxs("div", {
25687
- style: { marginLeft: `${level * 1.5}rem` },
26205
+ style: { marginInlineStart: `${level * 1.5}rem` },
25688
26206
  className: "flex-1",
25689
26207
  children: [/* @__PURE__ */ jsx("span", {
25690
26208
  className: "font-medium",
@@ -27028,7 +27546,20 @@ function Widgets() {
27028
27546
  const [activeId, setActiveId] = React.useState(null);
27029
27547
  const [activeDragData, setActiveDragData] = React.useState(null);
27030
27548
  const [expandedWidgets, setExpandedWidgets] = React.useState(/* @__PURE__ */ new Set());
27549
+ const [blockSidebarPanel, setBlockSidebarPanel] = React.useState(null);
27031
27550
  const draggingFromPaletteRef = React.useRef(false);
27551
+ const handleBlockSidebarOpen = React.useCallback((panel) => {
27552
+ setBlockSidebarPanel((prev) => {
27553
+ prev?.onClose();
27554
+ return panel;
27555
+ });
27556
+ }, []);
27557
+ const handleBlockSidebarClose = React.useCallback(() => {
27558
+ setBlockSidebarPanel((prev) => {
27559
+ prev?.onClose();
27560
+ return null;
27561
+ });
27562
+ }, []);
27032
27563
  const { data: areas = [], isLoading } = useQuery({
27033
27564
  queryKey: ["widget-areas"],
27034
27565
  queryFn: fetchWidgetAreas
@@ -27166,174 +27697,189 @@ function Widgets() {
27166
27697
  collisionDetection,
27167
27698
  onDragStart: handleDragStart,
27168
27699
  onDragEnd: handleDragEnd,
27169
- children: [/* @__PURE__ */ jsxs("div", {
27170
- className: "space-y-6",
27171
- children: [/* @__PURE__ */ jsxs("div", {
27172
- className: "flex items-center justify-between",
27173
- children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
27174
- className: "text-3xl font-bold",
27175
- children: "Widgets"
27176
- }), /* @__PURE__ */ jsx("p", {
27177
- className: "text-kumo-subtle",
27178
- children: "Manage content widgets in your widget areas"
27179
- })] }), /* @__PURE__ */ jsxs(Dialog.Root, {
27180
- open: isCreateAreaOpen,
27181
- onOpenChange: (open) => {
27182
- setIsCreateAreaOpen(open);
27183
- if (!open) setCreateAreaError(null);
27184
- },
27185
- children: [/* @__PURE__ */ jsx(Dialog.Trigger, { render: (props) => /* @__PURE__ */ jsx(Button, {
27186
- ...props,
27187
- icon: /* @__PURE__ */ jsx(Plus, {}),
27188
- children: "Add Widget Area"
27189
- }) }), /* @__PURE__ */ jsxs(Dialog, {
27190
- className: "p-6",
27191
- size: "lg",
27192
- children: [/* @__PURE__ */ jsxs("div", {
27193
- className: "flex items-start justify-between gap-4 mb-4",
27194
- children: [/* @__PURE__ */ jsx(Dialog.Title, {
27195
- className: "text-lg font-semibold leading-none tracking-tight",
27196
- children: "Create Widget Area"
27197
- }), /* @__PURE__ */ jsx(Dialog.Close, {
27198
- "aria-label": "Close",
27199
- render: (props) => /* @__PURE__ */ jsxs(Button, {
27200
- ...props,
27201
- variant: "ghost",
27202
- shape: "square",
27700
+ children: [
27701
+ /* @__PURE__ */ jsxs("div", {
27702
+ className: "space-y-6",
27703
+ children: [/* @__PURE__ */ jsxs("div", {
27704
+ className: "flex items-center justify-between",
27705
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
27706
+ className: "text-3xl font-bold",
27707
+ children: "Widgets"
27708
+ }), /* @__PURE__ */ jsx("p", {
27709
+ className: "text-kumo-subtle",
27710
+ children: "Manage content widgets in your widget areas"
27711
+ })] }), /* @__PURE__ */ jsxs(Dialog.Root, {
27712
+ open: isCreateAreaOpen,
27713
+ onOpenChange: (open) => {
27714
+ setIsCreateAreaOpen(open);
27715
+ if (!open) setCreateAreaError(null);
27716
+ },
27717
+ children: [/* @__PURE__ */ jsx(Dialog.Trigger, { render: (props) => /* @__PURE__ */ jsx(Button, {
27718
+ ...props,
27719
+ icon: /* @__PURE__ */ jsx(Plus, {}),
27720
+ children: "Add Widget Area"
27721
+ }) }), /* @__PURE__ */ jsxs(Dialog, {
27722
+ className: "p-6",
27723
+ size: "lg",
27724
+ children: [/* @__PURE__ */ jsxs("div", {
27725
+ className: "flex items-start justify-between gap-4 mb-4",
27726
+ children: [/* @__PURE__ */ jsx(Dialog.Title, {
27727
+ className: "text-lg font-semibold leading-none tracking-tight",
27728
+ children: "Create Widget Area"
27729
+ }), /* @__PURE__ */ jsx(Dialog.Close, {
27203
27730
  "aria-label": "Close",
27204
- className: "absolute end-4 top-4",
27205
- children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
27206
- className: "sr-only",
27207
- children: "Close"
27208
- })]
27209
- })
27731
+ render: (props) => /* @__PURE__ */ jsxs(Button, {
27732
+ ...props,
27733
+ variant: "ghost",
27734
+ shape: "square",
27735
+ "aria-label": "Close",
27736
+ className: "absolute end-4 top-4",
27737
+ children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
27738
+ className: "sr-only",
27739
+ children: "Close"
27740
+ })]
27741
+ })
27742
+ })]
27743
+ }), /* @__PURE__ */ jsxs("form", {
27744
+ onSubmit: handleCreateArea,
27745
+ className: "space-y-4",
27746
+ children: [
27747
+ /* @__PURE__ */ jsx(Input, {
27748
+ label: "Name",
27749
+ name: "name",
27750
+ required: true,
27751
+ placeholder: "sidebar",
27752
+ pattern: "[a-z0-9-]+"
27753
+ }),
27754
+ /* @__PURE__ */ jsx(Input, {
27755
+ label: "Label",
27756
+ name: "label",
27757
+ required: true,
27758
+ placeholder: "Main Sidebar"
27759
+ }),
27760
+ /* @__PURE__ */ jsx(Input, {
27761
+ label: "Description",
27762
+ name: "description",
27763
+ placeholder: "Appears on posts and pages"
27764
+ }),
27765
+ /* @__PURE__ */ jsx(DialogError, { message: createAreaError || getMutationError(createAreaMutation.error) }),
27766
+ /* @__PURE__ */ jsxs("div", {
27767
+ className: "flex justify-end gap-2",
27768
+ children: [/* @__PURE__ */ jsx(Button, {
27769
+ type: "button",
27770
+ variant: "outline",
27771
+ onClick: () => setIsCreateAreaOpen(false),
27772
+ children: "Cancel"
27773
+ }), /* @__PURE__ */ jsx(Button, {
27774
+ type: "submit",
27775
+ disabled: createAreaMutation.isPending,
27776
+ children: "Create"
27777
+ })]
27778
+ })
27779
+ ]
27210
27780
  })]
27211
- }), /* @__PURE__ */ jsxs("form", {
27212
- onSubmit: handleCreateArea,
27213
- className: "space-y-4",
27781
+ })]
27782
+ })]
27783
+ }), /* @__PURE__ */ jsxs("div", {
27784
+ className: "grid grid-cols-12 gap-6",
27785
+ children: [/* @__PURE__ */ jsx("div", {
27786
+ className: "col-span-4",
27787
+ children: /* @__PURE__ */ jsxs("div", {
27788
+ className: "rounded-lg border bg-kumo-base p-6 space-y-4",
27214
27789
  children: [
27215
- /* @__PURE__ */ jsx(Input, {
27216
- label: "Name",
27217
- name: "name",
27218
- required: true,
27219
- placeholder: "sidebar",
27220
- pattern: "[a-z0-9-]+"
27221
- }),
27222
- /* @__PURE__ */ jsx(Input, {
27223
- label: "Label",
27224
- name: "label",
27225
- required: true,
27226
- placeholder: "Main Sidebar"
27790
+ /* @__PURE__ */ jsx("h2", {
27791
+ className: "text-xl font-semibold",
27792
+ children: "Available Widgets"
27227
27793
  }),
27228
- /* @__PURE__ */ jsx(Input, {
27229
- label: "Description",
27230
- name: "description",
27231
- placeholder: "Appears on posts and pages"
27794
+ /* @__PURE__ */ jsx("p", {
27795
+ className: "text-sm text-kumo-subtle",
27796
+ children: "Drag widgets into an area to add them"
27232
27797
  }),
27233
- /* @__PURE__ */ jsx(DialogError, { message: createAreaError || getMutationError(createAreaMutation.error) }),
27234
27798
  /* @__PURE__ */ jsxs("div", {
27235
- className: "flex justify-end gap-2",
27236
- children: [/* @__PURE__ */ jsx(Button, {
27237
- type: "button",
27238
- variant: "outline",
27239
- onClick: () => setIsCreateAreaOpen(false),
27240
- children: "Cancel"
27241
- }), /* @__PURE__ */ jsx(Button, {
27242
- type: "submit",
27243
- disabled: createAreaMutation.isPending,
27244
- children: "Create"
27245
- })]
27799
+ className: "space-y-2",
27800
+ children: [BUILTIN_WIDGETS.map((item) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
27801
+ id: item.id,
27802
+ label: _t(item.label),
27803
+ description: _t(item.description),
27804
+ widgetInput: {
27805
+ ...item.input,
27806
+ title: _t(item.label)
27807
+ }
27808
+ }, item.id)), components.map((comp) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
27809
+ id: `palette-comp-${comp.id}`,
27810
+ label: comp.label,
27811
+ description: comp.description,
27812
+ widgetInput: {
27813
+ type: "component",
27814
+ title: comp.label,
27815
+ componentId: comp.id
27816
+ }
27817
+ }, `palette-comp-${comp.id}`))]
27246
27818
  })
27247
27819
  ]
27248
- })]
27820
+ })
27821
+ }), /* @__PURE__ */ jsx("div", {
27822
+ className: "col-span-8 space-y-4",
27823
+ children: areas.length === 0 ? /* @__PURE__ */ jsx("div", {
27824
+ className: "rounded-lg border bg-kumo-base p-12 text-center",
27825
+ children: /* @__PURE__ */ jsx("p", {
27826
+ className: "text-kumo-subtle",
27827
+ children: "No widget areas yet. Create one to get started."
27828
+ })
27829
+ }) : areas.map((area) => /* @__PURE__ */ jsx(WidgetAreaPanel, {
27830
+ area,
27831
+ expandedWidgets,
27832
+ onToggleWidget: toggleWidget,
27833
+ isDraggingPalette: activeDragData !== null && isPaletteItem(activeDragData),
27834
+ components,
27835
+ pluginBlocks,
27836
+ onBlockSidebarOpen: handleBlockSidebarOpen,
27837
+ onBlockSidebarClose: handleBlockSidebarClose
27838
+ }, area.id))
27249
27839
  })]
27250
27840
  })]
27251
- }), /* @__PURE__ */ jsxs("div", {
27252
- className: "grid grid-cols-12 gap-6",
27253
- children: [/* @__PURE__ */ jsx("div", {
27254
- className: "col-span-4",
27841
+ }),
27842
+ /* @__PURE__ */ jsx(DragOverlay, {
27843
+ dropAnimation: draggingFromPaletteRef.current ? null : void 0,
27844
+ children: activePaletteLabel ? /* @__PURE__ */ jsx("div", {
27845
+ className: "rounded border bg-kumo-base p-3 shadow-lg opacity-90",
27846
+ children: /* @__PURE__ */ jsx("div", {
27847
+ className: "font-medium",
27848
+ children: activePaletteLabel
27849
+ })
27850
+ }) : activeWidget ? /* @__PURE__ */ jsx("div", {
27851
+ className: "rounded border bg-kumo-base p-3 shadow-lg opacity-90",
27255
27852
  children: /* @__PURE__ */ jsxs("div", {
27256
- className: "rounded-lg border bg-kumo-base p-6 space-y-4",
27853
+ className: "flex items-center gap-2",
27257
27854
  children: [
27258
- /* @__PURE__ */ jsx("h2", {
27259
- className: "text-xl font-semibold",
27260
- children: "Available Widgets"
27261
- }),
27262
- /* @__PURE__ */ jsx("p", {
27263
- className: "text-sm text-kumo-subtle",
27264
- children: "Drag widgets into an area to add them"
27855
+ /* @__PURE__ */ jsx(DotsSixVertical, { className: "h-4 w-4 text-kumo-subtle" }),
27856
+ /* @__PURE__ */ jsx("span", {
27857
+ className: "font-medium",
27858
+ children: activeWidget.title || "Untitled Widget"
27265
27859
  }),
27266
- /* @__PURE__ */ jsxs("div", {
27267
- className: "space-y-2",
27268
- children: [BUILTIN_WIDGETS.map((item) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
27269
- id: item.id,
27270
- label: _t(item.label),
27271
- description: _t(item.description),
27272
- widgetInput: {
27273
- ...item.input,
27274
- title: _t(item.label)
27275
- }
27276
- }, item.id)), components.map((comp) => /* @__PURE__ */ jsx(DraggablePaletteItem, {
27277
- id: `palette-comp-${comp.id}`,
27278
- label: comp.label,
27279
- description: comp.description,
27280
- widgetInput: {
27281
- type: "component",
27282
- title: comp.label,
27283
- componentId: comp.id
27284
- }
27285
- }, `palette-comp-${comp.id}`))]
27860
+ /* @__PURE__ */ jsxs("span", {
27861
+ className: "text-xs text-kumo-subtle",
27862
+ children: [
27863
+ "(",
27864
+ activeWidget.type,
27865
+ ")"
27866
+ ]
27286
27867
  })
27287
27868
  ]
27288
27869
  })
27289
- }), /* @__PURE__ */ jsx("div", {
27290
- className: "col-span-8 space-y-4",
27291
- children: areas.length === 0 ? /* @__PURE__ */ jsx("div", {
27292
- className: "rounded-lg border bg-kumo-base p-12 text-center",
27293
- children: /* @__PURE__ */ jsx("p", {
27294
- className: "text-kumo-subtle",
27295
- children: "No widget areas yet. Create one to get started."
27296
- })
27297
- }) : areas.map((area) => /* @__PURE__ */ jsx(WidgetAreaPanel, {
27298
- area,
27299
- expandedWidgets,
27300
- onToggleWidget: toggleWidget,
27301
- isDraggingPalette: activeDragData !== null && isPaletteItem(activeDragData),
27302
- components,
27303
- pluginBlocks
27304
- }, area.id))
27305
- })]
27306
- })]
27307
- }), /* @__PURE__ */ jsx(DragOverlay, {
27308
- dropAnimation: draggingFromPaletteRef.current ? null : void 0,
27309
- children: activePaletteLabel ? /* @__PURE__ */ jsx("div", {
27310
- className: "rounded border bg-kumo-base p-3 shadow-lg opacity-90",
27311
- children: /* @__PURE__ */ jsx("div", {
27312
- className: "font-medium",
27313
- children: activePaletteLabel
27314
- })
27315
- }) : activeWidget ? /* @__PURE__ */ jsx("div", {
27316
- className: "rounded border bg-kumo-base p-3 shadow-lg opacity-90",
27317
- children: /* @__PURE__ */ jsxs("div", {
27318
- className: "flex items-center gap-2",
27319
- children: [
27320
- /* @__PURE__ */ jsx(DotsSixVertical, { className: "h-4 w-4 text-kumo-subtle" }),
27321
- /* @__PURE__ */ jsx("span", {
27322
- className: "font-medium",
27323
- children: activeWidget.title || "Untitled Widget"
27324
- }),
27325
- /* @__PURE__ */ jsxs("span", {
27326
- className: "text-xs text-kumo-subtle",
27327
- children: [
27328
- "(",
27329
- activeWidget.type,
27330
- ")"
27331
- ]
27332
- })
27333
- ]
27334
- })
27335
- }) : null
27336
- })]
27870
+ }) : null
27871
+ }),
27872
+ blockSidebarPanel?.type === "image" && /* @__PURE__ */ jsx(ImageDetailPanel, {
27873
+ attributes: blockSidebarPanel.attrs,
27874
+ onUpdate: (attrs) => blockSidebarPanel.onUpdate(attrs),
27875
+ onReplace: (attrs) => blockSidebarPanel.onReplace(attrs),
27876
+ onDelete: () => {
27877
+ blockSidebarPanel.onDelete();
27878
+ setBlockSidebarPanel(null);
27879
+ },
27880
+ onClose: handleBlockSidebarClose
27881
+ })
27882
+ ]
27337
27883
  });
27338
27884
  }
27339
27885
  /** A draggable item in the available widgets palette */
@@ -27360,7 +27906,7 @@ function DraggablePaletteItem({ id, label, description, widgetInput }) {
27360
27906
  })]
27361
27907
  });
27362
27908
  }
27363
- function WidgetAreaPanel({ area, expandedWidgets, onToggleWidget, isDraggingPalette, components, pluginBlocks }) {
27909
+ function WidgetAreaPanel({ area, expandedWidgets, onToggleWidget, isDraggingPalette, components, pluginBlocks, onBlockSidebarOpen, onBlockSidebarClose }) {
27364
27910
  const queryClient = useQueryClient();
27365
27911
  const toastManager = Toast.useToastManager();
27366
27912
  const [deleteAreaName, setDeleteAreaName] = React.useState(null);
@@ -27406,7 +27952,9 @@ function WidgetAreaPanel({ area, expandedWidgets, onToggleWidget, isDraggingPale
27406
27952
  isExpanded: expandedWidgets.has(widget.id),
27407
27953
  onToggle: () => onToggleWidget(widget.id),
27408
27954
  components,
27409
- pluginBlocks
27955
+ pluginBlocks,
27956
+ onBlockSidebarOpen,
27957
+ onBlockSidebarClose
27410
27958
  }, widget.id))
27411
27959
  }) : null,
27412
27960
  isDraggingPalette && /* @__PURE__ */ jsx("div", {
@@ -27436,7 +27984,7 @@ function WidgetAreaPanel({ area, expandedWidgets, onToggleWidget, isDraggingPale
27436
27984
  ]
27437
27985
  });
27438
27986
  }
27439
- function WidgetItem({ widget, areaName, isExpanded, onToggle, components, pluginBlocks }) {
27987
+ function WidgetItem({ widget, areaName, isExpanded, onToggle, components, pluginBlocks, onBlockSidebarOpen, onBlockSidebarClose }) {
27440
27988
  const queryClient = useQueryClient();
27441
27989
  const toastManager = Toast.useToastManager();
27442
27990
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
@@ -27528,12 +28076,14 @@ function WidgetItem({ widget, areaName, isExpanded, onToggle, components, plugin
27528
28076
  components,
27529
28077
  pluginBlocks,
27530
28078
  onSave: (input) => updateMutation.mutate(input),
27531
- isSaving: updateMutation.isPending
28079
+ isSaving: updateMutation.isPending,
28080
+ onBlockSidebarOpen,
28081
+ onBlockSidebarClose
27532
28082
  })]
27533
28083
  });
27534
28084
  }
27535
28085
  /** Inline editor form for a widget, rendered when the widget is expanded */
27536
- function WidgetEditor({ widget, components, pluginBlocks, onSave, isSaving }) {
28086
+ function WidgetEditor({ widget, components, pluginBlocks, onSave, isSaving, onBlockSidebarOpen, onBlockSidebarClose }) {
27537
28087
  const [title, setTitle] = React.useState(widget.title ?? "");
27538
28088
  const [content, setContent] = React.useState(Array.isArray(widget.content) ? widget.content : []);
27539
28089
  const [menuName, setMenuName] = React.useState(widget.menuName ?? "");
@@ -27572,7 +28122,9 @@ function WidgetEditor({ widget, components, pluginBlocks, onSave, isSaving }) {
27572
28122
  onChange: (value) => setContent(value),
27573
28123
  minimal: true,
27574
28124
  placeholder: "Write widget content...",
27575
- pluginBlocks
28125
+ pluginBlocks,
28126
+ onBlockSidebarOpen,
28127
+ onBlockSidebarClose
27576
28128
  })] }),
27577
28129
  widget.type === "menu" && /* @__PURE__ */ jsxs(Select, {
27578
28130
  label: "Menu",
@@ -30825,8 +31377,8 @@ function UserDetail({ user, isLoading, isOpen, isSaving, isSendingRecovery, reco
30825
31377
  return /* @__PURE__ */ jsx(Dialog$1.Root, {
30826
31378
  open: isOpen,
30827
31379
  onOpenChange: (open) => !open && stableOnClose(),
30828
- children: /* @__PURE__ */ jsxs(Dialog$1.Portal, { children: [/* @__PURE__ */ jsx(Dialog$1.Backdrop, { className: cn("fixed inset-0 z-40 bg-black/50 transition-opacity duration-200", "data-starting-style:opacity-0 data-ending-style:opacity-0") }), /* @__PURE__ */ jsxs(Dialog$1.Popup, {
30829
- className: cn("fixed top-0 end-0 z-50 flex h-full w-full max-w-md flex-col bg-kumo-base shadow-xl outline-none", "transform transition-transform duration-200 ease-out", "data-starting-style:ltr:translate-x-full data-starting-style:rtl:-translate-x-full", "data-ending-style:ltr:translate-x-full data-ending-style:rtl:-translate-x-full"),
31380
+ children: /* @__PURE__ */ jsxs(Dialog$1.Portal, { children: [/* @__PURE__ */ jsx(Dialog$1.Backdrop, { className: cn("fixed inset-0 bg-black/50 transition-opacity duration-200", "data-starting-style:opacity-0 data-ending-style:opacity-0") }), /* @__PURE__ */ jsxs(Dialog$1.Popup, {
31381
+ className: cn("fixed top-0 end-0 flex h-full w-full max-w-md flex-col bg-kumo-base shadow-xl outline-none", "transform transition-transform duration-200 ease-out", "data-starting-style:ltr:translate-x-full data-starting-style:rtl:-translate-x-full", "data-ending-style:ltr:translate-x-full data-ending-style:rtl:-translate-x-full"),
30830
31382
  children: [
30831
31383
  /* @__PURE__ */ jsxs("div", {
30832
31384
  className: "flex items-center justify-between border-b px-6 py-4",
@@ -31790,16 +32342,25 @@ function ContentListPage() {
31790
32342
  });
31791
32343
  const i18n = manifest?.i18n;
31792
32344
  const activeLocale = i18n ? localeParam ?? i18n.defaultLocale : void 0;
32345
+ const [sort, setSort] = React.useState({
32346
+ field: "updatedAt",
32347
+ direction: "desc"
32348
+ });
31793
32349
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, error } = useInfiniteQuery({
31794
32350
  queryKey: [
31795
32351
  "content",
31796
32352
  collection,
31797
- { locale: activeLocale }
32353
+ {
32354
+ locale: activeLocale,
32355
+ sort
32356
+ }
31798
32357
  ],
31799
32358
  queryFn: ({ pageParam }) => fetchContentList(collection, {
31800
32359
  locale: activeLocale,
31801
32360
  cursor: pageParam,
31802
- limit: 100
32361
+ limit: 100,
32362
+ orderBy: sort.field,
32363
+ order: sort.direction
31803
32364
  }),
31804
32365
  initialPageParam: void 0,
31805
32366
  getNextPageParam: (lastPage) => lastPage.nextCursor,
@@ -31910,7 +32471,9 @@ function ContentListPage() {
31910
32471
  i18n,
31911
32472
  activeLocale,
31912
32473
  onLocaleChange: handleLocaleChange,
31913
- urlPattern: collectionConfig.urlPattern
32474
+ urlPattern: collectionConfig.urlPattern,
32475
+ sort,
32476
+ onSortChange: setSort
31914
32477
  });
31915
32478
  }
31916
32479
  const contentNewRoute = createRoute({
@@ -32630,7 +33193,7 @@ function MarketplaceBrowsePage() {
32630
33193
  const { data: plugins } = useQuery({
32631
33194
  queryKey: ["plugins"],
32632
33195
  queryFn: async () => {
32633
- const { fetchPlugins } = await import("./plugins-XhZqfegd.js").then((n) => n.a);
33196
+ const { fetchPlugins } = await import("./plugins-DUJY_8HN.js").then((n) => n.a);
32634
33197
  return fetchPlugins();
32635
33198
  }
32636
33199
  });
@@ -32649,7 +33212,7 @@ function MarketplaceDetailPage() {
32649
33212
  const { data: plugins } = useQuery({
32650
33213
  queryKey: ["plugins"],
32651
33214
  queryFn: async () => {
32652
- const { fetchPlugins } = await import("./plugins-XhZqfegd.js").then((n) => n.a);
33215
+ const { fetchPlugins } = await import("./plugins-DUJY_8HN.js").then((n) => n.a);
32653
33216
  return fetchPlugins();
32654
33217
  }
32655
33218
  });
@@ -33062,7 +33625,8 @@ const router = createAdminRouter(queryClient);
33062
33625
  * Main Admin Application
33063
33626
  */
33064
33627
  const EMPTY_PLUGINS = {};
33065
- function AdminApp({ pluginAdmins = EMPTY_PLUGINS, locale = "en", messages = {} }) {
33628
+ const EMPTY_AUTH_PROVIDERS = {};
33629
+ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, authProviders = EMPTY_AUTH_PROVIDERS, locale = "en", messages = {} }) {
33066
33630
  React.useEffect(() => {
33067
33631
  document.getElementById("emdash-boot-loader")?.remove();
33068
33632
  }, []);
@@ -33076,16 +33640,19 @@ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, locale = "en", messages = {} }
33076
33640
  }
33077
33641
  return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(I18nProvider, {
33078
33642
  i18n,
33079
- children: /* @__PURE__ */ jsx(LocaleDirectionProvider, { children: /* @__PURE__ */ jsx(Toasty, { children: /* @__PURE__ */ jsx(PluginAdminProvider, {
33080
- pluginAdmins,
33081
- children: /* @__PURE__ */ jsx(QueryClientProvider, {
33082
- client: queryClient,
33083
- children: /* @__PURE__ */ jsx(RouterProvider, { router })
33643
+ children: /* @__PURE__ */ jsx(LocaleDirectionProvider, { children: /* @__PURE__ */ jsx(Toasty, { children: /* @__PURE__ */ jsx(AuthProviderProvider, {
33644
+ authProviders,
33645
+ children: /* @__PURE__ */ jsx(PluginAdminProvider, {
33646
+ pluginAdmins,
33647
+ children: /* @__PURE__ */ jsx(QueryClientProvider, {
33648
+ client: queryClient,
33649
+ children: /* @__PURE__ */ jsx(RouterProvider, { router })
33650
+ })
33084
33651
  })
33085
33652
  }) }) })
33086
33653
  }) });
33087
33654
  }
33088
33655
 
33089
33656
  //#endregion
33090
- export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, CAPABILITY_LABELS, ContentEditor, ContentList, DEFAULT_LOCALE, Dashboard, Header, Link, LoginPage, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, SUPPORTED_LOCALES, SUPPORTED_LOCALE_CODES, SaveButton, Settings, SetupWizard, Shell, KumoSidebar as Sidebar, SidebarNav, analyzeWpPluginSite, analyzeWxr, apiFetch, bulkCommentAction, checkPluginUpdates, cn, compareRevisions, completeSignup, createAdminRouter, createAllowedDomain, createApiToken, createByline, createCollection, createContent, createField, createMenu, createMenuItem, createRedirect, createSection, createTaxonomy, createTerm, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteCollection, deleteComment, deleteContent, deleteField, deleteFromProvider, deleteMedia, deleteMenu, deleteMenuItem, deletePasskey, deleteRedirect, deleteSection, deleteTerm, deleteWidget, deleteWidgetArea, describeCapability, disablePlugin, disableUser, discardDraft, duplicateContent, enablePlugin, enableUser, executeWpPluginImport, executeWxrImport, fetch404Summary, fetchAllowedDomains, fetchApiTokens, fetchByline, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, installMarketplacePlugin, inviteUser, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, resolveLocale, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, validateInviteToken, verifySignupToken };
33657
+ export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, AuthProviderProvider, CAPABILITY_LABELS, ContentEditor, ContentList, DEFAULT_LOCALE, Dashboard, Header, Link, LoginPage, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, SUPPORTED_LOCALES, SUPPORTED_LOCALE_CODES, SaveButton, Settings, SetupWizard, Shell, KumoSidebar as Sidebar, SidebarNav, analyzeWpPluginSite, analyzeWxr, apiFetch, bulkCommentAction, checkPluginUpdates, cn, compareRevisions, completeSignup, createAdminRouter, createAllowedDomain, createApiToken, createByline, createCollection, createContent, createField, createMenu, createMenuItem, createRedirect, createSection, createTaxonomy, createTerm, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteCollection, deleteComment, deleteContent, deleteField, deleteFromProvider, deleteMedia, deleteMenu, deleteMenuItem, deletePasskey, deleteRedirect, deleteSection, deleteTerm, deleteWidget, deleteWidgetArea, describeCapability, disablePlugin, disableUser, discardDraft, duplicateContent, enablePlugin, enableUser, executeWpPluginImport, executeWxrImport, fetch404Summary, fetchAllowedDomains, fetchApiTokens, fetchAuthMode, fetchByline, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, installMarketplacePlugin, inviteUser, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, resolveLocale, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useAuthProviderList, useAuthProviders, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, validateInviteToken, verifySignupToken };
33091
33658
  //# sourceMappingURL=index.js.map