@emdash-cms/admin 0.32.0 → 0.33.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 (150) hide show
  1. package/dist/{LocaleDirectionProvider-5U1vW9Op.js → LocaleDirectionProvider-C41G0UML.js} +39 -27
  2. package/dist/LocaleDirectionProvider-C41G0UML.js.map +1 -0
  3. package/dist/{config-BAgt8Ary.d.ts → config-fchhTOsm.d.ts} +1 -1
  4. package/dist/{config-BAgt8Ary.d.ts.map → config-fchhTOsm.d.ts.map} +1 -1
  5. package/dist/index.d.ts +22 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1107 -218
  8. package/dist/index.js.map +1 -1
  9. package/dist/locales/ar/messages.mjs +1 -1
  10. package/dist/locales/ca/messages.mjs +1 -1
  11. package/dist/locales/cs/messages.mjs +1 -0
  12. package/dist/locales/de/messages.mjs +1 -1
  13. package/dist/locales/en/messages.mjs +1 -1
  14. package/dist/locales/en-GB/messages.mjs +1 -1
  15. package/dist/locales/es-419/messages.mjs +1 -1
  16. package/dist/locales/es-ES/messages.mjs +1 -1
  17. package/dist/locales/eu/messages.mjs +1 -1
  18. package/dist/locales/fa/messages.mjs +1 -1
  19. package/dist/locales/fr/messages.mjs +1 -1
  20. package/dist/locales/hi/messages.mjs +1 -0
  21. package/dist/locales/hu/messages.mjs +1 -1
  22. package/dist/locales/id/messages.mjs +1 -1
  23. package/dist/locales/index.d.ts +1 -1
  24. package/dist/locales/index.js +1 -1
  25. package/dist/locales/ja/messages.mjs +1 -1
  26. package/dist/locales/ko/messages.mjs +1 -1
  27. package/dist/locales/nb/messages.mjs +1 -1
  28. package/dist/locales/nl/messages.mjs +1 -1
  29. package/dist/locales/pl/messages.mjs +1 -1
  30. package/dist/locales/pseudo/messages.mjs +1 -1
  31. package/dist/locales/pt-BR/messages.mjs +1 -1
  32. package/dist/locales/sr-Latn/messages.mjs +1 -1
  33. package/dist/locales/sv/messages.mjs +1 -1
  34. package/dist/locales/th/messages.mjs +1 -1
  35. package/dist/locales/tr/messages.mjs +1 -1
  36. package/dist/locales/uk/messages.mjs +1 -1
  37. package/dist/locales/zh-CN/messages.mjs +1 -1
  38. package/dist/locales/zh-TW/messages.mjs +1 -1
  39. package/dist/messages-2nITy6cb.js +6 -0
  40. package/dist/messages-2nITy6cb.js.map +1 -0
  41. package/dist/messages-B-s-GKf5.js +6 -0
  42. package/dist/messages-B-s-GKf5.js.map +1 -0
  43. package/dist/messages-BAIA7DfO.js +6 -0
  44. package/dist/messages-BAIA7DfO.js.map +1 -0
  45. package/dist/messages-BIe0Ym8X.js +6 -0
  46. package/dist/messages-BIe0Ym8X.js.map +1 -0
  47. package/dist/messages-BYdRq2w6.js +6 -0
  48. package/dist/messages-BYdRq2w6.js.map +1 -0
  49. package/dist/messages-BbP6qRzA.js +6 -0
  50. package/dist/messages-BbP6qRzA.js.map +1 -0
  51. package/dist/messages-BbypDsgO.js +6 -0
  52. package/dist/messages-BbypDsgO.js.map +1 -0
  53. package/dist/messages-BiQssJrA.js +6 -0
  54. package/dist/messages-BiQssJrA.js.map +1 -0
  55. package/dist/messages-BmjGFwEW.js +6 -0
  56. package/dist/messages-BmjGFwEW.js.map +1 -0
  57. package/dist/messages-BoMzEid6.js +6 -0
  58. package/dist/messages-BoMzEid6.js.map +1 -0
  59. package/dist/messages-Bq54RVtg.js +6 -0
  60. package/dist/messages-Bq54RVtg.js.map +1 -0
  61. package/dist/messages-Bqz9_4l0.js +6 -0
  62. package/dist/messages-Bqz9_4l0.js.map +1 -0
  63. package/dist/messages-BzV83X48.js +6 -0
  64. package/dist/messages-BzV83X48.js.map +1 -0
  65. package/dist/messages-CBfedvAL.js +6 -0
  66. package/dist/messages-CBfedvAL.js.map +1 -0
  67. package/dist/messages-CPe95E0E.js +6 -0
  68. package/dist/messages-CPe95E0E.js.map +1 -0
  69. package/dist/messages-CSnpmRJn.js +6 -0
  70. package/dist/messages-CSnpmRJn.js.map +1 -0
  71. package/dist/messages-CToObMDq.js +6 -0
  72. package/dist/messages-CToObMDq.js.map +1 -0
  73. package/dist/messages-C_VLhjAp.js +6 -0
  74. package/dist/messages-C_VLhjAp.js.map +1 -0
  75. package/dist/messages-C_zI4Kow.js +6 -0
  76. package/dist/messages-C_zI4Kow.js.map +1 -0
  77. package/dist/messages-Ci8G1wyr.js +6 -0
  78. package/dist/messages-Ci8G1wyr.js.map +1 -0
  79. package/dist/messages-CkcmC2uj.js +6 -0
  80. package/dist/messages-CkcmC2uj.js.map +1 -0
  81. package/dist/messages-ClIrJnnx.js +6 -0
  82. package/dist/messages-ClIrJnnx.js.map +1 -0
  83. package/dist/messages-D1BPX5RB.js +6 -0
  84. package/dist/messages-D1BPX5RB.js.map +1 -0
  85. package/dist/messages-D2Px_N2E.js +6 -0
  86. package/dist/messages-D2Px_N2E.js.map +1 -0
  87. package/dist/messages-D3et4y9E.js +6 -0
  88. package/dist/messages-D3et4y9E.js.map +1 -0
  89. package/dist/messages-DBQIDoWi.js +6 -0
  90. package/dist/messages-DBQIDoWi.js.map +1 -0
  91. package/dist/messages-EB4nBbR-.js +6 -0
  92. package/dist/messages-EB4nBbR-.js.map +1 -0
  93. package/dist/messages-wzeERbqn.js +6 -0
  94. package/dist/messages-wzeERbqn.js.map +1 -0
  95. package/dist/plugins-CQAV8Ap0.js.map +1 -1
  96. package/dist/styles.css +1 -1
  97. package/package.json +3 -2
  98. package/dist/LocaleDirectionProvider-5U1vW9Op.js.map +0 -1
  99. package/dist/messages-4Xvqxc1Y.js +0 -6
  100. package/dist/messages-4Xvqxc1Y.js.map +0 -1
  101. package/dist/messages-BEM_otIY.js +0 -6
  102. package/dist/messages-BEM_otIY.js.map +0 -1
  103. package/dist/messages-BGMVnYg4.js +0 -6
  104. package/dist/messages-BGMVnYg4.js.map +0 -1
  105. package/dist/messages-BclRCIJA.js +0 -6
  106. package/dist/messages-BclRCIJA.js.map +0 -1
  107. package/dist/messages-C1ze9eQE.js +0 -6
  108. package/dist/messages-C1ze9eQE.js.map +0 -1
  109. package/dist/messages-C4H363zG.js +0 -6
  110. package/dist/messages-C4H363zG.js.map +0 -1
  111. package/dist/messages-C6OGaloG.js +0 -6
  112. package/dist/messages-C6OGaloG.js.map +0 -1
  113. package/dist/messages-C8wqmrr8.js +0 -6
  114. package/dist/messages-C8wqmrr8.js.map +0 -1
  115. package/dist/messages-CA8wH0UZ.js +0 -6
  116. package/dist/messages-CA8wH0UZ.js.map +0 -1
  117. package/dist/messages-CIJnESHn.js +0 -6
  118. package/dist/messages-CIJnESHn.js.map +0 -1
  119. package/dist/messages-CM58Ca_k.js +0 -6
  120. package/dist/messages-CM58Ca_k.js.map +0 -1
  121. package/dist/messages-C_GxB5z5.js +0 -6
  122. package/dist/messages-C_GxB5z5.js.map +0 -1
  123. package/dist/messages-CyfnTXoj.js +0 -6
  124. package/dist/messages-CyfnTXoj.js.map +0 -1
  125. package/dist/messages-D0s7hcHS.js +0 -6
  126. package/dist/messages-D0s7hcHS.js.map +0 -1
  127. package/dist/messages-D1x_ybt3.js +0 -6
  128. package/dist/messages-D1x_ybt3.js.map +0 -1
  129. package/dist/messages-DGwx2Tk0.js +0 -6
  130. package/dist/messages-DGwx2Tk0.js.map +0 -1
  131. package/dist/messages-DK0nSPWb.js +0 -6
  132. package/dist/messages-DK0nSPWb.js.map +0 -1
  133. package/dist/messages-DN4lsll0.js +0 -6
  134. package/dist/messages-DN4lsll0.js.map +0 -1
  135. package/dist/messages-DTFf1KbO.js +0 -6
  136. package/dist/messages-DTFf1KbO.js.map +0 -1
  137. package/dist/messages-DbGMdPqR.js +0 -6
  138. package/dist/messages-DbGMdPqR.js.map +0 -1
  139. package/dist/messages-DhWQ6ntN.js +0 -6
  140. package/dist/messages-DhWQ6ntN.js.map +0 -1
  141. package/dist/messages-TIxLB_zK.js +0 -6
  142. package/dist/messages-TIxLB_zK.js.map +0 -1
  143. package/dist/messages-jmWwv5I9.js +0 -6
  144. package/dist/messages-jmWwv5I9.js.map +0 -1
  145. package/dist/messages-n5KSdGIR.js +0 -6
  146. package/dist/messages-n5KSdGIR.js.map +0 -1
  147. package/dist/messages-qZU4N886.js +0 -6
  148. package/dist/messages-qZU4N886.js.map +0 -1
  149. package/dist/messages-z8DCjl2v.js +0 -6
  150. package/dist/messages-z8DCjl2v.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as fetchPlugins, c as updatePluginSettings, d as fetchAuthMode, f as fetchManifest, i as fetchPluginSettings, l as API_BASE, m as throwResponseError, n as enablePlugin, p as parseApiResponse, r as fetchPlugin, s as setPluginMcpEnabled, t as disablePlugin, u as apiFetch } from "./plugins-CQAV8Ap0.js";
2
- import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, s as getLocaleDir, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-5U1vW9Op.js";
2
+ import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, s as getLocaleDir, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-C41G0UML.js";
3
3
  import "./locales/index.js";
4
4
  import { Autocomplete, Badge, Banner, Button, Checkbox, ClipboardText, Collapsible, Combobox, CommandPalette, Dialog, DropdownMenu, Field, Input, InputArea, Label, LayerCard, LinkButton, LinkProvider, Loader, Popover, Select, Sidebar, Sidebar as KumoSidebar, SkeletonLine, Switch, Tabs, Text, Toast, Toasty, Tooltip, TooltipProvider, buttonVariants, inputVariants, useKumoToastManager, useSidebar, useSidebar as useSidebar$1 } from "@cloudflare/kumo";
5
5
  import { i18n } from "@lingui/core";
@@ -18,7 +18,7 @@ import { SortableContext, arrayMove, rectSortingStrategy, sortableKeyboardCoordi
18
18
  import { CSS } from "@dnd-kit/utilities";
19
19
  import { EditorContent, Node as Node$1, NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes, useEditor, useEditorState } from "@tiptap/react";
20
20
  import { Popover as Popover$1 } from "@cloudflare/kumo/primitives/popover";
21
- import { Extension, InputRule, Node as Node$2, PasteRule, escapeForRegEx, mergeAttributes as mergeAttributes$1 } from "@tiptap/core";
21
+ import { Extension, InputRule, Node as Node$2, PasteRule, escapeForRegEx, mergeAttributes as mergeAttributes$1, wrappingInputRule } from "@tiptap/core";
22
22
  import CharacterCount from "@tiptap/extension-character-count";
23
23
  import Focus from "@tiptap/extension-focus";
24
24
  import Placeholder from "@tiptap/extension-placeholder";
@@ -30,7 +30,7 @@ import { TableHeader } from "@tiptap/extension-table-header";
30
30
  import { TableRow } from "@tiptap/extension-table-row";
31
31
  import TextAlign from "@tiptap/extension-text-align";
32
32
  import Typography from "@tiptap/extension-typography";
33
- import { AllSelection, NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
33
+ import { AllSelection, NodeSelection, Plugin, PluginKey, Selection, TextSelection } from "@tiptap/pm/state";
34
34
  import { CellSelection } from "@tiptap/pm/tables";
35
35
  import { BubbleMenu } from "@tiptap/react/menus";
36
36
  import StarterKit from "@tiptap/starter-kit";
@@ -40,6 +40,9 @@ import Code$1 from "@tiptap/extension-code";
40
40
  import { offset } from "@floating-ui/react";
41
41
  import { DragHandle } from "@tiptap/extension-drag-handle-react";
42
42
  import { isAllowedUri } from "@tiptap/extension-link";
43
+ import { OrderedList } from "@tiptap/extension-list";
44
+ import { Fragment as Fragment$1, Slice } from "@tiptap/pm/model";
45
+ import { canJoin, dropPoint } from "@tiptap/pm/transform";
43
46
  import { BlockRenderer } from "@emdash-cms/blocks";
44
47
  import DOMPurify from "dompurify";
45
48
  import { Marked, Renderer } from "marked";
@@ -1882,6 +1885,24 @@ async function reorderFields(collectionSlug, fieldSlugs) {
1882
1885
  }));
1883
1886
  }
1884
1887
  /**
1888
+ * Reorder collections in the admin sidebar.
1889
+ *
1890
+ * `slugs` is the full desired order — collections left out lose their
1891
+ * explicit position and fall back to alphabetical order after the ordered
1892
+ * ones.
1893
+ */
1894
+ async function reorderCollections(slugs) {
1895
+ const response = await apiFetch(`${API_BASE}/schema/collections/reorder`, {
1896
+ method: "POST",
1897
+ headers: { "Content-Type": "application/json" },
1898
+ body: JSON.stringify({ slugs })
1899
+ });
1900
+ if (!response.ok) await throwResponseError(response, i18n._({
1901
+ id: "VLe0JL",
1902
+ message: "Failed to reorder content types"
1903
+ }));
1904
+ }
1905
+ /**
1885
1906
  * Fetch orphaned content tables
1886
1907
  */
