@emdash-cms/admin 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { c as fetchManifest, i as fetchPlugins, l as parseApiResponse, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as throwResponseError } from "./plugins-XhZqfegd.js";
2
+ import { i as SUPPORTED_LOCALE_CODES, n as DEFAULT_LOCALE, o as resolveLocale, r as SUPPORTED_LOCALES, t as useLocale } from "./useLocale-CXsoFCFt.js";
3
+ import "./locales/index.js";
2
4
  import { Badge, Button, Checkbox, CommandPalette, Dialog, Input, InputArea, Label, LinkButton, Loader, Popover, Select, Sidebar as KumoSidebar, Switch, Tabs, Toast, Toasty, Tooltip, buttonVariants, useSidebar } from "@cloudflare/kumo";
5
+ import { i18n } from "@lingui/core";
6
+ import { I18nProvider, Trans, useLingui } from "@lingui/react";
3
7
  import { QueryClient, QueryClientProvider, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
8
  import { Link, Link as Link$1, Outlet, RouterProvider, createRootRouteWithContext, createRoute, createRouter, useLocation, useNavigate, useNavigate as useNavigate$1, useParams, useParams as useParams$1, useSearch } from "@tanstack/react-router";
5
9
  import * as React from "react";
@@ -3284,7 +3288,7 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
3284
3288
  open,
3285
3289
  onOpenChange: handleClose,
3286
3290
  children: /* @__PURE__ */ jsxs(Dialog, {
3287
- className: "p-6 max-w-4xl max-h-[80vh] flex flex-col",
3291
+ className: "p-6 max-w-4xl max-h-[80vh] flex flex-col overflow-hidden",
3288
3292
  size: "xl",
3289
3293
  children: [
3290
3294
  /* @__PURE__ */ jsxs("div", {
@@ -3412,7 +3416,7 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
3412
3416
  className: "mb-3"
3413
3417
  }),
3414
3418
  /* @__PURE__ */ jsx("div", {
3415
- className: "flex-1 overflow-y-auto min-h-[300px]",
3419
+ className: "flex-1 overflow-y-auto min-h-0",
3416
3420
  children: isLoading ? /* @__PURE__ */ jsx("div", {
3417
3421
  className: "flex items-center justify-center h-full",
3418
3422
  children: /* @__PURE__ */ jsx(Loader, {})
@@ -8112,43 +8116,40 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
8112
8116
  isSaving: isSaving || false
8113
8117
  }),
8114
8118
  !isNew && /* @__PURE__ */ jsxs(Fragment, { children: [
8115
- supportsDrafts && hasPendingChanges && onDiscardDraft && /* @__PURE__ */ jsxs(Dialog.Root, {
8116
- disablePointerDismissal: true,
8117
- children: [/* @__PURE__ */ jsx(Dialog.Trigger, { render: (p) => /* @__PURE__ */ jsx(Button, {
8118
- ...p,
8119
- type: "button",
8120
- variant: "outline",
8121
- size: "sm",
8122
- icon: /* @__PURE__ */ jsx(X, {}),
8123
- children: "Discard changes"
8124
- }) }), /* @__PURE__ */ jsxs(Dialog, {
8125
- className: "p-6",
8126
- size: "sm",
8127
- children: [
8128
- /* @__PURE__ */ jsx(Dialog.Title, {
8129
- className: "text-lg font-semibold",
8130
- children: "Discard draft changes?"
8131
- }),
8132
- /* @__PURE__ */ jsx(Dialog.Description, {
8133
- className: "text-kumo-subtle",
8134
- children: "This will revert to the published version. Your draft changes will be lost."
8135
- }),
8136
- /* @__PURE__ */ jsxs("div", {
8137
- className: "mt-6 flex justify-end gap-2",
8138
- children: [/* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
8139
- ...p,
8140
- variant: "secondary",
8141
- children: "Cancel"
8142
- }) }), /* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
8143
- ...p,
8144
- variant: "destructive",
8145
- onClick: onDiscardDraft,
8146
- children: "Discard changes"
8147
- }) })]
8148
- })
8149
- ]
8150
- })]
8151
- }),
8119
+ supportsDrafts && hasPendingChanges && onDiscardDraft && /* @__PURE__ */ jsxs(Dialog.Root, { children: [/* @__PURE__ */ jsx(Dialog.Trigger, { render: (p) => /* @__PURE__ */ jsx(Button, {
8120
+ ...p,
8121
+ type: "button",
8122
+ variant: "outline",
8123
+ size: "sm",
8124
+ icon: /* @__PURE__ */ jsx(X, {}),
8125
+ children: "Discard changes"
8126
+ }) }), /* @__PURE__ */ jsxs(Dialog, {
8127
+ className: "p-6",
8128
+ size: "sm",
8129
+ children: [
8130
+ /* @__PURE__ */ jsx(Dialog.Title, {
8131
+ className: "text-lg font-semibold",
8132
+ children: "Discard draft changes?"
8133
+ }),
8134
+ /* @__PURE__ */ jsx(Dialog.Description, {
8135
+ className: "text-kumo-subtle",
8136
+ children: "This will revert to the published version. Your draft changes will be lost."
8137
+ }),
8138
+ /* @__PURE__ */ jsxs("div", {
8139
+ className: "mt-6 flex justify-end gap-2",
8140
+ children: [/* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
8141
+ ...p,
8142
+ variant: "secondary",
8143
+ children: "Cancel"
8144
+ }) }), /* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
8145
+ ...p,
8146
+ variant: "destructive",
8147
+ onClick: onDiscardDraft,
8148
+ children: "Discard changes"
8149
+ }) })]
8150
+ })
8151
+ ]
8152
+ })] }),
8152
8153
  isLive ? /* @__PURE__ */ jsx(Fragment, { children: hasPendingChanges ? /* @__PURE__ */ jsx(Button, {
8153
8154
  type: "button",
8154
8155
  variant: "primary",
@@ -8246,6 +8247,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
8246
8247
  children: supportsDrafts ? /* @__PURE__ */ jsxs(Fragment, { children: [
8247
8248
  isLive && /* @__PURE__ */ jsx(Badge, {
8248
8249
  variant: "primary",
8250
+ className: "text-white",
8249
8251
  children: "Published"
8250
8252
  }),
8251
8253
  hasPendingChanges && /* @__PURE__ */ jsx(Badge, {
@@ -8538,27 +8540,31 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
8538
8540
  required: field.required
8539
8541
  });
8540
8542
  case "boolean": return /* @__PURE__ */ jsx(Switch, {
8543
+ id,
8541
8544
  label,
8542
8545
  checked: typeof value === "boolean" ? value : false,
8543
8546
  onCheckedChange: handleChange
8544
8547
  });
8545
8548
  case "portableText": {
8546
8549
  const labelId = `${id}-label`;
8547
- return /* @__PURE__ */ jsxs("div", { children: [!minimal && /* @__PURE__ */ jsx("span", {
8548
- id: labelId,
8549
- className: cn("text-sm font-medium leading-none text-kumo-default", labelClass),
8550
- children: label
8551
- }), /* @__PURE__ */ jsx(PortableTextEditor, {
8552
- value: Array.isArray(value) ? value : [],
8553
- onChange: handleChange,
8554
- placeholder: `Enter ${label.toLowerCase()}...`,
8555
- "aria-labelledby": labelId,
8556
- pluginBlocks,
8557
- onEditorReady,
8558
- minimal,
8559
- onBlockSidebarOpen,
8560
- onBlockSidebarClose
8561
- })] });
8550
+ return /* @__PURE__ */ jsxs("div", {
8551
+ id,
8552
+ children: [!minimal && /* @__PURE__ */ jsx("span", {
8553
+ id: labelId,
8554
+ className: cn("text-sm font-medium leading-none text-kumo-default", labelClass),
8555
+ children: label
8556
+ }), /* @__PURE__ */ jsx(PortableTextEditor, {
8557
+ value: Array.isArray(value) ? value : [],
8558
+ onChange: handleChange,
8559
+ placeholder: `Enter ${label.toLowerCase()}...`,
8560
+ "aria-labelledby": labelId,
8561
+ pluginBlocks,
8562
+ onEditorReady,
8563
+ minimal,
8564
+ onBlockSidebarOpen,
8565
+ onBlockSidebarClose
8566
+ })]
8567
+ });
8562
8568
  }
8563
8569
  case "richText": return /* @__PURE__ */ jsx(InputArea, {
8564
8570
  label,
@@ -8572,6 +8578,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
8572
8578
  const selectItems = {};
8573
8579
  for (const opt of field.options ?? []) selectItems[opt.value] = opt.label;
8574
8580
  return /* @__PURE__ */ jsx(Select, {
8581
+ id,
8575
8582
  label,
8576
8583
  value: typeof value === "string" ? value : "",
8577
8584
  onValueChange: (v) => handleChange(v ?? ""),
@@ -8610,6 +8617,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
8610
8617
  required: field.required
8611
8618
  });
8612
8619
  case "image": return /* @__PURE__ */ jsx(ImageFieldRenderer, {
8620
+ id,
8613
8621
  label,
8614
8622
  description: name === "featured_image" ? "Used as the main visual for this post on listing pages and at the top of the post" : void 0,
8615
8623
  value: value != null && typeof value === "object" ? value : void 0,
@@ -8639,7 +8647,7 @@ function FieldRenderer({ name, field, value, onChange, onEditorReady, minimal, p
8639
8647
  });
8640
8648
  }
8641
8649
  }
8642
- function ImageFieldRenderer({ label, description, value, onChange, required }) {
8650
+ function ImageFieldRenderer({ id, label, description, value, onChange, required }) {
8643
8651
  const [pickerOpen, setPickerOpen] = React.useState(false);
8644
8652
  const displayUrl = typeof value === "string" ? value : value?.previewUrl || value?.src || (value && (!value.provider || value.provider === "local") ? `/_emdash/api/media/file/${typeof value.meta?.storageKey === "string" ? value.meta.storageKey : value.id}` : void 0);
8645
8653
  const handleSelect = (item) => {
@@ -8660,58 +8668,61 @@ function ImageFieldRenderer({ label, description, value, onChange, required }) {
8660
8668
  const handleRemove = () => {
8661
8669
  onChange(void 0);
8662
8670
  };
8663
- return /* @__PURE__ */ jsxs("div", { children: [
8664
- /* @__PURE__ */ jsx(Label, { children: label }),
8665
- displayUrl ? /* @__PURE__ */ jsxs("div", {
8666
- className: "mt-2 relative group",
8667
- children: [/* @__PURE__ */ jsx("img", {
8668
- src: displayUrl,
8669
- alt: "",
8670
- className: "max-h-48 rounded-lg border object-cover"
8671
- }), /* @__PURE__ */ jsxs("div", {
8672
- className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
8673
- children: [/* @__PURE__ */ jsx(Button, {
8674
- type: "button",
8675
- size: "sm",
8676
- variant: "secondary",
8677
- onClick: () => setPickerOpen(true),
8678
- children: "Change"
8679
- }), /* @__PURE__ */ jsx(Button, {
8680
- type: "button",
8681
- shape: "square",
8682
- variant: "destructive",
8683
- className: "h-8 w-8",
8684
- onClick: handleRemove,
8685
- "aria-label": "Remove image",
8686
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
8671
+ return /* @__PURE__ */ jsxs("div", {
8672
+ id,
8673
+ children: [
8674
+ /* @__PURE__ */ jsx(Label, { children: label }),
8675
+ displayUrl ? /* @__PURE__ */ jsxs("div", {
8676
+ className: "mt-2 relative group",
8677
+ children: [/* @__PURE__ */ jsx("img", {
8678
+ src: displayUrl,
8679
+ alt: "",
8680
+ className: "max-h-48 rounded-lg border object-cover"
8681
+ }), /* @__PURE__ */ jsxs("div", {
8682
+ className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
8683
+ children: [/* @__PURE__ */ jsx(Button, {
8684
+ type: "button",
8685
+ size: "sm",
8686
+ variant: "secondary",
8687
+ onClick: () => setPickerOpen(true),
8688
+ children: "Change"
8689
+ }), /* @__PURE__ */ jsx(Button, {
8690
+ type: "button",
8691
+ shape: "square",
8692
+ variant: "destructive",
8693
+ className: "h-8 w-8",
8694
+ onClick: handleRemove,
8695
+ "aria-label": "Remove image",
8696
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
8697
+ })]
8687
8698
  })]
8688
- })]
8689
- }) : /* @__PURE__ */ jsx(Button, {
8690
- type: "button",
8691
- variant: "outline",
8692
- className: "mt-2 w-full h-32 border-dashed",
8693
- onClick: () => setPickerOpen(true),
8694
- children: /* @__PURE__ */ jsxs("div", {
8695
- className: "flex flex-col items-center gap-2 text-kumo-subtle",
8696
- children: [/* @__PURE__ */ jsx(Image$1, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: "Select image" })]
8699
+ }) : /* @__PURE__ */ jsx(Button, {
8700
+ type: "button",
8701
+ variant: "outline",
8702
+ className: "mt-2 w-full h-32 border-dashed",
8703
+ onClick: () => setPickerOpen(true),
8704
+ children: /* @__PURE__ */ jsxs("div", {
8705
+ className: "flex flex-col items-center gap-2 text-kumo-subtle",
8706
+ children: [/* @__PURE__ */ jsx(Image$1, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: "Select image" })]
8707
+ })
8708
+ }),
8709
+ /* @__PURE__ */ jsx(MediaPickerModal, {
8710
+ open: pickerOpen,
8711
+ onOpenChange: setPickerOpen,
8712
+ onSelect: handleSelect,
8713
+ mimeTypeFilter: "image/",
8714
+ title: `Select ${label}`
8715
+ }),
8716
+ description && /* @__PURE__ */ jsx("p", {
8717
+ className: "text-xs text-kumo-subtle mt-1",
8718
+ children: description
8719
+ }),
8720
+ required && !displayUrl && /* @__PURE__ */ jsx("p", {
8721
+ className: "text-sm text-kumo-danger mt-1",
8722
+ children: "This field is required"
8697
8723
  })
8698
- }),
8699
- /* @__PURE__ */ jsx(MediaPickerModal, {
8700
- open: pickerOpen,
8701
- onOpenChange: setPickerOpen,
8702
- onSelect: handleSelect,
8703
- mimeTypeFilter: "image/",
8704
- title: `Select ${label}`
8705
- }),
8706
- description && /* @__PURE__ */ jsx("p", {
8707
- className: "text-xs text-kumo-subtle mt-1",
8708
- children: description
8709
- }),
8710
- required && !displayUrl && /* @__PURE__ */ jsx("p", {
8711
- className: "text-sm text-kumo-danger mt-1",
8712
- children: "This field is required"
8713
- })
8714
- ] });
8724
+ ]
8725
+ });
8715
8726
  }
8716
8727
  function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuickEdit }) {
8717
8728
  const [selectedBylineId, setSelectedBylineId] = React.useState("");
@@ -8891,7 +8902,10 @@ function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuic
8891
8902
  children: [/* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
8892
8903
  ...p,
8893
8904
  variant: "secondary",
8894
- onClick: resetQuickCreate,
8905
+ onClick: (e) => {
8906
+ resetQuickCreate();
8907
+ p.onClick?.(e);
8908
+ },
8895
8909
  children: "Cancel"
8896
8910
  }) }), /* @__PURE__ */ jsx(Button, {
8897
8911
  type: "button",
@@ -9035,6 +9049,7 @@ function AuthorSelector({ authorId, users, onChange }) {
9035
9049
  * Only renders when i18n is configured (manifest.i18n is present).
9036
9050
  */
9037
9051
  function LocaleSwitcher({ locales, defaultLocale, value, onChange, showAll = false, className, size = "md" }) {
9052
+ const { _: _t } = useLingui();
9038
9053
  return /* @__PURE__ */ jsxs("div", {
9039
9054
  className: cn("flex items-center gap-1.5", className),
9040
9055
  children: [/* @__PURE__ */ jsx(GlobeSimple, {
@@ -9043,14 +9058,23 @@ function LocaleSwitcher({ locales, defaultLocale, value, onChange, showAll = fal
9043
9058
  }), /* @__PURE__ */ jsxs("select", {
9044
9059
  value,
9045
9060
  onChange: (e) => onChange(e.target.value),
9046
- "aria-label": "Locale",
9061
+ "aria-label": _t({
9062
+ id: "8NbHF7",
9063
+ message: "Locale"
9064
+ }),
9047
9065
  className: cn("rounded-md border bg-transparent font-medium transition-colors", "focus:ring-kumo-ring focus:outline-none focus:ring-2 focus:ring-offset-1", "hover:bg-kumo-tint/50 cursor-pointer", size === "sm" ? "px-1.5 py-0.5 text-xs" : "px-2 py-1 text-sm"),
9048
9066
  children: [showAll && /* @__PURE__ */ jsx("option", {
9049
9067
  value: "",
9050
- children: "All locales"
9068
+ children: _t({
9069
+ id: "JjOi48",
9070
+ message: "All locales"
9071
+ })
9051
9072
  }), locales.map((locale) => /* @__PURE__ */ jsxs("option", {
9052
9073
  value: locale,
9053
- children: [locale.toUpperCase(), locale === defaultLocale ? " (default)" : ""]
9074
+ children: [locale.toUpperCase(), locale === defaultLocale ? _t({
9075
+ id: "FozKV6",
9076
+ message: " (default)"
9077
+ }) : ""]
9054
9078
  }, locale))]
9055
9079
  })]
9056
9080
  });
@@ -9082,6 +9106,15 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
9082
9106
  }, [items, searchQuery]);
9083
9107
  const totalPages = Math.max(1, Math.ceil(filteredItems.length / PAGE_SIZE));
9084
9108
  const paginatedItems = filteredItems.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
9109
+ React.useEffect(() => {
9110
+ if (page >= totalPages - 1 && hasMore && onLoadMore && !searchQuery) onLoadMore();
9111
+ }, [
9112
+ page,
9113
+ totalPages,
9114
+ hasMore,
9115
+ onLoadMore,
9116
+ searchQuery
9117
+ ]);
9085
9118
  return /* @__PURE__ */ jsxs("div", {
9086
9119
  className: "space-y-4",
9087
9120
  children: [
@@ -9102,6 +9135,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
9102
9135
  }), /* @__PURE__ */ jsxs(Link$1, {
9103
9136
  to: "/content/$collection/new",
9104
9137
  params: { collection },
9138
+ search: { locale: activeLocale },
9105
9139
  className: buttonVariants(),
9106
9140
  children: [/* @__PURE__ */ jsx(Plus, {
9107
9141
  className: "mr-2 h-4 w-4",
@@ -9192,6 +9226,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
9192
9226
  /* @__PURE__ */ jsx(Link$1, {
9193
9227
  to: "/content/$collection/new",
9194
9228
  params: { collection },
9229
+ search: { locale: activeLocale },
9195
9230
  className: "text-kumo-brand underline",
9196
9231
  children: "Create your first one"
9197
9232
  })
@@ -9220,6 +9255,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
9220
9255
  className: "text-sm text-kumo-subtle",
9221
9256
  children: [
9222
9257
  filteredItems.length,
9258
+ hasMore && !searchQuery ? "+" : "",
9223
9259
  " ",
9224
9260
  filteredItems.length === 1 ? "item" : "items",
9225
9261
  searchQuery && ` matching "${searchQuery}"`
@@ -12076,6 +12112,7 @@ const OAUTH_PROVIDERS = [{
12076
12112
  icon: /* @__PURE__ */ jsx(GoogleIcon, { className: "h-5 w-5" })
12077
12113
  }];
12078
12114
  function MagicLinkForm({ onBack }) {
12115
+ const { _: _t } = useLingui();
12079
12116
  const [email, setEmail] = React.useState("");
12080
12117
  const [isLoading, setIsLoading] = React.useState(false);
12081
12118
  const [error, setError] = React.useState(null);
@@ -12092,11 +12129,17 @@ function MagicLinkForm({ onBack }) {
12092
12129
  });
12093
12130
  if (!response.ok) {
12094
12131
  const body = await response.json().catch(() => ({}));
12095
- throw new Error(body?.error?.message || "Failed to send magic link");
12132
+ throw new Error(body?.error?.message || _t({
12133
+ id: "dsPiA2",
12134
+ message: "Failed to send magic link"
12135
+ }));
12096
12136
  }
12097
12137
  setSent(true);
12098
12138
  } catch (err) {
12099
- setError(err instanceof Error ? err.message : "Failed to send magic link");
12139
+ setError(err instanceof Error ? err.message : _t({
12140
+ id: "dsPiA2",
12141
+ message: "Failed to send magic link"
12142
+ }));
12100
12143
  } finally {
12101
12144
  setIsLoading(false);
12102
12145
  }
@@ -12121,30 +12164,40 @@ function MagicLinkForm({ onBack }) {
12121
12164
  }),
12122
12165
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h2", {
12123
12166
  className: "text-xl font-semibold",
12124
- children: "Check your email"
12125
- }), /* @__PURE__ */ jsxs("p", {
12167
+ children: _t({
12168
+ id: "v4fiSg",
12169
+ message: "Check your email"
12170
+ })
12171
+ }), /* @__PURE__ */ jsx("p", {
12126
12172
  className: "text-kumo-subtle mt-2",
12127
- children: [
12128
- "If an account exists for ",
12129
- /* @__PURE__ */ jsx("span", {
12130
- className: "font-medium text-kumo-default",
12131
- children: email
12132
- }),
12133
- ", we've sent a sign-in link."
12134
- ]
12173
+ children: /* @__PURE__ */ jsx(Trans, {
12174
+ id: "HvU4EW",
12175
+ message: "If an account exists for <0>{email}</0>, we've sent a sign-in link.",
12176
+ values: { email },
12177
+ components: { 0: /* @__PURE__ */ jsx("span", { className: "font-medium text-kumo-default" }) }
12178
+ })
12135
12179
  })] }),
12136
12180
  /* @__PURE__ */ jsxs("div", {
12137
12181
  className: "text-sm text-kumo-subtle",
12138
- children: [/* @__PURE__ */ jsx("p", { children: "Click the link in the email to sign in." }), /* @__PURE__ */ jsx("p", {
12182
+ children: [/* @__PURE__ */ jsx("p", { children: _t({
12183
+ id: "tfQcxX",
12184
+ message: "Click the link in the email to sign in."
12185
+ }) }), /* @__PURE__ */ jsx("p", {
12139
12186
  className: "mt-2",
12140
- children: "The link will expire in 15 minutes."
12187
+ children: _t({
12188
+ id: "Cw9Xfg",
12189
+ message: "The link will expire in 15 minutes."
12190
+ })
12141
12191
  })]
12142
12192
  }),
12143
12193
  /* @__PURE__ */ jsx(Button, {
12144
12194
  variant: "outline",
12145
12195
  onClick: onBack,
12146
12196
  className: "mt-4 w-full justify-center",
12147
- children: "Back to login"
12197
+ children: _t({
12198
+ id: "VCoEm+",
12199
+ message: "Back to login"
12200
+ })
12148
12201
  })
12149
12202
  ]
12150
12203
  });
@@ -12153,7 +12206,10 @@ function MagicLinkForm({ onBack }) {
12153
12206
  className: "space-y-4",
12154
12207
  children: [
12155
12208
  /* @__PURE__ */ jsx(Input, {
12156
- label: "Email address",
12209
+ label: _t({
12210
+ id: "ATGYL1",
12211
+ message: "Email address"
12212
+ }),
12157
12213
  type: "email",
12158
12214
  value: email,
12159
12215
  onChange: (e) => setEmail(e.target.value),
@@ -12174,14 +12230,23 @@ function MagicLinkForm({ onBack }) {
12174
12230
  variant: "primary",
12175
12231
  loading: isLoading,
12176
12232
  disabled: !email,
12177
- children: isLoading ? "Sending..." : "Send magic link"
12233
+ children: isLoading ? _t({
12234
+ id: "IoAuJG",
12235
+ message: "Sending..."
12236
+ }) : _t({
12237
+ id: "+Ni3Gv",
12238
+ message: "Send magic link"
12239
+ })
12178
12240
  }),
12179
12241
  /* @__PURE__ */ jsx(Button, {
12180
12242
  type: "button",
12181
12243
  variant: "ghost",
12182
12244
  className: "w-full justify-center",
12183
12245
  onClick: onBack,
12184
- children: "Back to login"
12246
+ children: _t({
12247
+ id: "VCoEm+",
12248
+ message: "Back to login"
12249
+ })
12185
12250
  })
12186
12251
  ]
12187
12252
  });
@@ -12191,6 +12256,8 @@ function handleOAuthClick(providerId) {
12191
12256
  }
12192
12257
  function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12193
12258
  const safeRedirectUrl = sanitizeRedirectUrl(redirectUrl);
12259
+ const { _: _t2 } = useLingui();
12260
+ const { locale, setLocale } = useLocale();
12194
12261
  const [method, setMethod] = React.useState("passkey");
12195
12262
  const [urlError, setUrlError] = React.useState(null);
12196
12263
  const { data: manifest, isLoading: manifestLoading } = useQuery({
@@ -12205,7 +12272,11 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12205
12272
  const error = params.get("error");
12206
12273
  const message = params.get("message");
12207
12274
  if (error) {
12208
- setUrlError(message || `Authentication error: ${error}`);
12275
+ setUrlError(message || _t2({
12276
+ id: "Xeb2Gt",
12277
+ message: "Authentication error: {error}",
12278
+ values: { error }
12279
+ }));
12209
12280
  window.history.replaceState({}, "", window.location.pathname);
12210
12281
  }
12211
12282
  }, []);
@@ -12228,7 +12299,13 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12228
12299
  className: "text-center mb-8",
12229
12300
  children: [/* @__PURE__ */ jsx(LogoLockup, { className: "h-10 mx-auto mb-2" }), /* @__PURE__ */ jsxs("h1", {
12230
12301
  className: "text-2xl font-semibold text-kumo-default",
12231
- children: [method === "passkey" && "Sign in to your site", method === "magic-link" && "Sign in with email"]
12302
+ children: [method === "passkey" && _t2({
12303
+ id: "lE0wHD",
12304
+ message: "Sign in to your site"
12305
+ }), method === "magic-link" && _t2({
12306
+ id: "me9L29",
12307
+ message: "Sign in with email"
12308
+ })]
12232
12309
  })]
12233
12310
  }),
12234
12311
  urlError && /* @__PURE__ */ jsx("div", {
@@ -12244,7 +12321,10 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12244
12321
  optionsEndpoint: "/_emdash/api/auth/passkey/options",
12245
12322
  verifyEndpoint: "/_emdash/api/auth/passkey/verify",
12246
12323
  onSuccess: handleSuccess,
12247
- buttonText: "Sign in with Passkey"
12324
+ buttonText: _t2({
12325
+ id: "QjsOMP",
12326
+ message: "Sign in with Passkey"
12327
+ })
12248
12328
  }),
12249
12329
  /* @__PURE__ */ jsxs("div", {
12250
12330
  className: "relative",
@@ -12255,7 +12335,10 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12255
12335
  className: "relative flex justify-center text-xs uppercase",
12256
12336
  children: /* @__PURE__ */ jsx("span", {
12257
12337
  className: "bg-kumo-base px-2 text-kumo-subtle",
12258
- children: "Or continue with"
12338
+ children: _t2({
12339
+ id: "zW+FpA",
12340
+ message: "Or continue with"
12341
+ })
12259
12342
  })
12260
12343
  })]
12261
12344
  }),
@@ -12277,26 +12360,42 @@ function LoginPage({ redirectUrl = "/_emdash/admin" }) {
12277
12360
  className: "w-full justify-center",
12278
12361
  type: "button",
12279
12362
  onClick: () => setMethod("magic-link"),
12280
- children: "Sign in with email link"
12363
+ children: _t2({
12364
+ id: "RxerEl",
12365
+ message: "Sign in with email link"
12366
+ })
12281
12367
  })
12282
12368
  ]
12283
12369
  }), method === "magic-link" && /* @__PURE__ */ jsx(MagicLinkForm, { onBack: () => setMethod("passkey") })]
12284
12370
  }),
12285
12371
  /* @__PURE__ */ jsx("p", {
12286
12372
  className: "text-center mt-6 text-sm text-kumo-subtle",
12287
- children: method === "passkey" ? "Use your registered passkey to sign in securely." : "We'll send you a link to sign in without a password."
12373
+ children: method === "passkey" ? _t2({
12374
+ id: "+ET/av",
12375
+ message: "Use your registered passkey to sign in securely."
12376
+ }) : _t2({
12377
+ id: "pv+wH3",
12378
+ message: "We'll send you a link to sign in without a password."
12379
+ })
12288
12380
  }),
12289
- manifest?.signupEnabled && /* @__PURE__ */ jsxs("p", {
12381
+ manifest?.signupEnabled && /* @__PURE__ */ jsx("p", {
12290
12382
  className: "text-center mt-4 text-sm text-kumo-subtle",
12291
- children: [
12292
- "Don't have an account?",
12293
- " ",
12294
- /* @__PURE__ */ jsx(Link$1, {
12383
+ children: /* @__PURE__ */ jsx(Trans, {
12384
+ id: "352VU2",
12385
+ message: "Don't have an account? <0>Sign up</0>",
12386
+ components: { 0: /* @__PURE__ */ jsx(Link$1, {
12295
12387
  to: "/signup",
12296
- className: "text-kumo-brand hover:underline font-medium",
12297
- children: "Sign up"
12298
- })
12299
- ]
12388
+ className: "text-kumo-brand hover:underline font-medium"
12389
+ }) }
12390
+ })
12391
+ }),
12392
+ SUPPORTED_LOCALES.length > 1 && /* @__PURE__ */ jsx("div", {
12393
+ className: "mt-6 flex justify-center gap-2 text-xs text-kumo-subtle",
12394
+ children: SUPPORTED_LOCALES.map((l, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [i > 0 && /* @__PURE__ */ jsx("span", { children: "·" }), /* @__PURE__ */ jsx("button", {
12395
+ onClick: () => setLocale(l.code),
12396
+ className: l.code === locale ? "font-medium text-kumo-default" : "hover:text-kumo-default transition-colors",
12397
+ children: l.label
12398
+ })] }, l.code))
12300
12399
  })
12301
12400
  ]
12302
12401
  })
@@ -14471,7 +14570,7 @@ function ContentPickerModal({ open, onOpenChange, onSelect }) {
14471
14570
  open,
14472
14571
  onOpenChange,
14473
14572
  children: /* @__PURE__ */ jsxs(Dialog, {
14474
- className: "p-6 w-2xl h-[80vh] flex flex-col",
14573
+ className: "p-6 max-w-2xl h-[80vh] flex flex-col",
14475
14574
  size: "lg",
14476
14575
  children: [
14477
14576
  /* @__PURE__ */ jsxs("div", {
@@ -14821,9 +14920,11 @@ function MenuEditor() {
14821
14920
  /* @__PURE__ */ jsx(Input, {
14822
14921
  label: "URL",
14823
14922
  name: "url",
14824
- type: "url",
14923
+ type: "text",
14825
14924
  required: true,
14826
- placeholder: "https://example.com"
14925
+ pattern: "(https?://.+|/.*)",
14926
+ title: "Enter a URL (https://…) or a relative path (/…)",
14927
+ placeholder: "https://example.com or /about"
14827
14928
  }),
14828
14929
  /* @__PURE__ */ jsxs(Select, {
14829
14930
  label: "Target",
@@ -14987,8 +15088,10 @@ function MenuEditor() {
14987
15088
  editingItem.type === "custom" && /* @__PURE__ */ jsx(Input, {
14988
15089
  label: "URL",
14989
15090
  name: "url",
14990
- type: "url",
15091
+ type: "text",
14991
15092
  required: true,
15093
+ pattern: "(https?://.+|/.*)",
15094
+ title: "Enter a URL (https://…) or a relative path (/…)",
14992
15095
  defaultValue: editingItem.custom_url || ""
14993
15096
  }),
14994
15097
  /* @__PURE__ */ jsxs(Select, {
@@ -16463,13 +16566,18 @@ function Settings() {
16463
16566
  queryKey: ["manifest"],
16464
16567
  queryFn: fetchManifest
16465
16568
  });
16569
+ const { _: _t } = useLingui();
16570
+ const { locale, setLocale } = useLocale();
16466
16571
  const showSecuritySettings = manifest?.authMode === "passkey";
16467
16572
  return /* @__PURE__ */ jsxs("div", {
16468
16573
  className: "space-y-6",
16469
16574
  children: [
16470
16575
  /* @__PURE__ */ jsx("h1", {
16471
16576
  className: "text-2xl font-bold",
16472
- children: "Settings"
16577
+ children: _t({
16578
+ id: "Tz0i8g",
16579
+ message: "Settings"
16580
+ })
16473
16581
  }),
16474
16582
  /* @__PURE__ */ jsxs("div", {
16475
16583
  className: "space-y-2",
@@ -16477,20 +16585,38 @@ function Settings() {
16477
16585
  /* @__PURE__ */ jsx(SettingsLink, {
16478
16586
  to: "/settings/general",
16479
16587
  icon: /* @__PURE__ */ jsx(Gear, { className: "h-5 w-5" }),
16480
- title: "General",
16481
- description: "Site identity, logo, favicon, and reading preferences"
16588
+ title: _t({
16589
+ id: "Weq9zb",
16590
+ message: "General"
16591
+ }),
16592
+ description: _t({
16593
+ id: "RR0ADZ",
16594
+ message: "Site identity, logo, favicon, and reading preferences"
16595
+ })
16482
16596
  }),
16483
16597
  /* @__PURE__ */ jsx(SettingsLink, {
16484
16598
  to: "/settings/social",
16485
16599
  icon: /* @__PURE__ */ jsx(ShareNetwork, { className: "h-5 w-5" }),
16486
- title: "Social Links",
16487
- description: "Social media profile links"
16600
+ title: _t({
16601
+ id: "d0rUsW",
16602
+ message: "Social Links"
16603
+ }),
16604
+ description: _t({
16605
+ id: "qS3mgX",
16606
+ message: "Social media profile links"
16607
+ })
16488
16608
  }),
16489
16609
  /* @__PURE__ */ jsx(SettingsLink, {
16490
16610
  to: "/settings/seo",
16491
16611
  icon: /* @__PURE__ */ jsx(MagnifyingGlass, { className: "h-5 w-5" }),
16492
- title: "SEO",
16493
- description: "Search engine optimization and verification"
16612
+ title: _t({
16613
+ id: "4Ml90q",
16614
+ message: "SEO"
16615
+ }),
16616
+ description: _t({
16617
+ id: "zY0S+v",
16618
+ message: "Search engine optimization and verification"
16619
+ })
16494
16620
  })
16495
16621
  ]
16496
16622
  }),
@@ -16499,13 +16625,25 @@ function Settings() {
16499
16625
  children: [/* @__PURE__ */ jsx(SettingsLink, {
16500
16626
  to: "/settings/security",
16501
16627
  icon: /* @__PURE__ */ jsx(Shield, { className: "h-5 w-5" }),
16502
- title: "Security",
16503
- description: "Manage your passkeys and authentication"
16628
+ title: _t({
16629
+ id: "a3LDKx",
16630
+ message: "Security"
16631
+ }),
16632
+ description: _t({
16633
+ id: "OfnTKV",
16634
+ message: "Manage your passkeys and authentication"
16635
+ })
16504
16636
  }), /* @__PURE__ */ jsx(SettingsLink, {
16505
16637
  to: "/settings/allowed-domains",
16506
16638
  icon: /* @__PURE__ */ jsx(Globe, { className: "h-5 w-5" }),
16507
- title: "Self-Signup Domains",
16508
- description: "Allow users from specific domains to sign up"
16639
+ title: _t({
16640
+ id: "Tllxyd",
16641
+ message: "Self-Signup Domains"
16642
+ }),
16643
+ description: _t({
16644
+ id: "DsZc8w",
16645
+ message: "Allow users from specific domains to sign up"
16646
+ })
16509
16647
  })]
16510
16648
  }),
16511
16649
  /* @__PURE__ */ jsxs("div", {
@@ -16513,14 +16651,58 @@ function Settings() {
16513
16651
  children: [/* @__PURE__ */ jsx(SettingsLink, {
16514
16652
  to: "/settings/api-tokens",
16515
16653
  icon: /* @__PURE__ */ jsx(Key, { className: "h-5 w-5" }),
16516
- title: "API Tokens",
16517
- description: "Create personal access tokens for programmatic API access"
16654
+ title: _t({
16655
+ id: "ZiooJI",
16656
+ message: "API Tokens"
16657
+ }),
16658
+ description: _t({
16659
+ id: "dhH+RW",
16660
+ message: "Create personal access tokens for programmatic API access"
16661
+ })
16518
16662
  }), /* @__PURE__ */ jsx(SettingsLink, {
16519
16663
  to: "/settings/email",
16520
16664
  icon: /* @__PURE__ */ jsx(Envelope, { className: "h-5 w-5" }),
16521
- title: "Email",
16522
- description: "View email provider status and send test emails"
16665
+ title: _t({
16666
+ id: "O3oNi5",
16667
+ message: "Email"
16668
+ }),
16669
+ description: _t({
16670
+ id: "/JPN+P",
16671
+ message: "View email provider status and send test emails"
16672
+ })
16523
16673
  })]
16674
+ }),
16675
+ SUPPORTED_LOCALES.length > 1 && /* @__PURE__ */ jsx("div", {
16676
+ className: "space-y-2",
16677
+ children: /* @__PURE__ */ jsxs("div", {
16678
+ className: "flex items-center justify-between p-4 rounded-lg border bg-kumo-base",
16679
+ children: [/* @__PURE__ */ jsxs("div", {
16680
+ className: "flex items-center gap-3",
16681
+ children: [/* @__PURE__ */ jsx("div", {
16682
+ className: "text-kumo-subtle",
16683
+ children: /* @__PURE__ */ jsx(GlobeSimple, { className: "h-5 w-5" })
16684
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
16685
+ className: "font-medium",
16686
+ children: _t({
16687
+ id: "vXIe7J",
16688
+ message: "Language"
16689
+ })
16690
+ }), /* @__PURE__ */ jsx("div", {
16691
+ className: "text-sm text-kumo-subtle",
16692
+ children: _t({
16693
+ id: "Y8S9QC",
16694
+ message: "Choose your preferred admin language"
16695
+ })
16696
+ })] })]
16697
+ }), /* @__PURE__ */ jsx("div", {
16698
+ className: "flex gap-1",
16699
+ children: SUPPORTED_LOCALES.map((l) => /* @__PURE__ */ jsx("button", {
16700
+ onClick: () => setLocale(l.code),
16701
+ className: cn("rounded-md px-3 py-1.5 text-sm transition-colors", l.code === locale ? "bg-kumo-brand/10 text-kumo-brand font-medium" : "hover:bg-kumo-tint"),
16702
+ children: l.label
16703
+ }, l.code))
16704
+ })]
16705
+ })
16524
16706
  })
16525
16707
  ]
16526
16708
  });
@@ -17276,7 +17458,7 @@ function EmailSettings() {
17276
17458
  const timer = setTimeout(setStatus, 5e3, null);
17277
17459
  return () => clearTimeout(timer);
17278
17460
  }, [status]);
17279
- const { data: settings, isLoading } = useQuery({
17461
+ const { data: settings, isLoading, error: fetchError } = useQuery({
17280
17462
  queryKey: ["email-settings"],
17281
17463
  queryFn: fetchEmailSettings
17282
17464
  });
@@ -17305,6 +17487,27 @@ function EmailSettings() {
17305
17487
  className: "flex items-center justify-center py-12",
17306
17488
  children: /* @__PURE__ */ jsx(Loader, { size: "lg" })
17307
17489
  });
17490
+ if (fetchError) return /* @__PURE__ */ jsxs("div", {
17491
+ className: "space-y-6",
17492
+ children: [/* @__PURE__ */ jsxs("div", {
17493
+ className: "flex items-center gap-3",
17494
+ children: [/* @__PURE__ */ jsx(Link$1, {
17495
+ to: "/settings",
17496
+ children: /* @__PURE__ */ jsx(Button, {
17497
+ variant: "ghost",
17498
+ shape: "square",
17499
+ "aria-label": "Back to settings",
17500
+ children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
17501
+ })
17502
+ }), /* @__PURE__ */ jsx("h1", {
17503
+ className: "text-2xl font-bold",
17504
+ children: "Email Settings"
17505
+ })]
17506
+ }), /* @__PURE__ */ jsxs("div", {
17507
+ className: "flex items-center gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200",
17508
+ children: [/* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), getMutationError(fetchError) || "Failed to load email settings"]
17509
+ })]
17510
+ });
17308
17511
  return /* @__PURE__ */ jsxs("div", {
17309
17512
  className: "space-y-6",
17310
17513
  children: [
@@ -19489,20 +19692,13 @@ function SidebarNav({ manifest }) {
19489
19692
  icon: Stack,
19490
19693
  minRole: ROLE_EDITOR$1
19491
19694
  },
19492
- {
19493
- to: "/taxonomies/$taxonomy",
19494
- label: "Categories",
19495
- icon: FileText,
19496
- params: { taxonomy: "category" },
19497
- minRole: ROLE_EDITOR$1
19498
- },
19499
- {
19695
+ ...manifest.taxonomies.map((tax) => ({
19500
19696
  to: "/taxonomies/$taxonomy",
19501
- label: "Tags",
19697
+ label: tax.label,
19502
19698
  icon: FileText,
19503
- params: { taxonomy: "tag" },
19699
+ params: { taxonomy: tax.name },
19504
19700
  minRole: ROLE_EDITOR$1
19505
- },
19701
+ })),
19506
19702
  {
19507
19703
  to: "/bylines",
19508
19704
  label: "Bylines",
@@ -19894,7 +20090,6 @@ function WelcomeModal({ open, onClose, userName, userRole }) {
19894
20090
  onOpenChange: (isOpen) => !isOpen && handleGetStarted(),
19895
20091
  children: /* @__PURE__ */ jsxs(Dialog, {
19896
20092
  className: "p-6 sm:max-w-md",
19897
- size: "lg",
19898
20093
  children: [
19899
20094
  /* @__PURE__ */ jsxs("div", {
19900
20095
  className: "flex items-start justify-between gap-4",
@@ -25551,6 +25746,10 @@ const adminLayoutRoute = createRoute({
25551
25746
  id: "_admin",
25552
25747
  component: RootComponent
25553
25748
  });
25749
+ if (typeof window !== "undefined" && typeof window.requestIdleCallback === "undefined") {
25750
+ window.requestIdleCallback = (cb) => setTimeout(cb, 50);
25751
+ window.cancelIdleCallback = (id) => clearTimeout(id);
25752
+ }
25554
25753
  function RootComponent() {
25555
25754
  const { data: manifest, isLoading, error } = useQuery({
25556
25755
  queryKey: ["manifest"],
@@ -25683,6 +25882,9 @@ function ContentListPage() {
25683
25882
  });
25684
25883
  }
25685
25884
  });
25885
+ const items = React.useMemo(() => {
25886
+ return data?.pages.flatMap((page) => page.items) || [];
25887
+ }, [data]);
25686
25888
  if (!manifest) return /* @__PURE__ */ jsx(LoadingScreen, {});
25687
25889
  const collectionConfig = manifest.collections[collection];
25688
25890
  if (!collectionConfig) return /* @__PURE__ */ jsx(NotFoundPage, { message: `Collection "${collection}" not found` });
@@ -25694,9 +25896,6 @@ function ContentListPage() {
25694
25896
  search: { locale: locale || void 0 }
25695
25897
  });
25696
25898
  };
25697
- const items = React.useMemo(() => {
25698
- return data?.pages.flatMap((page) => page.items) || [];
25699
- }, [data]);
25700
25899
  return /* @__PURE__ */ jsx(ContentList, {
25701
25900
  collection,
25702
25901
  collectionLabel: collectionConfig.label,
@@ -25705,7 +25904,7 @@ function ContentListPage() {
25705
25904
  isLoading: isLoading || isFetchingNextPage,
25706
25905
  isTrashedLoading,
25707
25906
  hasMore: !!hasNextPage,
25708
- onLoadMore: () => void fetchNextPage(),
25907
+ onLoadMore: React.useCallback(() => void fetchNextPage(), [fetchNextPage]),
25709
25908
  trashedCount: trashedData?.items?.length || 0,
25710
25909
  onDelete: (id) => deleteMutation.mutate(id),
25711
25910
  onRestore: (id) => restoreMutation.mutate(id),
@@ -25729,10 +25928,12 @@ function getPluginBlocks(manifest) {
25729
25928
  const contentNewRoute = createRoute({
25730
25929
  getParentRoute: () => adminLayoutRoute,
25731
25930
  path: "/content/$collection/new",
25732
- component: ContentNewPage
25931
+ component: ContentNewPage,
25932
+ validateSearch: (search) => ({ locale: typeof search.locale === "string" ? search.locale : void 0 })
25733
25933
  });
25734
25934
  function ContentNewPage() {
25735
25935
  const { collection } = useParams({ from: "/_admin/content/$collection/new" });
25936
+ const { locale } = useSearch({ from: "/_admin/content/$collection/new" });
25736
25937
  const navigate = useNavigate();
25737
25938
  const queryClient = useQueryClient();
25738
25939
  const [selectedBylines, setSelectedBylines] = React.useState([]);
@@ -25741,7 +25942,10 @@ function ContentNewPage() {
25741
25942
  queryFn: fetchManifest
25742
25943
  });
25743
25944
  const createMutation = useMutation({
25744
- mutationFn: (data) => createContent(collection, data),
25945
+ mutationFn: (data) => createContent(collection, {
25946
+ ...data,
25947
+ locale
25948
+ }),
25745
25949
  onSuccess: (result) => {
25746
25950
  queryClient.invalidateQueries({ queryKey: ["content", collection] });
25747
25951
  navigate({
@@ -25808,11 +26012,13 @@ function ContentNewPage() {
25808
26012
  const contentEditRoute = createRoute({
25809
26013
  getParentRoute: () => adminLayoutRoute,
25810
26014
  path: "/content/$collection/$id",
25811
- component: ContentEditPage
26015
+ component: ContentEditPage,
26016
+ validateSearch: (search) => ({ ...typeof search.field === "string" && { field: search.field } })
25812
26017
  });
25813
26018
  const ROLE_EDITOR = 40;
25814
26019
  function ContentEditPage() {
25815
26020
  const { collection, id } = useParams({ from: "/_admin/content/$collection/$id" });
26021
+ const searchParams = useSearch({ from: "/_admin/content/$collection/$id" });
25816
26022
  const queryClient = useQueryClient();
25817
26023
  const navigate = useNavigate();
25818
26024
  const toastManager = Toast.useToastManager();
@@ -25829,6 +26035,29 @@ function ContentEditPage() {
25829
26035
  ],
25830
26036
  queryFn: () => fetchContent(collection, id)
25831
26037
  });
26038
+ React.useEffect(() => {
26039
+ if (typeof searchParams.field !== "string" || isLoading) return;
26040
+ const timeoutId = requestIdleCallback(() => {
26041
+ const el = document.getElementById(`field-${searchParams.field}`);
26042
+ if (el) {
26043
+ el.scrollIntoView({
26044
+ behavior: "smooth",
26045
+ block: "center"
26046
+ });
26047
+ el.focus();
26048
+ const { field: _, ...preservedSearch } = searchParams;
26049
+ navigate({
26050
+ search: preservedSearch,
26051
+ replace: true
26052
+ });
26053
+ }
26054
+ });
26055
+ return () => cancelIdleCallback(timeoutId);
26056
+ }, [
26057
+ searchParams,
26058
+ isLoading,
26059
+ navigate
26060
+ ]);
25832
26061
  const { data: translationsData } = useQuery({
25833
26062
  queryKey: [
25834
26063
  "translations",
@@ -26826,19 +27055,30 @@ const router = createAdminRouter(queryClient);
26826
27055
  * Main Admin Application
26827
27056
  */
26828
27057
  const EMPTY_PLUGINS = {};
26829
- function AdminApp({ pluginAdmins = EMPTY_PLUGINS }) {
27058
+ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, locale = "en", messages = {} }) {
26830
27059
  React.useEffect(() => {
26831
27060
  document.getElementById("emdash-boot-loader")?.remove();
26832
27061
  }, []);
26833
- return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(Toasty, { children: /* @__PURE__ */ jsx(PluginAdminProvider, {
26834
- pluginAdmins,
26835
- children: /* @__PURE__ */ jsx(QueryClientProvider, {
26836
- client: queryClient,
26837
- children: /* @__PURE__ */ jsx(RouterProvider, { router })
26838
- })
26839
- }) }) });
27062
+ const i18nInitialized = React.useRef(false);
27063
+ if (!i18nInitialized.current) {
27064
+ i18n.loadAndActivate({
27065
+ locale,
27066
+ messages
27067
+ });
27068
+ i18nInitialized.current = true;
27069
+ }
27070
+ return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(I18nProvider, {
27071
+ i18n,
27072
+ children: /* @__PURE__ */ jsx(Toasty, { children: /* @__PURE__ */ jsx(PluginAdminProvider, {
27073
+ pluginAdmins,
27074
+ children: /* @__PURE__ */ jsx(QueryClientProvider, {
27075
+ client: queryClient,
27076
+ children: /* @__PURE__ */ jsx(RouterProvider, { router })
27077
+ })
27078
+ }) })
27079
+ }) });
26840
27080
  }
26841
27081
 
26842
27082
  //#endregion
26843
- export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, CAPABILITY_LABELS, ContentEditor, ContentList, Dashboard, Header, Link, LoginPage, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, SaveButton, Settings, SetupWizard, Shell, KumoSidebar as Sidebar, SidebarNav, analyzeWpPluginSite, analyzeWxr, apiFetch, bulkCommentAction, checkPluginUpdates, cn, compareRevisions, completeSignup, createAdminRouter, createAllowedDomain, createApiToken, createByline, createCollection, createContent, createField, createMenu, createMenuItem, createRedirect, createSection, createTaxonomy, createTerm, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteCollection, deleteComment, deleteContent, deleteField, deleteFromProvider, deleteMedia, deleteMenu, deleteMenuItem, deletePasskey, deleteRedirect, deleteSection, deleteTerm, deleteWidget, deleteWidgetArea, describeCapability, disablePlugin, disableUser, discardDraft, duplicateContent, enablePlugin, enableUser, executeWpPluginImport, executeWxrImport, fetch404Summary, fetchAllowedDomains, fetchApiTokens, fetchByline, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, installMarketplacePlugin, inviteUser, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useCurrentUser, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, verifySignupToken };
27083
+ export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, CAPABILITY_LABELS, ContentEditor, ContentList, DEFAULT_LOCALE, Dashboard, Header, Link, LoginPage, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, SUPPORTED_LOCALES, SUPPORTED_LOCALE_CODES, SaveButton, Settings, SetupWizard, Shell, KumoSidebar as Sidebar, SidebarNav, analyzeWpPluginSite, analyzeWxr, apiFetch, bulkCommentAction, checkPluginUpdates, cn, compareRevisions, completeSignup, createAdminRouter, createAllowedDomain, createApiToken, createByline, createCollection, createContent, createField, createMenu, createMenuItem, createRedirect, createSection, createTaxonomy, createTerm, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteCollection, deleteComment, deleteContent, deleteField, deleteFromProvider, deleteMedia, deleteMenu, deleteMenuItem, deletePasskey, deleteRedirect, deleteSection, deleteTerm, deleteWidget, deleteWidgetArea, describeCapability, disablePlugin, disableUser, discardDraft, duplicateContent, enablePlugin, enableUser, executeWpPluginImport, executeWxrImport, fetch404Summary, fetchAllowedDomains, fetchApiTokens, fetchByline, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, installMarketplacePlugin, inviteUser, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, resolveLocale, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, verifySignupToken };
26844
27084
  //# sourceMappingURL=index.js.map