@emdash-cms/admin 0.26.0 → 0.28.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 (121) hide show
  1. package/dist/{LocaleDirectionProvider-elsAJo9B.js → LocaleDirectionProvider-C9lpIjJq.js} +28 -22
  2. package/dist/LocaleDirectionProvider-C9lpIjJq.js.map +1 -0
  3. package/dist/{config-DpQrGzsu.d.ts → config-BJBTqBAU.d.ts} +1 -1
  4. package/dist/{config-DpQrGzsu.d.ts.map → config-BJBTqBAU.d.ts.map} +1 -1
  5. package/dist/index.d.ts +52 -8
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1776 -997
  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 -0
  11. package/dist/locales/de/messages.mjs +1 -1
  12. package/dist/locales/en/messages.mjs +1 -1
  13. package/dist/locales/en-GB/messages.mjs +1 -1
  14. package/dist/locales/es-419/messages.mjs +1 -1
  15. package/dist/locales/es-ES/messages.mjs +1 -1
  16. package/dist/locales/eu/messages.mjs +1 -1
  17. package/dist/locales/fa/messages.mjs +1 -1
  18. package/dist/locales/fr/messages.mjs +1 -1
  19. package/dist/locales/hu/messages.mjs +1 -1
  20. package/dist/locales/id/messages.mjs +1 -1
  21. package/dist/locales/index.d.ts +1 -1
  22. package/dist/locales/index.js +1 -1
  23. package/dist/locales/ja/messages.mjs +1 -1
  24. package/dist/locales/ko/messages.mjs +1 -1
  25. package/dist/locales/nb/messages.mjs +1 -1
  26. package/dist/locales/pl/messages.mjs +1 -1
  27. package/dist/locales/pseudo/messages.mjs +1 -1
  28. package/dist/locales/pt-BR/messages.mjs +1 -1
  29. package/dist/locales/th/messages.mjs +1 -1
  30. package/dist/locales/tr/messages.mjs +1 -1
  31. package/dist/locales/zh-CN/messages.mjs +1 -1
  32. package/dist/locales/zh-TW/messages.mjs +1 -1
  33. package/dist/messages-6fY68LcP.js +6 -0
  34. package/dist/messages-6fY68LcP.js.map +1 -0
  35. package/dist/messages-6lTdmQGb.js +6 -0
  36. package/dist/messages-6lTdmQGb.js.map +1 -0
  37. package/dist/messages-AMpyLsVV.js +6 -0
  38. package/dist/messages-AMpyLsVV.js.map +1 -0
  39. package/dist/messages-B3sCXNcK.js +6 -0
  40. package/dist/messages-B3sCXNcK.js.map +1 -0
  41. package/dist/messages-BAyngJJl.js +6 -0
  42. package/dist/messages-BAyngJJl.js.map +1 -0
  43. package/dist/messages-BQZTS0MM.js +6 -0
  44. package/dist/messages-BQZTS0MM.js.map +1 -0
  45. package/dist/messages-BV9Wj2_G.js +6 -0
  46. package/dist/messages-BV9Wj2_G.js.map +1 -0
  47. package/dist/messages-CRMEv0fl.js +6 -0
  48. package/dist/messages-CRMEv0fl.js.map +1 -0
  49. package/dist/messages-CT15OURU.js +6 -0
  50. package/dist/messages-CT15OURU.js.map +1 -0
  51. package/dist/messages-CUCFPFJ3.js +6 -0
  52. package/dist/messages-CUCFPFJ3.js.map +1 -0
  53. package/dist/messages-CavWWCi3.js +6 -0
  54. package/dist/messages-CavWWCi3.js.map +1 -0
  55. package/dist/messages-CoBqWHqG.js +6 -0
  56. package/dist/messages-CoBqWHqG.js.map +1 -0
  57. package/dist/messages-D2o12uTm.js +6 -0
  58. package/dist/messages-D2o12uTm.js.map +1 -0
  59. package/dist/messages-D3pkHI7F.js +6 -0
  60. package/dist/messages-D3pkHI7F.js.map +1 -0
  61. package/dist/messages-D5PMXXV7.js +6 -0
  62. package/dist/messages-D5PMXXV7.js.map +1 -0
  63. package/dist/messages-D8FYHPeJ.js +6 -0
  64. package/dist/messages-D8FYHPeJ.js.map +1 -0
  65. package/dist/messages-D8kZj9qx.js +6 -0
  66. package/dist/messages-D8kZj9qx.js.map +1 -0
  67. package/dist/messages-I4Ix7mrr.js +6 -0
  68. package/dist/messages-I4Ix7mrr.js.map +1 -0
  69. package/dist/messages-LzxSk1w_.js +6 -0
  70. package/dist/messages-LzxSk1w_.js.map +1 -0
  71. package/dist/messages-YG3baZDu.js +6 -0
  72. package/dist/messages-YG3baZDu.js.map +1 -0
  73. package/dist/messages-maew9CuC.js +6 -0
  74. package/dist/messages-maew9CuC.js.map +1 -0
  75. package/dist/messages-ygue5BY_.js +6 -0
  76. package/dist/messages-ygue5BY_.js.map +1 -0
  77. package/dist/styles.css +1 -1
  78. package/package.json +6 -6
  79. package/dist/LocaleDirectionProvider-elsAJo9B.js.map +0 -1
  80. package/dist/messages-3sesjT8a.js +0 -6
  81. package/dist/messages-3sesjT8a.js.map +0 -1
  82. package/dist/messages-8fTMJtWA.js +0 -6
  83. package/dist/messages-8fTMJtWA.js.map +0 -1
  84. package/dist/messages-BBhuGu6K.js +0 -6
  85. package/dist/messages-BBhuGu6K.js.map +0 -1
  86. package/dist/messages-BT9Q9atq.js +0 -6
  87. package/dist/messages-BT9Q9atq.js.map +0 -1
  88. package/dist/messages-BXAjXbUr.js +0 -6
  89. package/dist/messages-BXAjXbUr.js.map +0 -1
  90. package/dist/messages-C6_69h5z.js +0 -6
  91. package/dist/messages-C6_69h5z.js.map +0 -1
  92. package/dist/messages-CIm8G_NT.js +0 -6
  93. package/dist/messages-CIm8G_NT.js.map +0 -1
  94. package/dist/messages-ChEF0PwF.js +0 -6
  95. package/dist/messages-ChEF0PwF.js.map +0 -1
  96. package/dist/messages-Cl5m9q7M.js +0 -6
  97. package/dist/messages-Cl5m9q7M.js.map +0 -1
  98. package/dist/messages-Cqpd51ck.js +0 -6
  99. package/dist/messages-Cqpd51ck.js.map +0 -1
  100. package/dist/messages-DBzgje1G.js +0 -6
  101. package/dist/messages-DBzgje1G.js.map +0 -1
  102. package/dist/messages-DEC9v1nR.js +0 -6
  103. package/dist/messages-DEC9v1nR.js.map +0 -1
  104. package/dist/messages-DOJAmOSK.js +0 -6
  105. package/dist/messages-DOJAmOSK.js.map +0 -1
  106. package/dist/messages-DjTHn-jd.js +0 -6
  107. package/dist/messages-DjTHn-jd.js.map +0 -1
  108. package/dist/messages-DxYrdNN-.js +0 -6
  109. package/dist/messages-DxYrdNN-.js.map +0 -1
  110. package/dist/messages-FkTGnwV-.js +0 -6
  111. package/dist/messages-FkTGnwV-.js.map +0 -1
  112. package/dist/messages-KiGKwzQW.js +0 -6
  113. package/dist/messages-KiGKwzQW.js.map +0 -1
  114. package/dist/messages-RWuknKyd.js +0 -6
  115. package/dist/messages-RWuknKyd.js.map +0 -1
  116. package/dist/messages-itTyZdsM.js +0 -6
  117. package/dist/messages-itTyZdsM.js.map +0 -1
  118. package/dist/messages-otHOIv0j.js +0 -6
  119. package/dist/messages-otHOIv0j.js.map +0 -1
  120. package/dist/messages-umZ9RCq_.js +0 -6
  121. package/dist/messages-umZ9RCq_.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { c as fetchAuthMode, d as throwResponseError, i as fetchPlugins, l as fetchManifest, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as parseApiResponse } from "./plugins-G9Z9NQ7n.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-elsAJo9B.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-C9lpIjJq.js";
3
3
  import "./locales/index.js";
4
- import { Autocomplete, Badge, Button, Checkbox, ClipboardText, Combobox, CommandPalette, Dialog, Input, InputArea, Label, LinkButton, LinkProvider, Loader, Popover, Select, Sidebar as KumoSidebar, Switch, Tabs, Toast, Toasty, Tooltip, buttonVariants, useSidebar } from "@cloudflare/kumo";
4
+ import { Autocomplete, Badge, Button, Checkbox, ClipboardText, Combobox, CommandPalette, Dialog, Input, InputArea, Label, LinkButton, LinkProvider, Loader, Popover, Select, Sidebar as KumoSidebar, Switch, Tabs, Toast, Toasty, Tooltip, buttonVariants, useKumoToastManager, useSidebar } from "@cloudflare/kumo";
5
5
  import { i18n } from "@lingui/core";
6
6
  import { I18nProvider, Trans, useLingui } from "@lingui/react";
7
7
  import { QueryClient, QueryClientProvider, keepPreviousData, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -10,7 +10,7 @@ import * as React$1 from "react";
10
10
  import React, { createContext, useCallback, useContext, useEffect, useState } from "react";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { Dialog as Dialog$1 } from "@cloudflare/kumo/primitives";
13
- import { ArrowCounterClockwise, ArrowDown, ArrowRight, ArrowRightIcon, ArrowSquareOut, ArrowUUpLeft, ArrowUUpRight, ArrowUp, ArrowsClockwise, ArrowsInSimple, ArrowsLeftRight, ArrowsOutSimple, Bell, BookOpen, BracketsAngle, BracketsCurly, Calendar, CalendarBlank, CaretDown, CaretRightIcon, CaretUp, CaretUpDown, ChartBar, ChartLine, ChatCircle, Check, CheckCircle, CircleDashed, CircleNotch, ClockCounterClockwise, Cloud, Code, CodeBlock, Columns, Copy, Crop, Cube, Database, DeviceMobile, DotsSixVertical, DownloadSimple, Envelope, EnvelopeSimple, Eye, EyeSlash, File, FileArrowDown, FileText, FloppyDisk, Folder, FolderOpen, Gear, GithubLogo, Globe, GlobeSimple, GridFour, HardDrive, Hash, IdentificationCard, Image as Image$1, ImageBroken, ImageSquare, Info, Key, Link as Link$2, LinkBreak, LinkSimple, List, ListBullets, ListChecks, ListNumbers, MagnifyingGlass, Medal, Minus, Monitor, Moon, Palette, PaperPlaneTilt, Paperclip, Paragraph, Pencil, PencilSimple, Plug, PlugsConnected, Plus, Prohibit, PuzzlePiece, Quotes, Rows, Ruler, ShareNetwork, Shield, ShieldCheck, ShieldWarning, SignOut, SlidersHorizontal, Sparkle, SquaresFour, Stack, Star, Storefront, Sun, Table, Tag, TextAlignCenter, TextAlignLeft, TextAlignRight, TextB, TextHOne, TextHThree, TextHTwo, TextItalic, TextStrikethrough, TextT, TextUnderline, ToggleLeft, Trash, Trophy, Upload, User, UserCircle, UserPlus, Users, Warning, WarningCircle, WebhooksLogo, X, YoutubeLogo } from "@phosphor-icons/react";
13
+ import { ArrowCounterClockwise, ArrowDown, ArrowRight, ArrowRightIcon, ArrowSquareOut, ArrowUUpLeft, ArrowUUpRight, ArrowUp, ArrowsClockwise, ArrowsInSimple, ArrowsLeftRight, ArrowsOutSimple, Bell, BookOpen, BracketsAngle, BracketsCurly, Calendar, CalendarBlank, CaretDown, CaretRightIcon, CaretUp, CaretUpDown, ChartBar, ChartLine, ChatCircle, Check, CheckCircle, CircleDashed, CircleNotch, ClockCounterClockwise, Cloud, Code, CodeBlock, Columns, Copy, Crop, Cube, Database, DeviceMobile, DotsSixVertical, DownloadSimple, Envelope, EnvelopeSimple, Eye, EyeSlash, File, FileArrowDown, FileText, FloppyDisk, Folder, FolderOpen, Gear, GithubLogo, Globe, GlobeSimple, GridFour, HardDrive, Hash, IdentificationCard, Image as Image$1, ImageBroken, ImageSquare, Images, Info, Key, Link as Link$2, LinkBreak, LinkSimple, List, ListBullets, ListChecks, ListNumbers, MagnifyingGlass, Medal, Minus, Monitor, Moon, Palette, PaperPlaneTilt, Paperclip, Paragraph, Pencil, PencilSimple, Plug, PlugsConnected, Plus, Prohibit, PuzzlePiece, Quotes, Rows, Ruler, ShareNetwork, Shield, ShieldCheck, ShieldWarning, SignOut, SlidersHorizontal, Sparkle, SquaresFour, Stack, Star, Storefront, Sun, Table, Tag, TextAlignCenter, TextAlignLeft, TextAlignRight, TextB, TextHOne, TextHThree, TextHTwo, TextItalic, TextStrikethrough, TextT, TextUnderline, ToggleLeft, Trash, Trophy, Upload, User, UserCircle, UserPlus, Users, Warning, WarningCircle, WebhooksLogo, X, YoutubeLogo } from "@phosphor-icons/react";
14
14
  import { clsx } from "clsx";
15
15
  import { twMerge } from "tailwind-merge";
16
16
  import { DndContext, DragOverlay, KeyboardSensor, PointerSensor, closestCenter, rectIntersection, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
@@ -2528,6 +2528,85 @@ async function createTermTranslation(taxonomyName, slug, input, options = {}) {
2528
2528
  /**
2529
2529
  * WordPress import and source probing APIs
2530
2530
  */
2531
+ async function executeWpPluginImportChunk(url, token, config, phase, cursor, state) {
2532
+ return parseApiResponse(await apiFetch(`${API_BASE}/import/wordpress-plugin/execute`, {
2533
+ method: "POST",
2534
+ headers: { "Content-Type": "application/json" },
2535
+ body: JSON.stringify({
2536
+ url,
2537
+ token,
2538
+ config,
2539
+ phase,
2540
+ cursor,
2541
+ idMap: state.idMap,
2542
+ translationGroups: state.translationGroups,
2543
+ commentRoots: state.commentRoots
2544
+ })
2545
+ }), "Failed to import from WordPress");
2546
+ }
2547
+ /** Merge a chunk's partial result into the running aggregate. */
2548
+ function mergeImportResults(into, chunk) {
2549
+ into.imported += chunk.imported;
2550
+ into.skipped += chunk.skipped;
2551
+ into.errors.push(...chunk.errors);
2552
+ for (const [collection, count] of Object.entries(chunk.byCollection)) into.byCollection[collection] = (into.byCollection[collection] || 0) + count;
2553
+ if (chunk.taxonomyAssignments) into.taxonomyAssignments = (into.taxonomyAssignments ?? 0) + chunk.taxonomyAssignments;
2554
+ if (chunk.missingTaxonomies?.length) into.missingTaxonomies = [...new Set([...into.missingTaxonomies ?? [], ...chunk.missingTaxonomies])];
2555
+ if (chunk.taxonomiesCreated?.length) into.taxonomiesCreated = [...into.taxonomiesCreated ?? [], ...chunk.taxonomiesCreated];
2556
+ if (chunk.menus) into.menus = chunk.menus;
2557
+ if (chunk.comments) into.comments = {
2558
+ imported: (into.comments?.imported ?? 0) + chunk.comments.imported,
2559
+ skipped: (into.comments?.skipped ?? 0) + chunk.comments.skipped
2560
+ };
2561
+ if (chunk.siteSettings) into.siteSettings = chunk.siteSettings;
2562
+ into.success = into.errors.length === 0;
2563
+ }
2564
+ /**
2565
+ * Run the full plugin import as a sequence of bounded requests: content
2566
+ * pages, then comment pages, then finalize (menus + site identity).
2567
+ * Each request stays well below Worker resource limits regardless of
2568
+ * site size. Re-running after an abort is safe: `skipExisting` skips
2569
+ * already-imported content while rebuilding the ID maps the later
2570
+ * phases need.
2571
+ */
2572
+ async function executeWpPluginImport(url, token, config, onProgress) {
2573
+ const aggregate = {
2574
+ success: true,
2575
+ imported: 0,
2576
+ skipped: 0,
2577
+ errors: [],
2578
+ byCollection: {}
2579
+ };
2580
+ const state = {
2581
+ idMap: {},
2582
+ translationGroups: {},
2583
+ commentRoots: {}
2584
+ };
2585
+ let comments = 0;
2586
+ const runPhase = async (phase) => {
2587
+ let cursor;
2588
+ let done = false;
2589
+ while (!done) {
2590
+ const chunk = await executeWpPluginImportChunk(url, token, config, phase, cursor, state);
2591
+ mergeImportResults(aggregate, chunk.result);
2592
+ Object.assign(state.idMap, chunk.chunk?.idMap);
2593
+ Object.assign(state.translationGroups, chunk.chunk?.translationGroups);
2594
+ Object.assign(state.commentRoots, chunk.chunk?.commentRoots);
2595
+ comments += (chunk.result.comments?.imported ?? 0) + (chunk.result.comments?.skipped ?? 0);
2596
+ onProgress?.({
2597
+ phase,
2598
+ processed: aggregate.imported + aggregate.skipped,
2599
+ comments
2600
+ });
2601
+ done = chunk.done;
2602
+ cursor = chunk.cursor;
2603
+ }
2604
+ };
2605
+ await runPhase("content");
2606
+ await runPhase("comments");
2607
+ await runPhase("finalize");
2608
+ return aggregate;
2609
+ }
2531
2610
  /**
2532
2611
  * Analyze a WordPress WXR file
2533
2612
  */
@@ -2613,6 +2692,32 @@ async function importWxrMedia(attachments, onProgress) {
2613
2692
  if (!result) throw new Error("No result received from media import");
2614
2693
  return result;
2615
2694
  }
2695
+ /** Attachments per media request. Bounds each Worker invocation (issue #475). */
2696
+ const MEDIA_BATCH_SIZE = 25;
2697
+ /**
2698
+ * Import media in bounded batches instead of one giant request, so each
2699
+ * Worker invocation stays below resource limits and an aborted run only
2700
+ * loses the batch in flight (the server dedupes re-sent files by content
2701
+ * hash). Progress is reported against the overall total.
2702
+ */
2703
+ async function importWxrMediaBatched(attachments, onProgress) {
2704
+ const merged = {
2705
+ imported: [],
2706
+ failed: [],
2707
+ urlMap: {}
2708
+ };
2709
+ for (let offset = 0; offset < attachments.length; offset += MEDIA_BATCH_SIZE) {
2710
+ const result = await importWxrMedia(attachments.slice(offset, offset + MEDIA_BATCH_SIZE), onProgress && ((progress) => onProgress({
2711
+ ...progress,
2712
+ current: offset + progress.current,
2713
+ total: attachments.length
2714
+ })));
2715
+ merged.imported.push(...result.imported);
2716
+ merged.failed.push(...result.failed);
2717
+ Object.assign(merged.urlMap, result.urlMap);
2718
+ }
2719
+ return merged;
2720
+ }
2616
2721
  /**
2617
2722
  * Probe a URL to detect import source
2618
2723
  */
@@ -2649,20 +2754,6 @@ async function analyzeWpPluginSite(url, token) {
2649
2754
  })
2650
2755
  }), "Failed to analyze WordPress site")).analysis;
2651
2756
  }
2652
- /**
2653
- * Execute import from WordPress plugin API
2654
- */
2655
- async function executeWpPluginImport(url, token, config) {
2656
- return (await parseApiResponse(await apiFetch(`${API_BASE}/import/wordpress-plugin/execute`, {
2657
- method: "POST",
2658
- headers: { "Content-Type": "application/json" },
2659
- body: JSON.stringify({
2660
- url,
2661
- token,
2662
- config
2663
- })
2664
- }), "Failed to import from WordPress")).result;
2665
- }
2666
2757
 
2667
2758
  //#endregion
2668
2759
  //#region src/lib/api/api-tokens.ts