1887
1908
  async function fetchOrphanedTables() {
@@ -2626,6 +2647,21 @@ async function updateTerm(taxonomyName, slug, input, options = {}) {
2626
2647
  }), "Failed to update term")).term;
2627
2648
  }
2628
2649
  /**
2650
+ * Set the manual order of one sibling group.
2651
+ *
2652
+ * `ids` and `parentId` are translation groups: a term holds one position across
2653
+ * every locale, so there is no locale to pass. `ids` may name only the terms
2654
+ * this locale renders — the server permutes them within the positions they
2655
+ * already occupy and leaves untranslated members where they are.
2656
+ */
2657
+ async function reorderTerms(taxonomyName, input) {
2658
+ await parseApiResponse(await apiFetch(`${API_BASE}/taxonomies/${taxonomyName}/reorder`, {
2659
+ method: "POST",
2660
+ headers: { "Content-Type": "application/json" },
2661
+ body: JSON.stringify(input)
2662
+ }), "Failed to reorder terms");
2663
+ }
2664
+ /**
2629
2665
  * Delete a term
2630
2666
  */
2631
2667
  async function deleteTerm(taxonomyName, slug, options = {}) {
@@ -4755,8 +4791,8 @@ function useContentStatusLabel(state) {
4755
4791
  const { _: _t } = useLingui();
4756
4792
  switch (state) {
4757
4793
  case "published": return _t({
4758
- id: "EEYbdt",
4759
- message: "Publish"
4794
+ id: "u3wRF+",
4795
+ message: "Published"
4760
4796
  });
4761
4797
  case "draft": return _t({
4762
4798
  id: "eneWvv",
@@ -6940,7 +6976,7 @@ function toInput(draft) {
6940
6976
  function serializeDraft(draft) {
6941
6977
  return JSON.stringify(draft);
6942
6978
  }
6943
- function SeoPanel({ contentKey, seo, onChange }) {
6979
+ function SeoPanel({ contentKey, seo, onChange, defaultTitle, defaultDescription }) {
6944
6980
  const { _: _t } = useLingui();
6945
6981
  const titleId = React$1.useId();
6946
6982
  const descriptionId = React$1.useId();
@@ -7064,6 +7100,7 @@ function SeoPanel({ contentKey, seo, onChange }) {
7064
7100
  }),
7065
7101
  className: "w-full",
7066
7102
  value: draft.title,
7103
+ placeholder: defaultTitle ?? void 0,
7067
7104
  onChange: (e) => {
7068
7105
  updateDraft({ title: e.target.value });
7069
7106
  },
@@ -7099,6 +7136,7 @@ function SeoPanel({ contentKey, seo, onChange }) {
7099
7136
  values: { 0: draft.description.length }
7100
7137
  }) : void 0,
7101
7138
  value: draft.description,
7139
+ placeholder: defaultDescription ?? void 0,
7102
7140
  onChange: (e) => {
7103
7141
  updateDraft({ description: e.target.value });
7104
7142
  },
@@ -8443,7 +8481,9 @@ const ContentSettingsPanel = React$1.memo(function ContentSettingsPanel({ collec
8443
8481
  }), /* @__PURE__ */ jsx(SeoPanel, {
8444
8482
  contentKey: item?.id ?? `new:${collection}`,
8445
8483
  seo: item?.seo,
8446
- onChange: onSeoChange
8484
+ onChange: onSeoChange,
8485
+ defaultTitle: typeof item?.data?.title === "string" ? item.data.title : null,
8486
+ defaultDescription: typeof item?.data?.excerpt === "string" ? item.data.excerpt : null
8447
8487
  })]
8448
8488
  })
8449
8489
  }),
@@ -11387,6 +11427,401 @@ const MarkdownLinkExtension = Extension.create({
11387
11427
  }
11388
11428
  });
11389
11429
 
11430
+ //#endregion
11431
+ //#region src/components/editor/ordered-list.ts
11432
+ const ORDERED_LIST_INPUT_REGEX = /^(\d+)\.\s$/;
11433
+ const MAX_START = 2147483647;
11434
+ const normalizeId = (value) => {
11435
+ if (typeof value !== "string") return void 0;
11436
+ const id = value.trim();
11437
+ return id.length > 0 && id.length <= 128 ? id : void 0;
11438
+ };
11439
+ const normalizeStart = (value) => typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_START ? value : void 0;
11440
+ const legacyId = (seed) => {
11441
+ const readable = `legacy:${seed}`;
11442
+ if (readable.length <= 128) return readable;
11443
+ let hash = 2166136261;
11444
+ for (let i = 0; i < seed.length; i++) {
11445
+ hash ^= seed.charCodeAt(i);
11446
+ hash = Math.imul(hash, 16777619);
11447
+ }
11448
+ return `legacy:${seed.slice(0, 96)}:${(hash >>> 0).toString(36)}:${seed.length.toString(36)}`;
11449
+ };
11450
+ const createId = () => globalThis.crypto?.randomUUID?.() ?? `list-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
11451
+ const createRepairId = (sourceId, scope, attempt) => {
11452
+ const base = legacyId(`repair:${sourceId}:${scope}`);
11453
+ const suffix = `:${attempt.toString(36)}`;
11454
+ return `${base.slice(0, 128 - suffix.length)}${suffix}`;
11455
+ };
11456
+ function collectLists(doc) {
11457
+ const lists = [];
11458
+ doc.descendants((node, pos) => {
11459
+ if (node.type.name !== "orderedList") return;
11460
+ const resolved = doc.resolve(pos);
11461
+ let context = "root";
11462
+ let contextPos = null;
11463
+ for (let depth = resolved.depth; depth > 0; depth--) if (resolved.node(depth).type.name === "listItem") {
11464
+ contextPos = resolved.before(depth);
11465
+ context = `listItem:${contextPos}`;
11466
+ break;
11467
+ }
11468
+ if (contextPos === null && resolved.depth > 0) {
11469
+ contextPos = resolved.before(resolved.depth);
11470
+ context = `${resolved.node(resolved.depth).type.name}:${contextPos}`;
11471
+ }
11472
+ lists.push({
11473
+ node,
11474
+ pos,
11475
+ depth: resolved.depth,
11476
+ context,
11477
+ contextPos
11478
+ });
11479
+ });
11480
+ return lists;
11481
+ }
11482
+ function normalizeOrderedListDocument(doc) {
11483
+ const canonicalBySourceScope = /* @__PURE__ */ new Map();
11484
+ const assignedCanonicalIds = /* @__PURE__ */ new Set();
11485
+ const lists = collectLists(doc).map((list) => {
11486
+ const sourceId = normalizeId(list.node.attrs.listId) ?? legacyId(`pm:${list.pos}:${list.depth}:${list.context}`);
11487
+ const scope = JSON.stringify([list.depth, list.context]);
11488
+ const sourceScope = JSON.stringify([sourceId, scope]);
11489
+ let listId = canonicalBySourceScope.get(sourceScope);
11490
+ if (!listId) {
11491
+ if (!assignedCanonicalIds.has(sourceId)) listId = sourceId;
11492
+ else {
11493
+ let attempt = 0;
11494
+ do
11495
+ listId = createRepairId(sourceId, scope, attempt++);
11496
+ while (assignedCanonicalIds.has(listId));
11497
+ }
11498
+ canonicalBySourceScope.set(sourceScope, listId);
11499
+ assignedCanonicalIds.add(listId);
11500
+ }
11501
+ return {
11502
+ ...list,
11503
+ listId,
11504
+ scopeKey: JSON.stringify([
11505
+ listId,
11506
+ list.depth,
11507
+ list.context
11508
+ ])
11509
+ };
11510
+ });
11511
+ const bases = /* @__PURE__ */ new Map();
11512
+ for (const list of lists) {
11513
+ const start = normalizeStart(list.node.attrs.listStart);
11514
+ if (start !== void 0 && !bases.has(list.scopeKey)) bases.set(list.scopeKey, start);
11515
+ }
11516
+ for (const list of lists) if (!bases.has(list.scopeKey)) bases.set(list.scopeKey, normalizeStart(list.node.attrs.start) ?? 1);
11517
+ const counts = /* @__PURE__ */ new Map();
11518
+ const changes = [];
11519
+ for (const list of lists) {
11520
+ const listStart = bases.get(list.scopeKey);
11521
+ const count = counts.get(list.scopeKey) ?? 0;
11522
+ const start = normalizeStart(listStart + count) ?? 1;
11523
+ counts.set(list.scopeKey, count + list.node.childCount);
11524
+ if (list.node.attrs.listId !== list.listId || list.node.attrs.listStart !== listStart || list.node.attrs.start !== start) changes.push({
11525
+ pos: list.pos,
11526
+ attrs: {
11527
+ ...list.node.attrs,
11528
+ listId: list.listId,
11529
+ listStart,
11530
+ start
11531
+ }
11532
+ });
11533
+ }
11534
+ return changes;
11535
+ }
11536
+ function normalizeTransaction(state) {
11537
+ const changes = normalizeOrderedListDocument(state.doc);
11538
+ if (changes.length === 0) {
11539
+ const lists = collectLists(state.doc);
11540
+ if (!lists.some((list, index) => {
11541
+ const next = lists[index + 1];
11542
+ return next !== void 0 && next.pos === list.pos + list.node.nodeSize && next.depth === list.depth && next.context === list.context && normalizeId(next.node.attrs.listId) === normalizeId(list.node.attrs.listId) && normalizeStart(next.node.attrs.listStart) === normalizeStart(list.node.attrs.listStart);
11543
+ })) return null;
11544
+ }
11545
+ const transaction = state.tr;
11546
+ for (const change of changes) transaction.setNodeMarkup(change.pos, void 0, change.attrs);
11547
+ const normalizedLists = collectLists(transaction.doc);
11548
+ const joinPositions = normalizedLists.flatMap((list, index) => {
11549
+ const next = normalizedLists[index + 1];
11550
+ return next !== void 0 && next.pos === list.pos + list.node.nodeSize && next.depth === list.depth && next.context === list.context && normalizeId(next.node.attrs.listId) === normalizeId(list.node.attrs.listId) && normalizeStart(next.node.attrs.listStart) === normalizeStart(list.node.attrs.listStart) ? [next.pos] : [];
11551
+ });
11552
+ for (const pos of joinPositions.toReversed()) if (canJoin(transaction.doc, pos)) transaction.join(pos);
11553
+ if (!transaction.docChanged) return null;
11554
+ return transaction;
11555
+ }
11556
+ function remapPaste(slice) {
11557
+ const identities = /* @__PURE__ */ new Map();
11558
+ let missingIndex = 0;
11559
+ const mapFragment = (fragment, depth, parentContext) => {
11560
+ const children = [];
11561
+ fragment.forEach((node, _offset, index) => {
11562
+ const childContext = node.type.name === "listItem" ? `${parentContext}/listItem:${index}` : parentContext;
11563
+ const content = mapFragment(node.content, depth + 1, childContext);
11564
+ if (node.type.name !== "orderedList") {
11565
+ children.push(node.copy(content));
11566
+ return;
11567
+ }
11568
+ const sourceId = normalizeId(node.attrs.listId) ?? `missing:${missingIndex++}`;
11569
+ const scope = JSON.stringify([
11570
+ sourceId,
11571
+ depth,
11572
+ parentContext
11573
+ ]);
11574
+ let identity = identities.get(scope);
11575
+ if (!identity) {
11576
+ identity = {
11577
+ listId: createId(),
11578
+ listStart: normalizeStart(node.attrs.start) ?? normalizeStart(node.attrs.listStart) ?? 1
11579
+ };
11580
+ identities.set(scope, identity);
11581
+ }
11582
+ children.push(node.type.create({
11583
+ ...node.attrs,
11584
+ ...identity,
11585
+ start: identity.listStart
11586
+ }, content, node.marks));
11587
+ });
11588
+ return Fragment$1.fromArray(children);
11589
+ };
11590
+ return new Slice(mapFragment(slice.content, 0, "root"), slice.openStart, slice.openEnd);
11591
+ }
11592
+ function sliceHasOrderedList(slice) {
11593
+ let found = false;
11594
+ slice.content.descendants((node) => {
11595
+ if (node.type.name !== "orderedList") return true;
11596
+ found = true;
11597
+ return false;
11598
+ });
11599
+ return found;
11600
+ }
11601
+ function displayedStartAtSelection(state) {
11602
+ const current = collectLists(state.doc).findLast((list) => state.selection.from >= list.pos && state.selection.from <= list.pos + list.node.nodeSize);
11603
+ if (!current) return null;
11604
+ const listId = normalizeId(current.node.attrs.listId);
11605
+ const start = normalizeStart(current.node.attrs.start);
11606
+ if (!listId || start === void 0) return null;
11607
+ let precedingItems = 0;
11608
+ current.node.forEach((child, offset) => {
11609
+ if (current.pos + 1 + offset + child.nodeSize <= state.selection.from) precedingItems++;
11610
+ });
11611
+ return {
11612
+ listId,
11613
+ listStart: normalizeStart(start + precedingItems) ?? 1
11614
+ };
11615
+ }
11616
+ function rewriteSliceIdentity(slice, sourceId, listId, listStart) {
11617
+ const mapFragment = (fragment) => {
11618
+ const children = [];
11619
+ fragment.forEach((node) => {
11620
+ const content = mapFragment(node.content);
11621
+ children.push(node.type.name === "orderedList" && normalizeId(node.attrs.listId) === sourceId ? node.type.create({
11622
+ ...node.attrs,
11623
+ listId,
11624
+ listStart,
11625
+ start: listStart
11626
+ }, content, node.marks) : node.copy(content));
11627
+ });
11628
+ return Fragment$1.fromArray(children);
11629
+ };
11630
+ return new Slice(mapFragment(slice.content), slice.openStart, slice.openEnd);
11631
+ }
11632
+ function prepareCopy(slice, state) {
11633
+ const selected = displayedStartAtSelection(state);
11634
+ if (!selected) return slice;
11635
+ let updated = false;
11636
+ const mapFragment = (fragment) => {
11637
+ const children = [];
11638
+ fragment.forEach((node) => {
11639
+ const isFirstSelectedList = !updated && node.type.name === "orderedList" && normalizeId(node.attrs.listId) === selected.listId;
11640
+ if (isFirstSelectedList) updated = true;
11641
+ const content = mapFragment(node.content);
11642
+ if (isFirstSelectedList) children.push(node.type.create({
11643
+ ...node.attrs,
11644
+ start: selected.listStart
11645
+ }, content, node.marks));
11646
+ else children.push(node.copy(content));
11647
+ });
11648
+ return Fragment$1.fromArray(children);
11649
+ };
11650
+ return new Slice(mapFragment(slice.content), slice.openStart, slice.openEnd);
11651
+ }
11652
+ function remapMove(slice, state) {
11653
+ const selected = displayedStartAtSelection(state);
11654
+ if (!selected) return slice;
11655
+ return rewriteSliceIdentity(slice, selected.listId, createId(), selected.listStart);
11656
+ }
11657
+ function selectedList(state) {
11658
+ const lists = collectLists(state.doc);
11659
+ const current = lists.findLast((list) => state.selection.from >= list.pos && state.selection.to <= list.pos + list.node.nodeSize);
11660
+ if (!current) return null;
11661
+ return lists.filter((list) => list.depth === current.depth && list.context === current.context && state.selection.from < list.pos + list.node.nodeSize && state.selection.to > list.pos).length === 1 ? {
11662
+ current,
11663
+ lists
11664
+ } : null;
11665
+ }
11666
+ function rewriteTail(state, dispatch, mode) {
11667
+ const selected = selectedList(state);
11668
+ if (!selected) return false;
11669
+ const { current, lists } = selected;
11670
+ const currentId = normalizeId(current.node.attrs.listId);
11671
+ if (!currentId) return false;
11672
+ const compatible = lists.filter((list) => list.depth === current.depth && list.context === current.context);
11673
+ const predecessor = compatible.findLast((list) => list.pos < current.pos);
11674
+ const predecessorId = predecessor ? normalizeId(predecessor.node.attrs.listId) : void 0;
11675
+ if (mode === "continue" && (!predecessorId || predecessorId === currentId)) return false;
11676
+ if (!dispatch) return true;
11677
+ const listId = mode === "continue" ? predecessorId : createId();
11678
+ const listStart = mode === "continue" ? normalizeStart(predecessor.node.attrs.listStart) ?? 1 : 1;
11679
+ const transaction = state.tr;
11680
+ for (const list of compatible) {
11681
+ if (list.pos < current.pos || normalizeId(list.node.attrs.listId) !== currentId) continue;
11682
+ transaction.setNodeMarkup(list.pos, void 0, {
11683
+ ...list.node.attrs,
11684
+ listId,
11685
+ listStart
11686
+ });
11687
+ }
11688
+ dispatch(transaction);
11689
+ return true;
11690
+ }
11691
+ function buildDropTransaction(state, slice, insertPos, deleteSource) {
11692
+ const transaction = state.tr;
11693
+ if (deleteSource) transaction.deleteSelection();
11694
+ const from = transaction.mapping.map(insertPos);
11695
+ const beforeInsert = transaction.doc;
11696
+ transaction.replaceRange(from, from, slice);
11697
+ if (transaction.doc.eq(beforeInsert)) return null;
11698
+ let to = from;
11699
+ transaction.mapping.maps.at(-1)?.forEach((_oldFrom, _oldTo, _newFrom, newTo) => {
11700
+ to = Math.max(to, newTo);
11701
+ });
11702
+ return {
11703
+ transaction,
11704
+ from,
11705
+ to
11706
+ };
11707
+ }
11708
+ function handleMovedDrop(view, event, slice, moved) {
11709
+ if (!sliceHasOrderedList(slice)) return false;
11710
+ const eventPos = view.posAtCoords({
11711
+ left: event.clientX,
11712
+ top: event.clientY
11713
+ });
11714
+ if (!eventPos) return false;
11715
+ const insertPos = dropPoint(view.state.doc, eventPos.pos, slice) ?? eventPos.pos;
11716
+ if (!moved) {
11717
+ const dropped = buildDropTransaction(view.state, remapPaste(slice), insertPos, false);
11718
+ if (!dropped) return false;
11719
+ const selectionPos = Math.min(dropped.to, dropped.transaction.doc.content.size);
11720
+ dropped.transaction.setSelection(Selection.near(dropped.transaction.doc.resolve(selectionPos), -1));
11721
+ view.focus();
11722
+ view.dispatch(dropped.transaction.setMeta("uiEvent", "drop").scrollIntoView());
11723
+ return true;
11724
+ }
11725
+ const selected = selectedList(view.state);
11726
+ if (!selected) return false;
11727
+ const preview = buildDropTransaction(view.state, slice, insertPos, true);
11728
+ if (!preview) return false;
11729
+ const sourceContextPos = selected.current.contextPos;
11730
+ const mappedSourceContextPos = sourceContextPos === null ? null : preview.transaction.mapping.map(sourceContextPos, 1);
11731
+ if (collectLists(preview.transaction.doc).some((list) => list.pos < preview.to && list.pos + list.node.nodeSize > preview.from && list.depth === selected.current.depth && list.contextPos === mappedSourceContextPos)) return false;
11732
+ const remapped = remapMove(slice, view.state);
11733
+ if (remapped === slice) return false;
11734
+ const dropped = buildDropTransaction(view.state, remapped, insertPos, true);
11735
+ if (!dropped) return false;
11736
+ const selectionPos = Math.min(dropped.to, dropped.transaction.doc.content.size);
11737
+ dropped.transaction.setSelection(Selection.near(dropped.transaction.doc.resolve(selectionPos), -1));
11738
+ view.focus();
11739
+ view.dispatch(dropped.transaction.setMeta("uiEvent", "drop").scrollIntoView());
11740
+ return true;
11741
+ }
11742
+ const EmDashOrderedList = OrderedList.extend({
11743
+ addAttributes() {
11744
+ const parent = this.parent?.() ?? {};
11745
+ return {
11746
+ ...parent,
11747
+ start: {
11748
+ ...parent.start,
11749
+ parseHTML: (element) => normalizeStart(Number(element.dataset.emdashListFirst)) ?? normalizeStart(Number(element.getAttribute("start"))) ?? 1
11750
+ },
11751
+ listId: {
11752
+ default: null,
11753
+ parseHTML: (element) => normalizeId(element.dataset.emdashListId) ?? null,
11754
+ rendered: false
11755
+ },
11756
+ listStart: {
11757
+ default: null,
11758
+ parseHTML: (element) => normalizeStart(Number(element.dataset.emdashListStart)) ?? null,
11759
+ rendered: false
11760
+ }
11761
+ };
11762
+ },
11763
+ renderHTML({ HTMLAttributes, node }) {
11764
+ const { start: _start, ...withoutStart } = HTMLAttributes;
11765
+ const start = normalizeStart(node.attrs.start) ?? 1;
11766
+ const continuity = {
11767
+ "data-emdash-list-id": normalizeId(node.attrs.listId),
11768
+ "data-emdash-list-start": normalizeStart(node.attrs.listStart),
11769
+ "data-emdash-list-first": start
11770
+ };
11771
+ return start === 1 ? [
11772
+ "ol",
11773
+ mergeAttributes$1(this.options.HTMLAttributes, withoutStart, continuity),
11774
+ 0
11775
+ ] : [
11776
+ "ol",
11777
+ mergeAttributes$1(this.options.HTMLAttributes, {
11778
+ ...withoutStart,
11779
+ start
11780
+ }, continuity),
11781
+ 0
11782
+ ];
11783
+ },
11784
+ addCommands() {
11785
+ return {
11786
+ toggleOrderedList: () => ({ commands, chain }) => this.editor.isActive(this.name) ? commands.toggleList(this.name, this.options.itemTypeName, this.options.keepMarks) : chain().toggleList(this.name, this.options.itemTypeName, this.options.keepMarks).updateAttributes(this.name, {
11787
+ listId: createId(),
11788
+ listStart: 1
11789
+ }).run(),
11790
+ continueOrderedList: () => ({ state, dispatch }) => rewriteTail(state, dispatch, "continue"),
11791
+ restartOrderedList: () => ({ state, dispatch }) => rewriteTail(state, dispatch, "restart")
11792
+ };
11793
+ },
11794
+ addInputRules() {
11795
+ return [wrappingInputRule({
11796
+ find: ORDERED_LIST_INPUT_REGEX,
11797
+ type: this.type,
11798
+ getAttributes: (match) => {
11799
+ const listStart = normalizeStart(Number(match[1])) ?? 1;
11800
+ return {
11801
+ start: listStart,
11802
+ listStart,
11803
+ listId: createId()
11804
+ };
11805
+ },
11806
+ joinPredicate: () => false
11807
+ })];
11808
+ },
11809
+ addProseMirrorPlugins() {
11810
+ return [new Plugin({
11811
+ appendTransaction: (_transactions, _oldState, newState) => normalizeTransaction(newState),
11812
+ props: {
11813
+ transformCopied: (slice, view) => prepareCopy(slice, view.state),
11814
+ handleDrop: handleMovedDrop,
11815
+ handlePaste(view, _event, slice) {
11816
+ if (!sliceHasOrderedList(slice)) return false;
11817
+ view.dispatch(view.state.tr.replaceSelection(remapPaste(slice)).setMeta("paste", true).setMeta("uiEvent", "paste").scrollIntoView());
11818
+ return true;
11819
+ }
11820
+ }
11821
+ })];
11822
+ }
11823
+ });
11824
+
11390
11825
  //#endregion
11391
11826
  //#region src/components/editor/PluginBlockNode.tsx
11392
11827
  /**
@@ -11973,17 +12408,124 @@ function sanitizeGalleryImages(value, withKeys = false) {
11973
12408
  }
11974
12409
  const attrStr = (v) => typeof v === "string" && v ? v : void 0;
11975
12410
  const attrNum = (v) => typeof v === "number" && v ? v : void 0;
12411
+ const MAX_ORDERED_LIST_START = 2147483647;
12412
+ function normalizeListId(value) {
12413
+ if (typeof value !== "string") return void 0;
12414
+ const normalized = value.trim();
12415
+ return normalized.length > 0 && normalized.length <= 128 ? normalized : void 0;
12416
+ }
12417
+ function normalizeListStart(value) {
12418
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_ORDERED_LIST_START ? value : void 0;
12419
+ }
12420
+ function deriveLegacyListId(seed) {
12421
+ const readable = `legacy:${seed}`;
12422
+ if (readable.length <= 128) return readable;
12423
+ let hash = 2166136261;
12424
+ for (let i = 0; i < seed.length; i++) {
12425
+ hash ^= seed.charCodeAt(i);
12426
+ hash = Math.imul(hash, 16777619);
12427
+ }
12428
+ return `legacy:${seed.slice(0, 96)}:${(hash >>> 0).toString(36)}:${seed.length.toString(36)}`;
12429
+ }
12430
+ function readOrderedListMetadata(attrs, fallbackId) {
12431
+ return {
12432
+ listId: normalizeListId(attrs?.listId) ?? deriveLegacyListId(fallbackId),
12433
+ listStart: normalizeListStart(attrs?.listStart) ?? normalizeListStart(attrs?.start) ?? 1
12434
+ };
12435
+ }
12436
+ function clonePortableTextProseMirrorNode(node) {
12437
+ return {
12438
+ ...node,
12439
+ attrs: node.attrs ? { ...node.attrs } : void 0,
12440
+ content: node.content?.map(clonePortableTextProseMirrorNode),
12441
+ marks: node.marks?.map((mark) => ({
12442
+ ...mark,
12443
+ attrs: mark.attrs ? { ...mark.attrs } : void 0
12444
+ }))
12445
+ };
12446
+ }
12447
+ function normalizeOrderedListJson(doc) {
12448
+ const normalized = {
12449
+ ...doc,
12450
+ content: doc.content.map(clonePortableTextProseMirrorNode)
12451
+ };
12452
+ const lists = [];
12453
+ const visit = (node, path, depth, context) => {
12454
+ if (node.type === "orderedList") lists.push({
12455
+ node,
12456
+ path,
12457
+ depth,
12458
+ context
12459
+ });
12460
+ for (const [index, child] of (node.content ?? []).entries()) {
12461
+ const childPath = `${path}:${index}`;
12462
+ visit(child, childPath, depth + 1, child.type === "listItem" ? `listItem:${childPath}` : context);
12463
+ }
12464
+ };
12465
+ for (const [index, node] of normalized.content.entries()) visit(node, `root:${index}`, 0, "root");
12466
+ const canonicalBySourceScope = /* @__PURE__ */ new Map();
12467
+ const assignedIds = /* @__PURE__ */ new Set();
12468
+ const descriptors = lists.map((list) => {
12469
+ const sourceId = normalizeListId(list.node.attrs?.listId) ?? deriveLegacyListId(`pm-json:${list.path}:${list.depth}:${list.context}`);
12470
+ const scope = JSON.stringify([list.depth, list.context]);
12471
+ const sourceScope = JSON.stringify([sourceId, scope]);
12472
+ let listId = canonicalBySourceScope.get(sourceScope);
12473
+ if (!listId) {
12474
+ if (!assignedIds.has(sourceId)) listId = sourceId;
12475
+ else {
12476
+ let attempt = 0;
12477
+ do {
12478
+ const suffix = `:${attempt.toString(36)}`;
12479
+ listId = `${deriveLegacyListId(`repair:${sourceId}:${scope}`).slice(0, 128 - suffix.length)}${suffix}`;
12480
+ attempt++;
12481
+ } while (assignedIds.has(listId));
12482
+ }
12483
+ canonicalBySourceScope.set(sourceScope, listId);
12484
+ assignedIds.add(listId);
12485
+ }
12486
+ return {
12487
+ ...list,
12488
+ listId,
12489
+ scopeKey: JSON.stringify([
12490
+ listId,
12491
+ list.depth,
12492
+ list.context
12493
+ ])
12494
+ };
12495
+ });
12496
+ const bases = /* @__PURE__ */ new Map();
12497
+ for (const list of descriptors) {
12498
+ const listStart = normalizeListStart(list.node.attrs?.listStart);
12499
+ if (listStart !== void 0 && !bases.has(list.scopeKey)) bases.set(list.scopeKey, listStart);
12500
+ }
12501
+ for (const list of descriptors) if (!bases.has(list.scopeKey)) bases.set(list.scopeKey, normalizeListStart(list.node.attrs?.start) ?? 1);
12502
+ const counts = /* @__PURE__ */ new Map();
12503
+ for (const list of descriptors) {
12504
+ const listStart = bases.get(list.scopeKey);
12505
+ const count = counts.get(list.scopeKey) ?? 0;
12506
+ const start = normalizeListStart(listStart + count) ?? 1;
12507
+ const directItemCount = list.node.content?.filter((node) => node.type === "listItem").length ?? 0;
12508
+ counts.set(list.scopeKey, count + directItemCount);
12509
+ list.node.attrs = {
12510
+ ...list.node.attrs,
12511
+ listId: list.listId,
12512
+ listStart,
12513
+ start
12514
+ };
12515
+ }
12516
+ return normalized;
12517
+ }
11976
12518
  function prosemirrorToPortableText(doc) {
11977
12519
  if (!doc || doc.type !== "doc" || !doc.content) return [];
11978
12520
  const blocks = [];
11979
- for (const node of doc.content) {
11980
- const converted = convertPMNode(node);
12521
+ for (let i = 0; i < doc.content.length; i++) {
12522
+ const converted = convertPMNode(doc.content[i], `root:${i}`);
11981
12523
  if (converted) if (Array.isArray(converted)) blocks.push(...converted);
11982
12524
  else blocks.push(converted);
11983
12525
  }
11984
12526
  return blocks;
11985
12527
  }
11986
- function convertPMNode(node) {
12528
+ function convertPMNode(node, path) {
11987
12529
  switch (node.type) {
11988
12530
  case "paragraph": {
11989
12531
  const { children, markDefs } = convertInlineContent(node.content || []);
@@ -12016,8 +12558,8 @@ function convertPMNode(node) {
12016
12558
  ...textAlign ? { textAlign } : {}
12017
12559
  };
12018
12560
  }
12019
- case "bulletList": return convertList(node.content || [], "bullet");
12020
- case "orderedList": return convertList(node.content || [], "number");
12561
+ case "bulletList": return convertList(node.content || [], "bullet", 1, node.attrs, path);
12562
+ case "orderedList": return convertList(node.content || [], "number", 1, node.attrs, path);
12021
12563
  case "blockquote": {
12022
12564
  const blocks = [];
12023
12565
  const blockquoteContent = node.content || [];
@@ -12141,24 +12683,32 @@ function convertPMNode(node) {
12141
12683
  default: return null;
12142
12684
  }
12143
12685
  }
12144
- function convertList(items, listItem, level = 1) {
12686
+ function convertList(items, listItem, level = 1, attrs, path = `list:${level}`) {
12145
12687
  const blocks = [];
12146
12688
  const typedItems = items;
12147
- for (const item of typedItems) if (item.type === "listItem") {
12148
- const listItemContent = item.content || [];
12149
- for (const child of listItemContent) if (child.type === "paragraph") {
12150
- const { children, markDefs } = convertInlineContent(child.content || []);
12151
- if (children.length > 0) blocks.push({
12152
- _type: "block",
12153
- _key: generateKey(),
12154
- style: "normal",
12155
- listItem,
12156
- level,
12157
- children,
12158
- markDefs: markDefs.length > 0 ? markDefs : void 0
12159
- });
12160
- } else if (child.type === "bulletList") blocks.push(...convertList(child.content || [], "bullet", level + 1));
12161
- else if (child.type === "orderedList") blocks.push(...convertList(child.content || [], "number", level + 1));
12689
+ const metadata = listItem === "number" ? readOrderedListMetadata(attrs, path) : void 0;
12690
+ for (let itemIndex = 0; itemIndex < typedItems.length; itemIndex++) {
12691
+ const item = typedItems[itemIndex];
12692
+ if (item.type === "listItem") {
12693
+ const listItemContent = item.content || [];
12694
+ for (let childIndex = 0; childIndex < listItemContent.length; childIndex++) {
12695
+ const child = listItemContent[childIndex];
12696
+ if (child.type === "paragraph") {
12697
+ const { children, markDefs } = convertInlineContent(child.content || []);
12698
+ if (children.length > 0) blocks.push({
12699
+ _type: "block",
12700
+ _key: generateKey(),
12701
+ style: "normal",
12702
+ listItem,
12703
+ level,
12704
+ ...metadata,
12705
+ children,
12706
+ markDefs: markDefs.length > 0 ? markDefs : void 0
12707
+ });
12708
+ } else if (child.type === "bulletList") blocks.push(...convertList(child.content || [], "bullet", level + 1, child.attrs, `${path}:${itemIndex}:${childIndex}`));
12709
+ else if (child.type === "orderedList") blocks.push(...convertList(child.content || [], "number", level + 1, child.attrs, `${path}:${itemIndex}:${childIndex}`));
12710
+ }
12711
+ }
12162
12712
  }
12163
12713
  return blocks;
12164
12714
  }
@@ -12245,26 +12795,46 @@ function portableTextToProsemirror(blocks) {
12245
12795
  if (isTextBlock(block) && block.listItem) {
12246
12796
  const listBlocks = [];
12247
12797
  const listType = block.listItem;
12798
+ const runStart = i;
12799
+ const rootId = listType === "number" ? normalizeListId(block.listId) : void 0;
12248
12800
  while (i < blocks.length) {
12249
12801
  const current = blocks[i];
12250
12802
  if (!isTextBlock(current) || !current.listItem) break;
12251
- if ((current.level || 1) > 1 || current.listItem === listType) {
12803
+ const level = current.level || 1;
12804
+ const currentId = current.listItem === "number" ? normalizeListId(current.listId) : void 0;
12805
+ const sameRootIdentity = listType !== "number" || level > 1 || (rootId ? currentId === rootId : currentId === void 0);
12806
+ if (level > 1 || current.listItem === listType && sameRootIdentity) {
12252
12807
  listBlocks.push(current);
12253
12808
  i++;
12254
12809
  } else break;
12255
12810
  }
12256
- content.push(convertPTList(listBlocks, listType));
12811
+ content.push(convertPTList(listBlocks, listType, `root:${runStart}`));
12257
12812
  } else {
12258
12813
  const converted = convertPTBlock(block);
12259
12814
  if (converted) content.push(converted);
12260
12815
  i++;
12261
12816
  }
12262
12817
  }
12263
- return {
12818
+ return normalizeOrderedListJson({
12264
12819
  type: "doc",
12265
12820
  content: content.length > 0 ? content : [{ type: "paragraph" }]
12821
+ });
12822
+ }
12823
+ function getListMetadata(item, fallbackSeed) {
12824
+ const listId = normalizeListId(item.listId) ?? deriveLegacyListId(fallbackSeed);
12825
+ const listStart = normalizeListStart(item.listStart);
12826
+ return {
12827
+ listId,
12828
+ ...listStart === void 0 ? {} : { listStart }
12266
12829
  };
12267
12830
  }
12831
+ function belongsToNestedGroup(item, minLevel, parentListType, anchorType, anchorId) {
12832
+ if ((item.level || 2) > minLevel) return true;
12833
+ if ((item.listItem || parentListType) !== anchorType) return false;
12834
+ if (anchorType !== "number") return true;
12835
+ const itemId = normalizeListId(item.listId);
12836
+ return anchorId ? itemId === anchorId : itemId === void 0;
12837
+ }
12268
12838
  function convertPTBlock(block) {
12269
12839
  switch (block._type) {
12270
12840
  case "block": {
@@ -12407,7 +12977,7 @@ function convertPTBlock(block) {
12407
12977
  }
12408
12978
  }
12409
12979
  }
12410
- function convertPTList(items, listType) {
12980
+ function convertPTList(items, listType, context) {
12411
12981
  const rootItems = [];
12412
12982
  let i = 0;
12413
12983
  while (i < items.length) {
@@ -12419,18 +12989,24 @@ function convertPTList(items, listType) {
12419
12989
  nestedItems.push(items[i]);
12420
12990
  i++;
12421
12991
  }
12422
- rootItems.push(convertPTListItem(item, nestedItems, listType));
12992
+ rootItems.push(convertPTListItem(item, nestedItems, listType, `${context}:${rootItems.length}`));
12423
12993
  } else {
12424
- rootItems.push(convertPTListItem(item, [], listType));
12994
+ rootItems.push(convertPTListItem(item, [], listType, `${context}:${rootItems.length}`));
12425
12995
  i++;
12426
12996
  }
12427
12997
  }
12998
+ const firstItem = items[0];
12999
+ const metadata = listType === "number" ? getListMetadata(firstItem, `${context}:${firstItem._key}`) : void 0;
12428
13000
  return {
12429
13001
  type: listType === "bullet" ? "bulletList" : "orderedList",
13002
+ attrs: metadata ? {
13003
+ ...metadata,
13004
+ start: metadata.listStart ?? 1
13005
+ } : void 0,
12430
13006
  content: rootItems
12431
13007
  };
12432
13008
  }
12433
- function convertPTListItem(item, nestedItems, parentListType) {
13009
+ function convertPTListItem(item, nestedItems, parentListType, context) {
12434
13010
  const content = [];
12435
13011
  const pmContent = convertPTSpans(item.children, item.markDefs || []);
12436
13012
  content.push({
@@ -12446,17 +13022,18 @@ function convertPTListItem(item, nestedItems, parentListType) {
12446
13022
  let j = 0;
12447
13023
  while (j < nestedItems.length) {
12448
13024
  const anchorType = nestedItems[j].listItem || parentListType;
13025
+ const anchorId = anchorType === "number" ? normalizeListId(nestedItems[j].listId) : void 0;
12449
13026
  const nestedGroup = [];
12450
13027
  do {
12451
13028
  nestedGroup.push(nestedItems[j]);
12452
13029
  j++;
12453
- } while (j < nestedItems.length && ((nestedItems[j].level || 2) > minLevel || (nestedItems[j].listItem || parentListType) === anchorType));
13030
+ } while (j < nestedItems.length && belongsToNestedGroup(nestedItems[j], minLevel, parentListType, anchorType, anchorId));
12454
13031
  if (nestedGroup.length > 0) {
12455
13032
  const adjustedGroup = nestedGroup.map((ni) => ({
12456
13033
  ...ni,
12457
13034
  level: (ni.level || 2) - 1
12458
13035
  }));
12459
- content.push(convertPTList(adjustedGroup, anchorType));
13036
+ content.push(convertPTList(adjustedGroup, anchorType, `${context}:nested:${j - nestedGroup.length}`));
12460
13037
  }
12461
13038
  }
12462
13039
  }
@@ -13731,6 +14308,7 @@ function PortableTextEditor({ value, onChange, placeholder, className, editable
13731
14308
  },
13732
14309
  codeBlock: false,
13733
14310
  code: false,
14311
+ orderedList: false,
13734
14312
  link: {
13735
14313
  openOnClick: false,
13736
14314
  enableClickSelection: true,
@@ -13738,6 +14316,7 @@ function PortableTextEditor({ value, onChange, placeholder, className, editable
13738
14316
  },
13739
14317
  underline: {}
13740
14318
  }),
14319
+ EmDashOrderedList,
13741
14320
  CodeMarkExtension,
13742
14321
  CodeBlockExtension,
13743
14322
  HtmlBlockExtension,
@@ -14587,6 +15166,7 @@ function EditorToolbar({ toolbarRef, editor, focusMode, onFocusModeChange, onIns
14587
15166
  editor,
14588
15167
  selector: (ctx) => {
14589
15168
  const textAlignment = getSelectionTextAlignment(ctx.editor);
15169
+ const isOrderedList = ctx.editor.isActive("orderedList");
14590
15170
  return {
14591
15171
  isBold: ctx.editor.isActive("bold"),
14592
15172
  isItalic: ctx.editor.isActive("italic"),
@@ -14596,7 +15176,9 @@ function EditorToolbar({ toolbarRef, editor, focusMode, onFocusModeChange, onIns
14596
15176
  isSuperscript: ctx.editor.isActive("superscript"),
14597
15177
  isCode: ctx.editor.isActive("code"),
14598
15178
  isBulletList: ctx.editor.isActive("bulletList"),
14599
- isOrderedList: ctx.editor.isActive("orderedList"),
15179
+ isOrderedList,
15180
+ canContinueOrderedList: isOrderedList && ctx.editor.can().continueOrderedList(),
15181
+ canRestartOrderedList: isOrderedList && ctx.editor.can().restartOrderedList(),
14600
15182
  isBlockquote: ctx.editor.isActive("blockquote"),
14601
15183
  isCodeBlock: ctx.editor.isActive("codeBlock"),
14602
15184
  isAlignLeft: textAlignment === "left",
@@ -14818,6 +15400,29 @@ function EditorToolbar({ toolbarRef, editor, focusMode, onFocusModeChange, onIns
14818
15400
  "aria-hidden": "true"
14819
15401
  })
14820
15402
  }),
15403
+ editorState.isOrderedList && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ToolbarButton, {
15404
+ onClick: () => editor.chain().focus().continueOrderedList().run(),
15405
+ disabled: !editorState.canContinueOrderedList,
15406
+ title: _t9({
15407
+ id: "AEfArz",
15408
+ message: "Continue numbering"
15409
+ }),
15410
+ children: /* @__PURE__ */ jsx(ArrowUUpRight, {
15411
+ className: "h-4 w-4 rtl:-scale-x-100",
15412
+ "aria-hidden": "true"
15413
+ })
15414
+ }), /* @__PURE__ */ jsx(ToolbarButton, {
15415
+ onClick: () => editor.chain().focus().restartOrderedList().run(),
15416
+ disabled: !editorState.canRestartOrderedList,
15417
+ title: _t9({
15418
+ id: "knExCm",
15419
+ message: "Restart numbering"
15420
+ }),
15421
+ children: /* @__PURE__ */ jsx(ArrowUUpLeft, {
15422
+ className: "h-4 w-4 rtl:-scale-x-100",
15423
+ "aria-hidden": "true"
15424
+ })
15425
+ })] }),
14821
15426
  /* @__PURE__ */ jsx(ToolbarButton, {
14822
15427
  onClick: () => editor.chain().focus().toggleBlockquote().run(),
14823
15428
  active: editorState.isBlockquote,
@@ -16805,8 +17410,8 @@ function FilterBar({ statusFilter, onStatusFilterChange, authors, authorFilter,
16805
17410
  message: "All statuses"
16806
17411
  }),
16807
17412
  published: _t2({
16808
- id: "EEYbdt",
16809
- message: "Publish"
17413
+ id: "u3wRF+",
17414
+ message: "Published"
16810
17415
  }),
16811
17416
  draft: _t2({
16812
17417
  id: "eneWvv",
@@ -19157,12 +19762,45 @@ function SystemFieldRow({ field }) {
19157
19762
  //#endregion
19158
19763
  //#region src/components/ContentTypeList.tsx
19159
19764
  /**
19765
+ * Apply a drag-and-drop move to the collection order. Returns the input array
19766
+ * unchanged when the move is a no-op.
19767
+ */
19768
+ function moveCollection(slugs, activeSlug, overSlug) {
19769
+ const from = slugs.indexOf(activeSlug);
19770
+ const to = slugs.indexOf(overSlug);
19771
+ if (from === -1 || to === -1 || from === to) return slugs;
19772
+ const next = [...slugs];
19773
+ next.splice(to, 0, next.splice(from, 1)[0]);
19774
+ return next;
19775
+ }
19776
+ /**
19160
19777
  * Content Type list view - shows all collections in the schema registry
19161
19778
  */
19162
- function ContentTypeList({ collections, orphanedTables, isLoading, onDelete, onRegisterOrphan }) {
19779
+ function ContentTypeList({ collections, orphanedTables, isLoading, onDelete, onRegisterOrphan, onReorder }) {
19163
19780
  const { _: _t } = useLingui();
19164
19781
  const [deleteTarget, setDeleteTarget] = React$1.useState(null);
19165
19782
  const hasOrphans = orphanedTables && orphanedTables.length > 0;
19783
+ const [order, setOrder] = React$1.useState(null);
19784
+ const serverOrder = React$1.useMemo(() => collections.map((c) => c.slug), [collections]);
19785
+ const orderedSlugs = order ?? serverOrder;
19786
+ React$1.useEffect(() => {
19787
+ setOrder(null);
19788
+ }, [serverOrder]);
19789
+ const orderedCollections = React$1.useMemo(() => {
19790
+ const bySlug = new Map(collections.map((c) => [c.slug, c]));
19791
+ return orderedSlugs.map((slug) => bySlug.get(slug)).filter((c) => c !== void 0);
19792
+ }, [collections, orderedSlugs]);
19793
+ const canReorder = !!onReorder && collections.length > 1;
19794
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
19795
+ const columnCount = canReorder ? 6 : 5;
19796
+ const handleDragEnd = (event) => {
19797
+ const { active, over } = event;
19798
+ if (!over || active.id === over.id) return;
19799
+ const next = moveCollection(orderedSlugs, String(active.id), String(over.id));
19800
+ if (next === orderedSlugs) return;
19801
+ setOrder(next);
19802
+ onReorder?.(next);
19803
+ };
19166
19804
  return /* @__PURE__ */ jsxs("div", {
19167
19805
  className: "space-y-4",
19168
19806
  children: [
@@ -19240,86 +19878,107 @@ function ContentTypeList({ collections, orphanedTables, isLoading, onDelete, onR
19240
19878
  })]
19241
19879
  })
19242
19880
  }),
19243
- /* @__PURE__ */ jsx("div", {
19244
- className: "rounded-md border bg-kumo-base overflow-x-auto",
19245
- children: /* @__PURE__ */ jsxs("table", {
19246
- className: "w-full",
19247
- children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
19248
- className: "border-b bg-kumo-tint/50",
19249
- children: [
19250
- /* @__PURE__ */ jsx("th", {
19251
- scope: "col",
19252
- className: "px-4 py-3 text-start text-sm font-medium",
19253
- children: _t({
19254
- id: "6YtxFj",
19255
- message: "Name"
19256
- })
19257
- }),
19258
- /* @__PURE__ */ jsx("th", {
19259
- scope: "col",
19260
- className: "px-4 py-3 text-start text-sm font-medium",
19261
- children: _t({
19262
- id: "L85WcV",
19263
- message: "Slug"
19264
- })
19265
- }),
19266
- /* @__PURE__ */ jsx("th", {
19267
- scope: "col",
19268
- className: "px-4 py-3 text-start text-sm font-medium",
19269
- children: _t({
19270
- id: "wdxz7K",
19271
- message: "Source"
19272
- })
19273
- }),
19274
- /* @__PURE__ */ jsx("th", {
19275
- scope: "col",
19276
- className: "px-4 py-3 text-start text-sm font-medium",
19277
- children: _t({
19278
- id: "PpZkda",
19279
- message: "Features"
19280
- })
19281
- }),
19282
- /* @__PURE__ */ jsx("th", {
19283
- scope: "col",
19284
- className: "px-4 py-3 text-end text-sm font-medium",
19285
- children: _t({
19286
- id: "7L01XJ",
19287
- message: "Actions"
19288
- })
19289
- })
19290
- ]
19291
- }) }), /* @__PURE__ */ jsx("tbody", {
19292
- className: "divide-y divide-kumo-line",
19293
- children: isLoading ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
19294
- colSpan: 5,
19295
- className: "px-4 py-8 text-center text-kumo-subtle",
19296
- children: _t({
19297
- id: "y+JWgj",
19298
- message: "Loading collections..."
19299
- })
19300
- }) }) : collections.length === 0 && !hasOrphans ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
19301
- colSpan: 5,
19302
- className: "px-4 py-8 text-center text-kumo-subtle",
19881
+ /* @__PURE__ */ jsx(DndContext, {
19882
+ sensors,
19883
+ collisionDetection: closestCenter,
19884
+ onDragEnd: handleDragEnd,
19885
+ children: /* @__PURE__ */ jsx("div", {
19886
+ className: "rounded-md border bg-kumo-base overflow-x-auto",
19887
+ children: /* @__PURE__ */ jsxs("table", {
19888
+ className: "w-full",
19889
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
19890
+ className: "border-b bg-kumo-tint/50",
19303
19891
  children: [
19304
- _t({
19305
- id: "T1ePSQ",
19306
- message: "No content types yet."
19892
+ canReorder && /* @__PURE__ */ jsx("th", {
19893
+ scope: "col",
19894
+ className: "w-10 px-2 py-3",
19895
+ children: /* @__PURE__ */ jsx("span", {
19896
+ className: "sr-only",
19897
+ children: _t({
19898
+ id: "toJdZA",
19899
+ message: "Reorder"
19900
+ })
19901
+ })
19307
19902
  }),
19308
- " ",
19309
- /* @__PURE__ */ jsx(Link$1, {
19310
- to: "/content-types/new",
19311
- className: "text-kumo-link underline",
19903
+ /* @__PURE__ */ jsx("th", {
19904
+ scope: "col",
19905
+ className: "px-4 py-3 text-start text-sm font-medium",
19906
+ children: _t({
19907
+ id: "6YtxFj",
19908
+ message: "Name"
19909
+ })
19910
+ }),
19911
+ /* @__PURE__ */ jsx("th", {
19912
+ scope: "col",
19913
+ className: "px-4 py-3 text-start text-sm font-medium",
19914
+ children: _t({
19915
+ id: "L85WcV",
19916
+ message: "Slug"
19917
+ })
19918
+ }),
19919
+ /* @__PURE__ */ jsx("th", {
19920
+ scope: "col",
19921
+ className: "px-4 py-3 text-start text-sm font-medium",
19922
+ children: _t({
19923
+ id: "wdxz7K",
19924
+ message: "Source"
19925
+ })
19926
+ }),
19927
+ /* @__PURE__ */ jsx("th", {
19928
+ scope: "col",
19929
+ className: "px-4 py-3 text-start text-sm font-medium",
19312
19930
  children: _t({
19313
- id: "+bjdyB",
19314
- message: "Create your first one"
19931
+ id: "PpZkda",
19932
+ message: "Features"
19933
+ })
19934
+ }),
19935
+ /* @__PURE__ */ jsx("th", {
19936
+ scope: "col",
19937
+ className: "px-4 py-3 text-end text-sm font-medium",
19938
+ children: _t({
19939
+ id: "7L01XJ",
19940
+ message: "Actions"
19315
19941
  })
19316
19942
  })
19317
19943
  ]
19318
- }) }) : collections.map((collection) => /* @__PURE__ */ jsx(ContentTypeRow, {
19319
- collection,
19320
- onRequestDelete: setDeleteTarget
19321
- }, collection.id))
19322
- })]
19944
+ }) }), /* @__PURE__ */ jsx("tbody", {
19945
+ className: "divide-y divide-kumo-line",
19946
+ children: isLoading ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
19947
+ colSpan: columnCount,
19948
+ className: "px-4 py-8 text-center text-kumo-subtle",
19949
+ children: _t({
19950
+ id: "y+JWgj",
19951
+ message: "Loading collections..."
19952
+ })
19953
+ }) }) : collections.length === 0 && !hasOrphans ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
19954
+ colSpan: columnCount,
19955
+ className: "px-4 py-8 text-center text-kumo-subtle",
19956
+ children: [
19957
+ _t({
19958
+ id: "T1ePSQ",
19959
+ message: "No content types yet."
19960
+ }),
19961
+ " ",
19962
+ /* @__PURE__ */ jsx(Link$1, {
19963
+ to: "/content-types/new",
19964
+ className: "text-kumo-link underline",
19965
+ children: _t({
19966
+ id: "+bjdyB",
19967
+ message: "Create your first one"
19968
+ })
19969
+ })
19970
+ ]
19971
+ }) }) : /* @__PURE__ */ jsx(SortableContext, {
19972
+ items: orderedSlugs,
19973
+ strategy: verticalListSortingStrategy,
19974
+ children: orderedCollections.map((collection) => /* @__PURE__ */ jsx(ContentTypeRow, {
19975
+ collection,
19976
+ canReorder,
19977
+ onRequestDelete: setDeleteTarget
19978
+ }, collection.id))
19979
+ })
19980
+ })]
19981
+ })
19323
19982
  })
19324
19983
  }),
19325
19984
  /* @__PURE__ */ jsx(ConfirmDialog, {
@@ -19354,12 +20013,36 @@ function ContentTypeList({ collections, orphanedTables, isLoading, onDelete, onR
19354
20013
  ]
19355
20014
  });
19356
20015
  }
19357
- function ContentTypeRow({ collection, onRequestDelete }) {
20016
+ function ContentTypeRow({ collection, canReorder, onRequestDelete }) {
19358
20017
  const { _: _t2 } = useLingui();
19359
20018
  const isFromCode = collection.source === "code";
20019
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: collection.slug });
19360
20020
  return /* @__PURE__ */ jsxs("tr", {
19361
- className: "hover:bg-kumo-tint/25",
20021
+ ref: setNodeRef,
20022
+ style: {
20023
+ transform: CSS.Transform.toString(transform),
20024
+ transition
20025
+ },
20026
+ className: cn("hover:bg-kumo-tint/25", isDragging && "relative z-10 bg-kumo-base shadow-sm"),
19362
20027
  children: [
20028
+ canReorder && /* @__PURE__ */ jsx("td", {
20029
+ className: "w-10 px-2 py-3",
20030
+ children: /* @__PURE__ */ jsx("button", {
20031
+ type: "button",
20032
+ ...attributes,
20033
+ ...listeners,
20034
+ "aria-label": _t2({
20035
+ id: "2BPVq8",
20036
+ message: "Reorder {0}",
20037
+ values: { 0: collection.label }
20038
+ }),
20039
+ className: "flex h-8 w-8 cursor-grab items-center justify-center rounded-md text-kumo-subtle hover:bg-kumo-tint focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-kumo-focus active:cursor-grabbing",
20040
+ children: /* @__PURE__ */ jsx(DotsSixVertical, {
20041
+ className: "h-4 w-4",
20042
+ "aria-hidden": "true"
20043
+ })
20044
+ })
20045
+ }),
19363
20046
  /* @__PURE__ */ jsx("td", {
19364
20047
  className: "px-4 py-3",
19365
20048
  children: /* @__PURE__ */ jsxs("div", {
@@ -19745,8 +20428,8 @@ function CollectionList({ collections, manifest, loading }) {
19745
20428
  count: col.published,
19746
20429
  variant: "success",
19747
20430
  label: _t4({
19748
- id: "EEYbdt",
19749
- message: "Publish"
20431
+ id: "u3wRF+",
20432
+ message: "Published"
19750
20433
  })
19751
20434
  }),
19752
20435
  /* @__PURE__ */ jsx(CountBadge, {
@@ -27359,14 +28042,14 @@ function RedirectFormDialog({ open, onClose, redirect, defaultSource }) {
27359
28042
  const createMutation = useMutation({
27360
28043
  mutationFn: (input) => createRedirect(input),
27361
28044
  onSuccess: () => {
27362
- queryClient.invalidateQueries({ queryKey: ["redirects"] });
28045
+ queryClient.resetQueries({ queryKey: ["redirects"] });
27363
28046
  onClose();
27364
28047
  }
27365
28048
  });
27366
28049
  const updateMutation = useMutation({
27367
28050
  mutationFn: (input) => updateRedirect(redirect.id, input),
27368
28051
  onSuccess: () => {
27369
- queryClient.invalidateQueries({ queryKey: ["redirects"] });
28052
+ queryClient.resetQueries({ queryKey: ["redirects"] });
27370
28053
  onClose();
27371
28054
  }
27372
28055
  });
@@ -27659,19 +28342,22 @@ function Redirects() {
27659
28342
  const [prefillSource, setPrefillSource] = useState("");
27660
28343
  const enabledFilter = filterEnabled === "all" ? void 0 : filterEnabled === "true";
27661
28344
  const autoFilter = filterAuto === "all" ? void 0 : filterAuto === "true";
27662
- const redirectsQuery = useQuery({
28345
+ const redirectsQuery = useInfiniteQuery({
27663
28346
  queryKey: [
27664
28347
  "redirects",
27665
28348
  debouncedSearch,
27666
28349
  enabledFilter,
27667
28350
  autoFilter
27668
28351
  ],
27669
- queryFn: () => fetchRedirects({
28352
+ queryFn: ({ pageParam }) => fetchRedirects({
27670
28353
  search: debouncedSearch || void 0,
27671
28354
  enabled: enabledFilter,
27672
28355
  auto: autoFilter,
28356
+ cursor: pageParam,
27673
28357
  limit: 100
27674
- })
28358
+ }),
28359
+ initialPageParam: void 0,
28360
+ getNextPageParam: (lastPage) => lastPage.nextCursor
27675
28361
  });
27676
28362
  const notFoundQuery = useQuery({
27677
28363
  queryKey: ["redirects", "404-summary"],
@@ -27681,17 +28367,17 @@ function Redirects() {
27681
28367
  const deleteMutation = useMutation({
27682
28368
  mutationFn: (id) => deleteRedirect(id),
27683
28369
  onSuccess: () => {
27684
- queryClient.invalidateQueries({ queryKey: ["redirects"] });
28370
+ queryClient.resetQueries({ queryKey: ["redirects"] });
27685
28371
  setDeleteId(null);
27686
28372
  }
27687
28373
  });
27688
28374
  const toggleMutation = useMutation({
27689
28375
  mutationFn: ({ id, enabled }) => updateRedirect(id, { enabled }),
27690
28376
  onSuccess: () => {
27691
- queryClient.invalidateQueries({ queryKey: ["redirects"] });
28377
+ queryClient.resetQueries({ queryKey: ["redirects"] });
27692
28378
  },
27693
28379
  onError: () => {
27694
- queryClient.invalidateQueries({ queryKey: ["redirects"] });
28380
+ queryClient.resetQueries({ queryKey: ["redirects"] });
27695
28381
  }
27696
28382
  });
27697
28383
  const markGoneMutation = useMutation({
@@ -27702,7 +28388,7 @@ function Redirects() {
27702
28388
  enabled: true
27703
28389
  }),
27704
28390
  onSuccess: () => {
27705
- queryClient.invalidateQueries({ queryKey: ["redirects"] });
28391
+ queryClient.resetQueries({ queryKey: ["redirects"] });
27706
28392
  }
27707
28393
  });
27708
28394
  function handleCreateFrom404(path) {
@@ -27710,8 +28396,8 @@ function Redirects() {
27710
28396
  setShowCreate(true);
27711
28397
  setTab("redirects");
27712
28398
  }
27713
- const redirects = redirectsQuery.data?.items ?? [];
27714
- const loopRedirectIds = new Set(redirectsQuery.data?.loopRedirectIds ?? []);
28399
+ const redirects = redirectsQuery.data?.pages.flatMap((page) => page.items) ?? [];
28400
+ const loopRedirectIds = new Set(redirectsQuery.data?.pages.flatMap((page) => page.loopRedirectIds ?? []) ?? []);
27715
28401
  return /* @__PURE__ */ jsxs("div", {
27716
28402
  className: "space-y-6",
27717
28403
  children: [
@@ -27749,7 +28435,7 @@ function Redirects() {
27749
28435
  }), redirectsQuery.data && /* @__PURE__ */ jsxs(Badge, {
27750
28436
  variant: "secondary",
27751
28437
  className: "ms-2",
27752
- children: [redirectsQuery.data.items.length, redirectsQuery.data.nextCursor ? "+" : ""]
28438
+ children: [redirects.length, redirectsQuery.hasNextPage ? "+" : ""]
27753
28439
  })]
27754
28440
  }), /* @__PURE__ */ jsx("button", {
27755
28441
  onClick: () => setTab("404s"),
@@ -27883,7 +28569,7 @@ function Redirects() {
27883
28569
  })
27884
28570
  })
27885
28571
  ]
27886
- }) : /* @__PURE__ */ jsxs("div", {
28572
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
27887
28573
  className: "border rounded-lg",
27888
28574
  children: [/* @__PURE__ */ jsxs("div", {
27889
28575
  className: "flex items-center gap-4 py-2 px-4 border-b bg-kumo-tint/50 text-sm font-medium text-kumo-subtle",
@@ -28031,7 +28717,21 @@ function Redirects() {
28031
28717
  })
28032
28718
  ]
28033
28719
  }, r.id))]
28034
- })
28720
+ }), redirectsQuery.hasNextPage && /* @__PURE__ */ jsx("div", {
28721
+ className: "flex justify-center",
28722
+ children: /* @__PURE__ */ jsx(Button, {
28723
+ variant: "outline",
28724
+ onClick: () => void redirectsQuery.fetchNextPage(),
28725
+ disabled: redirectsQuery.isFetchingNextPage,
28726
+ children: redirectsQuery.isFetchingNextPage ? _t3({
28727
+ id: "Z3FXyt",
28728
+ message: "Loading..."
28729
+ }) : _t3({
28730
+ id: "yQ2kGp",
28731
+ message: "Load more"
28732
+ })
28733
+ })
28734
+ })] })
28035
28735
  ] }),
28036
28736
  tab === "404s" && /* @__PURE__ */ jsx(NotFoundPanel, {
28037
28737
  items: notFoundQuery.data ?? [],
@@ -34443,6 +35143,18 @@ function filterNavItemsByRole(items, userRole) {
34443
35143
  return items.filter((item) => !item.minRole || userRole >= item.minRole);
34444
35144
  }
34445
35145
  /**
35146
+ * Manifest collections that get an auto-generated sidebar entry, in manifest
35147
+ * order. Pure function — exported so tests can pin the `hidden` contract
35148
+ * without rendering the sidebar.
35149
+ *
35150
+ * A hidden collection is still shipped in the manifest and stays fully
35151
+ * routable at `/content/:collection`; it only loses its nav link, so a plugin
35152
+ * that owns the collection end to end can steer editors to its own admin UI.
35153
+ */
35154
+ function visibleCollectionEntries(collections) {
35155
+ return Object.entries(collections).filter(([, config]) => !config.hidden);
35156
+ }
35157
+ /**
34446
35158
  * Navigation item rendered with Kumo's native Sidebar.MenuButton. Kumo's
34447
35159
  * LinkProvider maps the href to TanStack Router for client-side navigation.
34448
35160
  */
@@ -34524,7 +35236,7 @@ function SidebarNav({ manifest }) {
34524
35236
  }),
34525
35237
  icon: ADMIN_NAV_ICONS.dashboard
34526
35238
  }];
34527
- for (const [name, config] of Object.entries(manifest.collections)) contentItems.push({
35239
+ for (const [name, config] of visibleCollectionEntries(manifest.collections)) contentItems.push({
34528
35240
  to: "/content/$collection",
34529
35241
  label: config.label,
34530
35242
  icon: getCollectionNavIcon(name),
@@ -36143,11 +36855,90 @@ function getAvailableParentTerms(terms, currentTerm) {
36143
36855
  const excludedIds = new Set([currentTerm.id, ...flattenTerms(currentTerm.children).map((term) => term.id)]);
36144
36856
  return flatTerms.filter((term) => !excludedIds.has(term.id));
36145
36857
  }
36858
+ /** The translation group a term belongs to — what `parentId` and reorder ids hold. */
36859
+ function termGroup(term) {
36860
+ return term.translationGroup ?? term.id;
36861
+ }
36862
+ /**
36863
+ * Whether a term is rendered in a group it isn't a member of.
36864
+ *
36865
+ * A child whose parent has no row in this locale is listed at the top level so
36866
+ * it isn't lost, but its position belongs to its parent's group. It can't be
36867
+ * moved from here, and it can't be named in a reorder of the group it's drawn in.
36868
+ */
36869
+ function isStranded(term, parentId) {
36870
+ return parentId === null && !!term.parentId;
36871
+ }
36872
+ /**
36873
+ * Permute `movable` into the slots those same terms occupy in `rendered`,
36874
+ * leaving everything else where it is.
36875
+ *
36876
+ * Mirrors what the reorder endpoint does with the ids it's given, so the
36877
+ * optimistic list matches what comes back.
36878
+ */
36879
+ function reorderWithinSlots(rendered, movable, permuted) {
36880
+ const inGroup = new Set(movable);
36881
+ const next = [...rendered];
36882
+ let slot = 0;
36883
+ for (const [index, term] of rendered.entries()) {
36884
+ if (!inGroup.has(term)) continue;
36885
+ const replacement = permuted[slot++];
36886
+ if (replacement) next[index] = replacement;
36887
+ }
36888
+ return next;
36889
+ }
36890
+ /**
36891
+ * Replace one sibling group in a term tree with a reordered copy of itself.
36892
+ *
36893
+ * `parentId` is a translation group (what `term.parentId` holds), so it matches
36894
+ * the parent in whichever locale is on screen. `null` replaces the roots.
36895
+ */
36896
+ function replaceSiblingGroup(terms, parentId, ordered) {
36897
+ if (parentId === null) return ordered;
36898
+ return terms.map((term) => termGroup(term) === parentId ? {
36899
+ ...term,
36900
+ children: ordered
36901
+ } : {
36902
+ ...term,
36903
+ children: replaceSiblingGroup(term.children, parentId, ordered)
36904
+ });
36905
+ }
36906
+ /**
36907
+ * One sibling group's rows, in display order.
36908
+ *
36909
+ * The movable subset is worked out once for the whole group: each row needs it
36910
+ * to know whether its carets are live, and a move needs the same list to build
36911
+ * the order it sends.
36912
+ */
36913
+ function TermGroup({ siblings, parentId, level = 0, ...callbacks }) {
36914
+ const movable = siblings.filter((sibling) => !isStranded(sibling, parentId));
36915
+ const places = new Map(movable.map((term, index) => [term, index]));
36916
+ return /* @__PURE__ */ jsx(Fragment, { children: siblings.map((term) => /* @__PURE__ */ jsx(TermRow, {
36917
+ term,
36918
+ siblings,
36919
+ movable,
36920
+ place: places.get(term) ?? -1,
36921
+ parentId,
36922
+ level,
36923
+ ...callbacks
36924
+ }, term.id)) });
36925
+ }
36146
36926
  /**
36147
36927
  * Term row component (recursive for hierarchy)
36928
+ *
36929
+ * `siblings` is the group the term is rendered from, in display order, and
36930
+ * `parentId` is that group's parent. Moving a term reorders that group and
36931
+ * never changes its parent.
36932
+ *
36933
+ * A term whose parent has no row in this locale is rendered at the top level so
36934
+ * it isn't lost, but it belongs to its parent's group — a group this locale
36935
+ * can't show. Its move buttons are disabled rather than silently addressing the
36936
+ * wrong group, and `place` is -1 because it holds no slot in the group it is
36937
+ * drawn in.
36148
36938
  */
36149
- function TermRow({ term, level = 0, onEdit, onDelete, onTranslate, canTranslate }) {
36939
+ function TermRow({ term, siblings, movable, place, parentId, level = 0, onEdit, onDelete, onMove, onTranslate, canTranslate }) {
36150
36940
  const { _: _t } = useLingui();
36941
+ const stranded = isStranded(term, parentId);
36151
36942
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
36152
36943
  className: "flex items-center gap-4 py-2 px-4 hover:bg-kumo-tint/50",
36153
36944
  children: [
@@ -36173,6 +36964,45 @@ function TermRow({ term, level = 0, onEdit, onDelete, onTranslate, canTranslate
36173
36964
  /* @__PURE__ */ jsxs("div", {
36174
36965
  className: "flex gap-2",
36175
36966
  children: [
36967
+ /* @__PURE__ */ jsxs("span", {
36968
+ className: "flex gap-2",
36969
+ title: stranded ? _t({
36970
+ id: "+L2ab9",
36971
+ message: "{0} can't be moved here — its parent has no translation in this locale, so this list isn't the group it belongs to.",
36972
+ values: { 0: term.label }
36973
+ }) : void 0,
36974
+ children: [/* @__PURE__ */ jsx(Button, {
36975
+ variant: "ghost",
36976
+ size: "sm",
36977
+ "aria-label": stranded ? _t({
36978
+ id: "WtMC7E",
36979
+ message: "Move {0} up — unavailable, its parent has no translation in this locale",
36980
+ values: { 0: term.label }
36981
+ }) : _t({
36982
+ id: "GtRAA3",
36983
+ message: "Move {0} up",
36984
+ values: { 0: term.label }
36985
+ }),
36986
+ disabled: stranded || place <= 0,
36987
+ onClick: () => onMove(parentId, siblings, movable, term, -1),
36988
+ children: /* @__PURE__ */ jsx(CaretUp, { className: "w-4 h-4" })
36989
+ }), /* @__PURE__ */ jsx(Button, {
36990
+ variant: "ghost",
36991
+ size: "sm",
36992
+ "aria-label": stranded ? _t({
36993
+ id: "aeS7hl",
36994
+ message: "Move {0} down — unavailable, its parent has no translation in this locale",
36995
+ values: { 0: term.label }
36996
+ }) : _t({
36997
+ id: "tspiKT",
36998
+ message: "Move {0} down",
36999
+ values: { 0: term.label }
37000
+ }),
37001
+ disabled: stranded || place >= movable.length - 1,
37002
+ onClick: () => onMove(parentId, siblings, movable, term, 1),
37003
+ children: /* @__PURE__ */ jsx(CaretDown, { className: "w-4 h-4" })
37004
+ })]
37005
+ }),
36176
37006
  canTranslate && onTranslate ? /* @__PURE__ */ jsx(Button, {
36177
37007
  variant: "ghost",
36178
37008
  size: "sm",
@@ -36212,14 +37042,16 @@ function TermRow({ term, level = 0, onEdit, onDelete, onTranslate, canTranslate
36212
37042
  ]
36213
37043
  })
36214
37044
  ]
36215
- }), term.children.map((child) => /* @__PURE__ */ jsx(TermRow, {
36216
- term: child,
37045
+ }), /* @__PURE__ */ jsx(TermGroup, {
37046
+ siblings: term.children,
37047
+ parentId: termGroup(term),
36217
37048
  level: level + 1,
36218
37049
  onEdit,
36219
37050
  onDelete,
37051
+ onMove,
36220
37052
  onTranslate,
36221
37053
  canTranslate
36222
- }, child.id))] });
37054
+ })] });
36223
37055
  }
36224
37056
  /**
36225
37057
  * Dialog to pick a target locale for creating a term translation.
@@ -36860,13 +37692,14 @@ function TaxonomyManager({ taxonomyName }) {
36860
37692
  queryKey: ["taxonomy-def", taxonomyName],
36861
37693
  queryFn: () => fetchTaxonomyDef(taxonomyName)
36862
37694
  });
37695
+ const termsQueryKey = [
37696
+ "taxonomy-terms",
37697
+ taxonomyName,
37698
+ activeLocale,
37699
+ { includeCounts: true }
37700
+ ];
36863
37701
  const { data: terms = [], isLoading: termsLoading } = useQuery({
36864
- queryKey: [
36865
- "taxonomy-terms",
36866
- taxonomyName,
36867
- activeLocale,
36868
- { includeCounts: true }
36869
- ],
37702
+ queryKey: termsQueryKey,
36870
37703
  queryFn: () => fetchTerms(taxonomyName, { locale: activeLocale })
36871
37704
  });
36872
37705
  const deleteMutation = useMutation({
@@ -36880,6 +37713,43 @@ function TaxonomyManager({ taxonomyName }) {
36880
37713
  }) });
36881
37714
  }
36882
37715
  });
37716
+ const reorderMutationKey = ["taxonomy-terms-reorder", taxonomyName];
37717
+ const reorderMutation = useMutation({
37718
+ mutationKey: reorderMutationKey,
37719
+ scope: { id: `taxonomy-reorder:${taxonomyName}` },
37720
+ mutationFn: ({ parentId, ids }) => reorderTerms(taxonomyName, {
37721
+ parentId,
37722
+ ids
37723
+ }),
37724
+ onError: (error) => {
37725
+ toastManager.add({
37726
+ title: _t5({
37727
+ id: "SlfejT",
37728
+ message: "Error"
37729
+ }),
37730
+ description: error.message,
37731
+ type: "error"
37732
+ });
37733
+ },
37734
+ onSettled: () => {
37735
+ if (queryClient.isMutating({ mutationKey: reorderMutationKey }) > 1) return;
37736
+ queryClient.invalidateQueries({ queryKey: ["taxonomy-terms", taxonomyName] });
37737
+ }
37738
+ });
37739
+ const handleMove = (parentId, siblings, movable, term, direction) => {
37740
+ const from = movable.indexOf(term);
37741
+ const to = from + direction;
37742
+ const moving = movable[from];
37743
+ const displaced = movable[to];
37744
+ if (!moving || !displaced) return;
37745
+ const permuted = movable.map((sibling, index) => index === from ? displaced : index === to ? moving : sibling);
37746
+ const reordered = reorderWithinSlots(siblings, movable, permuted);
37747
+ queryClient.setQueryData(termsQueryKey, (current) => current ? replaceSiblingGroup(current, parentId, reordered) : current);
37748
+ reorderMutation.mutate({
37749
+ parentId,
37750
+ ids: permuted.map(termGroup)
37751
+ });
37752
+ };
36883
37753
  const translateMutation = useMutation({
36884
37754
  mutationFn: ({ term, locale }) => createTermTranslation(taxonomyName, term.slug, {
36885
37755
  locale,
@@ -37022,13 +37892,15 @@ function TaxonomyManager({ taxonomyName }) {
37022
37892
  })
37023
37893
  }) : /* @__PURE__ */ jsx("div", {
37024
37894
  className: "divide-y divide-kumo-line",
37025
- children: terms.map((term) => /* @__PURE__ */ jsx(TermRow, {
37026
- term,
37895
+ children: /* @__PURE__ */ jsx(TermGroup, {
37896
+ siblings: terms,
37897
+ parentId: null,
37027
37898
  onEdit: handleEdit,
37028
37899
  onDelete: handleDelete,
37900
+ onMove: handleMove,
37029
37901
  onTranslate: setTranslateTarget,
37030
37902
  canTranslate: !!i18n && !!activeLocale && i18n.locales.length > 1
37031
- }, term.id))
37903
+ })
37032
37904
  })]
37033
37905
  }),
37034
37906
  /* @__PURE__ */ jsx(TermFormDialog, {
@@ -44331,7 +45203,7 @@ function UsersPage() {
44331
45203
  * Defines all admin routes and their components.
44332
45204
  */
44333
45205
  function patchAutosaveQueries(queryClient, params) {
44334
- const { collection, id, savedItem, payload, locale } = params;
45206
+ const { collection, id, savedItem, payload } = params;
44335
45207
  const draftRevisionId = savedItem.draftRevisionId;
44336
45208
  if (draftRevisionId) queryClient.setQueryData(["revision", draftRevisionId], (existing) => {
44337
45209
  const nextData = {
@@ -44348,16 +45220,11 @@ function patchAutosaveQueries(queryClient, params) {
44348
45220
  createdAt: existing?.createdAt ?? savedItem.updatedAt
44349
45221
  };
44350
45222
  });
44351
- queryClient.setQueryData(locale ? [
44352
- "content",
44353
- collection,
44354
- id,
44355
- { locale }
44356
- ] : [
45223
+ queryClient.setQueriesData({ queryKey: [
44357
45224
  "content",
44358
45225
  collection,
44359
45226
  id
44360
- ], savedItem);
45227
+ ] }, savedItem);
44361
45228
  }
44362
45229
  const baseRootRoute = createRootRouteWithContext()({ component: () => /* @__PURE__ */ jsx(Outlet, {}) });
44363
45230
  const setupRoute = createRoute({
@@ -44775,6 +45642,8 @@ function ContentNewPage() {
44775
45642
  const { locale } = useSearch({ from: "/_admin/content/$collection/new" });
44776
45643
  const navigate = useNavigate();
44777
45644
  const queryClient = useQueryClient();
45645
+ const { _: _t3 } = useLingui();
45646
+ const toastManager = useKumoToastManager();
44778
45647
  const [selectedBylines, setSelectedBylines] = React$1.useState([]);
44779
45648
  const { data: manifest } = useQuery({
44780
45649
  queryKey: ["manifest"],
@@ -44796,6 +45665,19 @@ function ContentNewPage() {
44796
45665
  },
44797
45666
  search: { locale: result.locale }
44798
45667
  });
45668
+ },
45669
+ onError: (error) => {
45670
+ toastManager.add({
45671
+ title: _t3({
45672
+ id: "+Ya+b9",
45673
+ message: "Failed to save"
45674
+ }),
45675
+ description: error instanceof Error ? error.message : _t3({
45676
+ id: "Vw8l6h",
45677
+ message: "An error occurred"
45678
+ }),
45679
+ variant: "error"
45680
+ });
44799
45681
  }
44800
45682
  });
44801
45683
  const pluginBlocks = React$1.useMemo(() => manifest ? getPluginBlocks(manifest) : [], [manifest]);
@@ -44872,7 +45754,7 @@ const contentEditRoute = createRoute({
44872
45754
  });
44873
45755
  const ROLE_EDITOR = 40;
44874
45756
  function ContentEditPage() {
44875
- const { _: _t3 } = useLingui();
45757
+ const { _: _t4 } = useLingui();
44876
45758
  const { collection, id } = useParams({ from: "/_admin/content/$collection/$id" });
44877
45759
  const searchParams = useSearch({ from: "/_admin/content/$collection/$id" });
44878
45760
  const queryClient = useQueryClient();
@@ -44949,7 +45831,7 @@ function ContentEditPage() {
44949
45831
  const { data: currentUser } = useQuery({
44950
45832
  queryKey: ["currentUser"],
44951
45833
  queryFn: async () => {
44952
- return parseApiResponse(await apiFetch("/_emdash/api/auth/me"), _t3({
45834
+ return parseApiResponse(await apiFetch("/_emdash/api/auth/me"), _t4({
44953
45835
  id: "z7Mflq",
44954
45836
  message: "Failed to fetch user"
44955
45837
  }));
@@ -45035,17 +45917,17 @@ function ContentEditPage() {
45035
45917
  ]);
45036
45918
  const handleContentUpdateError = React$1.useCallback((error) => {
45037
45919
  toastManager.add({
45038
- title: _t3({
45920
+ title: _t4({
45039
45921
  id: "+Ya+b9",
45040
45922
  message: "Failed to save"
45041
45923
  }),
45042
- description: error instanceof Error ? error.message : _t3({
45924
+ description: error instanceof Error ? error.message : _t4({
45043
45925
  id: "Vw8l6h",
45044
45926
  message: "An error occurred"
45045
45927
  }),
45046
45928
  type: "error"
45047
45929
  });
45048
- }, [_t3, toastManager]);
45930
+ }, [_t4, toastManager]);
45049
45931
  const updateMutation = useMutation({
45050
45932
  mutationFn: ({ targetId, targetLocale, changes }) => updateContent(collection, targetId, changes, { locale: targetLocale }),
45051
45933
  onMutate: (variables) => {
@@ -45080,17 +45962,16 @@ function ContentEditPage() {
45080
45962
  payload: {
45081
45963
  data: variables.changes.data,
45082
45964
  slug: variables.changes.slug
45083
- },
45084
- locale: variables.targetLocale
45965
+ }
45085
45966
  });
45086
45967
  },
45087
45968
  onError: (err) => {
45088
45969
  toastManager.add({
45089
- title: _t3({
45970
+ title: _t4({
45090
45971
  id: "dnqStT",
45091
45972
  message: "Autosave failed"
45092
45973
  }),
45093
- description: err instanceof Error ? err.message : _t3({
45974
+ description: err instanceof Error ? err.message : _t4({
45094
45975
  id: "Vw8l6h",
45095
45976
  message: "An error occurred"
45096
45977
  }),
@@ -45112,11 +45993,11 @@ function ContentEditPage() {
45112
45993
  id
45113
45994
  ] });
45114
45995
  toastManager.add({
45115
- title: _t3({
45996
+ title: _t4({
45116
45997
  id: "u3wRF+",
45117
45998
  message: "Published"
45118
45999
  }),
45119
- description: _t3({
46000
+ description: _t4({
45120
46001
  id: "SM5lo+",
45121
46002
  message: "Content is now live"
45122
46003
  })
@@ -45124,11 +46005,11 @@ function ContentEditPage() {
45124
46005
  },
45125
46006
  onError: (error) => {
45126
46007
  toastManager.add({
45127
- title: _t3({
46008
+ title: _t4({
45128
46009
  id: "pxQAFW",
45129
46010
  message: "Failed to publish"
45130
46011
  }),
45131
- description: error instanceof Error ? error.message : _t3({
46012
+ description: error instanceof Error ? error.message : _t4({
45132
46013
  id: "Vw8l6h",
45133
46014
  message: "An error occurred"
45134
46015
  }),
@@ -45150,11 +46031,11 @@ function ContentEditPage() {
45150
46031
  id
45151
46032
  ] });
45152
46033
  toastManager.add({
45153
- title: _t3({
46034
+ title: _t4({
45154
46035
  id: "ejZKhw",
45155
46036
  message: "Unpublished"
45156
46037
  }),
45157
- description: _t3({
46038
+ description: _t4({
45158
46039
  id: "WWp6Cy",
45159
46040
  message: "Content removed from public view"
45160
46041
  })
@@ -45162,11 +46043,11 @@ function ContentEditPage() {
45162
46043
  },
45163
46044
  onError: (error) => {
45164
46045
  toastManager.add({
45165
- title: _t3({
46046
+ title: _t4({
45166
46047
  id: "l0S9au",
45167
46048
  message: "Failed to unpublish"
45168
46049
  }),
45169
- description: error instanceof Error ? error.message : _t3({
46050
+ description: error instanceof Error ? error.message : _t4({
45170
46051
  id: "Vw8l6h",
45171
46052
  message: "An error occurred"
45172
46053
  }),
@@ -45188,11 +46069,11 @@ function ContentEditPage() {
45188
46069
  id
45189
46070
  ] });
45190
46071
  toastManager.add({
45191
- title: _t3({
46072
+ title: _t4({
45192
46073
  id: "Uy0jGI",
45193
46074
  message: "Changes discarded"
45194
46075
  }),
45195
- description: _t3({
46076
+ description: _t4({
45196
46077
  id: "26Nir7",
45197
46078
  message: "Reverted to published version"
45198
46079
  })
@@ -45200,11 +46081,11 @@ function ContentEditPage() {
45200
46081
  },
45201
46082
  onError: (error) => {
45202
46083
  toastManager.add({
45203
- title: _t3({
46084
+ title: _t4({
45204
46085
  id: "7tFjEI",
45205
46086
  message: "Failed to discard changes"
45206
46087
  }),
45207
- description: error instanceof Error ? error.message : _t3({
46088
+ description: error instanceof Error ? error.message : _t4({
45208
46089
  id: "Vw8l6h",
45209
46090
  message: "An error occurred"
45210
46091
  }),
@@ -45221,11 +46102,11 @@ function ContentEditPage() {
45221
46102
  id
45222
46103
  ] });
45223
46104
  toastManager.add({
45224
- title: _t3({
46105
+ title: _t4({
45225
46106
  id: "4ba0NE",
45226
46107
  message: "Scheduled"
45227
46108
  }),
45228
- description: _t3({
46109
+ description: _t4({
45229
46110
  id: "wZIOmQ",
45230
46111
  message: "Content has been scheduled for publishing"
45231
46112
  })
@@ -45233,11 +46114,11 @@ function ContentEditPage() {
45233
46114
  },
45234
46115
  onError: (error) => {
45235
46116
  toastManager.add({
45236
- title: _t3({
46117
+ title: _t4({
45237
46118
  id: "gOH5Xh",
45238
46119
  message: "Failed to schedule"
45239
46120
  }),
45240
- description: error instanceof Error ? error.message : _t3({
46121
+ description: error instanceof Error ? error.message : _t4({
45241
46122
  id: "Vw8l6h",
45242
46123
  message: "An error occurred"
45243
46124
  }),
@@ -45254,11 +46135,11 @@ function ContentEditPage() {
45254
46135
  id
45255
46136
  ] });
45256
46137
  toastManager.add({
45257
- title: _t3({
46138
+ title: _t4({
45258
46139
  id: "cJ44lA",
45259
46140
  message: "Unscheduled"
45260
46141
  }),
45261
- description: _t3({
46142
+ description: _t4({
45262
46143
  id: "XhvaX7",
45263
46144
  message: "Content reverted to draft"
45264
46145
  })
@@ -45266,11 +46147,11 @@ function ContentEditPage() {
45266
46147
  },
45267
46148
  onError: (error) => {
45268
46149
  toastManager.add({
45269
- title: _t3({
46150
+ title: _t4({
45270
46151
  id: "fjtI07",
45271
46152
  message: "Failed to unschedule"
45272
46153
  }),
45273
- description: error instanceof Error ? error.message : _t3({
46154
+ description: error instanceof Error ? error.message : _t4({
45274
46155
  id: "Vw8l6h",
45275
46156
  message: "An error occurred"
45276
46157
  }),
@@ -45301,14 +46182,14 @@ function ContentEditPage() {
45301
46182
  search: { locale: result.locale }
45302
46183
  });
45303
46184
  toastManager.add({
45304
- title: _t3({
46185
+ title: _t4({
45305
46186
  id: "pBdGCu",
45306
46187
  message: "Translation created"
45307
46188
  }),
45308
- description: _t3({
46189
+ description: _t4({
45309
46190
  id: "JtUYuE",
45310
46191
  message: "Created {0} translation",
45311
- values: { 0: result.locale?.toUpperCase() ?? _t3({
46192
+ values: { 0: result.locale?.toUpperCase() ?? _t4({
45312
46193
  id: "Oz0N9s",
45313
46194
  message: "new"
45314
46195
  }) }
@@ -45317,11 +46198,11 @@ function ContentEditPage() {
45317
46198
  },
45318
46199
  onError: (error) => {
45319
46200
  toastManager.add({
45320
- title: _t3({
46201
+ title: _t4({
45321
46202
  id: "q0N9jk",
45322
46203
  message: "Failed to create translation"
45323
46204
  }),
45324
- description: error instanceof Error ? error.message : _t3({
46205
+ description: error instanceof Error ? error.message : _t4({
45325
46206
  id: "Vw8l6h",
45326
46207
  message: "An error occurred"
45327
46208
  }),
@@ -45346,11 +46227,11 @@ function ContentEditPage() {
45346
46227
  },
45347
46228
  onError: (error) => {
45348
46229
  toastManager.add({
45349
- title: _t3({
46230
+ title: _t4({
45350
46231
  id: "92o1+U",
45351
46232
  message: "Failed to delete"
45352
46233
  }),
45353
- description: error instanceof Error ? error.message : _t3({
46234
+ description: error instanceof Error ? error.message : _t4({
45354
46235
  id: "Vw8l6h",
45355
46236
  message: "An error occurred"
45356
46237
  }),
@@ -45522,7 +46403,7 @@ const commentsRoute = createRoute({
45522
46403
  });
45523
46404
  const ROLE_ADMIN = 50;
45524
46405
  function CommentsPage() {
45525
- const { _: _t4 } = useLingui();
46406
+ const { _: _t5 } = useLingui();
45526
46407
  const queryClient = useQueryClient();
45527
46408
  const toastManager = Toast.useToastManager();
45528
46409
  const { data: manifest } = useQuery({
@@ -45532,7 +46413,7 @@ function CommentsPage() {
45532
46413
  const { data: currentUser } = useQuery({
45533
46414
  queryKey: ["currentUser"],
45534
46415
  queryFn: async () => {
45535
- return parseApiResponse(await apiFetch("/_emdash/api/auth/me"), _t4({
46416
+ return parseApiResponse(await apiFetch("/_emdash/api/auth/me"), _t5({
45536
46417
  id: "z7Mflq",
45537
46418
  message: "Failed to fetch user"
45538
46419
  }));
@@ -45576,11 +46457,11 @@ function CommentsPage() {
45576
46457
  },
45577
46458
  onError: (error) => {
45578
46459
  toastManager.add({
45579
- title: _t4({
46460
+ title: _t5({
45580
46461
  id: "/gQXGv",
45581
46462
  message: "Failed to update status"
45582
46463
  }),
45583
- description: error instanceof Error ? error.message : _t4({
46464
+ description: error instanceof Error ? error.message : _t5({
45584
46465
  id: "Vw8l6h",
45585
46466
  message: "An error occurred"
45586
46467
  }),
@@ -45596,11 +46477,11 @@ function CommentsPage() {
45596
46477
  },
45597
46478
  onError: (error) => {
45598
46479
  toastManager.add({
45599
- title: _t4({
46480
+ title: _t5({
45600
46481
  id: "YpwXI2",
45601
46482
  message: "Failed to delete comment"
45602
46483
  }),
45603
- description: error instanceof Error ? error.message : _t4({
46484
+ description: error instanceof Error ? error.message : _t5({
45604
46485
  id: "Vw8l6h",
45605
46486
  message: "An error occurred"
45606
46487
  }),
@@ -45621,11 +46502,11 @@ function CommentsPage() {
45621
46502
  },
45622
46503
  onError: (error) => {
45623
46504
  toastManager.add({
45624
- title: _t4({
46505
+ title: _t5({
45625
46506
  id: "QposE2",
45626
46507
  message: "Failed to perform bulk action"
45627
46508
  }),
45628
- description: error instanceof Error ? error.message : _t4({
46509
+ description: error instanceof Error ? error.message : _t5({
45629
46510
  id: "Vw8l6h",
45630
46511
  message: "An error occurred"
45631
46512
  }),
@@ -45641,13 +46522,13 @@ function CommentsPage() {
45641
46522
  className: "text-center",
45642
46523
  children: [/* @__PURE__ */ jsx("h1", {
45643
46524
  className: "text-2xl font-semibold leading-tight",
45644
- children: _t4({
46525
+ children: _t5({
45645
46526
  id: "VTfZPy",
45646
46527
  message: "Access Denied"
45647
46528
  })
45648
46529
  }), /* @__PURE__ */ jsx("p", {
45649
46530
  className: "mt-2 text-sm text-kumo-subtle",
45650
- children: _t4({
46531
+ children: _t5({
45651
46532
  id: "xbYfFQ",
45652
46533
  message: "You need Editor permissions to moderate comments."
45653
46534
  })
@@ -45919,6 +46800,13 @@ function ContentTypesListPage() {
45919
46800
  queryClient.invalidateQueries({ queryKey: ["manifest"] });
45920
46801
  }
45921
46802
  });
46803
+ const reorderMutation = useMutation({
46804
+ mutationFn: (slugs) => reorderCollections(slugs),
46805
+ onSettled: () => {
46806
+ queryClient.invalidateQueries({ queryKey: ["schema", "collections"] });
46807
+ queryClient.invalidateQueries({ queryKey: ["manifest"] });
46808
+ }
46809
+ });
45922
46810
  const error = collectionsError || orphansError;
45923
46811
  if (error) return /* @__PURE__ */ jsx(ErrorScreen, { error: error.message });
45924
46812
  return /* @__PURE__ */ jsx(ContentTypeList, {
@@ -45926,7 +46814,8 @@ function ContentTypesListPage() {
45926
46814
  orphanedTables,
45927
46815
  isLoading: collectionsLoading || orphansLoading,
45928
46816
  onDelete: (slug) => deleteMutation.mutate(slug),
45929
- onRegisterOrphan: (slug) => registerOrphanMutation.mutate(slug)
46817
+ onRegisterOrphan: (slug) => registerOrphanMutation.mutate(slug),
46818
+ onReorder: (slugs) => reorderMutation.mutate(slugs)
45930
46819
  });
45931
46820
  }
45932
46821
  const contentTypesNewRoute = createRoute({
@@ -45965,7 +46854,7 @@ function ContentTypesEditPage() {
45965
46854
  const { slug } = useParams({ from: "/_admin/content-types/$slug" });
45966
46855
  const queryClient = useQueryClient();
45967
46856
  const toastManager = Toast.useToastManager();
45968
- const { _: _t5 } = useLingui();
46857
+ const { _: _t6 } = useLingui();
45969
46858
  const { data: collection, isLoading, error } = useQuery({
45970
46859
  queryKey: [
45971
46860
  "schema",
@@ -45999,11 +46888,11 @@ function ContentTypesEditPage() {
45999
46888
  },
46000
46889
  onError: (mutationError) => {
46001
46890
  toastManager.add({
46002
- title: _t5({
46891
+ title: _t6({
46003
46892
  id: "+Ya+b9",
46004
46893
  message: "Failed to save"
46005
46894
  }),
46006
- description: mutationError instanceof Error ? mutationError.message : _t5({
46895
+ description: mutationError instanceof Error ? mutationError.message : _t6({
46007
46896
  id: "Vw8l6h",
46008
46897
  message: "An error occurred"
46009
46898
  }),
@@ -46156,7 +47045,7 @@ function createAdminRouter(queryClient) {
46156
47045
  });
46157
47046
  }
46158
47047
  function ConfigurationLoadingScreen() {
46159
- const { _: _t6 } = useLingui();
47048
+ const { _: _t7 } = useLingui();
46160
47049
  return /* @__PURE__ */ jsx("div", {
46161
47050
  className: "emdash-configuration-loader",
46162
47051
  children: /* @__PURE__ */ jsxs("div", {
@@ -46164,13 +47053,13 @@ function ConfigurationLoadingScreen() {
46164
47053
  children: [/* @__PURE__ */ jsx("div", {
46165
47054
  className: "spinner emdash-configuration-spinner",
46166
47055
  role: "status",
46167
- "aria-label": _t6({
47056
+ "aria-label": _t7({
46168
47057
  id: "yQE2r9",
46169
47058
  message: "Loading"
46170
47059
  })
46171
47060
  }), /* @__PURE__ */ jsx("p", {
46172
47061
  className: "emdash-configuration-label",
46173
- children: _t6({
47062
+ children: _t7({
46174
47063
  id: "I6/uPI",
46175
47064
  message: "Loading configuration..."
46176
47065
  })
@@ -46179,14 +47068,14 @@ function ConfigurationLoadingScreen() {
46179
47068
  });
46180
47069
  }
46181
47070
  function LoadingScreen() {
46182
- const { _: _t7 } = useLingui();
47071
+ const { _: _t8 } = useLingui();
46183
47072
  return /* @__PURE__ */ jsx("div", {
46184
47073
  className: "flex items-center justify-center min-h-screen",
46185
47074
  children: /* @__PURE__ */ jsxs("div", {
46186
47075
  className: "text-center",
46187
47076
  children: [/* @__PURE__ */ jsx(Loader, {}), /* @__PURE__ */ jsx("p", {
46188
47077
  className: "mt-4 text-kumo-subtle",
46189
- children: _t7({
47078
+ children: _t8({
46190
47079
  id: "I6/uPI",
46191
47080
  message: "Loading configuration..."
46192
47081
  })
@@ -46195,7 +47084,7 @@ function LoadingScreen() {
46195
47084
  });
46196
47085
  }
46197
47086
  function ErrorScreen({ error }) {
46198
- const { _: _t8 } = useLingui();
47087
+ const { _: _t9 } = useLingui();
46199
47088
  return /* @__PURE__ */ jsx("div", {
46200
47089
  className: "flex items-center justify-center min-h-screen",
46201
47090
  children: /* @__PURE__ */ jsxs("div", {
@@ -46203,7 +47092,7 @@ function ErrorScreen({ error }) {
46203
47092
  children: [
46204
47093
  /* @__PURE__ */ jsx("h1", {
46205
47094
  className: "text-2xl font-semibold leading-tight text-kumo-danger",
46206
- children: _t8({
47095
+ children: _t9({
46207
47096
  id: "SlfejT",
46208
47097
  message: "Error"
46209
47098
  })
@@ -46215,7 +47104,7 @@ function ErrorScreen({ error }) {
46215
47104
  /* @__PURE__ */ jsx(Button, {
46216
47105
  onClick: () => window.location.reload(),
46217
47106
  className: "mt-4",
46218
- children: _t8({
47107
+ children: _t9({
46219
47108
  id: "6gRgw8",
46220
47109
  message: "Retry"
46221
47110
  })
@@ -46225,7 +47114,7 @@ function ErrorScreen({ error }) {
46225
47114
  });
46226
47115
  }
46227
47116
  function NotFoundPage({ message }) {
46228
- const { _: _t9 } = useLingui();
47117
+ const { _: _t0 } = useLingui();
46229
47118
  return /* @__PURE__ */ jsx("div", {
46230
47119
  className: "flex items-center justify-center min-h-[50vh]",
46231
47120
  children: /* @__PURE__ */ jsxs("div", {
@@ -46233,14 +47122,14 @@ function NotFoundPage({ message }) {
46233
47122
  children: [
46234
47123
  /* @__PURE__ */ jsx("h1", {
46235
47124
  className: "text-2xl font-semibold leading-tight",
46236
- children: _t9({
47125
+ children: _t0({
46237
47126
  id: "boJlGf",
46238
47127
  message: "Page Not Found"
46239
47128
  })
46240
47129
  }),
46241
47130
  /* @__PURE__ */ jsx("p", {
46242
47131
  className: "mt-2 text-sm text-kumo-subtle",
46243
- children: message ?? _t9({
47132
+ children: message ?? _t0({
46244
47133
  id: "zEqK2w",
46245
47134
  message: "The page you're looking for doesn't exist."
46246
47135
  })
@@ -46248,7 +47137,7 @@ function NotFoundPage({ message }) {
46248
47137
  /* @__PURE__ */ jsx(Link, {
46249
47138
  to: "/",
46250
47139
  className: "mt-4 inline-block text-kumo-link",
46251
- children: _t9({
47140
+ children: _t0({
46252
47141
  id: "cQPKZt",
46253
47142
  message: "Go to Dashboard"
46254
47143
  })
@@ -46353,5 +47242,5 @@ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, authProviders = EMPTY_AUTH_PRO
46353
47242
  }
46354
47243
 
46355
47244
  //#endregion
46356
- export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, AuthProviderProvider, CAPABILITY_LABELS, ContentEditor, ContentList, DEFAULT_LOCALE, Dashboard, EditorHeader, Header, Link, LoginPage, MEDIA_SEARCH_MAX_LENGTH, 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, createBylineField, createBylineTranslation, createCollection, createContent, createField, createMenu, createMenuItem, createMenuTranslation, createRedirect, createSection, createTaxonomy, createTerm, createTermTranslation, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteBylineField, 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, fetchBylineTranslations, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentAuthors, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaItem, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenuTranslations, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPluginSettings, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTermTranslations, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getBylineFieldUsage, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, importWxrMediaBatched, installMarketplacePlugin, inviteUser, listBylineFields, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderBylineFields, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, resolveLocale, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setPluginMcpEnabled, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateBylineField, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updatePluginSettings, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useAuthProviderList, useAuthProviders, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, validateInviteToken, verifySignupToken };
47245
+ export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, AuthProviderProvider, CAPABILITY_LABELS, ContentEditor, ContentList, DEFAULT_LOCALE, Dashboard, EditorHeader, Header, Link, LoginPage, MEDIA_SEARCH_MAX_LENGTH, 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, createBylineField, createBylineTranslation, createCollection, createContent, createField, createMenu, createMenuItem, createMenuTranslation, createRedirect, createSection, createTaxonomy, createTerm, createTermTranslation, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteBylineField, 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, fetchBylineTranslations, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentAuthors, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaItem, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenuTranslations, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPluginSettings, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTermTranslations, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getBylineFieldUsage, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, importWxrMediaBatched, installMarketplacePlugin, inviteUser, listBylineFields, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderBylineFields, reorderCollections, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, resolveLocale, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setPluginMcpEnabled, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateBylineField, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updatePluginSettings, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useAuthProviderList, useAuthProviders, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, validateInviteToken, verifySignupToken };
46357
47246
  //# sourceMappingURL=index.js.map