@@ -21058,332 +21149,543 @@ function formatBytes(bytes) {
21058
21149
  //#endregion
21059
21150
  //#region src/components/MediaDetailPanel.tsx
21060
21151
  /**
21061
- * Media Detail Panel
21152
+ * Media Detail Dialog
21062
21153
  *
21063
- * A slide-out panel for viewing and editing media item metadata.
21154
+ * A centered dialog for viewing and editing media item metadata.
21064
21155
  * Opens when clicking an item in the MediaLibrary.
21065
21156
  */
21157
+ const CLOSE_FALLBACK_MS = 500;
21066
21158
  /**
21067
- * Slide-out panel for viewing and editing media metadata
21159
+ * Centered dialog for viewing and editing media metadata.
21068
21160
  */
21069
- function MediaDetailPanel({ item, onClose, onDeleted }) {
21161
+ function MediaDetailPanel({ open, item, providerName, canDelete: canDeleteProp, restoreFocusTargetRef, onClose, onClosed, onUpdated, onDeleted }) {
21070
21162
  const { _: _t } = useLingui();
21071
21163
  const queryClient = useQueryClient();
21072
- const [filename, setFilename] = React$1.useState(item?.filename ?? "");
21073
- const [alt, setAlt] = React$1.useState(item?.alt ?? "");
21074
- const [caption, setCaption] = React$1.useState(item?.caption ?? "");
21164
+ const restoreFocusAfterDeleteRef = React$1.useRef(false);
21165
+ const closeFallbackTimerRef = React$1.useRef(null);
21166
+ const closeFinishedRef = React$1.useRef(false);
21167
+ const isProviderAsset = Boolean(item.provider);
21168
+ const isImage = item.mimeType.startsWith("image/");
21169
+ const isVideo = item.mimeType.startsWith("video/");
21170
+ const isAudio = item.mimeType.startsWith("audio/");
21171
+ const canEditMetadata = !isProviderAsset && isImage;
21172
+ const canDelete = !isProviderAsset || Boolean(canDeleteProp);
21173
+ const [filename, setFilename] = React$1.useState(item.filename);
21174
+ const [alt, setAlt] = React$1.useState(item.alt ?? "");
21175
+ const [caption, setCaption] = React$1.useState(item.caption ?? "");
21176
+ const [showDeleteConfirm, setShowDeleteConfirm] = React$1.useState(false);
21177
+ const [showDiscardConfirm, setShowDiscardConfirm] = React$1.useState(false);
21075
21178
  React$1.useEffect(() => {
21076
- if (item) {
21077
- setFilename(item.filename);
21078
- setAlt(item.alt ?? "");
21079
- setCaption(item.caption ?? "");
21080
- }
21081
- }, [item]);
21082
- const fileUrl = item ? new URL(item.url, window.location.origin).href : "";
21083
- const hasChanges = React$1.useMemo(() => {
21084
- if (!item) return false;
21085
- return filename !== item.filename || alt !== (item.alt ?? "") || caption !== (item.caption ?? "");
21086
- }, [
21087
- item,
21088
- filename,
21089
- alt,
21090
- caption
21091
- ]);
21179
+ if (!open) return;
21180
+ if (closeFallbackTimerRef.current !== null) {
21181
+ window.clearTimeout(closeFallbackTimerRef.current);
21182
+ closeFallbackTimerRef.current = null;
21183
+ }
21184
+ closeFinishedRef.current = false;
21185
+ restoreFocusAfterDeleteRef.current = false;
21186
+ setFilename(item.filename);
21187
+ setAlt(item.alt ?? "");
21188
+ setCaption(item.caption ?? "");
21189
+ setShowDeleteConfirm(false);
21190
+ setShowDiscardConfirm(false);
21191
+ }, [item.id, open]);
21192
+ React$1.useEffect(() => {
21193
+ return () => {
21194
+ if (closeFallbackTimerRef.current !== null) window.clearTimeout(closeFallbackTimerRef.current);
21195
+ };
21196
+ }, []);
21197
+ const finishClose = React$1.useCallback(() => {
21198
+ if (closeFinishedRef.current) return;
21199
+ closeFinishedRef.current = true;
21200
+ if (closeFallbackTimerRef.current !== null) {
21201
+ window.clearTimeout(closeFallbackTimerRef.current);
21202
+ closeFallbackTimerRef.current = null;
21203
+ }
21204
+ const shouldRestoreFocus = restoreFocusAfterDeleteRef.current;
21205
+ restoreFocusAfterDeleteRef.current = false;
21206
+ onClosed?.();
21207
+ if (shouldRestoreFocus) window.setTimeout(() => {
21208
+ restoreFocusTargetRef?.current?.focus();
21209
+ }, 0);
21210
+ }, [onClosed, restoreFocusTargetRef]);
21211
+ const closeDialog = React$1.useCallback(() => {
21212
+ onClose();
21213
+ if (closeFallbackTimerRef.current !== null) window.clearTimeout(closeFallbackTimerRef.current);
21214
+ closeFallbackTimerRef.current = window.setTimeout(finishClose, CLOSE_FALLBACK_MS);
21215
+ }, [finishClose, onClose]);
21216
+ const hasChanges = canEditMetadata && (alt !== (item.alt ?? "") || caption !== (item.caption ?? ""));
21217
+ const isConfirmOpen = showDeleteConfirm || showDiscardConfirm;
21218
+ const publicFileUrl = !isProviderAsset && item.url ? new URL(item.url, window.location.origin).href : "";
21219
+ const filenameHelp = _t({
21220
+ id: "Wz+2dx",
21221
+ message: "Filename cannot be changed after upload"
21222
+ });
21223
+ const filenameHelpLabel = _t({
21224
+ id: "Aef4ny",
21225
+ message: "Why can't this be changed?"
21226
+ });
21227
+ const altTextHelp = _t({
21228
+ id: "XOEr+a",
21229
+ message: "Used by screen readers and when image fails to load"
21230
+ });
21231
+ const altTextHelpLabel = _t({
21232
+ id: "k4GKvk",
21233
+ message: "Why is this important?"
21234
+ });
21092
21235
  const updateMutation = useMutation({
21093
- mutationFn: (data) => {
21094
- if (!item) throw new Error("No item selected");
21095
- return updateMedia(item.id, data);
21096
- },
21236
+ mutationFn: (data) => updateMedia(item.id, data),
21097
21237
  onSuccess: () => {
21098
21238
  queryClient.invalidateQueries({ queryKey: ["media"] });
21239
+ onUpdated?.();
21240
+ closeDialog();
21099
21241
  }
21100
21242
  });
21101
21243
  const deleteMutation = useMutation({
21102
- mutationFn: () => {
21103
- if (!item) throw new Error("No item selected");
21104
- return deleteMedia(item.id);
21105
- },
21244
+ mutationFn: () => item.provider ? deleteFromProvider(item.provider, item.id) : deleteMedia(item.id),
21106
21245
  onSuccess: () => {
21107
- queryClient.invalidateQueries({ queryKey: ["media"] });
21246
+ if (item.provider) queryClient.invalidateQueries({ queryKey: ["provider-media", item.provider] });
21247
+ else queryClient.invalidateQueries({ queryKey: ["media"] });
21248
+ restoreFocusAfterDeleteRef.current = true;
21249
+ setShowDeleteConfirm(false);
21108
21250
  onDeleted?.();
21109
- onClose();
21251
+ closeDialog();
21110
21252
  }
21111
21253
  });
21254
+ const isSaving = updateMutation.isPending;
21255
+ const isDeleting = deleteMutation.isPending;
21256
+ const isBusy = isSaving || isDeleting;
21257
+ const requestClose = React$1.useCallback(() => {
21258
+ if (isBusy) return;
21259
+ if (isConfirmOpen) return;
21260
+ if (hasChanges) {
21261
+ setShowDiscardConfirm(true);
21262
+ return;
21263
+ }
21264
+ closeDialog();
21265
+ }, [
21266
+ closeDialog,
21267
+ hasChanges,
21268
+ isBusy,
21269
+ isConfirmOpen
21270
+ ]);
21112
21271
  const handleSave = () => {
21113
- if (!item || !hasChanges) return;
21272
+ if (!canEditMetadata || !hasChanges || isSaving) return;
21114
21273
  updateMutation.mutate({
21115
- alt: alt || void 0,
21116
- caption: caption || void 0
21274
+ alt,
21275
+ caption
21117
21276
  });
21118
21277
  };
21119
- const [showDeleteConfirm, setShowDeleteConfirm] = React$1.useState(false);
21120
21278
  const handleDelete = () => {
21121
- if (!item) return;
21279
+ if (!canDelete || isBusy) return;
21122
21280
  setShowDeleteConfirm(true);
21123
21281
  };
21124
- const stableOnClose = useStableCallback(onClose);
21282
+ const handleDiscardConfirm = () => {
21283
+ setShowDiscardConfirm(false);
21284
+ closeDialog();
21285
+ };
21125
21286
  const stableHandleSave = useStableCallback(handleSave);
21126
21287
  React$1.useEffect(() => {
21127
- const handleKeyDown = (e) => {
21128
- if (e.key === "Escape") stableOnClose();
21129
- if ((e.metaKey || e.ctrlKey) && e.key === "s") {
21130
- e.preventDefault();
21288
+ if (!open) return;
21289
+ const handleKeyDown = (event) => {
21290
+ if (isConfirmOpen) return;
21291
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") {
21292
+ if (!canEditMetadata || !hasChanges || isSaving) return;
21293
+ event.preventDefault();
21131
21294
  stableHandleSave();
21132
21295
  }
21133
21296
  };
21134
21297
  window.addEventListener("keydown", handleKeyDown);
21135
21298
  return () => window.removeEventListener("keydown", handleKeyDown);
21136
- }, [stableOnClose, stableHandleSave]);
21137
- if (!item) return null;
21138
- const isImage = item.mimeType.startsWith("image/");
21139
- const isVideo = item.mimeType.startsWith("video/");
21140
- const isAudio = item.mimeType.startsWith("audio/");
21141
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
21142
- className: cn("fixed inset-y-0 end-0 w-96 bg-kumo-base border-s shadow-xl z-50", "flex flex-col"),
21143
- children: [
21144
- /* @__PURE__ */ jsxs("div", {
21145
- className: "flex items-center justify-between p-4 border-b",
21146
- children: [/* @__PURE__ */ jsx("h2", {
21147
- className: "font-semibold truncate pe-2",
21148
- children: _t({
21149
- id: "T9un00",
21150
- message: "Media Details"
21151
- })
21152
- }), /* @__PURE__ */ jsxs(Button, {
21153
- variant: "ghost",
21154
- shape: "square",
21155
- "aria-label": _t({
21156
- id: "yz7wBu",
21157
- message: "Close"
21158
- }),
21159
- onClick: onClose,
21160
- children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
21161
- className: "sr-only",
21162
- children: _t({
21163
- id: "yz7wBu",
21164
- message: "Close"
21165
- })
21166
- })]
21167
- })]
21168
- }),
21169
- /* @__PURE__ */ jsxs("div", {
21170
- className: "flex-1 overflow-y-auto",
21299
+ }, [
21300
+ canEditMetadata,
21301
+ hasChanges,
21302
+ isConfirmOpen,
21303
+ isSaving,
21304
+ open,
21305
+ stableHandleSave
21306
+ ]);
21307
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
21308
+ /* @__PURE__ */ jsx(Dialog.Root, {
21309
+ open,
21310
+ onOpenChange: (nextOpen) => {
21311
+ if (!nextOpen && !isConfirmOpen) requestClose();
21312
+ },
21313
+ onOpenChangeComplete: (nextOpen) => {
21314
+ if (nextOpen) return;
21315
+ finishClose();
21316
+ },
21317
+ children: /* @__PURE__ */ jsxs(Dialog, {
21318
+ size: "xl",
21319
+ className: "flex flex-col overflow-hidden p-0",
21320
+ style: {
21321
+ width: "min(94vw, 72rem)",
21322
+ maxHeight: "min(88dvh, 48rem)"
21323
+ },
21171
21324
  children: [
21172
- /* @__PURE__ */ jsx("div", {
21173
- className: "p-4 border-b",
21174
- children: /* @__PURE__ */ jsx("div", {
21175
- className: "aspect-video bg-kumo-tint rounded-lg overflow-hidden flex items-center justify-center",
21176
- children: isImage ? /* @__PURE__ */ jsx("img", {
21177
- src: item.url,
21178
- alt: item.alt || item.filename,
21179
- className: "max-h-full max-w-full object-contain"
21180
- }) : isVideo ? /* @__PURE__ */ jsx("video", {
21181
- src: item.url,
21182
- controls: true,
21183
- preload: "metadata",
21184
- className: "max-h-full max-w-full"
21185
- }) : isAudio ? /* @__PURE__ */ jsx("audio", {
21186
- src: item.url,
21187
- controls: true,
21188
- preload: "metadata",
21189
- className: "w-full"
21190
- }) : /* @__PURE__ */ jsxs("div", {
21191
- className: "text-center p-4",
21192
- children: [/* @__PURE__ */ jsx("span", {
21193
- className: "text-4xl",
21194
- children: getFileIcon(item.mimeType)
21195
- }), /* @__PURE__ */ jsx("p", {
21196
- className: "mt-2 text-sm text-kumo-subtle",
21197
- children: item.mimeType
21198
- })]
21325
+ /* @__PURE__ */ jsxs("div", {
21326
+ className: "flex shrink-0 items-start justify-between gap-4 border-b border-kumo-line",
21327
+ style: { padding: "1.25rem 2rem" },
21328
+ "data-testid": "media-detail-dialog-header",
21329
+ children: [/* @__PURE__ */ jsxs("div", {
21330
+ className: "min-w-0 flex-1",
21331
+ children: [/* @__PURE__ */ jsx(Dialog.Title, {
21332
+ className: "truncate text-lg font-semibold leading-none tracking-tight",
21333
+ children: _t({
21334
+ id: "T9un00",
21335
+ message: "Media Details"
21336
+ })
21337
+ }), /* @__PURE__ */ jsx("p", {
21338
+ className: "mt-1 truncate text-sm text-kumo-subtle",
21339
+ children: item.filename
21340
+ })]
21341
+ }), /* @__PURE__ */ jsx(Button, {
21342
+ variant: "ghost",
21343
+ shape: "square",
21344
+ "aria-label": _t({
21345
+ id: "yz7wBu",
21346
+ message: "Close"
21347
+ }),
21348
+ onClick: requestClose,
21349
+ disabled: isBusy,
21350
+ children: /* @__PURE__ */ jsx(X, {
21351
+ className: "h-4 w-4",
21352
+ "aria-hidden": "true"
21199
21353
  })
21200
- })
21354
+ })]
21201
21355
  }),
21202
21356
  /* @__PURE__ */ jsxs("div", {
21203
- className: "p-4 border-b space-y-3",
21204
- children: [
21205
- /* @__PURE__ */ jsxs("div", {
21206
- className: "flex items-center gap-2 text-sm",
21207
- children: [
21208
- /* @__PURE__ */ jsx(HardDrive, { className: "h-4 w-4 text-kumo-subtle" }),
21209
- /* @__PURE__ */ jsx("span", {
21210
- className: "text-kumo-subtle",
21211
- children: _t({
21212
- id: "QdGvPV",
21213
- message: "Size:"
21214
- })
21215
- }),
21216
- /* @__PURE__ */ jsx("span", { children: formatFileSize(item.size) })
21217
- ]
21218
- }),
21219
- item.width && item.height && /* @__PURE__ */ jsxs("div", {
21220
- className: "flex items-center gap-2 text-sm",
21357
+ className: "grid min-h-0 flex-1 grid-cols-1 overflow-y-auto md:grid-cols-2 md:overflow-hidden",
21358
+ "data-testid": "media-detail-dialog-body",
21359
+ children: [/* @__PURE__ */ jsxs("div", {
21360
+ className: "space-y-5 border-b border-kumo-line p-6 md:min-h-0 md:overflow-y-auto md:border-e md:border-b-0 md:p-8",
21361
+ "data-testid": "media-detail-dialog-preview-column",
21362
+ children: [/* @__PURE__ */ jsx("div", {
21363
+ className: "flex h-64 items-center justify-center overflow-hidden rounded-xl border border-kumo-line bg-kumo-tint md:h-80",
21364
+ children: isImage ? /* @__PURE__ */ jsx("img", {
21365
+ src: item.url,
21366
+ alt: item.alt || item.filename,
21367
+ className: "max-h-full max-w-full object-contain"
21368
+ }) : isVideo ? /* @__PURE__ */ jsx("video", {
21369
+ src: item.url,
21370
+ controls: true,
21371
+ preload: "metadata",
21372
+ className: "max-h-full max-w-full"
21373
+ }) : isAudio ? /* @__PURE__ */ jsx("audio", {
21374
+ src: item.url,
21375
+ controls: true,
21376
+ preload: "metadata",
21377
+ className: "w-full"
21378
+ }) : /* @__PURE__ */ jsxs("div", {
21379
+ className: "p-4 text-center",
21380
+ children: [/* @__PURE__ */ jsx("span", {
21381
+ className: "text-5xl",
21382
+ "aria-hidden": "true",
21383
+ children: getFileIcon(item.mimeType)
21384
+ }), /* @__PURE__ */ jsx("p", {
21385
+ className: "mt-3 text-sm text-kumo-subtle",
21386
+ children: item.mimeType
21387
+ })]
21388
+ })
21389
+ }), /* @__PURE__ */ jsxs("div", {
21390
+ className: "space-y-3",
21391
+ "data-testid": "media-detail-dialog-file-facts",
21221
21392
  children: [
21222
- /* @__PURE__ */ jsx(Ruler, { className: "h-4 w-4 text-kumo-subtle" }),
21223
- /* @__PURE__ */ jsx("span", {
21224
- className: "text-kumo-subtle",
21225
- children: _t({
21226
- id: "4957Ec",
21227
- message: "Dimensions:"
21228
- })
21393
+ /* @__PURE__ */ jsxs("div", {
21394
+ className: "flex items-center gap-2 text-sm",
21395
+ children: [
21396
+ /* @__PURE__ */ jsx(HardDrive, {
21397
+ className: "h-4 w-4 shrink-0 text-kumo-subtle",
21398
+ "aria-hidden": "true"
21399
+ }),
21400
+ /* @__PURE__ */ jsx("span", {
21401
+ className: "text-kumo-subtle",
21402
+ children: _t({
21403
+ id: "QdGvPV",
21404
+ message: "Size:"
21405
+ })
21406
+ }),
21407
+ /* @__PURE__ */ jsx("span", { children: formatFileSize(item.size) })
21408
+ ]
21229
21409
  }),
21230
- /* @__PURE__ */ jsxs("span", { children: [
21231
- item.width,
21232
- " × ",
21233
- item.height
21234
- ] })
21235
- ]
21236
- }),
21237
- /* @__PURE__ */ jsxs("div", {
21238
- className: "flex items-center gap-2 text-sm",
21239
- children: [
21240
- /* @__PURE__ */ jsx(Calendar, { className: "h-4 w-4 text-kumo-subtle" }),
21241
- /* @__PURE__ */ jsx("span", {
21242
- className: "text-kumo-subtle",
21243
- children: _t({
21244
- id: "LSbYWo",
21245
- message: "Uploaded:"
21246
- })
21410
+ item.width && item.height && /* @__PURE__ */ jsxs("div", {
21411
+ className: "flex items-center gap-2 text-sm",
21412
+ children: [
21413
+ /* @__PURE__ */ jsx(Ruler, {
21414
+ className: "h-4 w-4 shrink-0 text-kumo-subtle",
21415
+ "aria-hidden": "true"
21416
+ }),
21417
+ /* @__PURE__ */ jsx("span", {
21418
+ className: "text-kumo-subtle",
21419
+ children: _t({
21420
+ id: "4957Ec",
21421
+ message: "Dimensions:"
21422
+ })
21423
+ }),
21424
+ /* @__PURE__ */ jsxs("span", { children: [
21425
+ item.width,
21426
+ " × ",
21427
+ item.height
21428
+ ] })
21429
+ ]
21247
21430
  }),
21248
- /* @__PURE__ */ jsx("span", { children: formatDate$1(item.createdAt) })
21249
- ]
21250
- }),
21251
- /* @__PURE__ */ jsxs("div", {
21252
- className: "flex items-center gap-2 text-sm",
21253
- children: [
21254
- /* @__PURE__ */ jsx(LinkSimple, { className: "h-4 w-4 text-kumo-subtle shrink-0" }),
21255
- /* @__PURE__ */ jsx("span", {
21256
- className: "text-kumo-subtle shrink-0",
21257
- children: _t({
21258
- id: "PQs7eb",
21259
- message: "URL:"
21260
- })
21431
+ !isProviderAsset && /* @__PURE__ */ jsxs("div", {
21432
+ className: "flex items-center gap-2 text-sm",
21433
+ children: [
21434
+ /* @__PURE__ */ jsx(Calendar, {
21435
+ className: "h-4 w-4 shrink-0 text-kumo-subtle",
21436
+ "aria-hidden": "true"
21437
+ }),
21438
+ /* @__PURE__ */ jsx("span", {
21439
+ className: "text-kumo-subtle",
21440
+ children: _t({
21441
+ id: "LSbYWo",
21442
+ message: "Uploaded:"
21443
+ })
21444
+ }),
21445
+ /* @__PURE__ */ jsx("span", { children: formatDate$1(item.createdAt) })
21446
+ ]
21261
21447
  }),
21262
- /* @__PURE__ */ jsx(ClipboardText, {
21263
- text: fileUrl,
21264
- size: "sm",
21265
- className: "min-w-0 flex-1",
21266
- labels: { copyAction: _t({
21267
- id: "E6nRW7",
21268
- message: "Copy URL"
21269
- }) }
21448
+ /* @__PURE__ */ jsxs("div", {
21449
+ className: "flex items-center gap-2 text-sm",
21450
+ children: [
21451
+ /* @__PURE__ */ jsx(LinkSimple, {
21452
+ className: "h-4 w-4 shrink-0 text-kumo-subtle",
21453
+ "aria-hidden": "true"
21454
+ }),
21455
+ /* @__PURE__ */ jsx("span", {
21456
+ className: "shrink-0 text-kumo-subtle",
21457
+ children: _t({
21458
+ id: "PQs7eb",
21459
+ message: "URL:"
21460
+ })
21461
+ }),
21462
+ publicFileUrl ? /* @__PURE__ */ jsx(ClipboardText, {
21463
+ text: publicFileUrl,
21464
+ size: "sm",
21465
+ className: "min-w-0 flex-1",
21466
+ labels: { copyAction: _t({
21467
+ id: "E6nRW7",
21468
+ message: "Copy URL"
21469
+ }) }
21470
+ }) : /* @__PURE__ */ jsx("span", {
21471
+ className: "min-w-0 text-kumo-subtle",
21472
+ children: _t({
21473
+ id: "tvhPVo",
21474
+ message: "No public URL available"
21475
+ })
21476
+ })
21477
+ ]
21270
21478
  })
21271
21479
  ]
21272
- })
21273
- ]
21480
+ })]
21481
+ }), /* @__PURE__ */ jsxs("div", {
21482
+ className: "space-y-5 p-6 md:min-h-0 md:overflow-y-auto md:p-8",
21483
+ "data-testid": "media-detail-dialog-details-column",
21484
+ children: [
21485
+ isProviderAsset && /* @__PURE__ */ jsx("p", {
21486
+ className: "rounded-lg bg-kumo-tint p-3 text-sm text-kumo-subtle",
21487
+ children: providerName ? _t({
21488
+ id: "MAv+uq",
21489
+ message: "Managed by {providerName}",
21490
+ values: { providerName }
21491
+ }) : _t({
21492
+ id: "OQGcr0",
21493
+ message: "Managed by an external media provider"
21494
+ })
21495
+ }),
21496
+ /* @__PURE__ */ jsxs("div", {
21497
+ className: "space-y-4",
21498
+ children: [/* @__PURE__ */ jsxs("div", {
21499
+ className: "w-full space-y-2",
21500
+ children: [/* @__PURE__ */ jsxs("div", {
21501
+ className: "flex items-center gap-1.5",
21502
+ children: [/* @__PURE__ */ jsx("span", {
21503
+ className: "text-sm font-medium text-kumo-default",
21504
+ children: _t({
21505
+ id: "GN6L5F",
21506
+ message: "Filename"
21507
+ })
21508
+ }), /* @__PURE__ */ jsx(Tooltip, {
21509
+ content: filenameHelp,
21510
+ delay: 0,
21511
+ closeDelay: 0,
21512
+ render: /* @__PURE__ */ jsx("button", {
21513
+ type: "button",
21514
+ className: "inline-flex cursor-help rounded-full text-kumo-subtle hover:text-kumo-default focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-kumo-brand",
21515
+ "aria-label": filenameHelpLabel,
21516
+ children: /* @__PURE__ */ jsx(Info, {
21517
+ className: "h-4 w-4",
21518
+ "aria-hidden": "true"
21519
+ })
21520
+ })
21521
+ })]
21522
+ }), /* @__PURE__ */ jsx(Input, {
21523
+ "aria-label": _t({
21524
+ id: "GN6L5F",
21525
+ message: "Filename"
21526
+ }),
21527
+ value: filename,
21528
+ onChange: (event) => setFilename(event.target.value),
21529
+ disabled: true,
21530
+ className: "w-full bg-kumo-tint text-kumo-subtle"
21531
+ })]
21532
+ }), canEditMetadata && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
21533
+ className: "w-full space-y-2",
21534
+ children: [/* @__PURE__ */ jsxs("div", {
21535
+ className: "flex items-center gap-1.5",
21536
+ children: [/* @__PURE__ */ jsx("span", {
21537
+ className: "text-sm font-medium text-kumo-default",
21538
+ children: _t({
21539
+ id: "TEAx2g",
21540
+ message: "Alt Text"
21541
+ })
21542
+ }), /* @__PURE__ */ jsx(Tooltip, {
21543
+ content: altTextHelp,
21544
+ delay: 0,
21545
+ closeDelay: 0,
21546
+ render: /* @__PURE__ */ jsx("button", {
21547
+ type: "button",
21548
+ className: "inline-flex cursor-help rounded-full text-kumo-subtle hover:text-kumo-default focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-kumo-brand",
21549
+ "aria-label": altTextHelpLabel,
21550
+ children: /* @__PURE__ */ jsx(Info, {
21551
+ className: "h-4 w-4",
21552
+ "aria-hidden": "true"
21553
+ })
21554
+ })
21555
+ })]
21556
+ }), /* @__PURE__ */ jsx(Input, {
21557
+ "aria-label": _t({
21558
+ id: "TEAx2g",
21559
+ message: "Alt Text"
21560
+ }),
21561
+ value: alt,
21562
+ onChange: (event) => setAlt(event.target.value),
21563
+ placeholder: _t({
21564
+ id: "tCDcJ0",
21565
+ message: "Describe this image for accessibility"
21566
+ }),
21567
+ disabled: isSaving,
21568
+ className: "w-full"
21569
+ })]
21570
+ }), /* @__PURE__ */ jsx(InputArea, {
21571
+ label: _t({
21572
+ id: "3Z1aAQ",
21573
+ message: "Caption"
21574
+ }),
21575
+ value: caption,
21576
+ onChange: (event) => setCaption(event.target.value),
21577
+ placeholder: _t({
21578
+ id: "Ya91UA",
21579
+ message: "Optional caption for display"
21580
+ }),
21581
+ rows: 2,
21582
+ disabled: isSaving
21583
+ })] })]
21584
+ }),
21585
+ /* @__PURE__ */ jsx(DialogError, { message: getMutationError(updateMutation.error) })
21586
+ ]
21587
+ })]
21274
21588
  }),
21275
21589
  /* @__PURE__ */ jsxs("div", {
21276
- className: "p-4 space-y-4",
21277
- children: [/* @__PURE__ */ jsx(Input, {
21278
- label: _t({
21279
- id: "GN6L5F",
21280
- message: "Filename"
21281
- }),
21282
- value: filename,
21283
- onChange: (e) => setFilename(e.target.value),
21284
- disabled: true,
21285
- description: _t({
21286
- id: "Wz+2dx",
21287
- message: "Filename cannot be changed after upload"
21288
- })
21289
- }), isImage && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Input, {
21290
- label: _t({
21291
- id: "TEAx2g",
21292
- message: "Alt Text"
21293
- }),
21294
- value: alt,
21295
- onChange: (e) => setAlt(e.target.value),
21296
- placeholder: _t({
21297
- id: "tCDcJ0",
21298
- message: "Describe this image for accessibility"
21299
- }),
21300
- description: _t({
21301
- id: "XOEr+a",
21302
- message: "Used by screen readers and when image fails to load"
21590
+ className: "flex shrink-0 items-center justify-between gap-3 border-t border-kumo-line",
21591
+ style: { padding: "1.25rem 2rem" },
21592
+ "data-testid": "media-detail-dialog-footer",
21593
+ children: [/* @__PURE__ */ jsx("div", { children: canDelete && /* @__PURE__ */ jsx(Button, {
21594
+ variant: "destructive",
21595
+ size: "sm",
21596
+ icon: /* @__PURE__ */ jsx(Trash, {}),
21597
+ onClick: handleDelete,
21598
+ disabled: isBusy,
21599
+ children: isDeleting ? _t({
21600
+ id: "EF2EU9",
21601
+ message: "Deleting..."
21602
+ }) : _t({
21603
+ id: "cnGeoo",
21604
+ message: "Delete"
21303
21605
  })
21304
- }), /* @__PURE__ */ jsx(InputArea, {
21305
- label: _t({
21306
- id: "3Z1aAQ",
21307
- message: "Caption"
21308
- }),
21309
- value: caption,
21310
- onChange: (e) => setCaption(e.target.value),
21311
- placeholder: _t({
21312
- id: "Ya91UA",
21313
- message: "Optional caption for display"
21314
- }),
21315
- rows: 2
21316
- })] })]
21606
+ }) }), /* @__PURE__ */ jsxs("div", {
21607
+ className: "flex gap-2",
21608
+ children: [/* @__PURE__ */ jsx(Button, {
21609
+ variant: "outline",
21610
+ size: "sm",
21611
+ onClick: requestClose,
21612
+ disabled: isBusy,
21613
+ children: canEditMetadata ? _t({
21614
+ id: "dEgA5A",
21615
+ message: "Cancel"
21616
+ }) : _t({
21617
+ id: "yz7wBu",
21618
+ message: "Close"
21619
+ })
21620
+ }), canEditMetadata && /* @__PURE__ */ jsx(Button, {
21621
+ variant: "primary",
21622
+ size: "sm",
21623
+ onClick: handleSave,
21624
+ disabled: !hasChanges || isBusy,
21625
+ children: isSaving ? _t({
21626
+ id: "XvjC4F",
21627
+ message: "Saving..."
21628
+ }) : _t({
21629
+ id: "tfDRzk",
21630
+ message: "Save"
21631
+ })
21632
+ })]
21633
+ })]
21317
21634
  })
21318
21635
  ]
21319
- }),
21320
- /* @__PURE__ */ jsxs("div", {
21321
- className: "p-4 border-t flex items-center justify-between gap-2",
21322
- children: [/* @__PURE__ */ jsx(Button, {
21323
- variant: "destructive",
21324
- size: "sm",
21325
- icon: /* @__PURE__ */ jsx(Trash, {}),
21326
- onClick: handleDelete,
21327
- disabled: deleteMutation.isPending,
21328
- children: deleteMutation.isPending ? _t({
21329
- id: "EF2EU9",
21330
- message: "Deleting..."
21331
- }) : _t({
21332
- id: "cnGeoo",
21333
- message: "Delete"
21334
- })
21335
- }), /* @__PURE__ */ jsxs("div", {
21336
- className: "flex gap-2",
21337
- children: [/* @__PURE__ */ jsx(Button, {
21338
- variant: "outline",
21339
- size: "sm",
21340
- onClick: onClose,
21341
- children: _t({
21342
- id: "dEgA5A",
21343
- message: "Cancel"
21344
- })
21345
- }), /* @__PURE__ */ jsx(Button, {
21346
- size: "sm",
21347
- onClick: handleSave,
21348
- disabled: !hasChanges || updateMutation.isPending,
21349
- children: updateMutation.isPending ? _t({
21350
- id: "XvjC4F",
21351
- message: "Saving..."
21352
- }) : _t({
21353
- id: "tfDRzk",
21354
- message: "Save"
21355
- })
21356
- })]
21357
- })]
21358
21636
  })
21359
- ]
21360
- }), /* @__PURE__ */ jsx(ConfirmDialog, {
21361
- open: showDeleteConfirm,
21362
- onClose: () => {
21363
- setShowDeleteConfirm(false);
21364
- deleteMutation.reset();
21365
- },
21366
- title: _t({
21367
- id: "ML005Q",
21368
- message: "Delete Media?"
21369
- }),
21370
- description: _t({
21371
- id: "pLh0+f",
21372
- message: "Delete \"{0}\"? This cannot be undone.",
21373
- values: { 0: item.filename }
21374
- }),
21375
- confirmLabel: _t({
21376
- id: "cnGeoo",
21377
- message: "Delete"
21378
21637
  }),
21379
- pendingLabel: _t({
21380
- id: "EF2EU9",
21381
- message: "Deleting..."
21638
+ /* @__PURE__ */ jsx(ConfirmDialog, {
21639
+ open: showDiscardConfirm,
21640
+ onClose: () => setShowDiscardConfirm(false),
21641
+ title: _t({
21642
+ id: "0EcUWz",
21643
+ message: "Discard changes?"
21644
+ }),
21645
+ description: _t({
21646
+ id: "T/tC+i",
21647
+ message: "Your unsaved media changes will be lost."
21648
+ }),
21649
+ confirmLabel: _t({
21650
+ id: "bzSI52",
21651
+ message: "Discard"
21652
+ }),
21653
+ pendingLabel: _t({
21654
+ id: "FWamtZ",
21655
+ message: "Discarding..."
21656
+ }),
21657
+ isPending: false,
21658
+ error: null,
21659
+ onConfirm: handleDiscardConfirm
21382
21660
  }),
21383
- isPending: deleteMutation.isPending,
21384
- error: deleteMutation.error,
21385
- onConfirm: () => deleteMutation.mutate()
21386
- })] });
21661
+ /* @__PURE__ */ jsx(ConfirmDialog, {
21662
+ open: showDeleteConfirm,
21663
+ onClose: () => {
21664
+ setShowDeleteConfirm(false);
21665
+ deleteMutation.reset();
21666
+ },
21667
+ title: _t({
21668
+ id: "ML005Q",
21669
+ message: "Delete Media?"
21670
+ }),
21671
+ description: _t({
21672
+ id: "pLh0+f",
21673
+ message: "Delete \"{0}\"? This cannot be undone.",
21674
+ values: { 0: item.filename }
21675
+ }),
21676
+ confirmLabel: _t({
21677
+ id: "cnGeoo",
21678
+ message: "Delete"
21679
+ }),
21680
+ pendingLabel: _t({
21681
+ id: "EF2EU9",
21682
+ message: "Deleting..."
21683
+ }),
21684
+ isPending: deleteMutation.isPending,
21685
+ error: deleteMutation.error,
21686
+ onConfirm: () => deleteMutation.mutate()
21687
+ })
21688
+ ] });
21387
21689
  }
21388
21690
  function formatDate$1(isoString) {
21389
21691
  return new Date(isoString).toLocaleDateString(void 0, {
@@ -21410,13 +21712,16 @@ function mimeForTypeFilter(value) {
21410
21712
  /**
21411
21713
  * Media library component with upload, provider tabs, and grid view
21412
21714
  */
21413
- function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated, hasMore, onLoadMore, onLocalSearchChange, onLocalMimeFilterChange }) {
21715
+ function MediaLibrary({ items = [], isLoading, onUpload, onItemUpdated, hasMore, onLoadMore, onLocalSearchChange, onLocalMimeFilterChange }) {
21414
21716
  const { _: _t } = useLingui();
21415
21717
  const [viewMode, setViewMode] = React$1.useState("grid");
21416
- const [selectedItem, setSelectedItem] = React$1.useState(null);
21718
+ const [detailItem, setDetailItem] = React$1.useState(null);
21719
+ const [isDetailOpen, setIsDetailOpen] = React$1.useState(false);
21417
21720
  const [activeProvider, setActiveProvider] = React$1.useState("local");
21418
21721
  const [searchQuery, setSearchQuery] = React$1.useState("");
21419
21722
  const [localTypeFilter, setLocalTypeFilter] = React$1.useState("all");
21723
+ const mediaHeadingRef = React$1.useRef(null);
21724
+ const detailOpenFrameRef = React$1.useRef(null);
21420
21725
  const debouncedSearch = useDebouncedValue$1(searchQuery, 300);
21421
21726
  React$1.useEffect(() => {
21422
21727
  if (activeProvider === "local" && onLocalSearchChange) onLocalSearchChange(debouncedSearch.trim());
@@ -21465,17 +21770,28 @@ function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated
21465
21770
  providers,
21466
21771
  _t
21467
21772
  ]);
21468
- React$1.useEffect(() => {
21469
- if (selectedItem && activeProvider === "local") {
21470
- const updated = items.find((i) => i.id === selectedItem.id);
21471
- if (updated) setSelectedItem(updated);
21472
- else setSelectedItem(null);
21473
- }
21474
- }, [
21475
- items,
21476
- selectedItem?.id,
21477
- activeProvider
21478
- ]);
21773
+ const cancelPendingDetailOpen = React$1.useCallback(() => {
21774
+ if (detailOpenFrameRef.current === null) return;
21775
+ window.cancelAnimationFrame(detailOpenFrameRef.current);
21776
+ detailOpenFrameRef.current = null;
21777
+ }, []);
21778
+ React$1.useEffect(() => cancelPendingDetailOpen, [cancelPendingDetailOpen]);
21779
+ const openDetail = React$1.useCallback((item) => {
21780
+ cancelPendingDetailOpen();
21781
+ setIsDetailOpen(false);
21782
+ setDetailItem(item);
21783
+ detailOpenFrameRef.current = window.requestAnimationFrame(() => {
21784
+ detailOpenFrameRef.current = null;
21785
+ setIsDetailOpen(true);
21786
+ });
21787
+ }, [cancelPendingDetailOpen]);
21788
+ const closeDetail = React$1.useCallback(() => {
21789
+ cancelPendingDetailOpen();
21790
+ setIsDetailOpen(false);
21791
+ }, [cancelPendingDetailOpen]);
21792
+ const handleDetailClosed = React$1.useCallback(() => {
21793
+ setDetailItem(null);
21794
+ }, []);
21479
21795
  React$1.useEffect(() => {
21480
21796
  if (uploadState.status === "success" || uploadState.status === "error") {
21481
21797
  const timer = setTimeout(() => {
@@ -21623,77 +21939,41 @@ function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated
21623
21939
  const currentLoading = activeProvider === "local" ? isLoading : providerLoading;
21624
21940
  const canUpload = activeProviderInfo?.capabilities.upload ?? false;
21625
21941
  const canSearch = activeProviderInfo?.capabilities.search ?? false;
21942
+ const resultCount = activeProvider === "local" ? currentItems.length : currentProviderItems.length;
21943
+ const hasMoreCurrentItems = activeProvider === "local" ? Boolean(hasMore) : Boolean(providerData?.nextCursor);
21944
+ const resultCountText = resultCount > 0 && !hasMoreCurrentItems ? i18n._({
21945
+ id: "3eGBoB",
21946
+ message: "{resultCount, plural, one {# item} other {# items}}",
21947
+ values: { resultCount }
21948
+ }) : "";
21949
+ const hasActiveQuery = searchQuery.trim() !== "" || activeProvider === "local" && localTypeFilter !== "all";
21950
+ const clearLocalQuery = () => {
21951
+ setSearchQuery("");
21952
+ onLocalSearchChange?.("");
21953
+ setLocalTypeFilter("all");
21954
+ onLocalMimeFilterChange?.(mimeForTypeFilter("all"));
21955
+ };
21956
+ const handleSearchChange = (e) => {
21957
+ const next = e.target.value;
21958
+ setSearchQuery(next);
21959
+ if (activeProvider === "local" && next.trim() === "") onLocalSearchChange?.("");
21960
+ };
21961
+ const showToolbar = resultCount > 0 || hasActiveQuery;
21626
21962
  return /* @__PURE__ */ jsxs("div", {
21627
- className: "space-y-6",
21963
+ className: "space-y-4",
21628
21964
  children: [
21629
21965
  /* @__PURE__ */ jsxs("div", {
21630
- className: "flex items-center justify-between",
21966
+ className: "flex flex-wrap items-center justify-between gap-4",
21631
21967
  children: [/* @__PURE__ */ jsx("h1", {
21968
+ ref: mediaHeadingRef,
21969
+ tabIndex: -1,
21632
21970
  className: "text-2xl font-bold",
21633
21971
  children: _t({
21634
21972
  id: "ia4TsE",
21635
21973
  message: "Media Library"
21636
21974
  })
21637
21975
  }), /* @__PURE__ */ jsxs("div", {
21638
- className: "flex rounded-md border",
21639
- role: "group",
21640
- "aria-label": _t({
21641
- id: "OaiMaf",
21642
- message: "View mode"
21643
- }),
21644
- children: [/* @__PURE__ */ jsx(Button, {
21645
- variant: viewMode === "grid" ? "secondary" : "ghost",
21646
- shape: "square",
21647
- onClick: () => setViewMode("grid"),
21648
- "aria-label": _t({
21649
- id: "5gqNQl",
21650
- message: "Grid view"
21651
- }),
21652
- "aria-pressed": viewMode === "grid",
21653
- children: /* @__PURE__ */ jsx(SquaresFour, {
21654
- className: "h-4 w-4",
21655
- "aria-hidden": "true"
21656
- })
21657
- }), /* @__PURE__ */ jsx(Button, {
21658
- variant: viewMode === "list" ? "secondary" : "ghost",
21659
- shape: "square",
21660
- onClick: () => setViewMode("list"),
21661
- "aria-label": _t({
21662
- id: "5NZpX8",
21663
- message: "List view"
21664
- }),
21665
- "aria-pressed": viewMode === "list",
21666
- children: /* @__PURE__ */ jsx(List, {
21667
- className: "h-4 w-4",
21668
- "aria-hidden": "true"
21669
- })
21670
- })]
21671
- })]
21672
- }),
21673
- /* @__PURE__ */ jsxs("div", {
21674
- className: "flex items-center justify-between gap-4 border-b pb-3",
21675
- children: [providerTabs.length > 1 && /* @__PURE__ */ jsx("div", {
21676
- className: "flex gap-2 overflow-x-auto",
21677
- children: providerTabs.map((tab) => /* @__PURE__ */ jsxs("button", {
21678
- type: "button",
21679
- onClick: () => {
21680
- setActiveProvider(tab.id);
21681
- setSelectedItem(null);
21682
- setSearchQuery("");
21683
- },
21684
- className: cn("flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md transition-colors whitespace-nowrap", activeProvider === tab.id ? "bg-kumo-brand text-white" : "bg-kumo-tint hover:bg-kumo-tint/80 text-kumo-subtle"),
21685
- children: [tab.icon && (tab.icon.startsWith("data:") ? /* @__PURE__ */ jsx("img", {
21686
- src: tab.icon,
21687
- alt: "",
21688
- className: "h-4 w-4",
21689
- "aria-hidden": "true"
21690
- }) : /* @__PURE__ */ jsx("span", {
21691
- "aria-hidden": "true",
21692
- children: tab.icon
21693
- })), tab.name]
21694
- }, tab.id))
21695
- }), /* @__PURE__ */ jsxs("div", {
21696
- className: "flex items-center gap-3 flex-shrink-0",
21976
+ className: "flex items-center gap-3",
21697
21977
  children: [
21698
21978
  uploadState.status === "uploading" && /* @__PURE__ */ jsxs("div", {
21699
21979
  className: "flex items-center gap-2 text-sm text-kumo-subtle",
@@ -21710,12 +21990,18 @@ function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated
21710
21990
  }) })]
21711
21991
  }),
21712
21992
  uploadState.status === "success" && /* @__PURE__ */ jsxs("div", {
21713
- className: "flex items-center gap-2 text-sm text-green-600",
21714
- children: [/* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", { children: uploadState.message })]
21993
+ className: "flex items-center gap-2 text-sm text-kumo-success",
21994
+ children: [/* @__PURE__ */ jsx(Check, {
21995
+ className: "h-4 w-4",
21996
+ "aria-hidden": "true"
21997
+ }), /* @__PURE__ */ jsx("span", { children: uploadState.message })]
21715
21998
  }),
21716
21999
  uploadState.status === "error" && /* @__PURE__ */ jsxs("div", {
21717
22000
  className: "flex items-center gap-2 text-sm text-kumo-danger",
21718
- children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", { children: uploadState.message })]
22001
+ children: [/* @__PURE__ */ jsx(X, {
22002
+ className: "h-4 w-4",
22003
+ "aria-hidden": "true"
22004
+ }), /* @__PURE__ */ jsx("span", { children: uploadState.message })]
21719
22005
  }),
21720
22006
  canUpload && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
21721
22007
  onClick: () => fileInputRef.current?.click(),
@@ -21744,138 +22030,244 @@ function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated
21744
22030
  ]
21745
22031
  })]
21746
22032
  }),
21747
- (canSearch || activeProvider === "local") && /* @__PURE__ */ jsxs("div", {
21748
- className: "flex flex-wrap items-center gap-3",
22033
+ providerTabs.length > 1 && /* @__PURE__ */ jsx(Tabs, {
22034
+ variant: "underline",
22035
+ value: activeProvider,
22036
+ onValueChange: (v) => {
22037
+ if (!v) return;
22038
+ cancelPendingDetailOpen();
22039
+ setActiveProvider(v);
22040
+ setIsDetailOpen(false);
22041
+ setDetailItem(null);
22042
+ setSearchQuery("");
22043
+ },
22044
+ tabs: providerTabs.map((tab) => ({
22045
+ value: tab.id,
22046
+ label: /* @__PURE__ */ jsxs("span", {
22047
+ className: "flex items-center gap-2",
22048
+ children: [tab.icon && (tab.icon.startsWith("data:") ? /* @__PURE__ */ jsx("img", {
22049
+ src: tab.icon,
22050
+ alt: "",
22051
+ className: "h-4 w-4",
22052
+ "aria-hidden": "true"
22053
+ }) : /* @__PURE__ */ jsx("span", {
22054
+ "aria-hidden": "true",
22055
+ children: tab.icon
22056
+ })), tab.name]
22057
+ })
22058
+ }))
22059
+ }),
22060
+ showToolbar && /* @__PURE__ */ jsxs("div", {
22061
+ className: "flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between",
21749
22062
  children: [/* @__PURE__ */ jsxs("div", {
21750
- className: "relative max-w-sm flex-1",
21751
- children: [/* @__PURE__ */ jsx(MagnifyingGlass, { className: "absolute start-3 top-1/2 -translate-y-1/2 h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsx(Input, {
21752
- type: "search",
21753
- placeholder: activeProvider === "local" ? _t({
21754
- id: "Fwo2VZ",
21755
- message: "Search by filename..."
21756
- }) : _t({
21757
- id: "YIix5Y",
21758
- message: "Search..."
21759
- }),
22063
+ className: "flex min-w-0 items-center gap-3",
22064
+ children: [(canSearch || activeProvider === "local") && /* @__PURE__ */ jsxs("div", {
22065
+ className: "relative min-w-0 flex-1 sm:w-72 sm:flex-none",
22066
+ children: [/* @__PURE__ */ jsx(MagnifyingGlass, {
22067
+ className: "absolute start-3 top-1/2 h-4 w-4 -translate-y-1/2 text-kumo-subtle",
22068
+ "aria-hidden": "true"
22069
+ }), /* @__PURE__ */ jsx(Input, {
22070
+ type: "search",
22071
+ placeholder: activeProvider === "local" ? _t({
22072
+ id: "Fwo2VZ",
22073
+ message: "Search by filename..."
22074
+ }) : _t({
22075
+ id: "YIix5Y",
22076
+ message: "Search..."
22077
+ }),
22078
+ "aria-label": _t({
22079
+ id: "GzM+mT",
22080
+ message: "Search media"
22081
+ }),
22082
+ value: searchQuery,
22083
+ onChange: handleSearchChange,
22084
+ maxLength: MEDIA_SEARCH_MAX_LENGTH,
22085
+ className: "w-full ps-9"
22086
+ })]
22087
+ }), activeProvider === "local" && /* @__PURE__ */ jsx(Select, {
22088
+ value: localTypeFilter,
22089
+ onValueChange: (v) => {
22090
+ const next = v ?? "all";
22091
+ setLocalTypeFilter(next);
22092
+ onLocalMimeFilterChange?.(mimeForTypeFilter(next));
22093
+ },
22094
+ items: {
22095
+ all: _t({
22096
+ id: "h6zvpR",
22097
+ message: "All types"
22098
+ }),
22099
+ image: _t({
22100
+ id: "an5hVd",
22101
+ message: "Images"
22102
+ }),
22103
+ video: _t({
22104
+ id: "vSJd18",
22105
+ message: "Video"
22106
+ }),
22107
+ audio: _t({
22108
+ id: "/rTz0M",
22109
+ message: "Audio"
22110
+ }),
22111
+ document: _t({
22112
+ id: "E/muDO",
22113
+ message: "Documents"
22114
+ })
22115
+ },
21760
22116
  "aria-label": _t({
21761
- id: "GzM+mT",
21762
- message: "Search media"
21763
- }),
21764
- value: searchQuery,
21765
- onChange: (e) => setSearchQuery(e.target.value),
21766
- maxLength: MEDIA_SEARCH_MAX_LENGTH,
21767
- className: "ps-9"
22117
+ id: "mP+cvB",
22118
+ message: "Filter by type"
22119
+ })
21768
22120
  })]
21769
- }), activeProvider === "local" && /* @__PURE__ */ jsx(Select, {
21770
- value: localTypeFilter,
21771
- onValueChange: (v) => {
21772
- const next = v ?? "all";
21773
- setLocalTypeFilter(next);
21774
- onLocalMimeFilterChange?.(mimeForTypeFilter(next));
21775
- },
21776
- items: {
21777
- all: _t({
21778
- id: "h6zvpR",
21779
- message: "All types"
21780
- }),
21781
- image: _t({
21782
- id: "an5hVd",
21783
- message: "Images"
21784
- }),
21785
- video: _t({
21786
- id: "vSJd18",
21787
- message: "Video"
21788
- }),
21789
- audio: _t({
21790
- id: "/rTz0M",
21791
- message: "Audio"
22121
+ }), /* @__PURE__ */ jsxs("div", {
22122
+ className: "flex flex-shrink-0 items-center justify-between gap-3 sm:justify-end",
22123
+ children: [/* @__PURE__ */ jsx("span", {
22124
+ className: "text-sm text-kumo-subtle",
22125
+ "aria-live": "polite",
22126
+ children: resultCountText
22127
+ }), /* @__PURE__ */ jsx("div", {
22128
+ role: "group",
22129
+ "aria-label": _t({
22130
+ id: "OaiMaf",
22131
+ message: "View mode"
21792
22132
  }),
21793
- document: _t({
21794
- id: "E/muDO",
21795
- message: "Documents"
22133
+ children: /* @__PURE__ */ jsx(Tabs, {
22134
+ variant: "segmented",
22135
+ value: viewMode,
22136
+ onValueChange: (v) => {
22137
+ if (v === "grid" || v === "list") setViewMode(v);
22138
+ },
22139
+ tabs: [{
22140
+ value: "grid",
22141
+ label: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SquaresFour, {
22142
+ className: "h-4 w-4",
22143
+ "aria-hidden": "true"
22144
+ }), /* @__PURE__ */ jsx("span", {
22145
+ className: "sr-only",
22146
+ children: _t({
22147
+ id: "5gqNQl",
22148
+ message: "Grid view"
22149
+ })
22150
+ })] })
22151
+ }, {
22152
+ value: "list",
22153
+ label: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(List, {
22154
+ className: "h-4 w-4",
22155
+ "aria-hidden": "true"
22156
+ }), /* @__PURE__ */ jsx("span", {
22157
+ className: "sr-only",
22158
+ children: _t({
22159
+ id: "5NZpX8",
22160
+ message: "List view"
22161
+ })
22162
+ })] })
22163
+ }]
21796
22164
  })
21797
- },
21798
- "aria-label": _t({
21799
- id: "mP+cvB",
21800
- message: "Filter by type"
21801
- })
22165
+ })]
21802
22166
  })]
21803
22167
  }),
21804
22168
  currentLoading && currentItems.length === 0 && currentProviderItems.length === 0 ? /* @__PURE__ */ jsx("div", {
21805
22169
  className: "flex items-center justify-center py-12",
21806
22170
  children: /* @__PURE__ */ jsx(Loader, {})
21807
- }) : activeProvider === "local" && currentItems.length === 0 ? /* @__PURE__ */ jsxs("div", {
21808
- className: "rounded-lg border bg-kumo-base p-12 text-center",
21809
- children: [
21810
- /* @__PURE__ */ jsx(Image$1, {
21811
- className: "mx-auto h-12 w-12 text-kumo-subtle",
21812
- "aria-hidden": "true"
21813
- }),
21814
- /* @__PURE__ */ jsx("h2", {
21815
- className: "mt-4 text-lg font-medium",
21816
- children: _t({
21817
- id: "A/osbv",
21818
- message: "No media yet"
21819
- })
21820
- }),
21821
- /* @__PURE__ */ jsx("p", {
21822
- className: "mt-2 text-sm text-kumo-subtle",
21823
- children: _t({
21824
- id: "av15/K",
21825
- message: "Upload images, videos, and documents to get started."
21826
- })
21827
- }),
21828
- /* @__PURE__ */ jsx(Button, {
21829
- className: "mt-4",
21830
- onClick: () => fileInputRef.current?.click(),
21831
- icon: /* @__PURE__ */ jsx(Upload, {}),
21832
- children: _t({
21833
- id: "HAKBY9",
21834
- message: "Upload Files"
21835
- })
22171
+ }) : activeProvider === "local" && currentItems.length === 0 ? hasActiveQuery ? /* @__PURE__ */ jsx(MediaEmptyState, {
22172
+ hero: MagnifyingGlass,
22173
+ title: _t({
22174
+ id: "4Tt2CR",
22175
+ message: "No matching media"
22176
+ }),
22177
+ description: searchQuery.trim() ? _t({
22178
+ id: "kjxCep",
22179
+ message: "Try another filename, or clear your search and filters."
22180
+ }) : _t({
22181
+ id: "LRGNcW",
22182
+ message: "Try a broader media type or clear your filters."
22183
+ }),
22184
+ action: /* @__PURE__ */ jsx(Button, {
22185
+ variant: "outline",
22186
+ onClick: clearLocalQuery,
22187
+ children: searchQuery.trim() ? _t({
22188
+ id: "V8yTm6",
22189
+ message: "Clear search"
22190
+ }) : _t({
22191
+ id: "u8JHrO",
22192
+ message: "Clear filters"
21836
22193
  })
21837
- ]
21838
- }) : activeProvider !== "local" && currentProviderItems.length === 0 ? /* @__PURE__ */ jsxs("div", {
21839
- className: "rounded-lg border bg-kumo-base p-12 text-center",
21840
- children: [
21841
- /* @__PURE__ */ jsx(Image$1, {
21842
- className: "mx-auto h-12 w-12 text-kumo-subtle",
21843
- "aria-hidden": "true"
21844
- }),
21845
- /* @__PURE__ */ jsx("h2", {
21846
- className: "mt-4 text-lg font-medium",
21847
- children: _t({
21848
- id: "rwtX+w",
21849
- message: "No media found"
21850
- })
21851
- }),
21852
- /* @__PURE__ */ jsx("p", {
21853
- className: "mt-2 text-sm text-kumo-subtle",
21854
- children: canSearch && searchQuery ? _t({
21855
- id: "jKoZME",
21856
- message: "Try a different search term"
21857
- }) : canUpload ? _t({
21858
- id: "Gk3NwP",
21859
- message: "Upload media to get started"
21860
- }) : _t({
21861
- id: "IbZjS1",
21862
- message: "No media available from this provider"
21863
- })
22194
+ })
22195
+ }) : /* @__PURE__ */ jsx(MediaEmptyState, {
22196
+ hero: Images,
22197
+ title: _t({
22198
+ id: "Y1Ub1P",
22199
+ message: "Your media library is empty"
22200
+ }),
22201
+ description: _t({
22202
+ id: "Mvw+8j",
22203
+ message: "Upload images, videos, and documents to keep reusable assets in one place."
22204
+ }),
22205
+ action: /* @__PURE__ */ jsx(Button, {
22206
+ onClick: () => fileInputRef.current?.click(),
22207
+ icon: /* @__PURE__ */ jsx(Upload, {}),
22208
+ children: _t({
22209
+ id: "HAKBY9",
22210
+ message: "Upload Files"
21864
22211
  })
21865
- ]
22212
+ })
22213
+ }) : activeProvider !== "local" && currentProviderItems.length === 0 ? canSearch && searchQuery.trim() ? /* @__PURE__ */ jsx(MediaEmptyState, {
22214
+ hero: MagnifyingGlass,
22215
+ title: _t({
22216
+ id: "4Tt2CR",
22217
+ message: "No matching media"
22218
+ }),
22219
+ description: _t({
22220
+ id: "6ruzBH",
22221
+ message: "Try another filename or clear your search."
22222
+ }),
22223
+ action: /* @__PURE__ */ jsx(Button, {
22224
+ variant: "outline",
22225
+ onClick: () => setSearchQuery(""),
22226
+ children: _t({
22227
+ id: "V8yTm6",
22228
+ message: "Clear search"
22229
+ })
22230
+ })
22231
+ }) : canUpload ? /* @__PURE__ */ jsx(MediaEmptyState, {
22232
+ hero: Images,
22233
+ title: _t({
22234
+ id: "Y1Ub1P",
22235
+ message: "Your media library is empty"
22236
+ }),
22237
+ description: _t({
22238
+ id: "ZkYCmd",
22239
+ message: "Upload media to keep reusable assets in one place."
22240
+ }),
22241
+ action: /* @__PURE__ */ jsx(Button, {
22242
+ onClick: () => fileInputRef.current?.click(),
22243
+ icon: /* @__PURE__ */ jsx(Upload, {}),
22244
+ children: _t({
22245
+ id: "HAKBY9",
22246
+ message: "Upload Files"
22247
+ })
22248
+ })
22249
+ }) : /* @__PURE__ */ jsx(MediaEmptyState, {
22250
+ hero: Images,
22251
+ title: _t({
22252
+ id: "rwtX+w",
22253
+ message: "No media found"
22254
+ }),
22255
+ description: _t({
22256
+ id: "Pfw/cp",
22257
+ message: "No media available from this provider."
22258
+ })
21866
22259
  }) : viewMode === "grid" ? /* @__PURE__ */ jsx("div", {
21867
22260
  className: "grid gap-4 grid-cols-[repeat(auto-fill,minmax(160px,1fr))]",
21868
22261
  children: activeProvider === "local" ? currentItems.map((item) => /* @__PURE__ */ jsx(MediaGridItem, {
21869
22262
  item,
21870
- selected: selectedItem?.id === item.id,
21871
- onClick: () => setSelectedItem(item),
21872
- onDelete: () => onDelete?.(item.id)
22263
+ selected: detailItem?.id === item.id,
22264
+ onClick: () => openDetail(item)
21873
22265
  }, item.id)) : currentProviderItems.map((item) => /* @__PURE__ */ jsx(ProviderGridItem, {
21874
22266
  item,
21875
- selected: selectedItem?.id === item.id,
22267
+ selected: detailItem?.id === item.id,
21876
22268
  onClick: () => {
21877
22269
  const dims = loadedDimensions[item.id];
21878
- setSelectedItem(providerItemToMediaItem(activeProvider, dims ? {
22270
+ openDetail(providerItemToMediaItem(activeProvider, dims ? {
21879
22271
  ...item,
21880
22272
  width: item.width ?? dims.width,
21881
22273
  height: item.height ?? dims.height
@@ -21929,8 +22321,8 @@ function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated
21929
22321
  /* @__PURE__ */ jsx("th", {
21930
22322
  className: "px-4 py-3 text-end text-sm font-medium",
21931
22323
  children: _t({
21932
- id: "7L01XJ",
21933
- message: "Actions"
22324
+ id: "u/DP73",
22325
+ message: "Alt text"
21934
22326
  })
21935
22327
  })
21936
22328
  ]
@@ -21938,15 +22330,14 @@ function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated
21938
22330
  className: "divide-y divide-kumo-line",
21939
22331
  children: activeProvider === "local" ? currentItems.map((item) => /* @__PURE__ */ jsx(MediaListItem, {
21940
22332
  item,
21941
- selected: selectedItem?.id === item.id,
21942
- onClick: () => setSelectedItem(item),
21943
- onDelete: () => onDelete?.(item.id)
22333
+ selected: detailItem?.id === item.id,
22334
+ onClick: () => openDetail(item)
21944
22335
  }, item.id)) : currentProviderItems.map((item) => /* @__PURE__ */ jsx(ProviderListItem, {
21945
22336
  item,
21946
- selected: selectedItem?.id === item.id,
22337
+ selected: detailItem?.id === item.id,
21947
22338
  onClick: () => {
21948
22339
  const dims = loadedDimensions[item.id];
21949
- setSelectedItem(providerItemToMediaItem(activeProvider, dims ? {
22340
+ openDetail(providerItemToMediaItem(activeProvider, dims ? {
21950
22341
  ...item,
21951
22342
  width: item.width ?? dims.width,
21952
22343
  height: item.height ?? dims.height
@@ -21980,15 +22371,61 @@ function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated
21980
22371
  })
21981
22372
  })
21982
22373
  }),
21983
- selectedItem && /* @__PURE__ */ jsx(MediaDetailPanel, {
21984
- item: selectedItem,
21985
- onClose: () => setSelectedItem(null),
21986
- onDeleted: () => {
21987
- if (activeProvider === "local") {
21988
- onDelete?.(selectedItem.id);
21989
- onItemUpdated?.();
21990
- } else refetchProviderMedia();
21991
- }
22374
+ detailItem && /* @__PURE__ */ jsx(MediaDetailPanel, {
22375
+ open: isDetailOpen,
22376
+ item: detailItem,
22377
+ providerName: detailItem.provider ? activeProviderInfo?.name : void 0,
22378
+ canDelete: detailItem.provider ? activeProviderInfo?.capabilities.delete : void 0,
22379
+ restoreFocusTargetRef: mediaHeadingRef,
22380
+ onClose: closeDetail,
22381
+ onClosed: handleDetailClosed,
22382
+ onUpdated: onItemUpdated,
22383
+ onDeleted: detailItem.provider ? void 0 : onItemUpdated
22384
+ })
22385
+ ]
22386
+ });
22387
+ }
22388
+ /** Single-chip illustration: solid tinted circle + darker icon, decorative. */
22389
+ function MediaEmptyIllustration({ hero: Hero }) {
22390
+ return /* @__PURE__ */ jsx("div", {
22391
+ className: "flex items-center justify-center",
22392
+ style: {
22393
+ width: "5rem",
22394
+ height: "5rem",
22395
+ minWidth: "5rem",
22396
+ minHeight: "5rem",
22397
+ borderRadius: "9999px",
22398
+ backgroundColor: "var(--color-kumo-info-tint)"
22399
+ },
22400
+ "aria-hidden": "true",
22401
+ children: /* @__PURE__ */ jsx(Hero, {
22402
+ size: 36,
22403
+ className: "text-kumo-brand",
22404
+ "aria-hidden": "true"
22405
+ })
22406
+ });
22407
+ }
22408
+ /** Centered empty / no-results panel with the media illustration. */
22409
+ function MediaEmptyState({ hero, title, description, action }) {
22410
+ return /* @__PURE__ */ jsxs("div", {
22411
+ className: "flex flex-col items-center rounded-lg border bg-kumo-base px-6 py-20 text-center",
22412
+ style: { gap: "1.5rem" },
22413
+ children: [
22414
+ /* @__PURE__ */ jsx(MediaEmptyIllustration, { hero }),
22415
+ /* @__PURE__ */ jsxs("div", {
22416
+ className: "flex flex-col items-center",
22417
+ style: { gap: "0.75rem" },
22418
+ children: [/* @__PURE__ */ jsx("h2", {
22419
+ className: "text-2xl font-semibold leading-none tracking-tight",
22420
+ children: title
22421
+ }), /* @__PURE__ */ jsx("p", {
22422
+ className: "max-w-md text-base leading-6 text-kumo-subtle",
22423
+ children: description
22424
+ })]
22425
+ }),
22426
+ action && /* @__PURE__ */ jsx("div", {
22427
+ style: { marginTop: "0.25rem" },
22428
+ children: action
21992
22429
  })
21993
22430
  ]
21994
22431
  });
@@ -22425,6 +22862,53 @@ function ContentPickerModal({ open, onOpenChange, onSelect }) {
22425
22862
  *
22426
22863
  * Edit menu items with basic reordering (simplified version without drag-and-drop)
22427
22864
  */
22865
+ /**
22866
+ * Flattens items into parent-then-children display order. Guards against
22867
+ * cyclic parentId data (not currently possible via this UI, but the API
22868
+ * doesn't validate against it either) with a visited set.
22869
+ */
22870
+ function buildDisplayList(items) {
22871
+ const childrenByParent = /* @__PURE__ */ new Map();
22872
+ for (const item of items) {
22873
+ const siblings = childrenByParent.get(item.parentId) ?? [];
22874
+ siblings.push(item);
22875
+ childrenByParent.set(item.parentId, siblings);
22876
+ }
22877
+ const result = [];
22878
+ const visited = /* @__PURE__ */ new Set();
22879
+ function visit(parentId, depth) {
22880
+ for (const item of childrenByParent.get(parentId) ?? []) {
22881
+ if (visited.has(item.id)) continue;
22882
+ visited.add(item.id);
22883
+ result.push({
22884
+ item,
22885
+ depth
22886
+ });
22887
+ visit(item.id, depth + 1);
22888
+ }
22889
+ }
22890
+ visit(null, 0);
22891
+ return result;
22892
+ }
22893
+ /** IDs of `rootId` and everything nested under it, so it can't become its own descendant's child. */
22894
+ function getSelfAndDescendantIds(items, rootId) {
22895
+ const childrenByParent = /* @__PURE__ */ new Map();
22896
+ for (const item of items) {
22897
+ const siblings = childrenByParent.get(item.parentId) ?? [];
22898
+ siblings.push(item);
22899
+ childrenByParent.set(item.parentId, siblings);
22900
+ }
22901
+ const ids = new Set([rootId]);
22902
+ function visit(parentId) {
22903
+ for (const child of childrenByParent.get(parentId) ?? []) {
22904
+ if (ids.has(child.id)) continue;
22905
+ ids.add(child.id);
22906
+ visit(child.id);
22907
+ }
22908
+ }
22909
+ visit(rootId);
22910
+ return ids;
22911
+ }
22428
22912
  function MenuEditor() {
22429
22913
  const { _: _t } = useLingui();
22430
22914
  const { name } = useParams$1({ from: "/_admin/menus/$name" });
@@ -22628,24 +23112,31 @@ function MenuEditor() {
22628
23112
  const uLabelVal = formData.get("label");
22629
23113
  const uUrlVal = formData.get("url");
22630
23114
  const uTargetVal = formData.get("target");
23115
+ const uParentIdVal = formData.get("parentId");
22631
23116
  updateMutation.mutate({
22632
23117
  itemId: editingItem.id,
22633
23118
  input: {
22634
23119
  label: typeof uLabelVal === "string" ? uLabelVal : "",
22635
23120
  customUrl: editingItem.type === "custom" ? typeof uUrlVal === "string" ? uUrlVal : "" : void 0,
22636
- target: (typeof uTargetVal === "string" ? uTargetVal : "") || void 0
23121
+ target: (typeof uTargetVal === "string" ? uTargetVal : "") || void 0,
23122
+ parentId: typeof uParentIdVal === "string" && uParentIdVal !== "" ? uParentIdVal : null
22637
23123
  }
22638
23124
  });
22639
23125
  };
22640
- const moveItem = (index, direction) => {
23126
+ const moveItem = (itemId, direction) => {
23127
+ const currentItem = localItems.find((i) => i.id === itemId);
23128
+ if (!currentItem) return;
23129
+ const siblings = localItems.filter((i) => i.parentId === currentItem.parentId);
23130
+ const siblingIndex = siblings.findIndex((i) => i.id === itemId);
23131
+ const targetSiblingIndex = direction === "up" ? siblingIndex - 1 : siblingIndex + 1;
23132
+ if (targetSiblingIndex < 0 || targetSiblingIndex >= siblings.length) return;
23133
+ const targetItem = siblings[targetSiblingIndex];
23134
+ if (!targetItem) return;
23135
+ const currentPos = localItems.findIndex((i) => i.id === currentItem.id);
23136
+ const targetPos = localItems.findIndex((i) => i.id === targetItem.id);
22641
23137
  const newItems = [...localItems];
22642
- const targetIndex = direction === "up" ? index - 1 : index + 1;
22643
- if (targetIndex < 0 || targetIndex >= newItems.length) return;
22644
- const currentItem = newItems[index];
22645
- const targetItem = newItems[targetIndex];
22646
- if (!currentItem || !targetItem) return;
22647
- newItems[index] = targetItem;
22648
- newItems[targetIndex] = currentItem;
23138
+ newItems[currentPos] = targetItem;
23139
+ newItems[targetPos] = currentItem;
22649
23140
  const reorderedItems = newItems.map((item, i) => ({
22650
23141
  id: item.id,
22651
23142
  parentId: item.parentId,
@@ -22654,6 +23145,28 @@ function MenuEditor() {
22654
23145
  setLocalItems(newItems);
22655
23146
  reorderMutation.mutate({ items: reorderedItems });
22656
23147
  };
23148
+ const displayList = React$1.useMemo(() => buildDisplayList(localItems), [localItems]);
23149
+ const parentOptions = React$1.useMemo(() => {
23150
+ if (!editingItem) return [];
23151
+ const excluded = getSelfAndDescendantIds(localItems, editingItem.id);
23152
+ return [{
23153
+ value: "",
23154
+ label: _t({
23155
+ id: "ARS4+q",
23156
+ message: "No parent (top level)"
23157
+ })
23158
+ }, ...buildDisplayList(localItems).filter(({ item }) => !excluded.has(item.id)).map(({ item, depth }) => ({
23159
+ value: item.id,
23160
+ label: /* @__PURE__ */ jsx("span", {
23161
+ style: { marginInlineStart: `${depth * 1.5}rem` },
23162
+ children: item.label
23163
+ })
23164
+ }))];
23165
+ }, [
23166
+ editingItem,
23167
+ localItems,
23168
+ _t
23169
+ ]);
22657
23170
  if (isLoading) return /* @__PURE__ */ jsx("div", {
22658
23171
  className: "flex items-center justify-center h-64",
22659
23172
  children: /* @__PURE__ */ jsx("div", {
@@ -22918,70 +23431,75 @@ function MenuEditor() {
22918
23431
  ]
22919
23432
  }) : /* @__PURE__ */ jsx("div", {
22920
23433
  className: "space-y-2",
22921
- children: localItems.map((item, index) => /* @__PURE__ */ jsxs("div", {
22922
- className: "border rounded-lg p-4 flex items-center justify-between",
22923
- children: [/* @__PURE__ */ jsxs("div", {
22924
- className: "flex-1",
22925
- children: [/* @__PURE__ */ jsx("div", {
22926
- className: "font-medium",
22927
- children: item.label
22928
- }), /* @__PURE__ */ jsxs("div", {
22929
- className: "text-sm text-kumo-subtle",
22930
- children: [item.type === "custom" ? item.customUrl : /* @__PURE__ */ jsx("span", {
22931
- className: "inline-flex items-center rounded-full bg-kumo-brand/10 px-2 py-0.5 text-xs font-medium text-kumo-brand",
22932
- children: item.referenceCollection ?? item.type
22933
- }), item.target === "_blank" && _t({
22934
- id: "+Lmv8u",
22935
- message: " (opens in new window)"
23434
+ children: displayList.map(({ item, depth }) => {
23435
+ const siblings = localItems.filter((i) => i.parentId === item.parentId);
23436
+ const siblingIndex = siblings.findIndex((i) => i.id === item.id);
23437
+ return /* @__PURE__ */ jsxs("div", {
23438
+ className: "border rounded-lg p-4 flex items-center justify-between",
23439
+ style: { marginInlineStart: `${depth * 1.5}rem` },
23440
+ children: [/* @__PURE__ */ jsxs("div", {
23441
+ className: "flex-1",
23442
+ children: [/* @__PURE__ */ jsx("div", {
23443
+ className: "font-medium",
23444
+ children: item.label
23445
+ }), /* @__PURE__ */ jsxs("div", {
23446
+ className: "text-sm text-kumo-subtle",
23447
+ children: [item.type === "custom" ? item.customUrl : /* @__PURE__ */ jsx("span", {
23448
+ className: "inline-flex items-center rounded-full bg-kumo-brand/10 px-2 py-0.5 text-xs font-medium text-kumo-brand",
23449
+ children: item.referenceCollection ?? item.type
23450
+ }), item.target === "_blank" && _t({
23451
+ id: "+Lmv8u",
23452
+ message: " (opens in new window)"
23453
+ })]
22936
23454
  })]
22937
- })]
22938
- }), /* @__PURE__ */ jsxs("div", {
22939
- className: "flex gap-2",
22940
- children: [
22941
- /* @__PURE__ */ jsx(Button, {
22942
- variant: "ghost",
22943
- size: "sm",
22944
- "aria-label": _t({
22945
- id: "QyioBP",
22946
- message: "Move up"
23455
+ }), /* @__PURE__ */ jsxs("div", {
23456
+ className: "flex gap-2",
23457
+ children: [
23458
+ /* @__PURE__ */ jsx(Button, {
23459
+ variant: "ghost",
23460
+ size: "sm",
23461
+ "aria-label": _t({
23462
+ id: "QyioBP",
23463
+ message: "Move up"
23464
+ }),
23465
+ onClick: () => moveItem(item.id, "up"),
23466
+ disabled: siblingIndex === 0,
23467
+ children: /* @__PURE__ */ jsx(CaretUp, { className: "h-4 w-4" })
22947
23468
  }),
22948
- onClick: () => moveItem(index, "up"),
22949
- disabled: index === 0,
22950
- children: /* @__PURE__ */ jsx(CaretUp, { className: "h-4 w-4" })
22951
- }),
22952
- /* @__PURE__ */ jsx(Button, {
22953
- variant: "ghost",
22954
- size: "sm",
22955
- "aria-label": _t({
22956
- id: "3Ib6FN",
22957
- message: "Move down"
23469
+ /* @__PURE__ */ jsx(Button, {
23470
+ variant: "ghost",
23471
+ size: "sm",
23472
+ "aria-label": _t({
23473
+ id: "3Ib6FN",
23474
+ message: "Move down"
23475
+ }),
23476
+ onClick: () => moveItem(item.id, "down"),
23477
+ disabled: siblingIndex === siblings.length - 1,
23478
+ children: /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4" })
22958
23479
  }),
22959
- onClick: () => moveItem(index, "down"),
22960
- disabled: index === localItems.length - 1,
22961
- children: /* @__PURE__ */ jsx(CaretDown, { className: "h-4 w-4" })
22962
- }),
22963
- /* @__PURE__ */ jsx(Button, {
22964
- variant: "outline",
22965
- size: "sm",
22966
- onClick: () => setEditingItem(item),
22967
- children: _t({
22968
- id: "ePK91l",
22969
- message: "Edit"
22970
- })
22971
- }),
22972
- /* @__PURE__ */ jsx(Button, {
22973
- variant: "outline",
22974
- size: "sm",
22975
- "aria-label": _t({
22976
- id: "cnGeoo",
22977
- message: "Delete"
23480
+ /* @__PURE__ */ jsx(Button, {
23481
+ variant: "outline",
23482
+ size: "sm",
23483
+ onClick: () => setEditingItem(item),
23484
+ children: _t({
23485
+ id: "ePK91l",
23486
+ message: "Edit"
23487
+ })
22978
23488
  }),
22979
- onClick: () => deleteMutation.mutate(item.id),
22980
- children: /* @__PURE__ */ jsx(Trash, { className: "h-4 w-4" })
22981
- })
22982
- ]
22983
- })]
22984
- }, item.id))
23489
+ /* @__PURE__ */ jsx(Button, {
23490
+ variant: "outline",
23491
+ size: "sm",
23492
+ "aria-label": _t({
23493
+ id: "cnGeoo",
23494
+ message: "Delete"
23495
+ }),
23496
+ onClick: () => deleteMutation.mutate(item.id),
23497
+ children: /* @__PURE__ */ jsx(Trash, { className: "h-4 w-4" })
23498
+ })
23499
+ ]
23500
+ })]
23501
+ }, item.id);
23502
+ })
22985
23503
  }),
22986
23504
  /* @__PURE__ */ jsx(Dialog.Root, {
22987
23505
  open: editingItem !== null,
@@ -23084,6 +23602,15 @@ function MenuEditor() {
23084
23602
  })
23085
23603
  })]
23086
23604
  }),
23605
+ /* @__PURE__ */ jsx(Select, {
23606
+ label: _t({
23607
+ id: "sujToP",
23608
+ message: "Parent"
23609
+ }),
23610
+ name: "parentId",
23611
+ defaultValue: editingItem.parentId ?? "",
23612
+ items: parentOptions
23613
+ }),
23087
23614
  /* @__PURE__ */ jsx(DialogError, { message: editError || getMutationError(updateMutation.error) }),
23088
23615
  /* @__PURE__ */ jsxs("div", {
23089
23616
  className: "flex justify-end gap-2",
@@ -26518,10 +27045,10 @@ function AllowedDomainsSettings() {
26518
27045
  const { _: _t } = useLingui();
26519
27046
  const { getRoleLabel, signupRoles, signupRoleItems } = useAllowedDomainsRolesConfig();
26520
27047
  const queryClient = useQueryClient();
27048
+ const toastManager = useKumoToastManager();
26521
27049
  const [isAddingDomain, setIsAddingDomain] = React$1.useState(false);
26522
27050
  const [editingDomain, setEditingDomain] = React$1.useState(null);
26523
27051
  const [deletingDomain, setDeletingDomain] = React$1.useState(null);
26524
- const [saveStatus, setSaveStatus] = React$1.useState(null);
26525
27052
  const [newDomain, setNewDomain] = React$1.useState("");
26526
27053
  const [newRole, setNewRole] = React$1.useState(30);
26527
27054
  const { data: manifest, isLoading: manifestLoading } = useQuery({
@@ -26534,12 +27061,6 @@ function AllowedDomainsSettings() {
26534
27061
  queryFn: fetchAllowedDomains,
26535
27062
  enabled: !isExternalAuth && !manifestLoading
26536
27063
  });
26537
- React$1.useEffect(() => {
26538
- if (saveStatus) {
26539
- const timer = setTimeout(setSaveStatus, 3e3, null);
26540
- return () => clearTimeout(timer);
26541
- }
26542
- }, [saveStatus]);
26543
27064
  const createMutation = useMutation({
26544
27065
  mutationFn: createAllowedDomain,
26545
27066
  onSuccess: () => {
@@ -26547,21 +27068,27 @@ function AllowedDomainsSettings() {
26547
27068
  setIsAddingDomain(false);
26548
27069
  setNewDomain("");
26549
27070
  setNewRole(30);
26550
- setSaveStatus({
26551
- type: "success",
26552
- message: _t({
27071
+ toastManager.add({
27072
+ title: _t({
26553
27073
  id: "Iu2D01",
26554
27074
  message: "Domain added successfully"
26555
- })
27075
+ }),
27076
+ variant: "success",
27077
+ timeout: 3e3
26556
27078
  });
26557
27079
  },
26558
27080
  onError: (mutationError) => {
26559
- setSaveStatus({
26560
- type: "error",
26561
- message: mutationError instanceof Error ? mutationError.message : _t({
27081
+ toastManager.add({
27082
+ title: _t({
26562
27083
  id: "eNHvPh",
26563
27084
  message: "Failed to add domain"
26564
- })
27085
+ }),
27086
+ description: mutationError instanceof Error ? mutationError.message : _t({
27087
+ id: "Vw8l6h",
27088
+ message: "An error occurred"
27089
+ }),
27090
+ variant: "error",
27091
+ timeout: 3e3
26565
27092
  });
26566
27093
  }
26567
27094
  });
@@ -26570,21 +27097,27 @@ function AllowedDomainsSettings() {
26570
27097
  onSuccess: () => {
26571
27098
  queryClient.invalidateQueries({ queryKey: ["allowed-domains"] });
26572
27099
  setEditingDomain(null);
26573
- setSaveStatus({
26574
- type: "success",
26575
- message: _t({
27100
+ toastManager.add({
27101
+ title: _t({
26576
27102
  id: "qHWo9D",
26577
27103
  message: "Domain updated"
26578
- })
27104
+ }),
27105
+ variant: "success",
27106
+ timeout: 3e3
26579
27107
  });
26580
27108
  },
26581
27109
  onError: (mutationError) => {
26582
- setSaveStatus({
26583
- type: "error",
26584
- message: mutationError instanceof Error ? mutationError.message : _t({
27110
+ toastManager.add({
27111
+ title: _t({
26585
27112
  id: "6yxw0h",
26586
27113
  message: "Failed to update domain"
26587
- })
27114
+ }),
27115
+ description: mutationError instanceof Error ? mutationError.message : _t({
27116
+ id: "Vw8l6h",
27117
+ message: "An error occurred"
27118
+ }),
27119
+ variant: "error",
27120
+ timeout: 3e3
26588
27121
  });
26589
27122
  }
26590
27123
  });
@@ -26593,21 +27126,27 @@ function AllowedDomainsSettings() {
26593
27126
  onSuccess: () => {
26594
27127
  queryClient.invalidateQueries({ queryKey: ["allowed-domains"] });
26595
27128
  setDeletingDomain(null);
26596
- setSaveStatus({
26597
- type: "success",
26598
- message: _t({
27129
+ toastManager.add({
27130
+ title: _t({
26599
27131
  id: "gim/c7",
26600
27132
  message: "Domain removed"
26601
- })
27133
+ }),
27134
+ variant: "success",
27135
+ timeout: 3e3
26602
27136
  });
26603
27137
  },
26604
27138
  onError: (mutationError) => {
26605
- setSaveStatus({
26606
- type: "error",
26607
- message: mutationError instanceof Error ? mutationError.message : _t({
27139
+ toastManager.add({
27140
+ title: _t({
26608
27141
  id: "Dqma6K",
26609
27142
  message: "Failed to remove domain"
26610
- })
27143
+ }),
27144
+ description: mutationError instanceof Error ? mutationError.message : _t({
27145
+ id: "Vw8l6h",
27146
+ message: "An error occurred"
27147
+ }),
27148
+ variant: "error",
27149
+ timeout: 3e3
26611
27150
  });
26612
27151
  }
26613
27152
  });
@@ -26694,10 +27233,6 @@ function AllowedDomainsSettings() {
26694
27233
  className: "space-y-6",
26695
27234
  children: [
26696
27235
  settingsHeader,
26697
- saveStatus && /* @__PURE__ */ jsxs("div", {
26698
- className: `flex items-center gap-2 rounded-lg border p-3 text-sm ${saveStatus.type === "success" ? "border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-200" : "border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"}`,
26699
- children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 flex-shrink-0" }) : /* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), saveStatus.message]
26700
- }),
26701
27236
  /* @__PURE__ */ jsxs("div", {
26702
27237
  className: "rounded-lg border bg-kumo-base p-6",
26703
27238
  children: [
@@ -27561,13 +28096,8 @@ function CreateTokenForm({ expirySelectItems, isCreating, error, onSubmit, onCan
27561
28096
  */
27562
28097
  function EmailSettings() {
27563
28098
  const { _: _t } = useLingui();
28099
+ const toastManager = useKumoToastManager();
27564
28100
  const [testEmail, setTestEmail] = React$1.useState("");
27565
- const [status, setStatus] = React$1.useState(null);
27566
- React$1.useEffect(() => {
27567
- if (!status) return;
27568
- const timer = setTimeout(setStatus, 5e3, null);
27569
- return () => clearTimeout(timer);
27570
- }, [status]);
27571
28101
  const { data: settings, isLoading, error: fetchError } = useQuery({
27572
28102
  queryKey: ["email-settings"],
27573
28103
  queryFn: fetchEmailSettings
@@ -27575,19 +28105,25 @@ function EmailSettings() {
27575
28105
  const testMutation = useMutation({
27576
28106
  mutationFn: (to) => sendTestEmail(to),
27577
28107
  onSuccess: (result) => {
27578
- setStatus({
27579
- type: "success",
27580
- message: result.message
28108
+ toastManager.add({
28109
+ title: result.message,
28110
+ variant: "success",
28111
+ timeout: 5e3
27581
28112
  });
27582
28113
  setTestEmail("");
27583
28114
  },
27584
28115
  onError: (error) => {
27585
- setStatus({
27586
- type: "error",
27587
- message: getMutationError(error) || _t({
28116
+ toastManager.add({
28117
+ title: _t({
27588
28118
  id: "aH/q8K",
27589
28119
  message: "Failed to send test email"
27590
- })
28120
+ }),
28121
+ description: getMutationError(error) || _t({
28122
+ id: "Vw8l6h",
28123
+ message: "An error occurred"
28124
+ }),
28125
+ variant: "error",
28126
+ timeout: 5e3
27591
28127
  });
27592
28128
  }
27593
28129
  });
@@ -27632,10 +28168,6 @@ function EmailSettings() {
27632
28168
  })
27633
28169
  })]
27634
28170
  }),
27635
- status && /* @__PURE__ */ jsxs("div", {
27636
- className: `flex items-center gap-2 rounded-lg border p-3 text-sm ${status.type === "success" ? "border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-200" : "border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"}`,
27637
- children: [status.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 flex-shrink-0" }) : /* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), status.message]
27638
- }),
27639
28171
  /* @__PURE__ */ jsxs("div", {
27640
28172
  className: "rounded-lg border bg-kumo-base p-6",
27641
28173
  children: [/* @__PURE__ */ jsxs("div", {
@@ -27828,43 +28360,43 @@ function PipelineStatus({ settings }) {
27828
28360
  function GeneralSettings() {
27829
28361
  const { _: _t } = useLingui();
27830
28362
  const queryClient = useQueryClient();
28363
+ const toastManager = useKumoToastManager();
27831
28364
  const { data: settings, isLoading } = useQuery({
27832
28365
  queryKey: ["settings"],
27833
28366
  queryFn: fetchSettings,
27834
28367
  staleTime: Infinity
27835
28368
  });
27836
28369
  const [formData, setFormData] = React$1.useState({});
27837
- const [saveStatus, setSaveStatus] = React$1.useState(null);
27838
28370
  const [logoPickerOpen, setLogoPickerOpen] = React$1.useState(false);
27839
28371
  const [faviconPickerOpen, setFaviconPickerOpen] = React$1.useState(false);
27840
28372
  React$1.useEffect(() => {
27841
28373
  if (settings) setFormData(settings);
27842
28374
  }, [settings]);
27843
- React$1.useEffect(() => {
27844
- if (saveStatus) {
27845
- const timer = setTimeout(setSaveStatus, 3e3, null);
27846
- return () => clearTimeout(timer);
27847
- }
27848
- }, [saveStatus]);
27849
28375
  const saveMutation = useMutation({
27850
28376
  mutationFn: (data) => updateSettings(data),
27851
28377
  onSuccess: () => {
27852
28378
  queryClient.invalidateQueries({ queryKey: ["settings"] });
27853
- setSaveStatus({
27854
- type: "success",
27855
- message: _t({
28379
+ toastManager.add({
28380
+ title: _t({
27856
28381
  id: "gjNQhe",
27857
28382
  message: "Settings saved successfully"
27858
- })
28383
+ }),
28384
+ variant: "success",
28385
+ timeout: 3e3
27859
28386
  });
27860
28387
  },
27861
28388
  onError: (error) => {
27862
- setSaveStatus({
27863
- type: "error",
27864
- message: error instanceof Error ? error.message : _t({
28389
+ toastManager.add({
28390
+ title: _t({
27865
28391
  id: "iQ1BL4",
27866
28392
  message: "Failed to save settings"
27867
- })
28393
+ }),
28394
+ description: error instanceof Error ? error.message : _t({
28395
+ id: "Vw8l6h",
28396
+ message: "An error occurred"
28397
+ }),
28398
+ variant: "error",
28399
+ timeout: 3e3
27868
28400
  });
27869
28401
  }
27870
28402
  });
@@ -27959,10 +28491,6 @@ function GeneralSettings() {
27959
28491
  })
27960
28492
  })
27961
28493
  }),
27962
- saveStatus && /* @__PURE__ */ jsxs("div", {
27963
- className: `flex items-center gap-2 rounded-lg border p-3 text-sm ${saveStatus.type === "success" ? "border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-200" : "border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"}`,
27964
- children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 flex-shrink-0" }) : /* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), saveStatus.message]
27965
- }),
27966
28494
  /* @__PURE__ */ jsxs("form", {
27967
28495
  id: "general-settings-form",
27968
28496
  onSubmit: handleSubmit,
@@ -28476,8 +29004,8 @@ function PasskeyList({ passkeys, onRename, onDelete, isDeleting, isRenaming }) {
28476
29004
  function SecuritySettings() {
28477
29005
  const { _: _t } = useLingui();
28478
29006
  const queryClient = useQueryClient();
29007
+ const toastManager = useKumoToastManager();
28479
29008
  const [isAdding, setIsAdding] = React$1.useState(false);
28480
- const [saveStatus, setSaveStatus] = React$1.useState(null);
28481
29009
  const { data: manifest, isLoading: manifestLoading } = useQuery({
28482
29010
  queryKey: ["manifest"],
28483
29011
  queryFn: fetchManifest
@@ -28488,31 +29016,31 @@ function SecuritySettings() {
28488
29016
  queryFn: fetchPasskeys,
28489
29017
  enabled: !isExternalAuth && !manifestLoading
28490
29018
  });
28491
- React$1.useEffect(() => {
28492
- if (saveStatus) {
28493
- const timer = setTimeout(setSaveStatus, 3e3, null);
28494
- return () => clearTimeout(timer);
28495
- }
28496
- }, [saveStatus]);
28497
29019
  const renameMutation = useMutation({
28498
29020
  mutationFn: ({ id, name }) => renamePasskey(id, name),
28499
29021
  onSuccess: () => {
28500
29022
  queryClient.invalidateQueries({ queryKey: ["passkeys"] });
28501
- setSaveStatus({
28502
- type: "success",
28503
- message: _t({
29023
+ toastManager.add({
29024
+ title: _t({
28504
29025
  id: "ug8XC3",
28505
29026
  message: "Passkey renamed"
28506
- })
29027
+ }),
29028
+ variant: "success",
29029
+ timeout: 3e3
28507
29030
  });
28508
29031
  },
28509
29032
  onError: (mutationError) => {
28510
- setSaveStatus({
28511
- type: "error",
28512
- message: mutationError instanceof Error ? mutationError.message : _t({
29033
+ toastManager.add({
29034
+ title: _t({
28513
29035
  id: "8PbboA",
28514
29036
  message: "Failed to rename passkey"
28515
- })
29037
+ }),
29038
+ description: mutationError instanceof Error ? mutationError.message : _t({
29039
+ id: "Vw8l6h",
29040
+ message: "An error occurred"
29041
+ }),
29042
+ variant: "error",
29043
+ timeout: 3e3
28516
29044
  });
28517
29045
  }
28518
29046
  });
@@ -28520,21 +29048,27 @@ function SecuritySettings() {
28520
29048
  mutationFn: (id) => deletePasskey(id),
28521
29049
  onSuccess: () => {
28522
29050
  queryClient.invalidateQueries({ queryKey: ["passkeys"] });
28523
- setSaveStatus({
28524
- type: "success",
28525
- message: _t({
29051
+ toastManager.add({
29052
+ title: _t({
28526
29053
  id: "47qxLC",
28527
29054
  message: "Passkey removed"
28528
- })
29055
+ }),
29056
+ variant: "success",
29057
+ timeout: 3e3
28529
29058
  });
28530
29059
  },
28531
29060
  onError: (mutationError) => {
28532
- setSaveStatus({
28533
- type: "error",
28534
- message: mutationError instanceof Error ? mutationError.message : _t({
29061
+ toastManager.add({
29062
+ title: _t({
28535
29063
  id: "ZqQzHN",
28536
29064
  message: "Failed to remove passkey"
28537
- })
29065
+ }),
29066
+ description: mutationError instanceof Error ? mutationError.message : _t({
29067
+ id: "Vw8l6h",
29068
+ message: "An error occurred"
29069
+ }),
29070
+ variant: "error",
29071
+ timeout: 3e3
28538
29072
  });
28539
29073
  }
28540
29074
  });
@@ -28550,12 +29084,13 @@ function SecuritySettings() {
28550
29084
  const handleAddSuccess = () => {
28551
29085
  queryClient.invalidateQueries({ queryKey: ["passkeys"] });
28552
29086
  setIsAdding(false);
28553
- setSaveStatus({
28554
- type: "success",
28555
- message: _t({
29087
+ toastManager.add({
29088
+ title: _t({
28556
29089
  id: "1fv1af",
28557
29090
  message: "Passkey added successfully"
28558
- })
29091
+ }),
29092
+ variant: "success",
29093
+ timeout: 3e3
28559
29094
  });
28560
29095
  };
28561
29096
  const settingsHeader = /* @__PURE__ */ jsxs("div", {
@@ -28616,92 +29151,90 @@ function SecuritySettings() {
28616
29151
  });
28617
29152
  return /* @__PURE__ */ jsxs("div", {
28618
29153
  className: "space-y-6",
28619
- children: [
28620
- settingsHeader,
28621
- saveStatus && /* @__PURE__ */ jsxs("div", {
28622
- className: `flex items-center gap-2 rounded-lg border p-3 text-sm ${saveStatus.type === "success" ? "border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-200" : "border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"}`,
28623
- children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 flex-shrink-0" }) : /* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), saveStatus.message]
28624
- }),
28625
- /* @__PURE__ */ jsxs("div", {
28626
- className: "rounded-lg border bg-kumo-base p-6",
28627
- children: [
28628
- /* @__PURE__ */ jsxs("div", {
28629
- className: "flex items-center gap-2 mb-4",
28630
- children: [/* @__PURE__ */ jsx(Shield, { className: "h-5 w-5 text-kumo-subtle" }), /* @__PURE__ */ jsx("h2", {
28631
- className: "text-lg font-semibold",
28632
- children: _t({
28633
- id: "UZKLEA",
28634
- message: "Passkeys"
28635
- })
28636
- })]
28637
- }),
28638
- /* @__PURE__ */ jsx("p", {
28639
- className: "text-sm text-kumo-subtle mb-6",
28640
- children: _t({
28641
- id: "2gqSxq",
28642
- message: "Passkeys are a secure, passwordless way to sign in to your account. You can register multiple passkeys for different devices."
28643
- })
28644
- }),
28645
- passkeys && passkeys.length > 0 ? /* @__PURE__ */ jsx(PasskeyList, {
28646
- passkeys,
28647
- onRename: handleRename,
28648
- onDelete: handleDelete,
28649
- isDeleting: deleteMutation.isPending,
28650
- isRenaming: renameMutation.isPending
28651
- }) : /* @__PURE__ */ jsx("div", {
28652
- className: "rounded-lg border border-dashed p-6 text-center text-kumo-subtle",
29154
+ children: [settingsHeader, /* @__PURE__ */ jsxs("div", {
29155
+ className: "rounded-lg border bg-kumo-base p-6",
29156
+ children: [
29157
+ /* @__PURE__ */ jsxs("div", {
29158
+ className: "flex items-center gap-2 mb-4",
29159
+ children: [/* @__PURE__ */ jsx(Shield, { className: "h-5 w-5 text-kumo-subtle" }), /* @__PURE__ */ jsx("h2", {
29160
+ className: "text-lg font-semibold",
28653
29161
  children: _t({
28654
- id: "pUbgxd",
28655
- message: "No passkeys registered yet."
29162
+ id: "UZKLEA",
29163
+ message: "Passkeys"
28656
29164
  })
28657
- }),
28658
- /* @__PURE__ */ jsx("div", {
28659
- className: "mt-6 pt-6 border-t",
28660
- children: isAdding ? /* @__PURE__ */ jsxs("div", {
28661
- className: "space-y-4",
28662
- children: [/* @__PURE__ */ jsxs("div", {
28663
- className: "flex items-center justify-between",
28664
- children: [/* @__PURE__ */ jsx("h3", {
28665
- className: "font-medium",
28666
- children: _t({
28667
- id: "MzsjnL",
28668
- message: "Add a new passkey"
28669
- })
28670
- }), /* @__PURE__ */ jsx(Button, {
28671
- variant: "ghost",
28672
- size: "sm",
28673
- onClick: () => setIsAdding(false),
28674
- children: _t({
28675
- id: "dEgA5A",
28676
- message: "Cancel"
28677
- })
28678
- })]
28679
- }), /* @__PURE__ */ jsx(PasskeyRegistration, {
28680
- optionsEndpoint: "/_emdash/api/auth/passkey/register/options",
28681
- verifyEndpoint: "/_emdash/api/auth/passkey/register/verify",
28682
- onSuccess: handleAddSuccess,
28683
- onError: (registrationError) => setSaveStatus({
28684
- type: "error",
28685
- message: registrationError.message
28686
- }),
28687
- showNameInput: true,
28688
- buttonText: _t({
28689
- id: "0QF2SD",
28690
- message: "Register Passkey"
29165
+ })]
29166
+ }),
29167
+ /* @__PURE__ */ jsx("p", {
29168
+ className: "text-sm text-kumo-subtle mb-6",
29169
+ children: _t({
29170
+ id: "2gqSxq",
29171
+ message: "Passkeys are a secure, passwordless way to sign in to your account. You can register multiple passkeys for different devices."
29172
+ })
29173
+ }),
29174
+ passkeys && passkeys.length > 0 ? /* @__PURE__ */ jsx(PasskeyList, {
29175
+ passkeys,
29176
+ onRename: handleRename,
29177
+ onDelete: handleDelete,
29178
+ isDeleting: deleteMutation.isPending,
29179
+ isRenaming: renameMutation.isPending
29180
+ }) : /* @__PURE__ */ jsx("div", {
29181
+ className: "rounded-lg border border-dashed p-6 text-center text-kumo-subtle",
29182
+ children: _t({
29183
+ id: "pUbgxd",
29184
+ message: "No passkeys registered yet."
29185
+ })
29186
+ }),
29187
+ /* @__PURE__ */ jsx("div", {
29188
+ className: "mt-6 pt-6 border-t",
29189
+ children: isAdding ? /* @__PURE__ */ jsxs("div", {
29190
+ className: "space-y-4",
29191
+ children: [/* @__PURE__ */ jsxs("div", {
29192
+ className: "flex items-center justify-between",
29193
+ children: [/* @__PURE__ */ jsx("h3", {
29194
+ className: "font-medium",
29195
+ children: _t({
29196
+ id: "MzsjnL",
29197
+ message: "Add a new passkey"
29198
+ })
29199
+ }), /* @__PURE__ */ jsx(Button, {
29200
+ variant: "ghost",
29201
+ size: "sm",
29202
+ onClick: () => setIsAdding(false),
29203
+ children: _t({
29204
+ id: "dEgA5A",
29205
+ message: "Cancel"
28691
29206
  })
28692
29207
  })]
28693
- }) : /* @__PURE__ */ jsx(Button, {
28694
- onClick: () => setIsAdding(true),
28695
- icon: /* @__PURE__ */ jsx(Plus, {}),
28696
- children: _t({
28697
- id: "2wwxok",
28698
- message: "Add Passkey"
29208
+ }), /* @__PURE__ */ jsx(PasskeyRegistration, {
29209
+ optionsEndpoint: "/_emdash/api/auth/passkey/register/options",
29210
+ verifyEndpoint: "/_emdash/api/auth/passkey/register/verify",
29211
+ onSuccess: handleAddSuccess,
29212
+ onError: (registrationError) => toastManager.add({
29213
+ title: _t({
29214
+ id: "3UcvlX",
29215
+ message: "Failed to add passkey"
29216
+ }),
29217
+ description: registrationError.message,
29218
+ variant: "error",
29219
+ timeout: 3e3
29220
+ }),
29221
+ showNameInput: true,
29222
+ buttonText: _t({
29223
+ id: "0QF2SD",
29224
+ message: "Register Passkey"
28699
29225
  })
29226
+ })]
29227
+ }) : /* @__PURE__ */ jsx(Button, {
29228
+ onClick: () => setIsAdding(true),
29229
+ icon: /* @__PURE__ */ jsx(Plus, {}),
29230
+ children: _t({
29231
+ id: "2wwxok",
29232
+ message: "Add Passkey"
28700
29233
  })
28701
29234
  })
28702
- ]
28703
- })
28704
- ]
29235
+ })
29236
+ ]
29237
+ })]
28705
29238
  });
28706
29239
  }
28707
29240
 
@@ -28715,42 +29248,42 @@ function SecuritySettings() {
28715
29248
  function SeoSettings() {
28716
29249
  const { _: _t } = useLingui();
28717
29250
  const queryClient = useQueryClient();
29251
+ const toastManager = useKumoToastManager();
28718
29252
  const { data: settings, isLoading } = useQuery({
28719
29253
  queryKey: ["settings"],
28720
29254
  queryFn: fetchSettings,
28721
29255
  staleTime: Infinity
28722
29256
  });
28723
29257
  const [formData, setFormData] = React$1.useState({});
28724
- const [saveStatus, setSaveStatus] = React$1.useState(null);
28725
29258
  const [ogImagePickerOpen, setOgImagePickerOpen] = React$1.useState(false);
28726
29259
  React$1.useEffect(() => {
28727
29260
  if (settings) setFormData(settings);
28728
29261
  }, [settings]);
28729
- React$1.useEffect(() => {
28730
- if (saveStatus) {
28731
- const timer = setTimeout(setSaveStatus, 3e3, null);
28732
- return () => clearTimeout(timer);
28733
- }
28734
- }, [saveStatus]);
28735
29262
  const saveMutation = useMutation({
28736
29263
  mutationFn: (data) => updateSettings(data),
28737
29264
  onSuccess: () => {
28738
29265
  queryClient.invalidateQueries({ queryKey: ["settings"] });
28739
- setSaveStatus({
28740
- type: "success",
28741
- message: _t({
29266
+ toastManager.add({
29267
+ title: _t({
28742
29268
  id: "YmIwN3",
28743
29269
  message: "SEO settings saved"
28744
- })
29270
+ }),
29271
+ variant: "success",
29272
+ timeout: 3e3
28745
29273
  });
28746
29274
  },
28747
29275
  onError: (error) => {
28748
- setSaveStatus({
28749
- type: "error",
28750
- message: error instanceof Error ? error.message : _t({
29276
+ toastManager.add({
29277
+ title: _t({
28751
29278
  id: "iQ1BL4",
28752
29279
  message: "Failed to save settings"
28753
- })
29280
+ }),
29281
+ description: error instanceof Error ? error.message : _t({
29282
+ id: "Vw8l6h",
29283
+ message: "An error occurred"
29284
+ }),
29285
+ variant: "error",
29286
+ timeout: 3e3
28754
29287
  });
28755
29288
  }
28756
29289
  });
@@ -28838,10 +29371,6 @@ function SeoSettings() {
28838
29371
  })
28839
29372
  })
28840
29373
  }),
28841
- saveStatus && /* @__PURE__ */ jsxs("div", {
28842
- className: `flex items-center gap-2 rounded-lg border p-3 text-sm ${saveStatus.type === "success" ? "border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-200" : "border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"}`,
28843
- children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 flex-shrink-0" }) : /* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), saveStatus.message]
28844
- }),
28845
29374
  /* @__PURE__ */ jsxs("form", {
28846
29375
  id: "seo-settings-form",
28847
29376
  onSubmit: handleSubmit,
@@ -29021,41 +29550,41 @@ function SeoSettings() {
29021
29550
  function SocialSettings() {
29022
29551
  const { _: _t } = useLingui();
29023
29552
  const queryClient = useQueryClient();
29553
+ const toastManager = useKumoToastManager();
29024
29554
  const { data: settings, isLoading } = useQuery({
29025
29555
  queryKey: ["settings"],
29026
29556
  queryFn: fetchSettings,
29027
29557
  staleTime: Infinity
29028
29558
  });
29029
29559
  const [formData, setFormData] = React$1.useState({});
29030
- const [saveStatus, setSaveStatus] = React$1.useState(null);
29031
29560
  React$1.useEffect(() => {
29032
29561
  if (settings) setFormData(settings);
29033
29562
  }, [settings]);
29034
- React$1.useEffect(() => {
29035
- if (saveStatus) {
29036
- const timer = setTimeout(setSaveStatus, 3e3, null);
29037
- return () => clearTimeout(timer);
29038
- }
29039
- }, [saveStatus]);
29040
29563
  const saveMutation = useMutation({
29041
29564
  mutationFn: (data) => updateSettings(data),
29042
29565
  onSuccess: () => {
29043
29566
  queryClient.invalidateQueries({ queryKey: ["settings"] });
29044
- setSaveStatus({
29045
- type: "success",
29046
- message: _t({
29567
+ toastManager.add({
29568
+ title: _t({
29047
29569
  id: "UrvGqM",
29048
29570
  message: "Social links saved"
29049
- })
29571
+ }),
29572
+ variant: "success",
29573
+ timeout: 3e3
29050
29574
  });
29051
29575
  },
29052
29576
  onError: (error) => {
29053
- setSaveStatus({
29054
- type: "error",
29055
- message: error instanceof Error ? error.message : _t({
29577
+ toastManager.add({
29578
+ title: _t({
29056
29579
  id: "iQ1BL4",
29057
29580
  message: "Failed to save settings"
29058
- })
29581
+ }),
29582
+ description: error instanceof Error ? error.message : _t({
29583
+ id: "Vw8l6h",
29584
+ message: "An error occurred"
29585
+ }),
29586
+ variant: "error",
29587
+ timeout: 3e3
29059
29588
  });
29060
29589
  }
29061
29590
  });
@@ -29096,12 +29625,131 @@ function SocialSettings() {
29096
29625
  });
29097
29626
  return /* @__PURE__ */ jsxs("div", {
29098
29627
  className: "space-y-6",
29099
- children: [
29100
- /* @__PURE__ */ jsx(EditorHeader, {
29101
- leading: /* @__PURE__ */ jsx(BackToSettingsLink, {}),
29102
- actions: /* @__PURE__ */ jsx(Button, {
29628
+ children: [/* @__PURE__ */ jsx(EditorHeader, {
29629
+ leading: /* @__PURE__ */ jsx(BackToSettingsLink, {}),
29630
+ actions: /* @__PURE__ */ jsx(Button, {
29631
+ type: "submit",
29632
+ form: "social-settings-form",
29633
+ disabled: saveMutation.isPending,
29634
+ icon: /* @__PURE__ */ jsx(FloppyDisk, {}),
29635
+ children: saveMutation.isPending ? _t({
29636
+ id: "XvjC4F",
29637
+ message: "Saving..."
29638
+ }) : _t({
29639
+ id: "KZrfYJ",
29640
+ message: "Save Social Links"
29641
+ })
29642
+ }),
29643
+ children: /* @__PURE__ */ jsx("h1", {
29644
+ className: "text-2xl font-bold truncate",
29645
+ children: _t({
29646
+ id: "d0rUsW",
29647
+ message: "Social Links"
29648
+ })
29649
+ })
29650
+ }), /* @__PURE__ */ jsxs("form", {
29651
+ id: "social-settings-form",
29652
+ onSubmit: handleSubmit,
29653
+ className: "space-y-6",
29654
+ children: [/* @__PURE__ */ jsxs("div", {
29655
+ className: "rounded-lg border bg-kumo-base p-6",
29656
+ children: [
29657
+ /* @__PURE__ */ jsx("h2", {
29658
+ className: "mb-4 text-lg font-semibold",
29659
+ children: _t({
29660
+ id: "QnQNzm",
29661
+ message: "Social Profiles"
29662
+ })
29663
+ }),
29664
+ /* @__PURE__ */ jsx("p", {
29665
+ className: "text-sm text-kumo-subtle mb-6",
29666
+ children: _t({
29667
+ id: "JUYkJD",
29668
+ message: "Add your social media profiles. These are available to your site's theme and can be displayed in headers, footers, or author bios."
29669
+ })
29670
+ }),
29671
+ /* @__PURE__ */ jsxs("div", {
29672
+ className: "space-y-4",
29673
+ children: [
29674
+ /* @__PURE__ */ jsx(Input, {
29675
+ label: _t({
29676
+ id: "0F2sI+",
29677
+ message: "Twitter"
29678
+ }),
29679
+ value: formData.social?.twitter || "",
29680
+ onChange: (e) => handleSocialChange("twitter", e.target.value),
29681
+ description: _t({
29682
+ id: "k2LNK5",
29683
+ message: "Your Twitter/X handle (e.g., @username)"
29684
+ })
29685
+ }),
29686
+ /* @__PURE__ */ jsx(Input, {
29687
+ label: _t({
29688
+ id: "RkXlPZ",
29689
+ message: "GitHub"
29690
+ }),
29691
+ value: formData.social?.github || "",
29692
+ onChange: (e) => handleSocialChange("github", e.target.value),
29693
+ description: _t({
29694
+ id: "suUAON",
29695
+ message: "Your GitHub username"
29696
+ })
29697
+ }),
29698
+ /* @__PURE__ */ jsx(Input, {
29699
+ label: _t({
29700
+ id: "DwuoH0",
29701
+ message: "Facebook"
29702
+ }),
29703
+ value: formData.social?.facebook || "",
29704
+ onChange: (e) => handleSocialChange("facebook", e.target.value),
29705
+ description: _t({
29706
+ id: "mZtEvV",
29707
+ message: "Your Facebook page or profile username"
29708
+ })
29709
+ }),
29710
+ /* @__PURE__ */ jsx(Input, {
29711
+ label: _t({
29712
+ id: "CTWsuc",
29713
+ message: "Instagram"
29714
+ }),
29715
+ value: formData.social?.instagram || "",
29716
+ onChange: (e) => handleSocialChange("instagram", e.target.value),
29717
+ description: _t({
29718
+ id: "B32eQ0",
29719
+ message: "Your Instagram username"
29720
+ })
29721
+ }),
29722
+ /* @__PURE__ */ jsx(Input, {
29723
+ label: _t({
29724
+ id: "gggTBm",
29725
+ message: "LinkedIn"
29726
+ }),
29727
+ value: formData.social?.linkedin || "",
29728
+ onChange: (e) => handleSocialChange("linkedin", e.target.value),
29729
+ description: _t({
29730
+ id: "J3nNrX",
29731
+ message: "Your LinkedIn profile username"
29732
+ })
29733
+ }),
29734
+ /* @__PURE__ */ jsx(Input, {
29735
+ label: _t({
29736
+ id: "9Q6UKX",
29737
+ message: "YouTube"
29738
+ }),
29739
+ value: formData.social?.youtube || "",
29740
+ onChange: (e) => handleSocialChange("youtube", e.target.value),
29741
+ description: _t({
29742
+ id: "sTNsJ9",
29743
+ message: "Your YouTube channel ID or handle"
29744
+ })
29745
+ })
29746
+ ]
29747
+ })
29748
+ ]
29749
+ }), /* @__PURE__ */ jsx("div", {
29750
+ className: "flex justify-end",
29751
+ children: /* @__PURE__ */ jsx(Button, {
29103
29752
  type: "submit",
29104
- form: "social-settings-form",
29105
29753
  disabled: saveMutation.isPending,
29106
29754
  icon: /* @__PURE__ */ jsx(FloppyDisk, {}),
29107
29755
  children: saveMutation.isPending ? _t({
@@ -29111,135 +29759,9 @@ function SocialSettings() {
29111
29759
  id: "KZrfYJ",
29112
29760
  message: "Save Social Links"
29113
29761
  })
29114
- }),
29115
- children: /* @__PURE__ */ jsx("h1", {
29116
- className: "text-2xl font-bold truncate",
29117
- children: _t({
29118
- id: "d0rUsW",
29119
- message: "Social Links"
29120
- })
29121
29762
  })
29122
- }),
29123
- saveStatus && /* @__PURE__ */ jsxs("div", {
29124
- className: `flex items-center gap-2 rounded-lg border p-3 text-sm ${saveStatus.type === "success" ? "border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950/30 dark:text-green-200" : "border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950/30 dark:text-red-200"}`,
29125
- children: [saveStatus.type === "success" ? /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 flex-shrink-0" }) : /* @__PURE__ */ jsx(WarningCircle, { className: "h-4 w-4 flex-shrink-0" }), saveStatus.message]
29126
- }),
29127
- /* @__PURE__ */ jsxs("form", {
29128
- id: "social-settings-form",
29129
- onSubmit: handleSubmit,
29130
- className: "space-y-6",
29131
- children: [/* @__PURE__ */ jsxs("div", {
29132
- className: "rounded-lg border bg-kumo-base p-6",
29133
- children: [
29134
- /* @__PURE__ */ jsx("h2", {
29135
- className: "mb-4 text-lg font-semibold",
29136
- children: _t({
29137
- id: "QnQNzm",
29138
- message: "Social Profiles"
29139
- })
29140
- }),
29141
- /* @__PURE__ */ jsx("p", {
29142
- className: "text-sm text-kumo-subtle mb-6",
29143
- children: _t({
29144
- id: "JUYkJD",
29145
- message: "Add your social media profiles. These are available to your site's theme and can be displayed in headers, footers, or author bios."
29146
- })
29147
- }),
29148
- /* @__PURE__ */ jsxs("div", {
29149
- className: "space-y-4",
29150
- children: [
29151
- /* @__PURE__ */ jsx(Input, {
29152
- label: _t({
29153
- id: "0F2sI+",
29154
- message: "Twitter"
29155
- }),
29156
- value: formData.social?.twitter || "",
29157
- onChange: (e) => handleSocialChange("twitter", e.target.value),
29158
- description: _t({
29159
- id: "k2LNK5",
29160
- message: "Your Twitter/X handle (e.g., @username)"
29161
- })
29162
- }),
29163
- /* @__PURE__ */ jsx(Input, {
29164
- label: _t({
29165
- id: "RkXlPZ",
29166
- message: "GitHub"
29167
- }),
29168
- value: formData.social?.github || "",
29169
- onChange: (e) => handleSocialChange("github", e.target.value),
29170
- description: _t({
29171
- id: "suUAON",
29172
- message: "Your GitHub username"
29173
- })
29174
- }),
29175
- /* @__PURE__ */ jsx(Input, {
29176
- label: _t({
29177
- id: "DwuoH0",
29178
- message: "Facebook"
29179
- }),
29180
- value: formData.social?.facebook || "",
29181
- onChange: (e) => handleSocialChange("facebook", e.target.value),
29182
- description: _t({
29183
- id: "mZtEvV",
29184
- message: "Your Facebook page or profile username"
29185
- })
29186
- }),
29187
- /* @__PURE__ */ jsx(Input, {
29188
- label: _t({
29189
- id: "CTWsuc",
29190
- message: "Instagram"
29191
- }),
29192
- value: formData.social?.instagram || "",
29193
- onChange: (e) => handleSocialChange("instagram", e.target.value),
29194
- description: _t({
29195
- id: "B32eQ0",
29196
- message: "Your Instagram username"
29197
- })
29198
- }),
29199
- /* @__PURE__ */ jsx(Input, {
29200
- label: _t({
29201
- id: "gggTBm",
29202
- message: "LinkedIn"
29203
- }),
29204
- value: formData.social?.linkedin || "",
29205
- onChange: (e) => handleSocialChange("linkedin", e.target.value),
29206
- description: _t({
29207
- id: "J3nNrX",
29208
- message: "Your LinkedIn profile username"
29209
- })
29210
- }),
29211
- /* @__PURE__ */ jsx(Input, {
29212
- label: _t({
29213
- id: "9Q6UKX",
29214
- message: "YouTube"
29215
- }),
29216
- value: formData.social?.youtube || "",
29217
- onChange: (e) => handleSocialChange("youtube", e.target.value),
29218
- description: _t({
29219
- id: "sTNsJ9",
29220
- message: "Your YouTube channel ID or handle"
29221
- })
29222
- })
29223
- ]
29224
- })
29225
- ]
29226
- }), /* @__PURE__ */ jsx("div", {
29227
- className: "flex justify-end",
29228
- children: /* @__PURE__ */ jsx(Button, {
29229
- type: "submit",
29230
- disabled: saveMutation.isPending,
29231
- icon: /* @__PURE__ */ jsx(FloppyDisk, {}),
29232
- children: saveMutation.isPending ? _t({
29233
- id: "XvjC4F",
29234
- message: "Saving..."
29235
- }) : _t({
29236
- id: "KZrfYJ",
29237
- message: "Save Social Links"
29238
- })
29239
- })
29240
- })]
29241
- })
29242
- ]
29763
+ })]
29764
+ })]
29243
29765
  });
29244
29766
  }
29245
29767
 
@@ -34031,7 +34553,29 @@ function ComponentPropField({ def, value, onChange }) {
34031
34553
  //#region src/components/WordPressImport.tsx
34032
34554
  const TRAILING_SLASH_REGEX = /\/$/;
34033
34555
  const WHITESPACE_REGEX = /\s/g;
34556
+ /** Prefix of migration keys generated by the EmDash Exporter plugin wizard */
34557
+ const MIGRATION_KEY_PREFIX = "em1.";
34558
+ /**
34559
+ * Decode a migration key from the EmDash Exporter plugin
34560
+ * (`em1.` + base64url of `{ v, url, user, pass }`).
34561
+ * Returns null when the string isn't a valid key.
34562
+ */
34563
+ function decodeMigrationKey(key) {
34564
+ if (!key.startsWith(MIGRATION_KEY_PREFIX)) return null;
34565
+ try {
34566
+ const base64url = key.slice(4).replace(/-/g, "+").replace(/_/g, "/");
34567
+ const base64 = base64url.padEnd(base64url.length + (4 - base64url.length % 4) % 4, "=");
34568
+ const parsed = JSON.parse(atob(base64));
34569
+ if (typeof parsed === "object" && parsed !== null && "url" in parsed && typeof parsed.url === "string" && "user" in parsed && typeof parsed.user === "string" && "pass" in parsed && typeof parsed.pass === "string") return {
34570
+ url: parsed.url,
34571
+ user: parsed.user,
34572
+ pass: parsed.pass
34573
+ };
34574
+ } catch {}
34575
+ return null;
34576
+ }
34034
34577
  function WordPressImport() {
34578
+ const { _: _t } = useLingui();
34035
34579
  const [step, setStep] = React$1.useState("choose");
34036
34580
  const [urlInput, setUrlInput] = React$1.useState("");
34037
34581
  const [probeResult, setProbeResult] = React$1.useState(null);
@@ -34051,12 +34595,17 @@ function WordPressImport() {
34051
34595
  const [mediaError, setMediaError] = React$1.useState(null);
34052
34596
  const [skipMedia, setSkipMedia] = React$1.useState(false);
34053
34597
  const [mediaProgress, setMediaProgress] = React$1.useState(null);
34598
+ const [wpImportProgress, setWpImportProgress] = React$1.useState(null);
34054
34599
  const [importMenus, setImportMenus] = React$1.useState(true);
34055
34600
  const [importSiteTitle, setImportSiteTitle] = React$1.useState(true);
34056
34601
  const [importLogo, setImportLogo] = React$1.useState(true);
34057
- const [importSeo, setImportSeo] = React$1.useState(false);
34602
+ const [importSeo, setImportSeo] = React$1.useState(true);
34058
34603
  const [authorMappings, setAuthorMappings] = React$1.useState([]);
34059
34604
  const [emdashUsers, setEmDashUsers] = React$1.useState([]);
34605
+ const totalSelectedPosts = React$1.useMemo(() => {
34606
+ if (!analysis) return 0;
34607
+ return analysis.postTypes.reduce((sum, pt) => selections[pt.name]?.enabled ? sum + pt.count : sum, 0);
34608
+ }, [analysis, selections]);
34060
34609
  const initializeAuthorMappings = React$1.useCallback((importAnalysis, users) => {
34061
34610
  setAuthorMappings(importAnalysis.authors.map((author) => {
34062
34611
  const matchedUser = author.email ? users.find((u) => u.email.toLowerCase() === author.email?.toLowerCase()) : void 0;
@@ -34073,7 +34622,10 @@ function WordPressImport() {
34073
34622
  const params = new URLSearchParams(window.location.search);
34074
34623
  const authStatus = params.get("auth");
34075
34624
  if (params.get("error") === "auth_rejected") {
34076
- setImportError("WordPress authorization was rejected");
34625
+ setImportError(_t({
34626
+ id: "mMqZDg",
34627
+ message: "WordPress authorization was rejected"
34628
+ }));
34077
34629
  setStep("probe-result");
34078
34630
  window.history.replaceState({}, "", window.location.pathname);
34079
34631
  return;
@@ -34104,7 +34656,6 @@ function WordPressImport() {
34104
34656
  window.history.replaceState({}, "", window.location.pathname);
34105
34657
  }
34106
34658
  }, []);
34107
- const { _: _t } = useLingui();
34108
34659
  const probeMutation = useMutation({
34109
34660
  mutationFn: probeImportUrl,
34110
34661
  onSuccess: (data) => {
@@ -34175,7 +34726,7 @@ function WordPressImport() {
34175
34726
  }
34176
34727
  });
34177
34728
  const mediaMutation = useMutation({
34178
- mutationFn: (attachments) => importWxrMedia(attachments, (progress) => {
34729
+ mutationFn: (attachments) => importWxrMediaBatched(attachments, (progress) => {
34179
34730
  setMediaProgress(progress);
34180
34731
  }),
34181
34732
  onSuccess: (data) => {
@@ -34239,14 +34790,16 @@ function WordPressImport() {
34239
34790
  }
34240
34791
  });
34241
34792
  const wpPluginImportMutation = useMutation({
34242
- mutationFn: ({ url, token, config }) => executeWpPluginImport(url, token, config),
34793
+ mutationFn: ({ url, token, config }) => executeWpPluginImport(url, token, config, setWpImportProgress),
34243
34794
  onSuccess: (data) => {
34244
34795
  setImportError(null);
34796
+ setWpImportProgress(null);
34245
34797
  setResult(data);
34246
34798
  if (analysis && analysis.attachments.count > 0) setStep("media");
34247
34799
  else setStep("complete");
34248
34800
  },
34249
34801
  onError: (error) => {
34802
+ setWpImportProgress(null);
34250
34803
  setImportError(error instanceof Error ? error.message : _t({
34251
34804
  id: "C6AFAn",
34252
34805
  message: "Failed to import from WordPress"
@@ -34256,9 +34809,33 @@ function WordPressImport() {
34256
34809
  });
34257
34810
  const handleProbeUrl = (e) => {
34258
34811
  e.preventDefault();
34259
- if (!urlInput.trim()) return;
34812
+ const input = urlInput.trim();
34813
+ if (!input) return;
34814
+ if (handleMigrationKey(input)) return;
34260
34815
  setStep("probing");
34261
- probeMutation.mutate(urlInput.trim());
34816
+ probeMutation.mutate(input);
34817
+ };
34818
+ /**
34819
+ * Connect using a migration key from the EmDash Exporter plugin wizard.
34820
+ * Returns false when the string isn't a valid key.
34821
+ */
34822
+ const handleMigrationKey = (key) => {
34823
+ const decoded = decodeMigrationKey(key.trim());
34824
+ if (!decoded) return false;
34825
+ const token = btoa(`${decoded.user}:${decoded.pass.replace(WHITESPACE_REGEX, "")}`);
34826
+ setImportSource({
34827
+ type: "wordpress-plugin",
34828
+ url: decoded.url,
34829
+ token
34830
+ });
34831
+ setUrlInput(decoded.url);
34832
+ setImportError(null);
34833
+ setStep("analyzing-plugin");
34834
+ wpPluginAnalyzeMutation.mutate({
34835
+ url: decoded.url,
34836
+ token
34837
+ });
34838
+ return true;
34262
34839
  };
34263
34840
  const handleFileSelect = (e) => {
34264
34841
  const selectedFile = e.target.files?.[0];
@@ -34288,7 +34865,10 @@ function WordPressImport() {
34288
34865
  const handlePluginConnect = () => {
34289
34866
  if (!probeResult?.url) return;
34290
34867
  if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
34291
- setImportError("OAuth authorization requires HTTPS. Please use manual credentials.");
34868
+ setImportError(_t({
34869
+ id: "W5382e",
34870
+ message: "OAuth authorization requires HTTPS. Please use manual credentials."
34871
+ }));
34292
34872
  setStep("plugin-auth");
34293
34873
  return;
34294
34874
  }
@@ -34330,7 +34910,11 @@ function WordPressImport() {
34330
34910
  const config = {
34331
34911
  postTypeMappings: selections,
34332
34912
  skipExisting: true,
34333
- authorMappings: authorMappingsRecord
34913
+ authorMappings: authorMappingsRecord,
34914
+ importMenus,
34915
+ importSiteTitle,
34916
+ importLogo,
34917
+ importSeo
34334
34918
  };
34335
34919
  if (importSource.type === "wxr") importMutation.mutate({
34336
34920
  file: importSource.file,
@@ -34490,7 +35074,8 @@ function WordPressImport() {
34490
35074
  onUrlChange: setUrlInput,
34491
35075
  onProbeUrl: handleProbeUrl,
34492
35076
  onFileSelect: handleFileSelect,
34493
- onDrop: handleDrop
35077
+ onDrop: handleDrop,
35078
+ onMigrationKey: handleMigrationKey
34494
35079
  }),
34495
35080
  step === "probing" && /* @__PURE__ */ jsxs("div", {
34496
35081
  className: "rounded-lg border bg-kumo-base p-12 text-center",
@@ -34607,23 +35192,60 @@ function WordPressImport() {
34607
35192
  }),
34608
35193
  step === "importing" && /* @__PURE__ */ jsxs("div", {
34609
35194
  className: "rounded-lg border bg-kumo-base p-12 text-center",
34610
- children: [
34611
- /* @__PURE__ */ jsx(Loader, {}),
34612
- /* @__PURE__ */ jsx("p", {
35195
+ children: [/* @__PURE__ */ jsx(Loader, {}), wpImportProgress ? /* @__PURE__ */ jsxs(Fragment, { children: [
35196
+ /* @__PURE__ */ jsxs("p", {
34613
35197
  className: "mt-4 text-kumo-subtle",
34614
- children: _t({
34615
- id: "4ID0OO",
34616
- message: "Importing content..."
35198
+ children: [
35199
+ wpImportProgress.phase === "content" && (totalSelectedPosts > 0 ? _t({
35200
+ id: "2lWRW0",
35201
+ message: "Importing content... {0} of {totalSelectedPosts}",
35202
+ values: {
35203
+ 0: wpImportProgress.processed,
35204
+ totalSelectedPosts
35205
+ }
35206
+ }) : _t({
35207
+ id: "ycwotv",
35208
+ message: "Importing content... {0}",
35209
+ values: { 0: wpImportProgress.processed }
35210
+ })),
35211
+ wpImportProgress.phase === "comments" && _t({
35212
+ id: "8wksCM",
35213
+ message: "Importing comments... {0}",
35214
+ values: { 0: wpImportProgress.comments }
35215
+ }),
35216
+ wpImportProgress.phase === "finalize" && _t({
35217
+ id: "/LAVtu",
35218
+ message: "Importing menus and site settings..."
35219
+ })
35220
+ ]
35221
+ }),
35222
+ totalSelectedPosts > 0 && wpImportProgress.phase === "content" && /* @__PURE__ */ jsx("div", {
35223
+ className: "mx-auto mt-4 h-2 w-64 overflow-hidden rounded-full bg-kumo-fill",
35224
+ children: /* @__PURE__ */ jsx("div", {
35225
+ className: "h-full rounded-full bg-kumo-brand transition-all",
35226
+ style: { width: `${Math.min(100, Math.round(wpImportProgress.processed / totalSelectedPosts * 100))}%` }
34617
35227
  })
34618
35228
  }),
34619
35229
  /* @__PURE__ */ jsx("p", {
34620
- className: "text-sm text-kumo-subtle",
35230
+ className: "mt-2 text-sm text-kumo-subtle",
34621
35231
  children: _t({
34622
- id: "mBSl2N",
34623
- message: "This may take a while for large exports."
35232
+ id: "/NInpe",
35233
+ message: "Keep this tab open. The import runs in small steps and can be safely re-run if interrupted."
34624
35234
  })
34625
35235
  })
34626
- ]
35236
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("p", {
35237
+ className: "mt-4 text-kumo-subtle",
35238
+ children: _t({
35239
+ id: "4ID0OO",
35240
+ message: "Importing content..."
35241
+ })
35242
+ }), /* @__PURE__ */ jsx("p", {
35243
+ className: "text-sm text-kumo-subtle",
35244
+ children: _t({
35245
+ id: "mBSl2N",
35246
+ message: "This may take a while for large exports."
35247
+ })
35248
+ })] })]
34627
35249
  }),
34628
35250
  step === "media" && analysis && /* @__PURE__ */ jsx(MediaStep, {
34629
35251
  attachments: analysis.attachments,
@@ -34668,18 +35290,104 @@ function StepIndicator({ number, label, active, complete }) {
34668
35290
  })]
34669
35291
  });
34670
35292
  }
34671
- function ChooseStep({ urlInput, onUrlChange, onProbeUrl, onFileSelect, onDrop }) {
35293
+ function ChooseStep({ urlInput, onUrlChange, onProbeUrl, onFileSelect, onDrop, onMigrationKey }) {
34672
35294
  const { _: _t2 } = useLingui();
35295
+ const [keyInput, setKeyInput] = React$1.useState("");
35296
+ const [keyError, setKeyError] = React$1.useState(false);
35297
+ const handleKeySubmit = (e) => {
35298
+ e.preventDefault();
35299
+ if (!keyInput.trim()) return;
35300
+ setKeyError(!onMigrationKey(keyInput));
35301
+ };
34673
35302
  return /* @__PURE__ */ jsxs("div", {
34674
35303
  className: "space-y-6",
34675
35304
  children: [
35305
+ /* @__PURE__ */ jsx("div", {
35306
+ className: "rounded-lg border border-kumo-brand/40 bg-kumo-base p-6",
35307
+ children: /* @__PURE__ */ jsxs("div", {
35308
+ className: "flex items-start gap-4",
35309
+ children: [/* @__PURE__ */ jsx("div", {
35310
+ className: "p-3 rounded-full bg-kumo-brand/10",
35311
+ children: /* @__PURE__ */ jsx(Sparkle, { className: "h-6 w-6 text-kumo-brand" })
35312
+ }), /* @__PURE__ */ jsxs("div", {
35313
+ className: "flex-1",
35314
+ children: [
35315
+ /* @__PURE__ */ jsx("h3", {
35316
+ className: "text-lg font-medium",
35317
+ children: _t2({
35318
+ id: "iBKrt/",
35319
+ message: "Paste your migration key"
35320
+ })
35321
+ }),
35322
+ /* @__PURE__ */ jsx("p", {
35323
+ className: "text-kumo-subtle mt-1",
35324
+ children: _t2({
35325
+ id: "72d1Kl",
35326
+ message: "Install the EmDash Exporter plugin on your WordPress site and generate a key under Tools → EmDash Migration."
35327
+ })
35328
+ }),
35329
+ /* @__PURE__ */ jsxs("form", {
35330
+ onSubmit: handleKeySubmit,
35331
+ className: "mt-4 flex gap-2",
35332
+ children: [/* @__PURE__ */ jsx(Input, {
35333
+ type: "text",
35334
+ placeholder: _t2({
35335
+ id: "cLrEuE",
35336
+ message: "em1.…"
35337
+ }),
35338
+ value: keyInput,
35339
+ onChange: (e) => {
35340
+ setKeyInput(e.target.value);
35341
+ setKeyError(false);
35342
+ },
35343
+ className: "flex-1 font-mono",
35344
+ "aria-label": _t2({
35345
+ id: "9zFZIo",
35346
+ message: "Migration key"
35347
+ })
35348
+ }), /* @__PURE__ */ jsx(Button, {
35349
+ type: "submit",
35350
+ disabled: !keyInput.trim(),
35351
+ children: _t2({
35352
+ id: "iSLIjg",
35353
+ message: "Connect"
35354
+ })
35355
+ })]
35356
+ }),
35357
+ keyError && /* @__PURE__ */ jsx("p", {
35358
+ className: "mt-2 text-sm text-kumo-danger",
35359
+ children: _t2({
35360
+ id: "lcAr+f",
35361
+ message: "This doesn't look like a valid migration key. Generate a new one in the plugin and paste the full string starting with \"em1.\"."
35362
+ })
35363
+ })
35364
+ ]
35365
+ })]
35366
+ })
35367
+ }),
35368
+ /* @__PURE__ */ jsxs("div", {
35369
+ className: "relative",
35370
+ children: [/* @__PURE__ */ jsx("div", {
35371
+ className: "absolute inset-0 flex items-center",
35372
+ children: /* @__PURE__ */ jsx("div", { className: "w-full border-t" })
35373
+ }), /* @__PURE__ */ jsx("div", {
35374
+ className: "relative flex justify-center text-xs uppercase",
35375
+ children: /* @__PURE__ */ jsx("span", {
35376
+ className: "bg-kumo-base px-2 text-kumo-subtle",
35377
+ children: _t2({
35378
+ id: "1eiyMI",
35379
+ message: "or connect by URL"
35380
+ })
35381
+ })
35382
+ })]
35383
+ }),
34676
35384
  /* @__PURE__ */ jsx("div", {
34677
35385
  className: "rounded-lg border bg-kumo-base p-6",
34678
35386
  children: /* @__PURE__ */ jsxs("div", {
34679
35387
  className: "flex items-start gap-4",
34680
35388
  children: [/* @__PURE__ */ jsx("div", {
34681
- className: "p-3 rounded-full bg-blue-100 dark:bg-blue-900/30",
34682
- children: /* @__PURE__ */ jsx(Globe, { className: "h-6 w-6 text-blue-600 dark:text-blue-400" })
35389
+ className: "p-3 rounded-full bg-kumo-brand/10",
35390
+ children: /* @__PURE__ */ jsx(Globe, { className: "h-6 w-6 text-kumo-brand" })
34683
35391
  }), /* @__PURE__ */ jsxs("div", {
34684
35392
  className: "flex-1",
34685
35393
  children: [
@@ -34702,7 +35410,10 @@ function ChooseStep({ urlInput, onUrlChange, onProbeUrl, onFileSelect, onDrop })
34702
35410
  className: "mt-4 flex gap-2",
34703
35411
  children: [/* @__PURE__ */ jsx(Input, {
34704
35412
  type: "text",
34705
- placeholder: "https://yoursite.com",
35413
+ placeholder: _t2({
35414
+ id: "oA88Qj",
35415
+ message: "https://yoursite.com"
35416
+ }),
34706
35417
  value: urlInput,
34707
35418
  onChange: (e) => onUrlChange(e.target.value),
34708
35419
  className: "flex-1"
@@ -34780,61 +35491,106 @@ function ChooseStep({ urlInput, onUrlChange, onProbeUrl, onFileSelect, onDrop })
34780
35491
  }
34781
35492
  const FEATURE_COMPARISON = [
34782
35493
  {
34783
- feature: "Posts & Pages",
35494
+ feature: {
35495
+ id: "OngPWf",
35496
+ message: "Posts & Pages"
35497
+ },
34784
35498
  wxr: "full",
34785
35499
  plugin: "full"
34786
35500
  },
34787
35501
  {
34788
- feature: "Media",
35502
+ feature: {
35503
+ id: "xYilR2",
35504
+ message: "Media"
35505
+ },
34789
35506
  wxr: "full",
34790
35507
  plugin: "full"
34791
35508
  },
34792
35509
  {
34793
- feature: "Categories & Tags",
35510
+ feature: {
35511
+ id: "PBAVHO",
35512
+ message: "Categories & Tags"
35513
+ },
34794
35514
  wxr: "full",
34795
35515
  plugin: "full"
34796
35516
  },
34797
35517
  {
34798
- feature: "Custom Taxonomies",
35518
+ feature: {
35519
+ id: "oxe9Dw",
35520
+ message: "Custom Taxonomies"
35521
+ },
34799
35522
  wxr: "full",
34800
35523
  plugin: "full"
34801
35524
  },
34802
35525
  {
34803
- feature: "Featured Images",
35526
+ feature: {
35527
+ id: "0ruX1k",
35528
+ message: "Featured Images"
35529
+ },
34804
35530
  wxr: "full",
34805
35531
  plugin: "full"
34806
35532
  },
34807
35533
  {
34808
- feature: "Menus",
35534
+ feature: {
35535
+ id: "NXjnVQ",
35536
+ message: "Menus"
35537
+ },
34809
35538
  wxr: "full",
34810
35539
  plugin: "full"
34811
35540
  },
34812
35541
  {
34813
- feature: "Site Settings",
35542
+ feature: {
35543
+ id: "aaU5Fl",
35544
+ message: "Site Settings"
35545
+ },
34814
35546
  wxr: "partial",
34815
- wxrNote: "Partial",
35547
+ wxrNote: {
35548
+ id: "3nRszo",
35549
+ message: "Partial"
35550
+ },
34816
35551
  plugin: "full",
34817
- pluginNote: "Full"
35552
+ pluginNote: {
35553
+ id: "g2Tj8R",
35554
+ message: "Full"
35555
+ }
34818
35556
  },
34819
35557
  {
34820
- feature: "Widgets",
35558
+ feature: {
35559
+ id: "tL6W2K",
35560
+ message: "Widgets"
35561
+ },
34821
35562
  wxr: "none",
34822
35563
  plugin: "full"
34823
35564
  },
34824
35565
  {
34825
- feature: "ACF Fields",
35566
+ feature: {
35567
+ id: "3hECLi",
35568
+ message: "ACF Fields"
35569
+ },
34826
35570
  wxr: "none",
34827
35571
  plugin: "full"
34828
35572
  },
34829
35573
  {
34830
- feature: "Yoast/RankMath",
35574
+ feature: {
35575
+ id: "etVTyW",
35576
+ message: "Yoast/RankMath"
35577
+ },
34831
35578
  wxr: "partial",
34832
- wxrNote: "Raw meta",
35579
+ wxrNote: {
35580
+ id: "DRM0sZ",
35581
+ message: "Raw meta"
35582
+ },
34833
35583
  plugin: "full",
34834
- pluginNote: "Structured"
35584
+ pluginNote: {
35585
+ id: "yn3Cdh",
35586
+ message: "Structured"
35587
+ }
34835
35588
  },
34836
35589
  {
34837
- feature: "Drafts & Private",
35590
+ feature: {
35591
+ id: "4T8ArT",
35592
+ message: "Drafts & Private"
35593
+ },
34838
35594
  wxr: "full",
34839
35595
  plugin: "full"
34840
35596
  }
@@ -34888,32 +35644,32 @@ function FeatureComparison() {
34888
35644
  children: FEATURE_COMPARISON.map((item) => /* @__PURE__ */ jsxs("tr", { children: [
34889
35645
  /* @__PURE__ */ jsx("td", {
34890
35646
  className: "p-3 text-kumo-subtle",
34891
- children: item.feature
35647
+ children: _t3(item.feature)
34892
35648
  }),
34893
35649
  /* @__PURE__ */ jsx("td", {
34894
35650
  className: "p-3 text-center",
34895
35651
  children: /* @__PURE__ */ jsx(FeatureStatus, {
34896
35652
  status: item.wxr,
34897
- note: item.wxrNote
35653
+ note: item.wxrNote && _t3(item.wxrNote)
34898
35654
  })
34899
35655
  }),
34900
35656
  /* @__PURE__ */ jsx("td", {
34901
35657
  className: "p-3 text-center",
34902
35658
  children: /* @__PURE__ */ jsx(FeatureStatus, {
34903
35659
  status: item.plugin,
34904
- note: item.pluginNote
35660
+ note: item.pluginNote && _t3(item.pluginNote)
34905
35661
  })
34906
35662
  })
34907
- ] }, item.feature))
35663
+ ] }, item.feature.id))
34908
35664
  })]
34909
35665
  })
34910
35666
  }),
34911
35667
  /* @__PURE__ */ jsx("div", {
34912
- className: "border-t p-3 bg-blue-50 dark:bg-blue-900/20",
35668
+ className: "border-t p-3 bg-kumo-info-tint",
34913
35669
  children: /* @__PURE__ */ jsxs("div", {
34914
35670
  className: "flex items-start gap-2 text-sm",
34915
- children: [/* @__PURE__ */ jsx(Sparkle, { className: "h-4 w-4 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" }), /* @__PURE__ */ jsxs("p", {
34916
- className: "text-blue-800 dark:text-blue-200",
35671
+ children: [/* @__PURE__ */ jsx(Sparkle, { className: "h-4 w-4 text-kumo-brand flex-shrink-0 mt-0.5" }), /* @__PURE__ */ jsxs("p", {
35672
+ className: "text-kumo-default",
34917
35673
  children: [
34918
35674
  _t3({
34919
35675
  id: "m5nI37",
@@ -34941,11 +35697,11 @@ function FeatureComparison() {
34941
35697
  }
34942
35698
  function FeatureStatus({ status, note }) {
34943
35699
  if (status === "full") return /* @__PURE__ */ jsx("span", {
34944
- className: "inline-flex items-center gap-1 text-green-600 dark:text-green-400",
35700
+ className: "inline-flex items-center gap-1 text-kumo-success",
34945
35701
  children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })
34946
35702
  });
34947
35703
  if (status === "partial") return /* @__PURE__ */ jsxs("span", {
34948
- className: "inline-flex items-center gap-1 text-yellow-600 dark:text-yellow-400",
35704
+ className: "inline-flex items-center gap-1 text-kumo-warning",
34949
35705
  children: [/* @__PURE__ */ jsx(Warning, { className: "h-3.5 w-3.5" }), note && /* @__PURE__ */ jsx("span", {
34950
35706
  className: "text-xs",
34951
35707
  children: note
@@ -35047,10 +35803,10 @@ function ProbeResultStep({ result, onUploadFile, onPluginConnect, onPluginManual
35047
35803
  className: "space-y-6",
35048
35804
  children: [
35049
35805
  /* @__PURE__ */ jsx("div", {
35050
- className: "rounded-lg border-s-4 border-s-green-500 border border-kumo-line bg-kumo-base p-6",
35806
+ className: "rounded-lg border-s-4 border-s-kumo-success border border-kumo-line bg-kumo-base p-6",
35051
35807
  children: /* @__PURE__ */ jsxs("div", {
35052
35808
  className: "flex items-start gap-4",
35053
- children: [/* @__PURE__ */ jsx(Check, { className: "h-6 w-6 text-green-500 flex-shrink-0" }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
35809
+ children: [/* @__PURE__ */ jsx(Check, { className: "h-6 w-6 text-kumo-success flex-shrink-0" }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
35054
35810
  className: "font-medium",
35055
35811
  children: _t4({
35056
35812
  id: "J8mhUo",
@@ -35115,14 +35871,14 @@ function ProbeResultStep({ result, onUploadFile, onPluginConnect, onPluginManual
35115
35871
  }),
35116
35872
  !hasPlugin && /* @__PURE__ */ jsx(FeatureComparison, {}),
35117
35873
  hasPlugin && /* @__PURE__ */ jsx("div", {
35118
- className: "rounded-lg border-s-4 border-s-green-500 border border-kumo-line bg-kumo-base p-6",
35874
+ className: "rounded-lg border-s-4 border-s-kumo-success border border-kumo-line bg-kumo-base p-6",
35119
35875
  children: /* @__PURE__ */ jsxs("div", {
35120
35876
  className: "flex items-start gap-4",
35121
35877
  children: [/* @__PURE__ */ jsx("div", {
35122
- className: "p-2 rounded-full bg-green-100 dark:bg-green-900/50",
35878
+ className: "p-2 rounded-full bg-kumo-success-tint",
35123
35879
  children: /* @__PURE__ */ jsxs("svg", {
35124
35880
  viewBox: "0 0 24 24",
35125
- className: "h-5 w-5 text-green-600 dark:text-green-400",
35881
+ className: "h-5 w-5 text-kumo-success",
35126
35882
  fill: "none",
35127
35883
  stroke: "currentColor",
35128
35884
  strokeWidth: "2",
@@ -35244,10 +36000,10 @@ function PluginAuthStep({ siteTitle, siteUrl, username, password, onUsernameChan
35244
36000
  /* @__PURE__ */ jsxs("div", {
35245
36001
  className: "flex items-start gap-4",
35246
36002
  children: [/* @__PURE__ */ jsx("div", {
35247
- className: "p-3 rounded-full bg-green-100 dark:bg-green-900/30",
36003
+ className: "p-3 rounded-full bg-kumo-success-tint",
35248
36004
  children: /* @__PURE__ */ jsxs("svg", {
35249
36005
  viewBox: "0 0 24 24",
35250
- className: "h-6 w-6 text-green-600 dark:text-green-400",
36006
+ className: "h-6 w-6 text-kumo-success",
35251
36007
  fill: "none",
35252
36008
  stroke: "currentColor",
35253
36009
  strokeWidth: "2",
@@ -35306,7 +36062,10 @@ function PluginAuthStep({ siteTitle, siteUrl, username, password, onUsernameChan
35306
36062
  type: "text",
35307
36063
  value: username,
35308
36064
  onChange: (e) => onUsernameChange(e.target.value),
35309
- placeholder: "admin",
36065
+ placeholder: _t5({
36066
+ id: "cyLJM2",
36067
+ message: "admin"
36068
+ }),
35310
36069
  autoComplete: "username"
35311
36070
  })] }),
35312
36071
  /* @__PURE__ */ jsxs("div", { children: [
@@ -35323,7 +36082,10 @@ function PluginAuthStep({ siteTitle, siteUrl, username, password, onUsernameChan
35323
36082
  type: "password",
35324
36083
  value: password,
35325
36084
  onChange: (e) => onPasswordChange(e.target.value),
35326
- placeholder: "xxxx xxxx xxxx xxxx xxxx xxxx",
36085
+ placeholder: _t5({
36086
+ id: "NRI3Xq",
36087
+ message: "xxxx xxxx xxxx xxxx xxxx xxxx"
36088
+ }),
35327
36089
  autoComplete: "current-password"
35328
36090
  }),
35329
36091
  /* @__PURE__ */ jsx("p", {
@@ -35357,10 +36119,10 @@ function PluginAuthStep({ siteTitle, siteUrl, username, password, onUsernameChan
35357
36119
  })
35358
36120
  ]
35359
36121
  }), /* @__PURE__ */ jsx("div", {
35360
- className: "rounded-lg border-s-4 border-s-blue-500 border border-kumo-line bg-kumo-base p-4",
36122
+ className: "rounded-lg border-s-4 border-s-kumo-brand border border-kumo-line bg-kumo-base p-4",
35361
36123
  children: /* @__PURE__ */ jsxs("div", {
35362
36124
  className: "flex gap-3",
35363
- children: [/* @__PURE__ */ jsx(ArrowSquareOut, { className: "h-5 w-5 text-blue-500 flex-shrink-0" }), /* @__PURE__ */ jsxs("div", {
36125
+ children: [/* @__PURE__ */ jsx(ArrowSquareOut, { className: "h-5 w-5 text-kumo-brand flex-shrink-0" }), /* @__PURE__ */ jsxs("div", {
35364
36126
  className: "text-sm",
35365
36127
  children: [
35366
36128
  /* @__PURE__ */ jsx("p", {
@@ -35600,7 +36362,7 @@ function ReviewStep({ analysis, selections, expandedTypes, prepareError, importE
35600
36362
  }), /* @__PURE__ */ jsxs("div", {
35601
36363
  className: "divide-y",
35602
36364
  children: [
35603
- /* @__PURE__ */ jsx("div", {
36365
+ isPluginSource && /* @__PURE__ */ jsx("div", {
35604
36366
  className: "p-4",
35605
36367
  children: /* @__PURE__ */ jsx(Switch, {
35606
36368
  checked: importMenus,
@@ -35750,10 +36512,10 @@ function ReviewStep({ analysis, selections, expandedTypes, prepareError, importE
35750
36512
  })
35751
36513
  }),
35752
36514
  selectedCount > 0 && /* @__PURE__ */ jsx("div", {
35753
- className: "rounded-lg border-s-4 border-s-blue-500 border border-kumo-line bg-kumo-base p-4",
36515
+ className: "rounded-lg border-s-4 border-s-kumo-brand border border-kumo-line bg-kumo-base p-4",
35754
36516
  children: /* @__PURE__ */ jsxs("div", {
35755
36517
  className: "flex gap-3",
35756
- children: [/* @__PURE__ */ jsx(Database, { className: "h-5 w-5 text-blue-500 flex-shrink-0" }), /* @__PURE__ */ jsxs("div", {
36518
+ children: [/* @__PURE__ */ jsx(Database, { className: "h-5 w-5 text-kumo-brand flex-shrink-0" }), /* @__PURE__ */ jsxs("div", {
35757
36519
  className: "space-y-2",
35758
36520
  children: [/* @__PURE__ */ jsx("p", {
35759
36521
  className: "font-medium",
@@ -35894,7 +36656,7 @@ function PostTypeRow({ postType, selection, expanded, onToggleExpand, onToggleEn
35894
36656
  className: "mt-4 ms-8 p-3 rounded-lg bg-kumo-tint/50 text-sm",
35895
36657
  children: [
35896
36658
  !canImport && schemaStatus.reason && /* @__PURE__ */ jsxs("div", {
35897
- className: "mb-3 p-2 rounded bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-300",
36659
+ className: "mb-3 p-2 rounded bg-kumo-danger-tint text-kumo-danger",
35898
36660
  children: [/* @__PURE__ */ jsx(WarningCircle, { className: "inline h-4 w-4 me-1" }), schemaStatus.reason]
35899
36661
  }),
35900
36662
  /* @__PURE__ */ jsx("p", {
@@ -35922,7 +36684,7 @@ function PostTypeRow({ postType, selection, expanded, onToggleExpand, onToggleEn
35922
36684
  ]
35923
36685
  })
35924
36686
  ] }), status?.status === "compatible" ? /* @__PURE__ */ jsxs("span", {
35925
- className: "text-green-600 dark:text-green-400",
36687
+ className: "text-kumo-success",
35926
36688
  children: [
35927
36689
  /* @__PURE__ */ jsx(Check, { className: "inline h-3 w-3" }),
35928
36690
  " ",
@@ -35932,7 +36694,7 @@ function PostTypeRow({ postType, selection, expanded, onToggleExpand, onToggleEn
35932
36694
  })
35933
36695
  ]
35934
36696
  }) : status?.status === "missing" ? /* @__PURE__ */ jsxs("span", {
35935
- className: "text-blue-600 dark:text-blue-400",
36697
+ className: "text-kumo-brand",
35936
36698
  children: [
35937
36699
  /* @__PURE__ */ jsx(Plus, { className: "inline h-3 w-3" }),
35938
36700
  " ",
@@ -35942,7 +36704,7 @@ function PostTypeRow({ postType, selection, expanded, onToggleExpand, onToggleEn
35942
36704
  })
35943
36705
  ]
35944
36706
  }) : status?.status === "type_mismatch" ? /* @__PURE__ */ jsxs("span", {
35945
- className: "text-red-600 dark:text-red-400",
36707
+ className: "text-kumo-danger",
35946
36708
  children: [
35947
36709
  /* @__PURE__ */ jsx(X, { className: "inline h-3 w-3" }),
35948
36710
  " ",
@@ -35975,8 +36737,8 @@ function MediaStep({ attachments, error, onImport, onSkip }) {
35975
36737
  /* @__PURE__ */ jsxs("div", {
35976
36738
  className: "flex items-start gap-4",
35977
36739
  children: [/* @__PURE__ */ jsx("div", {
35978
- className: "p-3 rounded-full bg-blue-100 dark:bg-blue-900/30",
35979
- children: /* @__PURE__ */ jsx(Image$1, { className: "h-6 w-6 text-blue-600 dark:text-blue-400" })
36740
+ className: "p-3 rounded-full bg-kumo-brand/10",
36741
+ children: /* @__PURE__ */ jsx(Image$1, { className: "h-6 w-6 text-kumo-brand" })
35980
36742
  }), /* @__PURE__ */ jsxs("div", {
35981
36743
  className: "flex-1",
35982
36744
  children: [/* @__PURE__ */ jsx("h3", {
@@ -36023,10 +36785,10 @@ function MediaStep({ attachments, error, onImport, onSkip }) {
36023
36785
  })
36024
36786
  }),
36025
36787
  /* @__PURE__ */ jsx("div", {
36026
- className: "mt-4 p-4 rounded-lg border-s-4 border-s-blue-500 border border-kumo-line bg-kumo-base",
36788
+ className: "mt-4 p-4 rounded-lg border-s-4 border-s-kumo-brand border border-kumo-line bg-kumo-base",
36027
36789
  children: /* @__PURE__ */ jsxs("div", {
36028
36790
  className: "flex gap-3",
36029
- children: [/* @__PURE__ */ jsx(DownloadSimple, { className: "h-5 w-5 text-blue-500 flex-shrink-0" }), /* @__PURE__ */ jsxs("div", {
36791
+ children: [/* @__PURE__ */ jsx(DownloadSimple, { className: "h-5 w-5 text-kumo-brand flex-shrink-0" }), /* @__PURE__ */ jsxs("div", {
36030
36792
  className: "text-sm",
36031
36793
  children: [/* @__PURE__ */ jsx("p", {
36032
36794
  className: "font-medium",
@@ -36150,7 +36912,7 @@ function MediaProgressStep({ progress, total }) {
36150
36912
  values: { 0: progress.current }
36151
36913
  })
36152
36914
  }), /* @__PURE__ */ jsx("span", {
36153
- className: cn("px-2 py-0.5 rounded text-xs", progress.status === "downloading" && "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400", progress.status === "uploading" && "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400", progress.status === "done" && "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400", progress.status === "skipped" && "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400", progress.status === "failed" && "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"),
36915
+ className: cn("px-2 py-0.5 rounded text-xs", progress.status === "downloading" && "bg-kumo-info-tint text-kumo-default", progress.status === "uploading" && "bg-kumo-info-tint text-kumo-default", progress.status === "done" && "bg-kumo-success-tint text-kumo-success", progress.status === "skipped" && "bg-kumo-warning-tint text-kumo-warning", progress.status === "failed" && "bg-kumo-danger-tint text-kumo-danger"),
36154
36916
  children: statusLabels[progress.status]
36155
36917
  })]
36156
36918
  }), progress.error && /* @__PURE__ */ jsx("p", {
@@ -36192,6 +36954,30 @@ function CompleteStep({ result, prepareResult, mediaResult, rewriteResult, skipp
36192
36954
  message: "{0, plural, one {# media file imported} other {# media files imported}}",
36193
36955
  values: { 0: mediaResult.imported.length }
36194
36956
  }));
36957
+ if (result.taxonomiesCreated && result.taxonomiesCreated.length > 0) parts.push(i18n._({
36958
+ id: "nku2Y8",
36959
+ message: "{0, plural, one {# taxonomy created} other {# taxonomies created}}",
36960
+ values: { 0: result.taxonomiesCreated.length }
36961
+ }));
36962
+ if (result.taxonomyAssignments && result.taxonomyAssignments > 0) parts.push(i18n._({
36963
+ id: "3iIiv1",
36964
+ message: "{0, plural, one {# taxonomy assignment} other {# taxonomy assignments}}",
36965
+ values: { 0: result.taxonomyAssignments }
36966
+ }));
36967
+ if (result.menus && result.menus.created > 0) parts.push(i18n._({
36968
+ id: "PIlaPM",
36969
+ message: "{0, plural, one {# menu imported} other {# menus imported}}",
36970
+ values: { 0: result.menus.created }
36971
+ }));
36972
+ if (result.comments && result.comments.imported > 0) parts.push(i18n._({
36973
+ id: "ZirqdT",
36974
+ message: "{0, plural, one {# comment imported} other {# comments imported}}",
36975
+ values: { 0: result.comments.imported }
36976
+ }));
36977
+ if (result.siteSettings && result.siteSettings.length > 0) parts.push(_t1({
36978
+ id: "5R/Sn+",
36979
+ message: "site identity applied (title, tagline, logo)"
36980
+ }));
36195
36981
  if (hasContentErrors) parts.push(i18n._({
36196
36982
  id: "OBOFBb",
36197
36983
  message: "{0, plural, one {# content error} other {# content errors}}",
@@ -36208,9 +36994,9 @@ function CompleteStep({ result, prepareResult, mediaResult, rewriteResult, skipp
36208
36994
  className: "space-y-6",
36209
36995
  children: [
36210
36996
  /* @__PURE__ */ jsxs("div", {
36211
- className: cn("rounded-lg border p-6 text-center", overallSuccess ? "border-green-200 bg-green-50 dark:border-green-900/50 dark:bg-green-900/20" : "border-yellow-200 bg-yellow-50 dark:border-yellow-900/50 dark:bg-yellow-900/20"),
36997
+ className: cn("rounded-lg border p-6 text-center", overallSuccess ? "border-kumo-success/50 bg-kumo-success-tint" : "border-kumo-warning/50 bg-kumo-warning-tint"),
36212
36998
  children: [
36213
- overallSuccess ? /* @__PURE__ */ jsx(Check, { className: "mx-auto h-12 w-12 text-green-600 dark:text-green-400" }) : /* @__PURE__ */ jsx(Warning, { className: "mx-auto h-12 w-12 text-yellow-600 dark:text-yellow-400" }),
36999
+ overallSuccess ? /* @__PURE__ */ jsx(Check, { className: "mx-auto h-12 w-12 text-kumo-success" }) : /* @__PURE__ */ jsx(Warning, { className: "mx-auto h-12 w-12 text-kumo-warning" }),
36214
37000
  /* @__PURE__ */ jsx("h3", {
36215
37001
  className: "mt-4 text-lg font-medium",
36216
37002
  children: overallSuccess ? wasMediaOnlyImport ? _t1({
@@ -36424,8 +37210,8 @@ function AuthorMappingStep({ authorMappings, emdashUsers, onMappingChange, onCon
36424
37210
  children: /* @__PURE__ */ jsxs("div", {
36425
37211
  className: "flex items-start gap-4",
36426
37212
  children: [/* @__PURE__ */ jsx("div", {
36427
- className: "p-3 rounded-full bg-blue-100 dark:bg-blue-900/30",
36428
- children: /* @__PURE__ */ jsx(User, { className: "h-6 w-6 text-blue-600 dark:text-blue-400" })
37213
+ className: "p-3 rounded-full bg-kumo-brand/10",
37214
+ children: /* @__PURE__ */ jsx(User, { className: "h-6 w-6 text-kumo-brand" })
36429
37215
  }), /* @__PURE__ */ jsxs("div", { children: [
36430
37216
  /* @__PURE__ */ jsx("h3", {
36431
37217
  className: "text-lg font-medium",
@@ -36442,7 +37228,7 @@ function AuthorMappingStep({ authorMappings, emdashUsers, onMappingChange, onCon
36442
37228
  })
36443
37229
  }),
36444
37230
  matchedCount > 0 && /* @__PURE__ */ jsxs("p", {
36445
- className: "text-sm text-green-600 dark:text-green-400 mt-2",
37231
+ className: "text-sm text-kumo-success mt-2",
36446
37232
  children: [/* @__PURE__ */ jsx(Check, { className: "inline h-4 w-4 me-1" }), _t10({
36447
37233
  id: "gIZkFQ",
36448
37234
  message: "{matchedCount} of {totalCount} authors matched by email",
@@ -36526,10 +37312,10 @@ function AuthorMappingStep({ authorMappings, emdashUsers, onMappingChange, onCon
36526
37312
  })]
36527
37313
  }),
36528
37314
  emdashUsers.length === 0 && /* @__PURE__ */ jsx("div", {
36529
- className: "rounded-lg border-s-4 border-s-yellow-500 border border-kumo-line bg-kumo-base p-4",
37315
+ className: "rounded-lg border-s-4 border-s-kumo-warning border border-kumo-line bg-kumo-base p-4",
36530
37316
  children: /* @__PURE__ */ jsxs("div", {
36531
37317
  className: "flex gap-3",
36532
- children: [/* @__PURE__ */ jsx(Warning, { className: "h-5 w-5 text-yellow-500 flex-shrink-0" }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
37318
+ children: [/* @__PURE__ */ jsx(Warning, { className: "h-5 w-5 text-kumo-warning flex-shrink-0" }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
36533
37319
  className: "font-medium",
36534
37320
  children: _t10({
36535
37321
  id: "1WOyEN",
@@ -40292,12 +41078,6 @@ function MediaPage() {
40292
41078
  queryClient.invalidateQueries({ queryKey: ["media"] });
40293
41079
  }
40294
41080
  });
40295
- const deleteMutation = useMutation({
40296
- mutationFn: (id) => deleteMedia(id),
40297
- onSuccess: () => {
40298
- queryClient.invalidateQueries({ queryKey: ["media"] });
40299
- }
40300
- });
40301
41081
  const items = React$1.useMemo(() => {
40302
41082
  return data?.pages.flatMap((page) => page.items) || [];
40303
41083
  }, [data]);
@@ -40308,7 +41088,6 @@ function MediaPage() {
40308
41088
  hasMore: !!hasNextPage,
40309
41089
  onLoadMore: () => void fetchNextPage(),
40310
41090
  onUpload: (file) => uploadMutation.mutate(file),
40311
- onDelete: (id) => deleteMutation.mutate(id),
40312
41091
  onLocalSearchChange: setSearch,
40313
41092
  onLocalMimeFilterChange: setMimeFilter
40314
41093
  });
@@ -41107,5 +41886,5 @@ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, authProviders = EMPTY_AUTH_PRO
41107
41886
  }
41108
41887
 
41109
41888
  //#endregion
41110
- 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, 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, 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, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateBylineField, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useAuthProviderList, useAuthProviders, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, validateInviteToken, verifySignupToken };
41889
+ 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, 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, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateBylineField, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useAuthProviderList, useAuthProviders, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, validateInviteToken, verifySignupToken };
41111
41890
  //# sourceMappingURL=index.js.map