@emdash-cms/admin 0.18.0 → 0.20.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 (107) hide show
  1. package/dist/{LocaleDirectionProvider-CfQs5YYk.js → LocaleDirectionProvider-D701o676.js} +25 -19
  2. package/dist/LocaleDirectionProvider-D701o676.js.map +1 -0
  3. package/dist/{config-76UcBWeH.d.ts → config-CPRInh7U.d.ts} +1 -1
  4. package/dist/{config-76UcBWeH.d.ts.map → config-CPRInh7U.d.ts.map} +1 -1
  5. package/dist/index.d.ts +91 -12
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1079 -393
  8. package/dist/index.js.map +1 -1
  9. package/dist/locales/ar/messages.mjs +1 -1
  10. package/dist/locales/de/messages.mjs +1 -1
  11. package/dist/locales/en/messages.mjs +1 -1
  12. package/dist/locales/en-GB/messages.mjs +1 -0
  13. package/dist/locales/es-419/messages.mjs +1 -1
  14. package/dist/locales/es-ES/messages.mjs +1 -1
  15. package/dist/locales/eu/messages.mjs +1 -1
  16. package/dist/locales/fa/messages.mjs +1 -1
  17. package/dist/locales/fr/messages.mjs +1 -1
  18. package/dist/locales/id/messages.mjs +1 -1
  19. package/dist/locales/index.d.ts +1 -1
  20. package/dist/locales/index.js +1 -1
  21. package/dist/locales/ja/messages.mjs +1 -1
  22. package/dist/locales/ko/messages.mjs +1 -1
  23. package/dist/locales/nb/messages.mjs +1 -1
  24. package/dist/locales/pl/messages.mjs +1 -1
  25. package/dist/locales/pseudo/messages.mjs +1 -1
  26. package/dist/locales/pt-BR/messages.mjs +1 -1
  27. package/dist/locales/th/messages.mjs +1 -1
  28. package/dist/locales/zh-CN/messages.mjs +1 -1
  29. package/dist/locales/zh-TW/messages.mjs +1 -1
  30. package/dist/messages-BCdr0vs6.js +6 -0
  31. package/dist/messages-BCdr0vs6.js.map +1 -0
  32. package/dist/messages-BEba-i5h.js +6 -0
  33. package/dist/messages-BEba-i5h.js.map +1 -0
  34. package/dist/messages-BOJU79wz.js +6 -0
  35. package/dist/messages-BOJU79wz.js.map +1 -0
  36. package/dist/messages-B_776u_s.js +6 -0
  37. package/dist/messages-B_776u_s.js.map +1 -0
  38. package/dist/messages-BuTB8H87.js +6 -0
  39. package/dist/messages-BuTB8H87.js.map +1 -0
  40. package/dist/messages-BxxGIhf4.js +6 -0
  41. package/dist/messages-BxxGIhf4.js.map +1 -0
  42. package/dist/messages-CBkzQcd-.js +6 -0
  43. package/dist/messages-CBkzQcd-.js.map +1 -0
  44. package/dist/messages-CNCPtLaI.js +6 -0
  45. package/dist/messages-CNCPtLaI.js.map +1 -0
  46. package/dist/messages-CY65b3kz.js +6 -0
  47. package/dist/messages-CY65b3kz.js.map +1 -0
  48. package/dist/messages-CvaPem7G.js +6 -0
  49. package/dist/messages-CvaPem7G.js.map +1 -0
  50. package/dist/messages-DHo2mVqL.js +6 -0
  51. package/dist/messages-DHo2mVqL.js.map +1 -0
  52. package/dist/messages-DHqYoT8m.js +6 -0
  53. package/dist/messages-DHqYoT8m.js.map +1 -0
  54. package/dist/messages-DUYqNIID.js +6 -0
  55. package/dist/messages-DUYqNIID.js.map +1 -0
  56. package/dist/messages-DflNUNOT.js +6 -0
  57. package/dist/messages-DflNUNOT.js.map +1 -0
  58. package/dist/messages-DfrYsR2M.js +6 -0
  59. package/dist/messages-DfrYsR2M.js.map +1 -0
  60. package/dist/messages-DiOQ8Er0.js +6 -0
  61. package/dist/messages-DiOQ8Er0.js.map +1 -0
  62. package/dist/messages-MtFRSNif.js +6 -0
  63. package/dist/messages-MtFRSNif.js.map +1 -0
  64. package/dist/messages-TCMOQPXZ.js +6 -0
  65. package/dist/messages-TCMOQPXZ.js.map +1 -0
  66. package/dist/messages-Z5LBofRQ.js +6 -0
  67. package/dist/messages-Z5LBofRQ.js.map +1 -0
  68. package/dist/{plugins-BZzztFdK.js → plugins-G9Z9NQ7n.js} +26 -7
  69. package/dist/{plugins-BZzztFdK.js.map → plugins-G9Z9NQ7n.js.map} +1 -1
  70. package/package.json +8 -7
  71. package/dist/LocaleDirectionProvider-CfQs5YYk.js.map +0 -1
  72. package/dist/messages-BKmEGNNr.js +0 -6
  73. package/dist/messages-BKmEGNNr.js.map +0 -1
  74. package/dist/messages-BLyCd3bH.js +0 -6
  75. package/dist/messages-BLyCd3bH.js.map +0 -1
  76. package/dist/messages-BPzwf13U.js +0 -6
  77. package/dist/messages-BPzwf13U.js.map +0 -1
  78. package/dist/messages-BunNlQic.js +0 -6
  79. package/dist/messages-BunNlQic.js.map +0 -1
  80. package/dist/messages-C3wS1VMq.js +0 -6
  81. package/dist/messages-C3wS1VMq.js.map +0 -1
  82. package/dist/messages-C4ONkHEI.js +0 -6
  83. package/dist/messages-C4ONkHEI.js.map +0 -1
  84. package/dist/messages-C78AzU3C.js +0 -6
  85. package/dist/messages-C78AzU3C.js.map +0 -1
  86. package/dist/messages-Cc5L5f4F.js +0 -6
  87. package/dist/messages-Cc5L5f4F.js.map +0 -1
  88. package/dist/messages-CjirHcRw.js +0 -6
  89. package/dist/messages-CjirHcRw.js.map +0 -1
  90. package/dist/messages-CkDIrR_T.js +0 -6
  91. package/dist/messages-CkDIrR_T.js.map +0 -1
  92. package/dist/messages-ClrLd7GR.js +0 -6
  93. package/dist/messages-ClrLd7GR.js.map +0 -1
  94. package/dist/messages-DM8WFh_S.js +0 -6
  95. package/dist/messages-DM8WFh_S.js.map +0 -1
  96. package/dist/messages-DW5uspYc.js +0 -6
  97. package/dist/messages-DW5uspYc.js.map +0 -1
  98. package/dist/messages-DrMj1Hby.js +0 -6
  99. package/dist/messages-DrMj1Hby.js.map +0 -1
  100. package/dist/messages-Du-_Uh8B.js +0 -6
  101. package/dist/messages-Du-_Uh8B.js.map +0 -1
  102. package/dist/messages-DzQ203Bb.js +0 -6
  103. package/dist/messages-DzQ203Bb.js.map +0 -1
  104. package/dist/messages-ngvpRuG5.js +0 -6
  105. package/dist/messages-ngvpRuG5.js.map +0 -1
  106. package/dist/messages-rxZmXkNz.js +0 -6
  107. package/dist/messages-rxZmXkNz.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
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-BZzztFdK.js";
2
- import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-CfQs5YYk.js";
1
+ import { c as fetchAuthMode, d as throwResponseError, i as fetchPlugins, l as fetchManifest, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as parseApiResponse } from "./plugins-G9Z9NQ7n.js";
2
+ import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-D701o676.js";
3
3
  import "./locales/index.js";
4
- import { Autocomplete, Badge, Button, Checkbox, CommandPalette, Dialog, Input, InputArea, Label, LinkButton, Loader, Popover, Select, Sidebar as KumoSidebar, Switch, Tabs, Toast, Toasty, Tooltip, buttonVariants, useSidebar } from "@cloudflare/kumo";
4
+ import { Autocomplete, Badge, Button, Checkbox, ClipboardText, CommandPalette, Dialog, Input, InputArea, Label, LinkButton, Loader, Popover, Select, Sidebar as KumoSidebar, Switch, Tabs, Toast, Toasty, Tooltip, buttonVariants, useSidebar } from "@cloudflare/kumo";
5
5
  import { i18n } from "@lingui/core";
6
6
  import { I18nProvider, Trans, useLingui } from "@lingui/react";
7
7
  import { QueryClient, QueryClientProvider, keepPreviousData, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -40,6 +40,7 @@ import { BlockRenderer } from "@emdash-cms/blocks";
40
40
  import DOMPurify from "dompurify";
41
41
  import { Marked, Renderer } from "marked";
42
42
  import { checkEnvCompatibility, hostEnvFromVersions } from "@emdash-cms/registry-client/env";
43
+ import { declaredAccessToCapabilities } from "@emdash-cms/plugin-types";
43
44
  import { useHotkeys } from "react-hotkeys-hook";
44
45
 
45
46
  //#region src/components/ThemeProvider.tsx
@@ -279,7 +280,10 @@ function ArrowPrev({ className, ...props }) {
279
280
  function getMutationError(error) {
280
281
  if (!error) return null;
281
282
  if (error instanceof Error) return error.message;
282
- return "An error occurred";
283
+ return i18n._({
284
+ id: "Vw8l6h",
285
+ message: "An error occurred"
286
+ });
283
287
  }
284
288
  /** Inline error banner for use inside dialogs. */
285
289
  function DialogError({ message, className }) {
@@ -1181,9 +1185,6 @@ function getDraftStatus(item) {
1181
1185
  async function fetchTranslations(collection, id) {
1182
1186
  return parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/translations`), "Failed to fetch translations");
1183
1187
  }
1184
- /**
1185
- * Fetch content list
1186
- */
1187
1188
  async function fetchContentList(collection, options) {
1188
1189
  const params = new URLSearchParams();
1189
1190
  if (options?.cursor) params.set("cursor", options.cursor);
@@ -1193,13 +1194,29 @@ async function fetchContentList(collection, options) {
1193
1194
  if (options?.orderBy) params.set("orderBy", options.orderBy);
1194
1195
  if (options?.order) params.set("order", options.order);
1195
1196
  if (options?.search) params.set("q", options.search);
1197
+ if (options?.authorId) params.set("authorId", options.authorId);
1198
+ if (options?.dateField && (options.dateFrom || options.dateTo)) {
1199
+ params.set("dateField", options.dateField);
1200
+ if (options.dateFrom) params.set("dateFrom", options.dateFrom);
1201
+ if (options.dateTo) params.set("dateTo", options.dateTo);
1202
+ }
1196
1203
  return parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}${params.toString() ? `?${params}` : ""}`), "Failed to fetch content");
1197
1204
  }
1198
1205
  /**
1206
+ * Fetch the distinct authors of a collection's content. Gated on
1207
+ * `content:read`, so unlike the user-management API it's available to any
1208
+ * editor. Returns only users who have authored at least one live entry.
1209
+ */
1210
+ async function fetchContentAuthors(collection) {
1211
+ return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/authors`), "Failed to fetch content authors")).items;
1212
+ }
1213
+ /**
1199
1214
  * Fetch single content item
1200
1215
  */
1201
- async function fetchContent(collection, id) {
1202
- return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}`), "Failed to fetch content")).item;
1216
+ async function fetchContent(collection, id, options) {
1217
+ const params = new URLSearchParams();
1218
+ if (options?.locale) params.set("locale", options.locale);
1219
+ return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}${params.toString() ? `?${params}` : ""}`), "Failed to fetch content")).item;
1203
1220
  }
1204
1221
  /**
1205
1222
  * Create content
@@ -1221,8 +1238,10 @@ async function createContent(collection, input) {
1221
1238
  /**
1222
1239
  * Update content
1223
1240
  */
1224
- async function updateContent(collection, id, input) {
1225
- return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}`, {
1241
+ async function updateContent(collection, id, input, options) {
1242
+ const params = new URLSearchParams();
1243
+ if (options?.locale) params.set("locale", options.locale);
1244
+ return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}${params.toString() ? `?${params}` : ""}`, {
1226
1245
  method: "PUT",
1227
1246
  headers: { "Content-Type": "application/json" },
1228
1247
  body: JSON.stringify(input)
@@ -1231,8 +1250,10 @@ async function updateContent(collection, id, input) {
1231
1250
  /**
1232
1251
  * Delete content (moves to trash)
1233
1252
  */
1234
- async function deleteContent(collection, id) {
1235
- const response = await apiFetch(`${API_BASE}/content/${collection}/${id}`, { method: "DELETE" });
1253
+ async function deleteContent(collection, id, options) {
1254
+ const params = new URLSearchParams();
1255
+ if (options?.locale) params.set("locale", options.locale);
1256
+ const response = await apiFetch(`${API_BASE}/content/${collection}/${id}${params.toString() ? `?${params}` : ""}`, { method: "DELETE" });
1236
1257
  if (!response.ok) await throwResponseError(response, i18n._({
1237
1258
  id: "t5wtI+",
1238
1259
  message: "Failed to delete content"
@@ -1276,8 +1297,10 @@ async function duplicateContent(collection, id) {
1276
1297
  /**
1277
1298
  * Schedule content for future publishing
1278
1299
  */
1279
- async function scheduleContent(collection, id, scheduledAt) {
1280
- return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/schedule`, {
1300
+ async function scheduleContent(collection, id, scheduledAt, options) {
1301
+ const params = new URLSearchParams();
1302
+ if (options?.locale) params.set("locale", options.locale);
1303
+ return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/schedule${params.toString() ? `?${params}` : ""}`, {
1281
1304
  method: "POST",
1282
1305
  headers: { "Content-Type": "application/json" },
1283
1306
  body: JSON.stringify({ scheduledAt })
@@ -1286,8 +1309,10 @@ async function scheduleContent(collection, id, scheduledAt) {
1286
1309
  /**
1287
1310
  * Unschedule content (revert to draft)
1288
1311
  */
1289
- async function unscheduleContent(collection, id) {
1290
- return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/schedule`, { method: "DELETE" }), "Failed to unschedule content")).item;
1312
+ async function unscheduleContent(collection, id, options) {
1313
+ const params = new URLSearchParams();
1314
+ if (options?.locale) params.set("locale", options.locale);
1315
+ return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/schedule${params.toString() ? `?${params}` : ""}`, { method: "DELETE" }), "Failed to unschedule content")).item;
1291
1316
  }
1292
1317
  /**
1293
1318
  * Get a preview URL for content
@@ -1317,20 +1342,26 @@ async function getPreviewUrl(collection, id, options) {
1317
1342
  /**
1318
1343
  * Publish content - promotes current draft to live
1319
1344
  */
1320
- async function publishContent(collection, id) {
1321
- return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/publish`, { method: "POST" }), "Failed to publish content")).item;
1345
+ async function publishContent(collection, id, options) {
1346
+ const params = new URLSearchParams();
1347
+ if (options?.locale) params.set("locale", options.locale);
1348
+ return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/publish${params.toString() ? `?${params}` : ""}`, { method: "POST" }), "Failed to publish content")).item;
1322
1349
  }
1323
1350
  /**
1324
1351
  * Unpublish content - removes from public, preserves draft
1325
1352
  */
1326
- async function unpublishContent(collection, id) {
1327
- return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/unpublish`, { method: "POST" }), "Failed to unpublish content")).item;
1353
+ async function unpublishContent(collection, id, options) {
1354
+ const params = new URLSearchParams();
1355
+ if (options?.locale) params.set("locale", options.locale);
1356
+ return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/unpublish${params.toString() ? `?${params}` : ""}`, { method: "POST" }), "Failed to unpublish content")).item;
1328
1357
  }
1329
1358
  /**
1330
1359
  * Discard draft changes - reverts to live version
1331
1360
  */
1332
- async function discardDraft(collection, id) {
1333
- return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/discard-draft`, { method: "POST" }), "Failed to discard draft")).item;
1361
+ async function discardDraft(collection, id, options) {
1362
+ const params = new URLSearchParams();
1363
+ if (options?.locale) params.set("locale", options.locale);
1364
+ return (await parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}/${id}/discard-draft${params.toString() ? `?${params}` : ""}`, { method: "POST" }), "Failed to discard draft")).item;
1334
1365
  }
1335
1366
  /**
1336
1367
  * Compare live and draft revisions
@@ -1410,7 +1441,22 @@ async function fetchMediaList(options) {
1410
1441
  const q = normalizeMediaSearch(options.search);
1411
1442
  if (q) params.set("q", q);
1412
1443
  }
1413
- return parseApiResponse(await apiFetch(`${API_BASE}/media${params.toString() ? `?${params}` : ""}`), "Failed to fetch media");
1444
+ return parseApiResponse(await apiFetch(`${API_BASE}/media${params.toString() ? `?${params}` : ""}`), i18n._({
1445
+ id: "gtFAk8",
1446
+ message: "Failed to fetch media"
1447
+ }));
1448
+ }
1449
+ /**
1450
+ * Fetch a single media item by id.
1451
+ *
1452
+ * Used to resolve an id-only reference (e.g. a byline's `avatarMediaId`)
1453
+ * back into a full media item for display.
1454
+ */
1455
+ async function fetchMediaItem(id) {
1456
+ return (await parseApiResponse(await apiFetch(`${API_BASE}/media/${id}`), i18n._({
1457
+ id: "8rtatN",
1458
+ message: "Failed to fetch media item"
1459
+ }))).item;
1414
1460
  }
1415
1461
  /**
1416
1462
  * Try to get a signed upload URL
@@ -1429,7 +1475,10 @@ async function getUploadUrl(file, opts) {
1429
1475
  })
1430
1476
  });
1431
1477
  if (response.status === 501) return null;
1432
- return parseApiResponse(response, "Failed to get upload URL");
1478
+ return parseApiResponse(response, i18n._({
1479
+ id: "YTKTMb",
1480
+ message: "Failed to get upload URL"
1481
+ }));
1433
1482
  } catch (error) {
1434
1483
  if (error instanceof TypeError && error.message.includes("fetch")) return null;
1435
1484
  throw error;
@@ -1443,7 +1492,10 @@ async function confirmUpload(mediaId, metadata) {
1443
1492
  method: "POST",
1444
1493
  headers: { "Content-Type": "application/json" },
1445
1494
  body: JSON.stringify(metadata || {})
1446
- }), "Failed to confirm upload")).item;
1495
+ }), i18n._({
1496
+ id: "QZDXMs",
1497
+ message: "Failed to confirm upload"
1498
+ }))).item;
1447
1499
  }
1448
1500
  /**
1449
1501
  * Upload directly to signed URL
@@ -1496,7 +1548,10 @@ async function uploadMediaDirect(file, opts) {
1496
1548
  return (await parseApiResponse(await apiFetch(`${API_BASE}/media`, {
1497
1549
  method: "POST",
1498
1550
  body: formData
1499
- }), "Failed to upload media")).item;
1551
+ }), i18n._({
1552
+ id: "5IFNMM",
1553
+ message: "Failed to upload media"
1554
+ }))).item;
1500
1555
  }
1501
1556
  /**
1502
1557
  * Upload media file
@@ -1533,13 +1588,19 @@ async function updateMedia(id, input) {
1533
1588
  method: "PUT",
1534
1589
  headers: { "Content-Type": "application/json" },
1535
1590
  body: JSON.stringify(input)
1536
- }), "Failed to update media")).item;
1591
+ }), i18n._({
1592
+ id: "nr0Bxd",
1593
+ message: "Failed to update media"
1594
+ }))).item;
1537
1595
  }
1538
1596
  /**
1539
1597
  * Fetch all configured media providers
1540
1598
  */
1541
1599
  async function fetchMediaProviders() {
1542
- return (await parseApiResponse(await apiFetch(`${API_BASE}/media/providers`), "Failed to fetch media providers")).items;
1600
+ return (await parseApiResponse(await apiFetch(`${API_BASE}/media/providers`), i18n._({
1601
+ id: "sj4xOB",
1602
+ message: "Failed to fetch media providers"
1603
+ }))).items;
1543
1604
  }
1544
1605
  /**
1545
1606
  * Fetch media items from a specific provider
@@ -1553,7 +1614,10 @@ async function fetchProviderMedia(providerId, options) {
1553
1614
  const value = Array.isArray(options.mimeType) ? options.mimeType.join(",") : options.mimeType;
1554
1615
  if (value) params.set("mimeType", value);
1555
1616
  }
1556
- return parseApiResponse(await apiFetch(`${API_BASE}/media/providers/${providerId}${params.toString() ? `?${params}` : ""}`), "Failed to fetch provider media");
1617
+ return parseApiResponse(await apiFetch(`${API_BASE}/media/providers/${providerId}${params.toString() ? `?${params}` : ""}`), i18n._({
1618
+ id: "ZrDiVv",
1619
+ message: "Failed to fetch provider media"
1620
+ }));
1557
1621
  }
1558
1622
  /**
1559
1623
  * Upload media to a specific provider
@@ -1565,7 +1629,10 @@ async function uploadToProvider(providerId, file, alt) {
1565
1629
  return (await parseApiResponse(await apiFetch(`${API_BASE}/media/providers/${providerId}`, {
1566
1630
  method: "POST",
1567
1631
  body: formData
1568
- }), "Failed to upload to provider")).item;
1632
+ }), i18n._({
1633
+ id: "j66jlb",
1634
+ message: "Failed to upload to provider"
1635
+ }))).item;
1569
1636
  }
1570
1637
  /**
1571
1638
  * Delete media from a specific provider
@@ -1712,7 +1779,10 @@ async function registerOrphanedTable(slug, options) {
1712
1779
  * Fetch site settings
1713
1780
  */
1714
1781
  async function fetchSettings() {
1715
- return parseApiResponse(await apiFetch(`${API_BASE}/settings`), "Failed to fetch settings");
1782
+ return parseApiResponse(await apiFetch(`${API_BASE}/settings`), i18n._({
1783
+ id: "naCprc",
1784
+ message: "Failed to fetch settings"
1785
+ }));
1716
1786
  }
1717
1787
  /**
1718
1788
  * Update site settings
@@ -1722,7 +1792,10 @@ async function updateSettings(settings) {
1722
1792
  method: "POST",
1723
1793
  headers: { "Content-Type": "application/json" },
1724
1794
  body: JSON.stringify(settings)
1725
- }), "Failed to update settings");
1795
+ }), i18n._({
1796
+ id: "tOcErW",
1797
+ message: "Failed to update settings"
1798
+ }));
1726
1799
  }
1727
1800
 
1728
1801
  //#endregion
@@ -2312,13 +2385,19 @@ async function fetchSections(options) {
2312
2385
  if (options?.search) params.set("search", options.search);
2313
2386
  if (options?.limit) params.set("limit", String(options.limit));
2314
2387
  if (options?.cursor) params.set("cursor", options.cursor);
2315
- return parseApiResponse(await apiFetch(params.toString() ? `${API_BASE}/sections?${params}` : `${API_BASE}/sections`), "Failed to fetch sections");
2388
+ return parseApiResponse(await apiFetch(params.toString() ? `${API_BASE}/sections?${params}` : `${API_BASE}/sections`), i18n._({
2389
+ id: "CEv4Y+",
2390
+ message: "Failed to fetch sections"
2391
+ }));
2316
2392
  }
2317
2393
  /**
2318
2394
  * Fetch a single section by slug
2319
2395
  */
2320
2396
  async function fetchSection(slug) {
2321
- return parseApiResponse(await apiFetch(`${API_BASE}/sections/${slug}`), "Failed to fetch section");
2397
+ return parseApiResponse(await apiFetch(`${API_BASE}/sections/${slug}`), i18n._({
2398
+ id: "FZmyhf",
2399
+ message: "Failed to fetch section"
2400
+ }));
2322
2401
  }
2323
2402
  /**
2324
2403
  * Create a section
@@ -2328,7 +2407,10 @@ async function createSection(input) {
2328
2407
  method: "POST",
2329
2408
  headers: { "Content-Type": "application/json" },
2330
2409
  body: JSON.stringify(input)
2331
- }), "Failed to create section");
2410
+ }), i18n._({
2411
+ id: "NL9xsV",
2412
+ message: "Failed to create section"
2413
+ }));
2332
2414
  }
2333
2415
  /**
2334
2416
  * Update a section
@@ -2338,7 +2420,10 @@ async function updateSection(slug, input) {
2338
2420
  method: "PUT",
2339
2421
  headers: { "Content-Type": "application/json" },
2340
2422
  body: JSON.stringify(input)
2341
- }), "Failed to update section");
2423
+ }), i18n._({
2424
+ id: "cCyATW",
2425
+ message: "Failed to update section"
2426
+ }));
2342
2427
  }
2343
2428
  /**
2344
2429
  * Delete a section
@@ -2700,7 +2785,10 @@ async function bulkCommentAction(ids, action) {
2700
2785
  * Fetch dashboard statistics
2701
2786
  */
2702
2787
  async function fetchDashboardStats() {
2703
- return parseApiResponse(await apiFetch(`${API_BASE}/dashboard`), "Failed to fetch dashboard stats");
2788
+ return parseApiResponse(await apiFetch(`${API_BASE}/dashboard`), i18n._({
2789
+ id: "IZPHXp",
2790
+ message: "Failed to fetch dashboard stats"
2791
+ }));
2704
2792
  }
2705
2793
 
2706
2794
  //#endregion
@@ -2720,7 +2808,13 @@ async function setSearchEnabled(collection, enabled, weights) {
2720
2808
  enabled,
2721
2809
  weights
2722
2810
  })
2723
- }), `Failed to ${enabled ? "enable" : "disable"} search`);
2811
+ }), enabled ? i18n._({
2812
+ id: "2DwuuY",
2813
+ message: "Failed to enable search"
2814
+ }) : i18n._({
2815
+ id: "5YQYqs",
2816
+ message: "Failed to disable search"
2817
+ }));
2724
2818
  }
2725
2819
 
2726
2820
  //#endregion
@@ -2897,14 +2991,20 @@ function describeCapability(capability, allowedHosts) {
2897
2991
  * Email settings API client functions
2898
2992
  */
2899
2993
  async function fetchEmailSettings() {
2900
- return parseApiResponse(await apiFetch(`${API_BASE}/settings/email`), "Failed to fetch email settings");
2994
+ return parseApiResponse(await apiFetch(`${API_BASE}/settings/email`), i18n._({
2995
+ id: "QxS/sM",
2996
+ message: "Failed to fetch email settings"
2997
+ }));
2901
2998
  }
2902
2999
  async function sendTestEmail(to) {
2903
3000
  return parseApiResponse(await apiFetch(`${API_BASE}/settings/email`, {
2904
3001
  method: "POST",
2905
3002
  headers: { "Content-Type": "application/json" },
2906
3003
  body: JSON.stringify({ to })
2907
- }), "Failed to send test email");
3004
+ }), i18n._({
3005
+ id: "aH/q8K",
3006
+ message: "Failed to send test email"
3007
+ }));
2908
3008
  }
2909
3009
 
2910
3010
  //#endregion
@@ -3015,7 +3115,10 @@ async function fetch404Summary(limit) {
3015
3115
  * Current user query — shared across Shell, Header, Sidebar, and CommandPalette.
3016
3116
  */
3017
3117
  async function fetchCurrentUser() {
3018
- return parseApiResponse(await apiFetch("/_emdash/api/auth/me"), "Failed to fetch user");
3118
+ return parseApiResponse(await apiFetch("/_emdash/api/auth/me"), i18n._({
3119
+ id: "z7Mflq",
3120
+ message: "Failed to fetch user"
3121
+ }));
3019
3122
  }
3020
3123
  function useCurrentUser() {
3021
3124
  return useQuery({
@@ -3099,6 +3202,57 @@ function providerItemToMediaItem(providerId, item) {
3099
3202
  meta: item.meta
3100
3203
  };
3101
3204
  }
3205
+ /** Root-absolute path prefix for locally stored media served by EmDash. */
3206
+ const INTERNAL_MEDIA_PREFIX = "/_emdash/api/media/file/";
3207
+ /**
3208
+ * Default rendered width (CSS px) for admin grid thumbnails, requested at ~2x
3209
+ * the largest grid cell (200px) so they stay crisp on HiDPI displays.
3210
+ */
3211
+ const MEDIA_THUMBNAIL_WIDTH = 400;
3212
+ /**
3213
+ * Build a display URL for a media thumbnail in the admin grid/list views.
3214
+ *
3215
+ * Large libraries were slow to browse and search because every grid cell loaded
3216
+ * the full-size original through the media proxy (#1488). This routes
3217
+ * same-origin raster images through Astro's runtime image endpoint (`/_image`)
3218
+ * to request a small resized rendition instead.
3219
+ *
3220
+ * Where a runtime image service transforms — sharp on Node, or the Cloudflare
3221
+ * Images binding on Workers (the `@astrojs/cloudflare` v13 default) — the grid
3222
+ * gets a lightweight thumbnail. Where none does (a `passthrough` config, or
3223
+ * behind Cloudflare Access where the endpoint's same-origin source fetch is
3224
+ * blocked) `/_image` streams the original, so this never renders worse than
3225
+ * before. Callers should still fall back to the original on image `error` for
3226
+ * the rare case where the endpoint rejects the request (e.g. a site whose
3227
+ * configured origin differs from the admin's).
3228
+ *
3229
+ * Returns the URL unchanged for non-raster media (an icon renders instead),
3230
+ * SVGs (vector — nothing to downscale, and some services reject them), and
3231
+ * anything not served from the local media route (external/provider URLs are
3232
+ * already remote renditions, not same-origin originals).
3233
+ */
3234
+ function getMediaThumbnailUrl(originalUrl, mimeType, width = MEDIA_THUMBNAIL_WIDTH) {
3235
+ if (!mimeType.startsWith("image/") || mimeType === "image/svg+xml") return originalUrl;
3236
+ if (!originalUrl.startsWith(INTERNAL_MEDIA_PREFIX)) return originalUrl;
3237
+ const origin = typeof window === "undefined" ? "" : window.location.origin;
3238
+ if (!origin) return originalUrl;
3239
+ return `/_image?${new URLSearchParams({
3240
+ href: `${origin}${originalUrl}`,
3241
+ w: String(width),
3242
+ f: "webp"
3243
+ }).toString()}`;
3244
+ }
3245
+ /**
3246
+ * `onError` fallback for grid thumbnails: if a `/_image` rendition fails to
3247
+ * load (e.g. the endpoint rejects the request on a site whose configured origin
3248
+ * differs from the admin's), swap in the original URL once. Guarded with a data
3249
+ * attribute so a failing original can't trigger a reload loop.
3250
+ */
3251
+ function fallbackToOriginalThumbnail(img, originalUrl) {
3252
+ if (img.dataset.thumbFallback) return;
3253
+ img.dataset.thumbFallback = "1";
3254
+ img.src = originalUrl;
3255
+ }
3102
3256
  function getFileIcon(mimeType) {
3103
3257
  if (mimeType.startsWith("video/")) return "🎬";
3104
3258
  if (mimeType.startsWith("audio/")) return "🎵";
@@ -3325,7 +3479,10 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
3325
3479
  const activeProviderInfo = React$1.useMemo(() => {
3326
3480
  if (activeProvider === "local") return {
3327
3481
  id: "local",
3328
- name: "Library",
3482
+ name: _t({
3483
+ id: "exYcTF",
3484
+ message: "Library"
3485
+ }),
3329
3486
  icon: void 0,
3330
3487
  capabilities: {
3331
3488
  browse: true,
@@ -3335,7 +3492,11 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
3335
3492
  }
3336
3493
  };
3337
3494
  return providers?.find((p) => p.id === activeProvider);
3338
- }, [activeProvider, providers]);
3495
+ }, [
3496
+ activeProvider,
3497
+ providers,
3498
+ _t
3499
+ ]);
3339
3500
  const mediaQueryKey = [
3340
3501
  "media",
3341
3502
  filters?.join(",") ?? "",
@@ -3553,7 +3714,10 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
3553
3714
  const providerTabs = React$1.useMemo(() => {
3554
3715
  const tabs = [{
3555
3716
  id: "local",
3556
- name: "Library",
3717
+ name: _t({
3718
+ id: "exYcTF",
3719
+ message: "Library"
3720
+ }),
3557
3721
  icon: void 0
3558
3722
  }];
3559
3723
  if (providers && !localOnly) {
@@ -3564,7 +3728,11 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
3564
3728
  });
3565
3729
  }
3566
3730
  return tabs;
3567
- }, [providers, localOnly]);
3731
+ }, [
3732
+ providers,
3733
+ localOnly,
3734
+ _t
3735
+ ]);
3568
3736
  return /* @__PURE__ */ jsx(Dialog.Root, {
3569
3737
  open,
3570
3738
  onOpenChange: handleClose,
@@ -3888,6 +4056,7 @@ function MediaPickerItem({ item, selected, onClick, onDoubleClick, onDimensionsD
3888
4056
  const { _: _t2 } = useLingui();
3889
4057
  const isImage = item.mimeType.startsWith("image/");
3890
4058
  const needsDimensions = isImage && (!item.width || !item.height);
4059
+ const displayUrl = needsDimensions ? item.url : getMediaThumbnailUrl(item.url, item.mimeType);
3891
4060
  const handleImageLoad = React$1.useCallback((e) => {
3892
4061
  if (needsDimensions && onDimensionsDetected) {
3893
4062
  const img = e.currentTarget;
@@ -3919,10 +4088,11 @@ function MediaPickerItem({ item, selected, onClick, onDoubleClick, onDimensionsD
3919
4088
  }),
3920
4089
  children: [
3921
4090
  isImage ? /* @__PURE__ */ jsx("img", {
3922
- src: item.url,
4091
+ src: displayUrl,
3923
4092
  alt: "",
3924
4093
  className: "h-full w-full object-cover",
3925
- onLoad: handleImageLoad
4094
+ onLoad: handleImageLoad,
4095
+ onError: (e) => fallbackToOriginalThumbnail(e.currentTarget, item.url)
3926
4096
  }) : /* @__PURE__ */ jsx("div", {
3927
4097
  className: "flex h-full w-full items-center justify-center bg-kumo-tint",
3928
4098
  children: /* @__PURE__ */ jsx("span", {
@@ -4381,6 +4551,153 @@ function DocumentOutline({ editor, className }) {
4381
4551
  });
4382
4552
  }
4383
4553
 
4554
+ //#endregion
4555
+ //#region src/components/ImageFieldRenderer.tsx
4556
+ /**
4557
+ * Image field with media picker
4558
+ *
4559
+ * Stores full image metadata including dimensions for responsive images.
4560
+ * Handles backwards compatibility with legacy string URLs.
4561
+ *
4562
+ * Extracted from ContentEditor so non-top-level field UIs (e.g. repeater
4563
+ * sub-fields) can reuse the same picker without a circular import.
4564
+ */
4565
+ function ImageFieldRenderer({ id, label, description, value, onChange, required, allowedMimeTypes, fieldId }) {
4566
+ const { _: _t } = useLingui();
4567
+ const [pickerOpen, setPickerOpen] = React$1.useState(false);
4568
+ const [imageBroken, setImageBroken] = React$1.useState(false);
4569
+ const displayUrl = typeof value === "string" ? value : value?.previewUrl || value?.src || (value && (!value.provider || value.provider === "local") ? `/_emdash/api/media/file/${typeof value.meta?.storageKey === "string" ? value.meta.storageKey : value.id}` : void 0);
4570
+ React$1.useEffect(() => {
4571
+ setImageBroken(false);
4572
+ }, [displayUrl]);
4573
+ const handleSelect = (item) => {
4574
+ const isLocalProvider = !item.provider || item.provider === "local";
4575
+ onChange({
4576
+ id: item.id,
4577
+ provider: item.provider || "local",
4578
+ previewUrl: isLocalProvider ? void 0 : item.url,
4579
+ alt: item.alt || "",
4580
+ width: item.width,
4581
+ height: item.height,
4582
+ meta: isLocalProvider ? {
4583
+ ...item.meta,
4584
+ storageKey: item.storageKey
4585
+ } : item.meta
4586
+ });
4587
+ };
4588
+ const handleRemove = () => {
4589
+ onChange(null);
4590
+ };
4591
+ return /* @__PURE__ */ jsxs("div", {
4592
+ id,
4593
+ children: [
4594
+ /* @__PURE__ */ jsx(Label, { children: label }),
4595
+ displayUrl ? imageBroken ? /* @__PURE__ */ jsxs("div", {
4596
+ className: "mt-2 relative group",
4597
+ children: [/* @__PURE__ */ jsxs("div", {
4598
+ className: "min-h-20 rounded-lg border bg-kumo-muted flex items-center justify-center gap-2 text-kumo-subtle",
4599
+ children: [/* @__PURE__ */ jsx(ImageBroken, { className: "h-5 w-5" }), /* @__PURE__ */ jsx("span", {
4600
+ className: "text-sm",
4601
+ children: _t({
4602
+ id: "1pdLQw",
4603
+ message: "Image not found"
4604
+ })
4605
+ })]
4606
+ }), /* @__PURE__ */ jsxs("div", {
4607
+ className: "absolute top-2 end-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
4608
+ children: [/* @__PURE__ */ jsx(Button, {
4609
+ type: "button",
4610
+ size: "sm",
4611
+ variant: "secondary",
4612
+ onClick: () => setPickerOpen(true),
4613
+ children: _t({
4614
+ id: "o+XJ9D",
4615
+ message: "Change"
4616
+ })
4617
+ }), /* @__PURE__ */ jsx(Button, {
4618
+ type: "button",
4619
+ shape: "square",
4620
+ variant: "destructive",
4621
+ className: "h-8 w-8",
4622
+ onClick: handleRemove,
4623
+ "aria-label": _t({
4624
+ id: "5ywtDz",
4625
+ message: "Remove image"
4626
+ }),
4627
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
4628
+ })]
4629
+ })]
4630
+ }) : /* @__PURE__ */ jsxs("div", {
4631
+ className: "mt-2 relative group",
4632
+ children: [/* @__PURE__ */ jsx("img", {
4633
+ src: displayUrl,
4634
+ alt: "",
4635
+ className: "max-h-48 min-h-20 rounded-lg border object-cover",
4636
+ onError: () => setImageBroken(true)
4637
+ }), /* @__PURE__ */ jsxs("div", {
4638
+ className: "absolute top-2 end-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
4639
+ children: [/* @__PURE__ */ jsx(Button, {
4640
+ type: "button",
4641
+ size: "sm",
4642
+ variant: "secondary",
4643
+ onClick: () => setPickerOpen(true),
4644
+ children: _t({
4645
+ id: "o+XJ9D",
4646
+ message: "Change"
4647
+ })
4648
+ }), /* @__PURE__ */ jsx(Button, {
4649
+ type: "button",
4650
+ shape: "square",
4651
+ variant: "destructive",
4652
+ className: "h-8 w-8",
4653
+ onClick: handleRemove,
4654
+ "aria-label": _t({
4655
+ id: "5ywtDz",
4656
+ message: "Remove image"
4657
+ }),
4658
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
4659
+ })]
4660
+ })]
4661
+ }) : /* @__PURE__ */ jsx(Button, {
4662
+ type: "button",
4663
+ variant: "outline",
4664
+ className: "mt-2 w-full h-32 border-dashed",
4665
+ onClick: () => setPickerOpen(true),
4666
+ children: /* @__PURE__ */ jsxs("div", {
4667
+ className: "flex flex-col items-center gap-2 text-kumo-subtle",
4668
+ children: [/* @__PURE__ */ jsx(Image$1, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: _t({
4669
+ id: "oDodff",
4670
+ message: "Select image"
4671
+ }) })]
4672
+ })
4673
+ }),
4674
+ /* @__PURE__ */ jsx(MediaPickerModal, {
4675
+ open: pickerOpen,
4676
+ onOpenChange: setPickerOpen,
4677
+ onSelect: handleSelect,
4678
+ mimeTypeFilters: allowedMimeTypes && allowedMimeTypes.length > 0 ? allowedMimeTypes : ["image/"],
4679
+ fieldId,
4680
+ title: _t({
4681
+ id: "pov/PW",
4682
+ message: "Select {label}",
4683
+ values: { label }
4684
+ })
4685
+ }),
4686
+ description && /* @__PURE__ */ jsx("p", {
4687
+ className: "text-xs text-kumo-subtle mt-1",
4688
+ children: description
4689
+ }),
4690
+ required && !displayUrl && /* @__PURE__ */ jsx("p", {
4691
+ className: "text-sm text-kumo-danger mt-1",
4692
+ children: _t({
4693
+ id: "B8AaMI",
4694
+ message: "This field is required"
4695
+ })
4696
+ })
4697
+ ]
4698
+ });
4699
+ }
4700
+
4384
4701
  //#endregion
4385
4702
  //#region src/components/PluginFieldErrorBoundary.tsx
4386
4703
  /**
@@ -4404,11 +4721,17 @@ var PluginFieldErrorBoundary = class extends React$1.Component {
4404
4721
  children: [
4405
4722
  /* @__PURE__ */ jsx("p", {
4406
4723
  className: "text-sm font-medium text-kumo-danger",
4407
- children: "Plugin widget error"
4724
+ children: /* @__PURE__ */ jsx(Trans, {
4725
+ id: "zyTWI0",
4726
+ message: "Plugin widget error"
4727
+ })
4408
4728
  }),
4409
4729
  /* @__PURE__ */ jsx("p", {
4410
4730
  className: "mt-1 text-xs text-kumo-subtle",
4411
- children: this.state.error?.message || "The plugin field widget failed to render."
4731
+ children: this.state.error?.message || /* @__PURE__ */ jsx(Trans, {
4732
+ id: "Q/HIiC",
4733
+ message: "The plugin field widget failed to render."
4734
+ })
4412
4735
  }),
4413
4736
  /* @__PURE__ */ jsx("button", {
4414
4737
  type: "button",
@@ -4417,7 +4740,10 @@ var PluginFieldErrorBoundary = class extends React$1.Component {
4417
4740
  hasError: false,
4418
4741
  error: void 0
4419
4742
  }),
4420
- children: "Retry"
4743
+ children: /* @__PURE__ */ jsx(Trans, {
4744
+ id: "6gRgw8",
4745
+ message: "Retry"
4746
+ })
4421
4747
  })
4422
4748
  ]
4423
4749
  });
@@ -4468,7 +4794,7 @@ function RepeaterField({ label, id, value, onChange, subFields, minItems = 0, ma
4468
4794
  const handleAdd = () => {
4469
4795
  if (maxItems && items.length >= maxItems) return;
4470
4796
  const newItem = { _key: `item-${Date.now()}` };
4471
- for (const sf of subFields) newItem[sf.slug] = sf.type === "boolean" ? false : sf.type === "number" || sf.type === "integer" ? null : "";
4797
+ for (const sf of subFields) newItem[sf.slug] = sf.type === "boolean" ? false : sf.type === "number" || sf.type === "integer" || sf.type === "image" ? null : "";
4472
4798
  emitChange([...items, newItem]);
4473
4799
  };
4474
4800
  const handleRemove = (key) => {
@@ -4676,6 +5002,12 @@ function SubFieldInput({ subField, value, onChange }) {
4676
5002
  ...Object.fromEntries((subField.options ?? []).map((opt) => [opt, opt]))
4677
5003
  }
4678
5004
  });
5005
+ case "image": return /* @__PURE__ */ jsx(ImageFieldRenderer, {
5006
+ label: subField.label,
5007
+ value: value != null && typeof value === "object" ? value : typeof value === "string" && value ? value : void 0,
5008
+ onChange: (v) => onChange(v),
5009
+ required: subField.required
5010
+ });
4679
5011
  default: return /* @__PURE__ */ jsx(Input, {
4680
5012
  label: subField.label,
4681
5013
  value: typeof value === "string" ? value : "",
@@ -5656,7 +5988,10 @@ var index_default = Suggestion;
5656
5988
  const CODE_BLOCK_LANGUAGES = [
5657
5989
  {
5658
5990
  id: "plaintext",
5659
- label: "Plain text",
5991
+ label: {
5992
+ id: "pOo4Aa",
5993
+ message: "Plain text"
5994
+ },
5660
5995
  aliases: [
5661
5996
  "text",
5662
5997
  "plain",
@@ -5665,11 +6000,17 @@ const CODE_BLOCK_LANGUAGES = [
5665
6000
  },
5666
6001
  {
5667
6002
  id: "astro",
5668
- label: "Astro"
6003
+ label: {
6004
+ id: "2qNhQ4",
6005
+ message: "Astro"
6006
+ }
5669
6007
  },
5670
6008
  {
5671
6009
  id: "bash",
5672
- label: "Bash",
6010
+ label: {
6011
+ id: "DjECRV",
6012
+ message: "Bash"
6013
+ },
5673
6014
  aliases: [
5674
6015
  "sh",
5675
6016
  "shell",
@@ -5678,137 +6019,227 @@ const CODE_BLOCK_LANGUAGES = [
5678
6019
  },
5679
6020
  {
5680
6021
  id: "c",
5681
- label: "C"
6022
+ label: {
6023
+ id: "qvh3Th",
6024
+ message: "C"
6025
+ }
5682
6026
  },
5683
6027
  {
5684
6028
  id: "cpp",
5685
- label: "C++",
6029
+ label: {
6030
+ id: "ZfVDhL",
6031
+ message: "C++"
6032
+ },
5686
6033
  aliases: ["c++"]
5687
6034
  },
5688
6035
  {
5689
6036
  id: "csharp",
5690
- label: "C#",
6037
+ label: {
6038
+ id: "JMN/Cx",
6039
+ message: "C#"
6040
+ },
5691
6041
  aliases: ["cs", "c#"]
5692
6042
  },
5693
6043
  {
5694
6044
  id: "css",
5695
- label: "CSS"
6045
+ label: {
6046
+ id: "Tfspdu",
6047
+ message: "CSS"
6048
+ }
5696
6049
  },
5697
6050
  {
5698
6051
  id: "diff",
5699
- label: "Diff",
6052
+ label: {
6053
+ id: "bspG20",
6054
+ message: "Diff"
6055
+ },
5700
6056
  aliases: ["patch"]
5701
6057
  },
5702
6058
  {
5703
6059
  id: "dockerfile",
5704
- label: "Dockerfile",
6060
+ label: {
6061
+ id: "xbo2Nv",
6062
+ message: "Dockerfile"
6063
+ },
5705
6064
  aliases: ["docker"]
5706
6065
  },
5707
6066
  {
5708
6067
  id: "go",
5709
- label: "Go",
6068
+ label: {
6069
+ id: "4pSxwB",
6070
+ message: "Go"
6071
+ },
5710
6072
  aliases: ["golang"]
5711
6073
  },
5712
6074
  {
5713
6075
  id: "graphql",
5714
- label: "GraphQL",
6076
+ label: {
6077
+ id: "gBiL6J",
6078
+ message: "GraphQL"
6079
+ },
5715
6080
  aliases: ["gql"]
5716
6081
  },
5717
6082
  {
5718
6083
  id: "html",
5719
- label: "HTML"
6084
+ label: {
6085
+ id: "FBknns",
6086
+ message: "HTML"
6087
+ }
5720
6088
  },
5721
6089
  {
5722
6090
  id: "java",
5723
- label: "Java"
6091
+ label: {
6092
+ id: "d/bW+Y",
6093
+ message: "Java"
6094
+ }
5724
6095
  },
5725
6096
  {
5726
6097
  id: "javascript",
5727
- label: "JavaScript",
6098
+ label: {
6099
+ id: "k8pHx9",
6100
+ message: "JavaScript"
6101
+ },
5728
6102
  aliases: ["js"]
5729
6103
  },
5730
6104
  {
5731
6105
  id: "json",
5732
- label: "JSON"
6106
+ label: {
6107
+ id: "B2Zb/F",
6108
+ message: "JSON"
6109
+ }
5733
6110
  },
5734
6111
  {
5735
6112
  id: "jsx",
5736
- label: "JSX"
6113
+ label: {
6114
+ id: "ijupPe",
6115
+ message: "JSX"
6116
+ }
5737
6117
  },
5738
6118
  {
5739
6119
  id: "kotlin",
5740
- label: "Kotlin",
6120
+ label: {
6121
+ id: "ouqXGV",
6122
+ message: "Kotlin"
6123
+ },
5741
6124
  aliases: ["kt"]
5742
6125
  },
5743
6126
  {
5744
6127
  id: "markdown",
5745
- label: "Markdown",
6128
+ label: {
6129
+ id: "u6Hp4N",
6130
+ message: "Markdown"
6131
+ },
5746
6132
  aliases: ["md"]
5747
6133
  },
5748
6134
  {
5749
6135
  id: "mdx",
5750
- label: "MDX"
6136
+ label: {
6137
+ id: "6TGIsL",
6138
+ message: "MDX"
6139
+ }
5751
6140
  },
5752
6141
  {
5753
6142
  id: "php",
5754
- label: "PHP"
6143
+ label: {
6144
+ id: "Zw6ISu",
6145
+ message: "PHP"
6146
+ }
5755
6147
  },
5756
6148
  {
5757
6149
  id: "python",
5758
- label: "Python",
6150
+ label: {
6151
+ id: "zY6PMh",
6152
+ message: "Python"
6153
+ },
5759
6154
  aliases: ["py"]
5760
6155
  },
5761
6156
  {
5762
6157
  id: "ruby",
5763
- label: "Ruby",
6158
+ label: {
6159
+ id: "7jTlsu",
6160
+ message: "Ruby"
6161
+ },
5764
6162
  aliases: ["rb"]
5765
6163
  },
5766
6164
  {
5767
6165
  id: "rust",
5768
- label: "Rust",
6166
+ label: {
6167
+ id: "Sc8PMh",
6168
+ message: "Rust"
6169
+ },
5769
6170
  aliases: ["rs"]
5770
6171
  },
5771
6172
  {
5772
6173
  id: "scss",
5773
- label: "SCSS",
6174
+ label: {
6175
+ id: "xEGQEX",
6176
+ message: "SCSS"
6177
+ },
5774
6178
  aliases: ["sass"]
5775
6179
  },
5776
6180
  {
5777
6181
  id: "sql",
5778
- label: "SQL"
6182
+ label: {
6183
+ id: "mZT1Nu",
6184
+ message: "SQL"
6185
+ }
5779
6186
  },
5780
6187
  {
5781
6188
  id: "svelte",
5782
- label: "Svelte"
6189
+ label: {
6190
+ id: "I0X6aq",
6191
+ message: "Svelte"
6192
+ }
5783
6193
  },
5784
6194
  {
5785
6195
  id: "swift",
5786
- label: "Swift"
6196
+ label: {
6197
+ id: "D8sGaA",
6198
+ message: "Swift"
6199
+ }
5787
6200
  },
5788
6201
  {
5789
6202
  id: "toml",
5790
- label: "TOML"
6203
+ label: {
6204
+ id: "ZmWJqk",
6205
+ message: "TOML"
6206
+ }
5791
6207
  },
5792
6208
  {
5793
6209
  id: "tsx",
5794
- label: "TSX"
6210
+ label: {
6211
+ id: "lFP3br",
6212
+ message: "TSX"
6213
+ }
5795
6214
  },
5796
6215
  {
5797
6216
  id: "typescript",
5798
- label: "TypeScript",
6217
+ label: {
6218
+ id: "49rpnf",
6219
+ message: "TypeScript"
6220
+ },
5799
6221
  aliases: ["ts"]
5800
6222
  },
5801
6223
  {
5802
6224
  id: "vue",
5803
- label: "Vue"
6225
+ label: {
6226
+ id: "M6Uejz",
6227
+ message: "Vue"
6228
+ }
5804
6229
  },
5805
6230
  {
5806
6231
  id: "xml",
5807
- label: "XML"
6232
+ label: {
6233
+ id: "8W/ILN",
6234
+ message: "XML"
6235
+ }
5808
6236
  },
5809
6237
  {
5810
6238
  id: "yaml",
5811
- label: "YAML",
6239
+ label: {
6240
+ id: "h51QFW",
6241
+ message: "YAML"
6242
+ },
5812
6243
  aliases: ["yml"]
5813
6244
  }
5814
6245
  ];
@@ -5818,11 +6249,11 @@ const CODE_BLOCK_LANGUAGES = [
5818
6249
  */
5819
6250
  function findLanguage(value) {
5820
6251
  if (!value) return null;
5821
- const needle = value.trim().toLowerCase();
5822
- if (!needle) return null;
6252
+ const searchText = value.trim().toLowerCase();
6253
+ if (!searchText) return null;
5823
6254
  for (const lang of CODE_BLOCK_LANGUAGES) {
5824
- if (lang.id === needle) return lang;
5825
- if (lang.aliases?.includes(needle)) return lang;
6255
+ if (lang.id === searchText) return lang;
6256
+ if (lang.aliases?.includes(searchText)) return lang;
5826
6257
  }
5827
6258
  return null;
5828
6259
  }
@@ -5855,8 +6286,11 @@ function normalizeLanguage(value) {
5855
6286
  * Human-readable label for a stored language id. Falls back to the id itself
5856
6287
  * for unknown values so the editor never shows "undefined".
5857
6288
  */
5858
- function languageLabel(value) {
5859
- if (!value) return "Plain text";
6289
+ function languageLabelDescriptor(value) {
6290
+ if (!value) return {
6291
+ id: "pOo4Aa",
6292
+ message: "Plain text"
6293
+ };
5860
6294
  const match = findLanguage(value);
5861
6295
  if (match) return match.label;
5862
6296
  return value;
@@ -5879,37 +6313,51 @@ function languageLabel(value) {
5879
6313
  * sanitized to a single safe CSS class token via `normalizeLanguage` so the
5880
6314
  * frontend's `language-{id}` class stays well-formed.
5881
6315
  */
5882
- const LANGUAGE_ITEMS = CODE_BLOCK_LANGUAGES.map((l) => l.label);
5883
- function filterLanguages(item, query) {
5884
- if (!query) return true;
5885
- const needle = query.toLowerCase();
5886
- const lang = CODE_BLOCK_LANGUAGES.find((l) => l.label === item);
5887
- if (!lang) return false;
5888
- if (lang.label.toLowerCase().includes(needle)) return true;
5889
- if (lang.id.toLowerCase().includes(needle)) return true;
5890
- return lang.aliases?.some((alias) => alias.toLowerCase().includes(needle)) ?? false;
5891
- }
5892
6316
  function CodeBlockNodeView({ node, updateAttributes, selected }) {
5893
6317
  const { _: _t } = useLingui();
5894
6318
  const [isEditing, setIsEditing] = React$1.useState(false);
5895
6319
  const storedLanguage = typeof node.attrs.language === "string" ? node.attrs.language : "";
5896
- const [draft, setDraft] = React$1.useState(() => languageLabel(storedLanguage));
6320
+ const labelText = React$1.useCallback((value) => {
6321
+ const label = languageLabelDescriptor(value);
6322
+ return typeof label === "string" ? label : _t(label);
6323
+ }, [_t]);
6324
+ const languageItems = React$1.useMemo(() => CODE_BLOCK_LANGUAGES.map((language) => _t(language.label)), [_t]);
6325
+ const findLanguageByDisplayLabel = React$1.useCallback((label) => CODE_BLOCK_LANGUAGES.find((language) => _t(language.label) === label), [_t]);
6326
+ const filterLanguages = React$1.useCallback((item, query) => {
6327
+ if (!query) return true;
6328
+ const searchText = query.toLowerCase();
6329
+ const lang = findLanguageByDisplayLabel(item);
6330
+ if (!lang) return false;
6331
+ if (_t(lang.label).toLowerCase().includes(searchText)) return true;
6332
+ if (lang.id.toLowerCase().includes(searchText)) return true;
6333
+ return lang.aliases?.some((alias) => alias.toLowerCase().includes(searchText)) ?? false;
6334
+ }, [findLanguageByDisplayLabel, _t]);
6335
+ const [draft, setDraft] = React$1.useState(() => labelText(storedLanguage));
5897
6336
  const popoverRef = React$1.useRef(null);
5898
6337
  React$1.useEffect(() => {
5899
- if (!isEditing) setDraft(languageLabel(storedLanguage));
5900
- }, [storedLanguage, isEditing]);
6338
+ if (!isEditing) setDraft(labelText(storedLanguage));
6339
+ }, [
6340
+ storedLanguage,
6341
+ isEditing,
6342
+ labelText
6343
+ ]);
5901
6344
  const openPicker = React$1.useCallback(() => {
5902
- setDraft(storedLanguage ? languageLabel(storedLanguage) : "");
6345
+ setDraft(storedLanguage ? labelText(storedLanguage) : "");
5903
6346
  setIsEditing(true);
5904
- }, [storedLanguage]);
6347
+ }, [storedLanguage, labelText]);
5905
6348
  const closePicker = React$1.useCallback(() => {
5906
6349
  setIsEditing(false);
5907
- setDraft(languageLabel(storedLanguage));
5908
- }, [storedLanguage]);
6350
+ setDraft(labelText(storedLanguage));
6351
+ }, [storedLanguage, labelText]);
5909
6352
  const commit = React$1.useCallback((value) => {
5910
- updateAttributes({ language: normalizeLanguage(value ?? draft) ?? null });
6353
+ const raw = value ?? draft;
6354
+ updateAttributes({ language: findLanguageByDisplayLabel(raw)?.id ?? normalizeLanguage(raw) ?? null });
5911
6355
  setIsEditing(false);
5912
- }, [draft, updateAttributes]);
6356
+ }, [
6357
+ draft,
6358
+ findLanguageByDisplayLabel,
6359
+ updateAttributes
6360
+ ]);
5913
6361
  const handleKeyDown = (e) => {
5914
6362
  if (e.key === "Enter") {
5915
6363
  e.preventDefault();
@@ -5928,7 +6376,7 @@ function CodeBlockNodeView({ node, updateAttributes, selected }) {
5928
6376
  document.addEventListener("mousedown", onMouseDown);
5929
6377
  return () => document.removeEventListener("mousedown", onMouseDown);
5930
6378
  }, [isEditing, closePicker]);
5931
- const label = languageLabel(storedLanguage);
6379
+ const label = labelText(storedLanguage);
5932
6380
  const chipPersistent = isEditing || Boolean(storedLanguage) || selected;
5933
6381
  return /* @__PURE__ */ jsxs(NodeViewWrapper, {
5934
6382
  className: "group relative my-4",
@@ -5945,7 +6393,7 @@ function CodeBlockNodeView({ node, updateAttributes, selected }) {
5945
6393
  onKeyDown: handleKeyDown,
5946
6394
  children: [
5947
6395
  /* @__PURE__ */ jsxs(Autocomplete, {
5948
- items: LANGUAGE_ITEMS,
6396
+ items: languageItems,
5949
6397
  value: draft,
5950
6398
  onValueChange: (next) => setDraft(next),
5951
6399
  filter: filterLanguages,
@@ -7550,7 +7998,7 @@ function convertPMNode(node) {
7550
7998
  default: return null;
7551
7999
  }
7552
8000
  }
7553
- function convertList(items, listItem) {
8001
+ function convertList(items, listItem, level = 1) {
7554
8002
  const blocks = [];
7555
8003
  const typedItems = items;
7556
8004
  for (const item of typedItems) if (item.type === "listItem") {
@@ -7562,11 +8010,12 @@ function convertList(items, listItem) {
7562
8010
  _key: generateKey(),
7563
8011
  style: "normal",
7564
8012
  listItem,
7565
- level: 1,
8013
+ level,
7566
8014
  children,
7567
8015
  markDefs: markDefs.length > 0 ? markDefs : void 0
7568
8016
  });
7569
- }
8017
+ } else if (child.type === "bulletList") blocks.push(...convertList(child.content || [], "bullet", level + 1));
8018
+ else if (child.type === "orderedList") blocks.push(...convertList(child.content || [], "number", level + 1));
7570
8019
  }
7571
8020
  return blocks;
7572
8021
  }
@@ -7655,7 +8104,8 @@ function portableTextToProsemirror(blocks) {
7655
8104
  const listType = block.listItem;
7656
8105
  while (i < blocks.length) {
7657
8106
  const current = blocks[i];
7658
- if (isTextBlock(current) && current.listItem === listType) {
8107
+ if (!isTextBlock(current) || !current.listItem) break;
8108
+ if ((current.level || 1) > 1 || current.listItem === listType) {
7659
8109
  listBlocks.push(current);
7660
8110
  i++;
7661
8111
  } else break;
@@ -7787,19 +8237,61 @@ function convertPTBlock(block) {
7787
8237
  }
7788
8238
  }
7789
8239
  function convertPTList(items, listType) {
7790
- const listItems = items.map((item) => {
7791
- const pmContent = convertPTSpans(item.children, item.markDefs || []);
7792
- return {
7793
- type: "listItem",
7794
- content: [{
7795
- type: "paragraph",
7796
- content: pmContent.length > 0 ? pmContent : void 0
7797
- }]
7798
- };
7799
- });
8240
+ const rootItems = [];
8241
+ let i = 0;
8242
+ while (i < items.length) {
8243
+ const item = items[i];
8244
+ if ((item.level || 1) === 1) {
8245
+ const nestedItems = [];
8246
+ i++;
8247
+ while (i < items.length && (items[i].level || 1) > 1) {
8248
+ nestedItems.push(items[i]);
8249
+ i++;
8250
+ }
8251
+ rootItems.push(convertPTListItem(item, nestedItems, listType));
8252
+ } else {
8253
+ rootItems.push(convertPTListItem(item, [], listType));
8254
+ i++;
8255
+ }
8256
+ }
7800
8257
  return {
7801
8258
  type: listType === "bullet" ? "bulletList" : "orderedList",
7802
- content: listItems
8259
+ content: rootItems
8260
+ };
8261
+ }
8262
+ function convertPTListItem(item, nestedItems, parentListType) {
8263
+ const content = [];
8264
+ const pmContent = convertPTSpans(item.children, item.markDefs || []);
8265
+ content.push({
8266
+ type: "paragraph",
8267
+ content: pmContent.length > 0 ? pmContent : void 0
8268
+ });
8269
+ if (nestedItems.length > 0) {
8270
+ let minLevel = Infinity;
8271
+ for (const ni of nestedItems) {
8272
+ const level = ni.level || 2;
8273
+ if (level < minLevel) minLevel = level;
8274
+ }
8275
+ let j = 0;
8276
+ while (j < nestedItems.length) {
8277
+ const anchorType = nestedItems[j].listItem || parentListType;
8278
+ const nestedGroup = [];
8279
+ do {
8280
+ nestedGroup.push(nestedItems[j]);
8281
+ j++;
8282
+ } while (j < nestedItems.length && ((nestedItems[j].level || 2) > minLevel || (nestedItems[j].listItem || parentListType) === anchorType));
8283
+ if (nestedGroup.length > 0) {
8284
+ const adjustedGroup = nestedGroup.map((ni) => ({
8285
+ ...ni,
8286
+ level: (ni.level || 2) - 1
8287
+ }));
8288
+ content.push(convertPTList(adjustedGroup, anchorType));
8289
+ }
8290
+ }
8291
+ }
8292
+ return {
8293
+ type: "listItem",
8294
+ content
7803
8295
  };
7804
8296
  }
7805
8297
  function convertPTSpans(spans, markDefs) {
@@ -9751,6 +10243,20 @@ function EditorToolbar({ editor, focusMode, onFocusModeChange }) {
9751
10243
  "aria-hidden": "true"
9752
10244
  })
9753
10245
  }),
10246
+ /* @__PURE__ */ jsx(ToolbarButton, {
10247
+ onClick: () => editor.chain().focus().insertContent({
10248
+ type: "htmlBlock",
10249
+ attrs: { html: "" }
10250
+ }).run(),
10251
+ title: _t9({
10252
+ id: "k76x3g",
10253
+ message: "Insert HTML"
10254
+ }),
10255
+ children: /* @__PURE__ */ jsx(BracketsAngle, {
10256
+ className: "h-4 w-4",
10257
+ "aria-hidden": "true"
10258
+ })
10259
+ }),
9754
10260
  /* @__PURE__ */ jsx(ToolbarButton, {
9755
10261
  onClick: () => editor.chain().focus().setHorizontalRule().run(),
9756
10262
  title: _t9({
@@ -10422,8 +10928,11 @@ function SeoPanel({ contentKey, seo, onChange }) {
10422
10928
  }
10423
10929
  }, []);
10424
10930
  const flushPendingDraft = React$1.useCallback(() => {
10931
+ const nextDraft = currentDraftRef.current;
10932
+ const nextSnapshot = serializeDraft(nextDraft);
10425
10933
  clearPendingTextFlush();
10426
- emitChange(currentDraftRef.current);
10934
+ if (nextSnapshot === lastEmittedSnapshotRef.current) return;
10935
+ emitChange(nextDraft);
10427
10936
  }, [clearPendingTextFlush, emitChange]);
10428
10937
  React$1.useEffect(() => {
10429
10938
  if (activeContentKeyRef.current === contentKey) {
@@ -11623,8 +12132,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, entry
11623
12132
  className: "mt-1 flex flex-wrap items-center gap-1.5",
11624
12133
  children: supportsDrafts ? /* @__PURE__ */ jsxs(Fragment, { children: [
11625
12134
  isLive && /* @__PURE__ */ jsx(Badge, {
11626
- variant: "primary",
11627
- className: "text-white",
12135
+ variant: "success",
11628
12136
  children: _t({
11629
12137
  id: "u3wRF+",
11630
12138
  message: "Published"
@@ -11845,7 +12353,8 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, entry
11845
12353
  params: {
11846
12354
  collection,
11847
12355
  id: tr.id
11848
- }
12356
+ },
12357
+ search: { locale: tr.locale }
11849
12358
  }),
11850
12359
  onCreate: onTranslate
11851
12360
  })
@@ -12206,141 +12715,6 @@ function JsonFieldEditor({ label, id, value, onChange, required }) {
12206
12715
  children: error
12207
12716
  })] });
12208
12717
  }
12209
- function ImageFieldRenderer({ id, label, description, value, onChange, required, allowedMimeTypes, fieldId }) {
12210
- const { _: _t5 } = useLingui();
12211
- const [pickerOpen, setPickerOpen] = React$1.useState(false);
12212
- const [imageBroken, setImageBroken] = React$1.useState(false);
12213
- const displayUrl = typeof value === "string" ? value : value?.previewUrl || value?.src || (value && (!value.provider || value.provider === "local") ? `/_emdash/api/media/file/${typeof value.meta?.storageKey === "string" ? value.meta.storageKey : value.id}` : void 0);
12214
- React$1.useEffect(() => {
12215
- setImageBroken(false);
12216
- }, [displayUrl]);
12217
- const handleSelect = (item) => {
12218
- const isLocalProvider = !item.provider || item.provider === "local";
12219
- onChange({
12220
- id: item.id,
12221
- provider: item.provider || "local",
12222
- previewUrl: isLocalProvider ? void 0 : item.url,
12223
- alt: item.alt || "",
12224
- width: item.width,
12225
- height: item.height,
12226
- meta: isLocalProvider ? {
12227
- ...item.meta,
12228
- storageKey: item.storageKey
12229
- } : item.meta
12230
- });
12231
- };
12232
- const handleRemove = () => {
12233
- onChange(null);
12234
- };
12235
- return /* @__PURE__ */ jsxs("div", {
12236
- id,
12237
- children: [
12238
- /* @__PURE__ */ jsx(Label, { children: label }),
12239
- displayUrl ? imageBroken ? /* @__PURE__ */ jsxs("div", {
12240
- className: "mt-2 relative group",
12241
- children: [/* @__PURE__ */ jsxs("div", {
12242
- className: "min-h-20 rounded-lg border bg-kumo-muted flex items-center justify-center gap-2 text-kumo-subtle",
12243
- children: [/* @__PURE__ */ jsx(ImageBroken, { className: "h-5 w-5" }), /* @__PURE__ */ jsx("span", {
12244
- className: "text-sm",
12245
- children: _t5({
12246
- id: "1pdLQw",
12247
- message: "Image not found"
12248
- })
12249
- })]
12250
- }), /* @__PURE__ */ jsxs("div", {
12251
- className: "absolute top-2 end-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
12252
- children: [/* @__PURE__ */ jsx(Button, {
12253
- type: "button",
12254
- size: "sm",
12255
- variant: "secondary",
12256
- onClick: () => setPickerOpen(true),
12257
- children: _t5({
12258
- id: "o+XJ9D",
12259
- message: "Change"
12260
- })
12261
- }), /* @__PURE__ */ jsx(Button, {
12262
- type: "button",
12263
- shape: "square",
12264
- variant: "destructive",
12265
- className: "h-8 w-8",
12266
- onClick: handleRemove,
12267
- "aria-label": _t5({
12268
- id: "5ywtDz",
12269
- message: "Remove image"
12270
- }),
12271
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
12272
- })]
12273
- })]
12274
- }) : /* @__PURE__ */ jsxs("div", {
12275
- className: "mt-2 relative group",
12276
- children: [/* @__PURE__ */ jsx("img", {
12277
- src: displayUrl,
12278
- alt: "",
12279
- className: "max-h-48 min-h-20 rounded-lg border object-cover",
12280
- onError: () => setImageBroken(true)
12281
- }), /* @__PURE__ */ jsxs("div", {
12282
- className: "absolute top-2 end-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1",
12283
- children: [/* @__PURE__ */ jsx(Button, {
12284
- type: "button",
12285
- size: "sm",
12286
- variant: "secondary",
12287
- onClick: () => setPickerOpen(true),
12288
- children: _t5({
12289
- id: "o+XJ9D",
12290
- message: "Change"
12291
- })
12292
- }), /* @__PURE__ */ jsx(Button, {
12293
- type: "button",
12294
- shape: "square",
12295
- variant: "destructive",
12296
- className: "h-8 w-8",
12297
- onClick: handleRemove,
12298
- "aria-label": _t5({
12299
- id: "5ywtDz",
12300
- message: "Remove image"
12301
- }),
12302
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
12303
- })]
12304
- })]
12305
- }) : /* @__PURE__ */ jsx(Button, {
12306
- type: "button",
12307
- variant: "outline",
12308
- className: "mt-2 w-full h-32 border-dashed",
12309
- onClick: () => setPickerOpen(true),
12310
- children: /* @__PURE__ */ jsxs("div", {
12311
- className: "flex flex-col items-center gap-2 text-kumo-subtle",
12312
- children: [/* @__PURE__ */ jsx(Image$1, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: _t5({
12313
- id: "oDodff",
12314
- message: "Select image"
12315
- }) })]
12316
- })
12317
- }),
12318
- /* @__PURE__ */ jsx(MediaPickerModal, {
12319
- open: pickerOpen,
12320
- onOpenChange: setPickerOpen,
12321
- onSelect: handleSelect,
12322
- mimeTypeFilters: allowedMimeTypes && allowedMimeTypes.length > 0 ? allowedMimeTypes : ["image/"],
12323
- fieldId,
12324
- title: _t5({
12325
- id: "pov/PW",
12326
- message: "Select {label}",
12327
- values: { label }
12328
- })
12329
- }),
12330
- description && /* @__PURE__ */ jsx("p", {
12331
- className: "text-xs text-kumo-subtle mt-1",
12332
- children: description
12333
- }),
12334
- required && !displayUrl && /* @__PURE__ */ jsx("p", {
12335
- className: "text-sm text-kumo-danger mt-1",
12336
- children: _t5({
12337
- id: "B8AaMI",
12338
- message: "This field is required"
12339
- })
12340
- })
12341
- ]
12342
- });
12343
- }
12344
12718
  /**
12345
12719
  * File field with media picker
12346
12720
  *
@@ -12348,7 +12722,7 @@ function ImageFieldRenderer({ id, label, description, value, onChange, required,
12348
12722
  * icon, filename, and size instead of an image preview.
12349
12723
  */
12350
12724
  function FileFieldRenderer({ id, label, value, onChange, required, allowedMimeTypes, fieldId }) {
12351
- const { _: _t6 } = useLingui();
12725
+ const { _: _t5 } = useLingui();
12352
12726
  const [pickerOpen, setPickerOpen] = React$1.useState(false);
12353
12727
  const normalized = React$1.useMemo(() => {
12354
12728
  if (!value) return null;
@@ -12359,14 +12733,14 @@ function FileFieldRenderer({ id, label, value, onChange, required, allowedMimeTy
12359
12733
  const externalUrl = !isLocal && value.src && isSafeUrl$1(value.src) ? value.src : void 0;
12360
12734
  return {
12361
12735
  displayUrl: localUrl ?? externalUrl,
12362
- filename: value.filename || _t6({
12736
+ filename: value.filename || _t5({
12363
12737
  id: "cChtk1",
12364
12738
  message: "Untitled file"
12365
12739
  }),
12366
12740
  mimeType: value.mimeType || "",
12367
12741
  size: value.size
12368
12742
  };
12369
- }, [value, _t6]);
12743
+ }, [value, _t5]);
12370
12744
  const handleSelect = (item) => {
12371
12745
  const isLocalProvider = !item.provider || item.provider === "local";
12372
12746
  onChange({
@@ -12427,7 +12801,7 @@ function FileFieldRenderer({ id, label, value, onChange, required, allowedMimeTy
12427
12801
  size: "sm",
12428
12802
  variant: "secondary",
12429
12803
  onClick: () => setPickerOpen(true),
12430
- children: _t6({
12804
+ children: _t5({
12431
12805
  id: "o+XJ9D",
12432
12806
  message: "Change"
12433
12807
  })
@@ -12437,7 +12811,7 @@ function FileFieldRenderer({ id, label, value, onChange, required, allowedMimeTy
12437
12811
  variant: "destructive",
12438
12812
  className: "h-8 w-8",
12439
12813
  onClick: handleRemove,
12440
- "aria-label": _t6({
12814
+ "aria-label": _t5({
12441
12815
  id: "HND1q3",
12442
12816
  message: "Remove {label}",
12443
12817
  values: { label }
@@ -12451,14 +12825,14 @@ function FileFieldRenderer({ id, label, value, onChange, required, allowedMimeTy
12451
12825
  variant: "outline",
12452
12826
  className: "mt-2 w-full h-32 border-dashed",
12453
12827
  onClick: () => setPickerOpen(true),
12454
- "aria-label": _t6({
12828
+ "aria-label": _t5({
12455
12829
  id: "pov/PW",
12456
12830
  message: "Select {label}",
12457
12831
  values: { label }
12458
12832
  }),
12459
12833
  children: /* @__PURE__ */ jsxs("div", {
12460
12834
  className: "flex flex-col items-center gap-2 text-kumo-subtle",
12461
- children: [/* @__PURE__ */ jsx(Paperclip, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: _t6({
12835
+ children: [/* @__PURE__ */ jsx(Paperclip, { className: "h-8 w-8" }), /* @__PURE__ */ jsx("span", { children: _t5({
12462
12836
  id: "J6WI1B",
12463
12837
  message: "Select file"
12464
12838
  }) })]
@@ -12472,7 +12846,7 @@ function FileFieldRenderer({ id, label, value, onChange, required, allowedMimeTy
12472
12846
  fieldId,
12473
12847
  hideUrlInput: true,
12474
12848
  mediaKind: "file",
12475
- title: _t6({
12849
+ title: _t5({
12476
12850
  id: "pov/PW",
12477
12851
  message: "Select {label}",
12478
12852
  values: { label }
@@ -12480,7 +12854,7 @@ function FileFieldRenderer({ id, label, value, onChange, required, allowedMimeTy
12480
12854
  }),
12481
12855
  required && !normalized && /* @__PURE__ */ jsx("p", {
12482
12856
  className: "text-sm text-kumo-danger mt-1",
12483
- children: _t6({
12857
+ children: _t5({
12484
12858
  id: "B8AaMI",
12485
12859
  message: "This field is required"
12486
12860
  })
@@ -12489,7 +12863,7 @@ function FileFieldRenderer({ id, label, value, onChange, required, allowedMimeTy
12489
12863
  });
12490
12864
  }
12491
12865
  function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange, onQuickCreate, onQuickEdit, entryLocale, i18n, bylinesLoaded = true }) {
12492
- const { _: _t7 } = useLingui();
12866
+ const { _: _t6 } = useLingui();
12493
12867
  const [search, setSearch] = React$1.useState("");
12494
12868
  const debouncedSearch = useDebouncedValue$1(search, 300);
12495
12869
  const [quickName, setQuickName] = React$1.useState("");
@@ -12572,7 +12946,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12572
12946
  className: "rounded border border-dashed p-3 text-sm space-y-2",
12573
12947
  children: [/* @__PURE__ */ jsx("p", {
12574
12948
  className: "text-kumo-subtle",
12575
- children: _t7({
12949
+ children: _t6({
12576
12950
  id: "Tmb8Fw",
12577
12951
  message: "No bylines available in {entryLocale}. Create a variant from the Bylines page before crediting one on this entry.",
12578
12952
  values: { entryLocale }
@@ -12582,7 +12956,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12582
12956
  search: { locale: entryLocale ?? void 0 },
12583
12957
  variant: "secondary",
12584
12958
  size: "sm",
12585
- children: _t7({
12959
+ children: _t6({
12586
12960
  id: "yXD9HA",
12587
12961
  message: "Manage bylines in {entryLocale}",
12588
12962
  values: { entryLocale }
@@ -12595,18 +12969,18 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12595
12969
  /* @__PURE__ */ jsx(Input, {
12596
12970
  value: search,
12597
12971
  onChange: (e) => setSearch(e.target.value),
12598
- placeholder: _t7({
12972
+ placeholder: _t6({
12599
12973
  id: "96Um6i",
12600
12974
  message: "Search bylines to add..."
12601
12975
  }),
12602
- "aria-label": _t7({
12976
+ "aria-label": _t6({
12603
12977
  id: "4L2LjM",
12604
12978
  message: "Search bylines"
12605
12979
  })
12606
12980
  }),
12607
12981
  searchEnabled && searchResults.isLoading ? /* @__PURE__ */ jsx("p", {
12608
12982
  className: "text-sm text-kumo-subtle",
12609
- children: _t7({
12983
+ children: _t6({
12610
12984
  id: "Du+zn+",
12611
12985
  message: "Searching..."
12612
12986
  })
@@ -12627,7 +13001,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12627
13001
  })]
12628
13002
  }), /* @__PURE__ */ jsx("span", {
12629
13003
  className: "text-xs text-kumo-subtle",
12630
- children: _t7({
13004
+ children: _t6({
12631
13005
  id: "m16xKo",
12632
13006
  message: "Add"
12633
13007
  })
@@ -12635,20 +13009,20 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12635
13009
  }) }, b.id))
12636
13010
  }) : searchEnabled && searchResults.isError ? /* @__PURE__ */ jsx("p", {
12637
13011
  className: "text-sm text-kumo-danger",
12638
- children: _t7({
13012
+ children: _t6({
12639
13013
  id: "617r+T",
12640
13014
  message: "Couldn't search bylines. Please try again."
12641
13015
  })
12642
13016
  }) : searchEnabled ? /* @__PURE__ */ jsx("p", {
12643
13017
  className: "text-sm text-kumo-subtle",
12644
- children: _t7({
13018
+ children: _t6({
12645
13019
  id: "ncgkI9",
12646
13020
  message: "No matching bylines."
12647
13021
  })
12648
13022
  }) : null,
12649
13023
  hasMoreResults && /* @__PURE__ */ jsx("p", {
12650
13024
  className: "text-xs text-kumo-subtle",
12651
- children: _t7({
13025
+ children: _t6({
12652
13026
  id: "7Jpj2/",
12653
13027
  message: "Keep typing to narrow down more bylines."
12654
13028
  })
@@ -12678,7 +13052,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12678
13052
  variant: "ghost",
12679
13053
  size: "sm",
12680
13054
  onClick: () => move(index, -1),
12681
- children: _t7({
13055
+ children: _t6({
12682
13056
  id: "n+PHuv",
12683
13057
  message: "Up"
12684
13058
  })
@@ -12688,7 +13062,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12688
13062
  variant: "ghost",
12689
13063
  size: "sm",
12690
13064
  onClick: () => move(index, 1),
12691
- children: _t7({
13065
+ children: _t6({
12692
13066
  id: "gUjkl/",
12693
13067
  message: "Down"
12694
13068
  })
@@ -12698,7 +13072,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12698
13072
  variant: "ghost",
12699
13073
  size: "sm",
12700
13074
  onClick: () => openEditByline(byline),
12701
- children: _t7({
13075
+ children: _t6({
12702
13076
  id: "ePK91l",
12703
13077
  message: "Edit"
12704
13078
  })
@@ -12708,7 +13082,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12708
13082
  variant: "destructive",
12709
13083
  size: "sm",
12710
13084
  onClick: () => onChange(credits.filter((_, i) => i !== index)),
12711
- children: _t7({
13085
+ children: _t6({
12712
13086
  id: "t/YqKh",
12713
13087
  message: "Remove"
12714
13088
  })
@@ -12716,7 +13090,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12716
13090
  ]
12717
13091
  })]
12718
13092
  }), /* @__PURE__ */ jsx(Input, {
12719
- label: _t7({
13093
+ label: _t6({
12720
13094
  id: "QeScoL",
12721
13095
  message: "Role label"
12722
13096
  }),
@@ -12736,7 +13110,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12736
13110
  })
12737
13111
  }) : /* @__PURE__ */ jsx("p", {
12738
13112
  className: "text-sm text-kumo-subtle",
12739
- children: _t7({
13113
+ children: _t6({
12740
13114
  id: "dyZY24",
12741
13115
  message: "No bylines selected."
12742
13116
  })
@@ -12745,7 +13119,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12745
13119
  ...p,
12746
13120
  type: "button",
12747
13121
  variant: "secondary",
12748
- children: _t7({
13122
+ children: _t6({
12749
13123
  id: "gqa8eq",
12750
13124
  message: "Quick create byline"
12751
13125
  })
@@ -12755,7 +13129,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12755
13129
  children: [
12756
13130
  /* @__PURE__ */ jsx(Dialog.Title, {
12757
13131
  className: "text-lg font-semibold",
12758
- children: _t7({
13132
+ children: _t6({
12759
13133
  id: "d6HWlw",
12760
13134
  message: "Create byline"
12761
13135
  })
@@ -12764,7 +13138,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12764
13138
  className: "mt-4 space-y-3",
12765
13139
  children: [
12766
13140
  /* @__PURE__ */ jsx(Input, {
12767
- label: _t7({
13141
+ label: _t6({
12768
13142
  id: "pfa8F0",
12769
13143
  message: "Display name"
12770
13144
  }),
@@ -12775,7 +13149,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12775
13149
  }
12776
13150
  }),
12777
13151
  /* @__PURE__ */ jsx(Input, {
12778
- label: _t7({
13152
+ label: _t6({
12779
13153
  id: "L85WcV",
12780
13154
  message: "Slug"
12781
13155
  }),
@@ -12797,7 +13171,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12797
13171
  resetQuickCreate();
12798
13172
  p.onClick?.(e);
12799
13173
  },
12800
- children: _t7({
13174
+ children: _t6({
12801
13175
  id: "dEgA5A",
12802
13176
  message: "Cancel"
12803
13177
  })
@@ -12818,7 +13192,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12818
13192
  }]);
12819
13193
  resetQuickCreate();
12820
13194
  } catch (err) {
12821
- setQuickError(err instanceof Error ? err.message : _t7({
13195
+ setQuickError(err instanceof Error ? err.message : _t6({
12822
13196
  id: "Kd9Ewv",
12823
13197
  message: "Failed to create byline"
12824
13198
  }));
@@ -12826,10 +13200,10 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12826
13200
  setIsCreating(false);
12827
13201
  }
12828
13202
  },
12829
- children: isCreating ? _t7({
13203
+ children: isCreating ? _t6({
12830
13204
  id: "HM56Bx",
12831
13205
  message: "Creating..."
12832
- }) : _t7({
13206
+ }) : _t6({
12833
13207
  id: "hYgDIe",
12834
13208
  message: "Create"
12835
13209
  })
@@ -12846,7 +13220,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12846
13220
  children: [
12847
13221
  /* @__PURE__ */ jsx(Dialog.Title, {
12848
13222
  className: "text-lg font-semibold",
12849
- children: _t7({
13223
+ children: _t6({
12850
13224
  id: "d9lVyw",
12851
13225
  message: "Edit byline"
12852
13226
  })
@@ -12855,7 +13229,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12855
13229
  className: "mt-4 space-y-3",
12856
13230
  children: [
12857
13231
  /* @__PURE__ */ jsx(Input, {
12858
- label: _t7({
13232
+ label: _t6({
12859
13233
  id: "pfa8F0",
12860
13234
  message: "Display name"
12861
13235
  }),
@@ -12866,7 +13240,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12866
13240
  }
12867
13241
  }),
12868
13242
  /* @__PURE__ */ jsx(Input, {
12869
- label: _t7({
13243
+ label: _t6({
12870
13244
  id: "L85WcV",
12871
13245
  message: "Slug"
12872
13246
  }),
@@ -12885,7 +13259,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12885
13259
  type: "button",
12886
13260
  variant: "secondary",
12887
13261
  onClick: resetQuickEdit,
12888
- children: _t7({
13262
+ children: _t6({
12889
13263
  id: "dEgA5A",
12890
13264
  message: "Cancel"
12891
13265
  })
@@ -12902,7 +13276,7 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12902
13276
  });
12903
13277
  resetQuickEdit();
12904
13278
  } catch (err) {
12905
- setEditError(err instanceof Error ? err.message : _t7({
13279
+ setEditError(err instanceof Error ? err.message : _t6({
12906
13280
  id: "fd6o+d",
12907
13281
  message: "Failed to update byline"
12908
13282
  }));
@@ -12910,10 +13284,10 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12910
13284
  setIsEditing(false);
12911
13285
  }
12912
13286
  },
12913
- children: isEditing ? _t7({
13287
+ children: isEditing ? _t6({
12914
13288
  id: "XvjC4F",
12915
13289
  message: "Saving..."
12916
- }) : _t7({
13290
+ }) : _t6({
12917
13291
  id: "tfDRzk",
12918
13292
  message: "Save"
12919
13293
  })
@@ -12926,9 +13300,9 @@ function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange
12926
13300
  });
12927
13301
  }
12928
13302
  function AuthorSelector({ authorId, users, onChange }) {
12929
- const { _: _t8 } = useLingui();
13303
+ const { _: _t7 } = useLingui();
12930
13304
  const currentAuthor = users.find((u) => u.id === authorId);
12931
- const authorItems = { unassigned: _t8({
13305
+ const authorItems = { unassigned: _t7({
12932
13306
  id: "EbMPZJ",
12933
13307
  message: "Unassigned"
12934
13308
  }) };
@@ -12943,7 +13317,7 @@ function AuthorSelector({ authorId, users, onChange }) {
12943
13317
  value: "unassigned",
12944
13318
  children: /* @__PURE__ */ jsx("span", {
12945
13319
  className: "text-kumo-subtle",
12946
- children: _t8({
13320
+ children: _t7({
12947
13321
  id: "EbMPZJ",
12948
13322
  message: "Unassigned"
12949
13323
  })
@@ -13023,6 +13397,12 @@ function useI18nConfig(manifest) {
13023
13397
 
13024
13398
  //#endregion
13025
13399
  //#region src/components/ContentList.tsx
13400
+ /** An empty (inactive) date filter, defaulting to the created-at column. */
13401
+ const EMPTY_DATE_FILTER = {
13402
+ field: "createdAt",
13403
+ from: "",
13404
+ to: ""
13405
+ };
13026
13406
  const PAGE_SIZE = 20;
13027
13407
  function getItemTitle$1(item) {
13028
13408
  const rawTitle = item.data.title;
@@ -13032,7 +13412,7 @@ function getItemTitle$1(item) {
13032
13412
  /**
13033
13413
  * Content list view with table display and trash tab
13034
13414
  */
13035
- function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n, activeLocale, onLocaleChange, urlPattern, sort, onSortChange, total, onSearchChange }) {
13415
+ function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n, activeLocale, onLocaleChange, urlPattern, sort, onSortChange, total, onSearchChange, statusFilter = "all", onStatusFilterChange, authors, authorFilter = "", onAuthorFilterChange, dateFilter = EMPTY_DATE_FILTER, onDateFilterChange }) {
13036
13416
  const { _: _t } = useLingui();
13037
13417
  const [activeTab, setActiveTab] = React$1.useState("all");
13038
13418
  const [searchQuery, setSearchQuery] = React$1.useState("");
@@ -13152,6 +13532,15 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
13152
13532
  }]
13153
13533
  }),
13154
13534
  activeTab === "all" ? /* @__PURE__ */ jsxs(Fragment, { children: [
13535
+ onStatusFilterChange && /* @__PURE__ */ jsx(FilterBar, {
13536
+ statusFilter,
13537
+ onStatusFilterChange,
13538
+ authors,
13539
+ authorFilter,
13540
+ onAuthorFilterChange,
13541
+ dateFilter,
13542
+ onDateFilterChange
13543
+ }),
13155
13544
  /* @__PURE__ */ jsx("div", {
13156
13545
  className: "rounded-md border bg-kumo-base overflow-x-auto",
13157
13546
  children: /* @__PURE__ */ jsxs("table", {
@@ -13405,6 +13794,171 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
13405
13794
  });
13406
13795
  }
13407
13796
  /**
13797
+ * Filter controls for the content list: status, author, and a date range over
13798
+ * a chosen timestamp column (#1288). All controls report changes to the
13799
+ * parent, which owns the state and refetches. Filtering happens server-side,
13800
+ * so it works across the whole collection rather than the loaded page.
13801
+ */
13802
+ function FilterBar({ statusFilter, onStatusFilterChange, authors, authorFilter, onAuthorFilterChange, dateFilter, onDateFilterChange }) {
13803
+ const { _: _t2 } = useLingui();
13804
+ const showAuthorFilter = !!onAuthorFilterChange && !!authors && authors.length > 0;
13805
+ const showDateFilter = !!onDateFilterChange;
13806
+ const statusItems = {
13807
+ all: _t2({
13808
+ id: "jBtpMP",
13809
+ message: "All statuses"
13810
+ }),
13811
+ published: _t2({
13812
+ id: "u3wRF+",
13813
+ message: "Published"
13814
+ }),
13815
+ draft: _t2({
13816
+ id: "eneWvv",
13817
+ message: "Draft"
13818
+ }),
13819
+ scheduled: _t2({
13820
+ id: "4ba0NE",
13821
+ message: "Scheduled"
13822
+ }),
13823
+ archived: _t2({
13824
+ id: "TdfEV7",
13825
+ message: "Archived"
13826
+ })
13827
+ };
13828
+ const dateFieldItems = {
13829
+ createdAt: _t2({
13830
+ id: "d+F6q9",
13831
+ message: "Created"
13832
+ }),
13833
+ updatedAt: _t2({
13834
+ id: "+b7T3G",
13835
+ message: "Updated"
13836
+ }),
13837
+ publishedAt: _t2({
13838
+ id: "u3wRF+",
13839
+ message: "Published"
13840
+ })
13841
+ };
13842
+ const hasActiveFilter = statusFilter !== "all" || authorFilter !== "" || !!dateFilter.from || !!dateFilter.to;
13843
+ const handleClear = () => {
13844
+ onStatusFilterChange("all");
13845
+ onAuthorFilterChange?.("");
13846
+ onDateFilterChange?.(EMPTY_DATE_FILTER);
13847
+ };
13848
+ return /* @__PURE__ */ jsxs("div", {
13849
+ className: "flex flex-wrap items-end gap-3",
13850
+ children: [
13851
+ /* @__PURE__ */ jsx(Select, {
13852
+ size: "sm",
13853
+ "aria-label": _t2({
13854
+ id: "KwybLi",
13855
+ message: "Filter by status"
13856
+ }),
13857
+ value: statusFilter,
13858
+ onValueChange: (v) => onStatusFilterChange(v ?? "all"),
13859
+ items: statusItems,
13860
+ children: Object.entries(statusItems).map(([value, label]) => /* @__PURE__ */ jsx(Select.Option, {
13861
+ value,
13862
+ children: label
13863
+ }, value))
13864
+ }),
13865
+ showAuthorFilter && /* @__PURE__ */ jsxs(Select, {
13866
+ size: "sm",
13867
+ "aria-label": _t2({
13868
+ id: "Z7V4V+",
13869
+ message: "Filter by author"
13870
+ }),
13871
+ value: authorFilter,
13872
+ onValueChange: (v) => onAuthorFilterChange?.(v ?? ""),
13873
+ items: {
13874
+ "": _t2({
13875
+ id: "35E2My",
13876
+ message: "All authors"
13877
+ }),
13878
+ ...Object.fromEntries(authors.map((a) => [a.id, a.name || a.email]))
13879
+ },
13880
+ children: [/* @__PURE__ */ jsx(Select.Option, {
13881
+ value: "",
13882
+ children: _t2({
13883
+ id: "35E2My",
13884
+ message: "All authors"
13885
+ })
13886
+ }), authors.map((a) => /* @__PURE__ */ jsx(Select.Option, {
13887
+ value: a.id,
13888
+ children: a.name || a.email
13889
+ }, a.id))]
13890
+ }),
13891
+ showDateFilter && /* @__PURE__ */ jsxs("div", {
13892
+ className: "flex flex-wrap items-end gap-2",
13893
+ children: [
13894
+ /* @__PURE__ */ jsx(Select, {
13895
+ size: "sm",
13896
+ "aria-label": _t2({
13897
+ id: "unVZzy",
13898
+ message: "Date field to filter on"
13899
+ }),
13900
+ value: dateFilter.field,
13901
+ onValueChange: (v) => onDateFilterChange?.({
13902
+ ...dateFilter,
13903
+ field: v ?? "createdAt"
13904
+ }),
13905
+ items: dateFieldItems,
13906
+ children: Object.entries(dateFieldItems).map(([value, label]) => /* @__PURE__ */ jsx(Select.Option, {
13907
+ value,
13908
+ children: label
13909
+ }, value))
13910
+ }),
13911
+ /* @__PURE__ */ jsx(Input, {
13912
+ type: "date",
13913
+ size: "sm",
13914
+ "aria-label": _t2({
13915
+ id: "xRJSOj",
13916
+ message: "From date"
13917
+ }),
13918
+ value: dateFilter.from,
13919
+ max: dateFilter.to || void 0,
13920
+ onChange: (e) => onDateFilterChange?.({
13921
+ ...dateFilter,
13922
+ from: e.target.value
13923
+ })
13924
+ }),
13925
+ /* @__PURE__ */ jsx("span", {
13926
+ className: "pb-2 text-sm text-kumo-subtle",
13927
+ children: _t2({
13928
+ id: "dMtLDE",
13929
+ message: "to"
13930
+ })
13931
+ }),
13932
+ /* @__PURE__ */ jsx(Input, {
13933
+ type: "date",
13934
+ size: "sm",
13935
+ "aria-label": _t2({
13936
+ id: "w4eKlT",
13937
+ message: "To date"
13938
+ }),
13939
+ value: dateFilter.to,
13940
+ min: dateFilter.from || void 0,
13941
+ onChange: (e) => onDateFilterChange?.({
13942
+ ...dateFilter,
13943
+ to: e.target.value
13944
+ })
13945
+ })
13946
+ ]
13947
+ }),
13948
+ hasActiveFilter && /* @__PURE__ */ jsx(Button, {
13949
+ variant: "ghost",
13950
+ size: "sm",
13951
+ onClick: handleClear,
13952
+ icon: /* @__PURE__ */ jsx(X, {}),
13953
+ children: _t2({
13954
+ id: "u8JHrO",
13955
+ message: "Clear filters"
13956
+ })
13957
+ })
13958
+ ]
13959
+ });
13960
+ }
13961
+ /**
13408
13962
  * Table header that doubles as a sort control when the parent opted in by
13409
13963
  * passing `onSortChange`. When no callback is provided we fall back to a
13410
13964
  * plain `<th>` so legacy callers (and screen readers) see exactly the same
@@ -13488,7 +14042,7 @@ function renderItemCount({ searchQuery, filteredCount, total, hasMore, serverSea
13488
14042
  });
13489
14043
  }
13490
14044
  function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale, urlPattern }) {
13491
- const { _: _t2 } = useLingui();
14045
+ const { _: _t3 } = useLingui();
13492
14046
  const title = getItemTitle$1(item);
13493
14047
  const date = new Date(item.updatedAt || item.createdAt);
13494
14048
  return /* @__PURE__ */ jsxs("tr", {
@@ -13502,6 +14056,7 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale,
13502
14056
  collection,
13503
14057
  id: item.id
13504
14058
  },
14059
+ search: { locale: item.locale },
13505
14060
  className: "font-medium hover:text-kumo-brand",
13506
14061
  children: title
13507
14062
  })
@@ -13534,7 +14089,7 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale,
13534
14089
  external: true,
13535
14090
  variant: "ghost",
13536
14091
  shape: "square",
13537
- "aria-label": _t2({
14092
+ "aria-label": _t3({
13538
14093
  id: "fB86ir",
13539
14094
  message: "View published {title}",
13540
14095
  values: { title }
@@ -13547,7 +14102,8 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale,
13547
14102
  collection,
13548
14103
  id: item.id
13549
14104
  },
13550
- "aria-label": _t2({
14105
+ search: { locale: item.locale },
14106
+ "aria-label": _t3({
13551
14107
  id: "8mPwRM",
13552
14108
  message: "Edit {title}",
13553
14109
  values: { title }
@@ -13559,7 +14115,7 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale,
13559
14115
  /* @__PURE__ */ jsx(Button, {
13560
14116
  variant: "ghost",
13561
14117
  shape: "square",
13562
- "aria-label": _t2({
14118
+ "aria-label": _t3({
13563
14119
  id: "JPNGWC",
13564
14120
  message: "Duplicate {title}",
13565
14121
  values: { title }
@@ -13576,7 +14132,7 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale,
13576
14132
  ...p,
13577
14133
  variant: "ghost",
13578
14134
  shape: "square",
13579
- "aria-label": _t2({
14135
+ "aria-label": _t3({
13580
14136
  id: "+ijHsD",
13581
14137
  message: "Move {title} to trash",
13582
14138
  values: { title }
@@ -13591,14 +14147,14 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale,
13591
14147
  children: [
13592
14148
  /* @__PURE__ */ jsx(Dialog.Title, {
13593
14149
  className: "text-lg font-semibold",
13594
- children: _t2({
14150
+ children: _t3({
13595
14151
  id: "eAnjuA",
13596
14152
  message: "Move to Trash?"
13597
14153
  })
13598
14154
  }),
13599
14155
  /* @__PURE__ */ jsx(Dialog.Description, {
13600
14156
  className: "text-kumo-subtle",
13601
- children: _t2({
14157
+ children: _t3({
13602
14158
  id: "Zrd4Eo",
13603
14159
  message: "Move \"{title}\" to trash? You can restore it later.",
13604
14160
  values: { title }
@@ -13609,7 +14165,7 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale,
13609
14165
  children: [/* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
13610
14166
  ...p,
13611
14167
  variant: "secondary",
13612
- children: _t2({
14168
+ children: _t3({
13613
14169
  id: "dEgA5A",
13614
14170
  message: "Cancel"
13615
14171
  })
@@ -13617,7 +14173,7 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale,
13617
14173
  ...p,
13618
14174
  variant: "destructive",
13619
14175
  onClick: () => onDelete?.(item.id),
13620
- children: _t2({
14176
+ children: _t3({
13621
14177
  id: "q3TWG7",
13622
14178
  message: "Move to Trash"
13623
14179
  })
@@ -13633,7 +14189,7 @@ function ContentListItem({ item, collection, onDelete, onDuplicate, showLocale,
13633
14189
  });
13634
14190
  }
13635
14191
  function TrashedListItem({ item, onRestore, onPermanentDelete }) {
13636
- const { _: _t3 } = useLingui();
14192
+ const { _: _t4 } = useLingui();
13637
14193
  const title = getItemTitle$1(item);
13638
14194
  const deletedDate = new Date(item.deletedAt);
13639
14195
  return /* @__PURE__ */ jsxs("tr", {
@@ -13657,7 +14213,7 @@ function TrashedListItem({ item, onRestore, onPermanentDelete }) {
13657
14213
  children: [/* @__PURE__ */ jsx(Button, {
13658
14214
  variant: "ghost",
13659
14215
  shape: "square",
13660
- "aria-label": _t3({
14216
+ "aria-label": _t4({
13661
14217
  id: "+Ss4nB",
13662
14218
  message: "Restore {title}",
13663
14219
  values: { title }
@@ -13673,7 +14229,7 @@ function TrashedListItem({ item, onRestore, onPermanentDelete }) {
13673
14229
  ...p,
13674
14230
  variant: "ghost",
13675
14231
  shape: "square",
13676
- "aria-label": _t3({
14232
+ "aria-label": _t4({
13677
14233
  id: "V7GDXC",
13678
14234
  message: "Permanently delete {title}",
13679
14235
  values: { title }
@@ -13688,14 +14244,14 @@ function TrashedListItem({ item, onRestore, onPermanentDelete }) {
13688
14244
  children: [
13689
14245
  /* @__PURE__ */ jsx(Dialog.Title, {
13690
14246
  className: "text-lg font-semibold",
13691
- children: _t3({
14247
+ children: _t4({
13692
14248
  id: "rXL/I6",
13693
14249
  message: "Delete Permanently?"
13694
14250
  })
13695
14251
  }),
13696
14252
  /* @__PURE__ */ jsx(Dialog.Description, {
13697
14253
  className: "text-kumo-subtle",
13698
- children: _t3({
14254
+ children: _t4({
13699
14255
  id: "7w6TBl",
13700
14256
  message: "Permanently delete \"{title}\"? This cannot be undone.",
13701
14257
  values: { title }
@@ -13706,7 +14262,7 @@ function TrashedListItem({ item, onRestore, onPermanentDelete }) {
13706
14262
  children: [/* @__PURE__ */ jsx(Dialog.Close, { render: (p) => /* @__PURE__ */ jsx(Button, {
13707
14263
  ...p,
13708
14264
  variant: "secondary",
13709
- children: _t3({
14265
+ children: _t4({
13710
14266
  id: "dEgA5A",
13711
14267
  message: "Cancel"
13712
14268
  })
@@ -13714,7 +14270,7 @@ function TrashedListItem({ item, onRestore, onPermanentDelete }) {
13714
14270
  ...p,
13715
14271
  variant: "destructive",
13716
14272
  onClick: () => onPermanentDelete?.(item.id),
13717
- children: _t3({
14273
+ children: _t4({
13718
14274
  id: "UBv8UK",
13719
14275
  message: "Delete Permanently"
13720
14276
  })
@@ -13729,17 +14285,17 @@ function TrashedListItem({ item, onRestore, onPermanentDelete }) {
13729
14285
  });
13730
14286
  }
13731
14287
  function StatusBadge({ status, hasPendingChanges }) {
13732
- const { _: _t4 } = useLingui();
13733
- const statusLabel = status === "published" ? _t4({
14288
+ const { _: _t5 } = useLingui();
14289
+ const statusLabel = status === "published" ? _t5({
13734
14290
  id: "7wML9s",
13735
14291
  message: "published"
13736
- }) : status === "draft" ? _t4({
14292
+ }) : status === "draft" ? _t5({
13737
14293
  id: "eHnlVy",
13738
14294
  message: "draft"
13739
- }) : status === "scheduled" ? _t4({
14295
+ }) : status === "scheduled" ? _t5({
13740
14296
  id: "0p6/4o",
13741
14297
  message: "scheduled"
13742
- }) : status === "archived" ? _t4({
14298
+ }) : status === "archived" ? _t5({
13743
14299
  id: "NXn85g",
13744
14300
  message: "archived"
13745
14301
  }) : status;
@@ -13750,7 +14306,7 @@ function StatusBadge({ status, hasPendingChanges }) {
13750
14306
  children: statusLabel
13751
14307
  }), hasPendingChanges && /* @__PURE__ */ jsx(Badge, {
13752
14308
  variant: "secondary",
13753
- children: _t4({
14309
+ children: _t5({
13754
14310
  id: "S48xcO",
13755
14311
  message: "pending"
13756
14312
  })
@@ -14681,6 +15237,10 @@ function FieldEditor({ open, onOpenChange, field, onSave, isSaving }) {
14681
15237
  url: _t({
14682
15238
  id: "IagCbF",
14683
15239
  message: "URL"
15240
+ }),
15241
+ image: _t({
15242
+ id: "hG89Ed",
15243
+ message: "Image"
14684
15244
  })
14685
15245
  }
14686
15246
  }) })]
@@ -15104,7 +15664,10 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
15104
15664
  }),
15105
15665
  value: labelSingular,
15106
15666
  onChange: (e) => handleSingularLabelChange(e.target.value),
15107
- placeholder: "Post",
15667
+ placeholder: _t({
15668
+ id: "y28hnO",
15669
+ message: "Post"
15670
+ }),
15108
15671
  disabled: isFromCode
15109
15672
  }),
15110
15673
  /* @__PURE__ */ jsx(Input, {
@@ -15114,7 +15677,10 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
15114
15677
  }),
15115
15678
  value: label,
15116
15679
  onChange: (e) => handleLabelChange(e.target.value),
15117
- placeholder: "Posts",
15680
+ placeholder: _t({
15681
+ id: "+owNNn",
15682
+ message: "Posts"
15683
+ }),
15118
15684
  disabled: isFromCode
15119
15685
  }),
15120
15686
  isNew && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input, {
@@ -15177,7 +15743,10 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
15177
15743
  ] }),
15178
15744
  /* @__PURE__ */ jsxs("div", {
15179
15745
  className: "space-y-3",
15180
- children: [/* @__PURE__ */ jsx(Label, { children: "Features" }), SUPPORT_OPTIONS.map((option) => /* @__PURE__ */ jsx("div", {
15746
+ children: [/* @__PURE__ */ jsx(Label, { children: _t({
15747
+ id: "PpZkda",
15748
+ message: "Features"
15749
+ }) }), SUPPORT_OPTIONS.map((option) => /* @__PURE__ */ jsx("div", {
15181
15750
  className: cn("p-2 rounded-md hover:bg-kumo-tint/50", isFromCode && "opacity-60"),
15182
15751
  children: /* @__PURE__ */ jsx(Checkbox, {
15183
15752
  checked: supports.includes(option.value),
@@ -20342,6 +20911,7 @@ function MediaDetailPanel({ item, onClose, onDeleted }) {
20342
20911
  setCaption(item.caption ?? "");
20343
20912
  }
20344
20913
  }, [item]);
20914
+ const fileUrl = item ? new URL(item.url, window.location.origin).href : "";
20345
20915
  const hasChanges = React$1.useMemo(() => {
20346
20916
  if (!item) return false;
20347
20917
  return filename !== item.filename || alt !== (item.alt ?? "") || caption !== (item.caption ?? "");
@@ -20509,6 +21079,28 @@ function MediaDetailPanel({ item, onClose, onDeleted }) {
20509
21079
  }),
20510
21080
  /* @__PURE__ */ jsx("span", { children: formatDate$1(item.createdAt) })
20511
21081
  ]
21082
+ }),
21083
+ /* @__PURE__ */ jsxs("div", {
21084
+ className: "flex items-center gap-2 text-sm",
21085
+ children: [
21086
+ /* @__PURE__ */ jsx(LinkSimple, { className: "h-4 w-4 text-kumo-subtle shrink-0" }),
21087
+ /* @__PURE__ */ jsx("span", {
21088
+ className: "text-kumo-subtle shrink-0",
21089
+ children: _t({
21090
+ id: "PQs7eb",
21091
+ message: "URL:"
21092
+ })
21093
+ }),
21094
+ /* @__PURE__ */ jsx(ClipboardText, {
21095
+ text: fileUrl,
21096
+ size: "sm",
21097
+ className: "min-w-0 flex-1",
21098
+ labels: { copyAction: _t({
21099
+ id: "E6nRW7",
21100
+ message: "Copy URL"
21101
+ }) }
21102
+ })
21103
+ ]
20512
21104
  })
20513
21105
  ]
20514
21106
  }),
@@ -21242,9 +21834,10 @@ function MediaGridItem({ item, selected, onClick }) {
21242
21834
  children: [/* @__PURE__ */ jsx("div", {
21243
21835
  className: "aspect-square",
21244
21836
  children: isImage ? /* @__PURE__ */ jsx("img", {
21245
- src: item.url,
21837
+ src: getMediaThumbnailUrl(item.url, item.mimeType, MEDIA_THUMBNAIL_WIDTH),
21246
21838
  alt: item.alt || item.filename,
21247
- className: "h-full w-full object-cover"
21839
+ className: "h-full w-full object-cover",
21840
+ onError: (e) => fallbackToOriginalThumbnail(e.currentTarget, item.url)
21248
21841
  }) : /* @__PURE__ */ jsx("div", {
21249
21842
  className: "flex h-full w-full items-center justify-center bg-kumo-tint",
21250
21843
  children: /* @__PURE__ */ jsx("span", {
@@ -21312,9 +21905,10 @@ function MediaListItem({ item, selected, onClick }) {
21312
21905
  children: /* @__PURE__ */ jsx("div", {
21313
21906
  className: "h-10 w-10 overflow-hidden rounded",
21314
21907
  children: isImage ? /* @__PURE__ */ jsx("img", {
21315
- src: item.url,
21908
+ src: getMediaThumbnailUrl(item.url, item.mimeType, 80),
21316
21909
  alt: item.alt || item.filename,
21317
- className: "h-full w-full object-cover"
21910
+ className: "h-full w-full object-cover",
21911
+ onError: (e) => fallbackToOriginalThumbnail(e.currentTarget, item.url)
21318
21912
  }) : /* @__PURE__ */ jsx("div", {
21319
21913
  className: "flex h-full w-full items-center justify-center bg-kumo-tint text-xl",
21320
21914
  children: getFileIcon(item.mimeType)
@@ -23704,7 +24298,7 @@ function RegistryPluginDetail({ pluginId, config }) {
23704
24298
  const { data: installedPlugins } = useQuery({
23705
24299
  queryKey: ["plugins"],
23706
24300
  queryFn: async () => {
23707
- const { fetchPlugins } = await import("./plugins-BZzztFdK.js").then((n) => n.a);
24301
+ const { fetchPlugins } = await import("./plugins-G9Z9NQ7n.js").then((n) => n.a);
23708
24302
  return fetchPlugins();
23709
24303
  }
23710
24304
  });
@@ -23767,7 +24361,7 @@ function RegistryPluginDetail({ pluginId, config }) {
23767
24361
  const release = React$1.useMemo(() => releases.find((r) => r.version === effectiveVersion), [releases, effectiveVersion]);
23768
24362
  const isPreRelease = release ? isPreReleaseVersion(release.version) : false;
23769
24363
  const ext = (release?.release?.extensions)?.["com.emdashcms.experimental.package.releaseExtension"];
23770
- const capabilities = Array.isArray(ext?.capabilities) ? canonicalCapabilitiesForDriftCheck(ext?.capabilities) : canonicalCapabilitiesForDriftCheck(declaredAccessToCapabilityList(ext?.declaredAccess));
24364
+ const capabilities = ext?.declaredAccess ? canonicalCapabilitiesForDriftCheck(declaredAccessToCapabilities(ext.declaredAccess).capabilities) : [];
23771
24365
  const pkgProfile = pkg?.profile ?? null;
23772
24366
  const displayName = pkgProfile?.name;
23773
24367
  const description = pkgProfile?.description;
@@ -24352,26 +24946,6 @@ function BackLink() {
24352
24946
  })]
24353
24947
  });
24354
24948
  }
24355
- /**
24356
- * Flatten an RFC-0001 `declaredAccess` block (`{ content: { read: true },
24357
- * email: { send: { allowedHosts: [...] } }, ... }`) into the legacy
24358
- * `capabilities: string[]` shape that the existing sandbox runtime
24359
- * enforces today. One entry per declared operation under each
24360
- * category. Unknown values are skipped silently -- the consent dialog
24361
- * shows only what the current runtime recognises.
24362
- */
24363
- function declaredAccessToCapabilityList(declaredAccess) {
24364
- if (!declaredAccess || typeof declaredAccess !== "object") return [];
24365
- const out = [];
24366
- for (const [category, value] of Object.entries(declaredAccess)) {
24367
- if (!value || typeof value !== "object") continue;
24368
- for (const [operation, opValue] of Object.entries(value)) {
24369
- if (opValue === false) continue;
24370
- out.push(`${category}:${operation}`);
24371
- }
24372
- }
24373
- return out;
24374
- }
24375
24949
  const PRE_RELEASE_VERSION_RE = /^\d+\.\d+\.\d+-/;
24376
24950
  /**
24377
24951
  * Detects semver pre-release identifiers (`1.0.0-alpha.1`, `2.0.0-rc.2`). The
@@ -31188,10 +31762,11 @@ function TermFormDialog({ open, onClose, taxonomyName, taxonomyDef, term, allTer
31188
31762
  }
31189
31763
  },
31190
31764
  children: /* @__PURE__ */ jsx(Dialog, {
31191
- className: "p-6",
31765
+ className: "p-6 max-h-[85vh] flex flex-col",
31192
31766
  size: "lg",
31193
31767
  children: /* @__PURE__ */ jsxs("form", {
31194
31768
  onSubmit: handleSubmit,
31769
+ className: "flex flex-col flex-1 min-h-0",
31195
31770
  children: [
31196
31771
  /* @__PURE__ */ jsxs("div", {
31197
31772
  className: "flex items-start justify-between gap-4 mb-4",
@@ -31251,7 +31826,7 @@ function TermFormDialog({ open, onClose, taxonomyName, taxonomyDef, term, allTer
31251
31826
  })]
31252
31827
  }),
31253
31828
  /* @__PURE__ */ jsxs("div", {
31254
- className: "space-y-4 py-4",
31829
+ className: "space-y-4 py-4 flex-1 overflow-y-auto -mx-1 px-1 min-h-0",
31255
31830
  children: [
31256
31831
  /* @__PURE__ */ jsx(Input, {
31257
31832
  label: _t3({
@@ -36741,6 +37316,53 @@ function FieldRow({ field, isFirst, isLast, onMoveUp, onMoveDown, onEdit, onDele
36741
37316
  });
36742
37317
  }
36743
37318
 
37319
+ //#endregion
37320
+ //#region src/components/BylineAvatarField.tsx
37321
+ /**
37322
+ * Avatar picker for the byline editor (#1250).
37323
+ *
37324
+ * Bylines store only an `avatarMediaId` (the media item's id), not a
37325
+ * URL or embedded media object. This component resolves that id back
37326
+ * into a full media item so it can hand a populated value to the
37327
+ * shared {@link ImageFieldRenderer} — reusing its picker, preview, and
37328
+ * broken-image handling rather than duplicating the markup.
37329
+ *
37330
+ * On change it strips the resolved media back down to the bare id for
37331
+ * the byline update body.
37332
+ */
37333
+ function BylineAvatarField({ value, onChange }) {
37334
+ const { _: _t } = useLingui();
37335
+ const { data: media, isLoading } = useQuery({
37336
+ queryKey: ["media", value],
37337
+ queryFn: () => value ? fetchMediaItem(value) : Promise.resolve(null),
37338
+ enabled: !!value
37339
+ });
37340
+ if (value && isLoading) return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Label, { children: _t({
37341
+ id: "kfcRb0",
37342
+ message: "Avatar"
37343
+ }) }), /* @__PURE__ */ jsx("div", {
37344
+ className: "mt-2 flex h-32 items-center justify-center rounded-lg border",
37345
+ children: /* @__PURE__ */ jsx(Loader, {})
37346
+ })] });
37347
+ const fieldValue = value && media ? {
37348
+ id: media.id,
37349
+ provider: media.provider || "local",
37350
+ previewUrl: media.provider && media.provider !== "local" ? media.url : void 0,
37351
+ alt: media.alt ?? "",
37352
+ width: media.width,
37353
+ height: media.height,
37354
+ meta: media.storageKey ? { storageKey: media.storageKey } : void 0
37355
+ } : void 0;
37356
+ return /* @__PURE__ */ jsx(ImageFieldRenderer, {
37357
+ label: _t({
37358
+ id: "kfcRb0",
37359
+ message: "Avatar"
37360
+ }),
37361
+ value: fieldValue,
37362
+ onChange: (next) => onChange(next?.id ?? null)
37363
+ });
37364
+ }
37365
+
36744
37366
  //#endregion
36745
37367
  //#region src/routes/bylines.tsx
36746
37368
  /**
@@ -36758,6 +37380,7 @@ function toFormState(byline) {
36758
37380
  websiteUrl: "",
36759
37381
  userId: null,
36760
37382
  isGuest: false,
37383
+ avatarMediaId: null,
36761
37384
  customFields: {}
36762
37385
  };
36763
37386
  return {
@@ -36767,6 +37390,7 @@ function toFormState(byline) {
36767
37390
  websiteUrl: byline.websiteUrl ?? "",
36768
37391
  userId: byline.userId,
36769
37392
  isGuest: byline.isGuest,
37393
+ avatarMediaId: byline.avatarMediaId ?? null,
36770
37394
  customFields: byline.customFields ?? {}
36771
37395
  };
36772
37396
  }
@@ -36881,6 +37505,7 @@ function BylinesPage() {
36881
37505
  websiteUrl: form.websiteUrl || null,
36882
37506
  userId: form.userId,
36883
37507
  isGuest: form.isGuest,
37508
+ avatarMediaId: form.avatarMediaId,
36884
37509
  locale: activeLocale
36885
37510
  };
36886
37511
  if (!customFieldsError && Object.keys(form.customFields).length > 0) body.customFields = form.customFields;
@@ -36901,7 +37526,8 @@ function BylinesPage() {
36901
37526
  bio: form.bio || null,
36902
37527
  websiteUrl: form.websiteUrl || null,
36903
37528
  userId: form.userId,
36904
- isGuest: form.isGuest
37529
+ isGuest: form.isGuest,
37530
+ avatarMediaId: form.avatarMediaId
36905
37531
  };
36906
37532
  if (!customFieldsError) body.customFields = form.customFields;
36907
37533
  return updateByline(selectedId, body);
@@ -37149,6 +37775,13 @@ function BylinesPage() {
37149
37775
  })),
37150
37776
  rows: 5
37151
37777
  }),
37778
+ /* @__PURE__ */ jsx(BylineAvatarField, {
37779
+ value: form.avatarMediaId,
37780
+ onChange: (mediaId) => setForm((prev) => ({
37781
+ ...prev,
37782
+ avatarMediaId: mediaId
37783
+ }))
37784
+ }),
37152
37785
  /* @__PURE__ */ jsx(Select, {
37153
37786
  label: _t({
37154
37787
  id: "nti0N3",
@@ -38601,7 +39234,7 @@ function UsersPage() {
38601
39234
  * Defines all admin routes and their components.
38602
39235
  */
38603
39236
  function patchAutosaveQueries(queryClient, params) {
38604
- const { collection, id, savedItem, payload } = params;
39237
+ const { collection, id, savedItem, payload, locale } = params;
38605
39238
  const draftRevisionId = savedItem.draftRevisionId;
38606
39239
  if (draftRevisionId) queryClient.setQueryData(["revision", draftRevisionId], (existing) => {
38607
39240
  const nextData = {
@@ -38618,7 +39251,12 @@ function patchAutosaveQueries(queryClient, params) {
38618
39251
  createdAt: existing?.createdAt ?? savedItem.updatedAt
38619
39252
  };
38620
39253
  });
38621
- queryClient.setQueryData([
39254
+ queryClient.setQueryData(locale ? [
39255
+ "content",
39256
+ collection,
39257
+ id,
39258
+ { locale }
39259
+ ] : [
38622
39260
  "content",
38623
39261
  collection,
38624
39262
  id
@@ -38716,6 +39354,26 @@ function ContentListPage() {
38716
39354
  direction: "desc"
38717
39355
  });
38718
39356
  const [searchTerm, setSearchTerm] = React$1.useState("");
39357
+ const [statusFilter, setStatusFilter] = React$1.useState("all");
39358
+ const [authorFilter, setAuthorFilter] = React$1.useState("");
39359
+ const [dateFilter, setDateFilter] = React$1.useState(EMPTY_DATE_FILTER);
39360
+ const dateApiParams = React$1.useMemo(() => {
39361
+ if (!(!!dateFilter.from || !!dateFilter.to)) return void 0;
39362
+ return {
39363
+ dateField: dateFilter.field,
39364
+ dateFrom: dateFilter.from ? `${dateFilter.from}T00:00:00.000Z` : void 0,
39365
+ dateTo: dateFilter.to ? `${dateFilter.to}T23:59:59.999Z` : void 0
39366
+ };
39367
+ }, [dateFilter]);
39368
+ const { data: authors } = useQuery({
39369
+ queryKey: [
39370
+ "content",
39371
+ collection,
39372
+ "authors"
39373
+ ],
39374
+ queryFn: () => fetchContentAuthors(collection),
39375
+ enabled: !!manifest
39376
+ });
38719
39377
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, error } = useInfiniteQuery({
38720
39378
  queryKey: [
38721
39379
  "content",
@@ -38723,7 +39381,10 @@ function ContentListPage() {
38723
39381
  {
38724
39382
  locale: activeLocale,
38725
39383
  sort,
38726
- search: searchTerm
39384
+ search: searchTerm,
39385
+ status: statusFilter,
39386
+ author: authorFilter,
39387
+ date: dateApiParams
38727
39388
  }
38728
39389
  ],
38729
39390
  queryFn: ({ pageParam }) => fetchContentList(collection, {
@@ -38732,7 +39393,10 @@ function ContentListPage() {
38732
39393
  limit: 100,
38733
39394
  orderBy: sort.field,
38734
39395
  order: sort.direction,
38735
- search: searchTerm || void 0
39396
+ search: searchTerm || void 0,
39397
+ status: statusFilter === "all" ? void 0 : statusFilter,
39398
+ authorId: authorFilter || void 0,
39399
+ ...dateApiParams
38736
39400
  }),
38737
39401
  initialPageParam: void 0,
38738
39402
  getNextPageParam: (lastPage) => lastPage.nextCursor,
@@ -38872,7 +39536,14 @@ function ContentListPage() {
38872
39536
  sort,
38873
39537
  onSortChange: setSort,
38874
39538
  total,
38875
- onSearchChange: setSearchTerm
39539
+ onSearchChange: setSearchTerm,
39540
+ statusFilter,
39541
+ onStatusFilterChange: setStatusFilter,
39542
+ authors,
39543
+ authorFilter,
39544
+ onAuthorFilterChange: setAuthorFilter,
39545
+ dateFilter,
39546
+ onDateFilterChange: setDateFilter
38876
39547
  });
38877
39548
  }
38878
39549
  const contentNewRoute = createRoute({
@@ -38904,7 +39575,8 @@ function ContentNewPage() {
38904
39575
  params: {
38905
39576
  collection,
38906
39577
  id: result.id
38907
- }
39578
+ },
39579
+ search: { locale: result.locale }
38908
39580
  });
38909
39581
  }
38910
39582
  });
@@ -38976,7 +39648,10 @@ const contentEditRoute = createRoute({
38976
39648
  getParentRoute: () => adminLayoutRoute,
38977
39649
  path: "/content/$collection/$id",
38978
39650
  component: ContentEditPage,
38979
- validateSearch: (search) => ({ ...typeof search.field === "string" && { field: search.field } })
39651
+ validateSearch: (search) => ({
39652
+ ...typeof search.field === "string" && { field: search.field },
39653
+ ...typeof search.locale === "string" && { locale: search.locale }
39654
+ })
38980
39655
  });
38981
39656
  const ROLE_EDITOR = 40;
38982
39657
  function ContentEditPage() {
@@ -38991,13 +39666,16 @@ function ContentEditPage() {
38991
39666
  queryFn: fetchManifest
38992
39667
  });
38993
39668
  const i18n = manifest?.i18n;
39669
+ const activeLocale = i18n ? searchParams.locale ?? i18n.defaultLocale : void 0;
38994
39670
  const { data: rawItem, isLoading } = useQuery({
38995
39671
  queryKey: [
38996
39672
  "content",
38997
39673
  collection,
38998
- id
39674
+ id,
39675
+ { locale: activeLocale }
38999
39676
  ],
39000
- queryFn: () => fetchContent(collection, id)
39677
+ queryFn: () => fetchContent(collection, id, { locale: activeLocale }),
39678
+ enabled: !i18n || !!activeLocale
39001
39679
  });
39002
39680
  React$1.useEffect(() => {
39003
39681
  if (typeof searchParams.field !== "string" || isLoading) return;
@@ -39100,12 +39778,13 @@ function ContentEditPage() {
39100
39778
  }
39101
39779
  });
39102
39780
  const updateMutation = useMutation({
39103
- mutationFn: (data) => updateContent(collection, id, data),
39781
+ mutationFn: (data) => updateContent(collection, id, data, { locale: rawItem?.locale ?? activeLocale }),
39104
39782
  onSuccess: () => {
39105
39783
  queryClient.invalidateQueries({ queryKey: [
39106
39784
  "content",
39107
39785
  collection,
39108
- id
39786
+ id,
39787
+ { locale: rawItem?.locale ?? activeLocale }
39109
39788
  ] });
39110
39789
  queryClient.invalidateQueries({ queryKey: [
39111
39790
  "revisions",
@@ -39133,7 +39812,7 @@ function ContentEditPage() {
39133
39812
  mutationFn: (data) => updateContent(collection, id, {
39134
39813
  ...data,
39135
39814
  skipRevision: true
39136
- }),
39815
+ }, { locale: rawItem?.locale ?? activeLocale }),
39137
39816
  onSuccess: (savedItem, variables) => {
39138
39817
  patchAutosaveQueries(queryClient, {
39139
39818
  collection,
@@ -39142,7 +39821,8 @@ function ContentEditPage() {
39142
39821
  payload: {
39143
39822
  data: variables.data,
39144
39823
  slug: variables.slug
39145
- }
39824
+ },
39825
+ locale: rawItem?.locale ?? activeLocale
39146
39826
  });
39147
39827
  setLastAutosaveAt(/* @__PURE__ */ new Date());
39148
39828
  },
@@ -39161,12 +39841,13 @@ function ContentEditPage() {
39161
39841
  }
39162
39842
  });
39163
39843
  const publishMutation = useMutation({
39164
- mutationFn: () => publishContent(collection, id),
39844
+ mutationFn: () => publishContent(collection, id, { locale: rawItem?.locale ?? activeLocale }),
39165
39845
  onSuccess: () => {
39166
39846
  queryClient.invalidateQueries({ queryKey: [
39167
39847
  "content",
39168
39848
  collection,
39169
- id
39849
+ id,
39850
+ { locale: rawItem?.locale ?? activeLocale }
39170
39851
  ] });
39171
39852
  queryClient.invalidateQueries({ queryKey: [
39172
39853
  "revisions",
@@ -39199,12 +39880,13 @@ function ContentEditPage() {
39199
39880
  }
39200
39881
  });
39201
39882
  const unpublishMutation = useMutation({
39202
- mutationFn: () => unpublishContent(collection, id),
39883
+ mutationFn: () => unpublishContent(collection, id, { locale: rawItem?.locale ?? activeLocale }),
39203
39884
  onSuccess: () => {
39204
39885
  queryClient.invalidateQueries({ queryKey: [
39205
39886
  "content",
39206
39887
  collection,
39207
- id
39888
+ id,
39889
+ { locale: rawItem?.locale ?? activeLocale }
39208
39890
  ] });
39209
39891
  queryClient.invalidateQueries({ queryKey: [
39210
39892
  "revisions",
@@ -39237,12 +39919,13 @@ function ContentEditPage() {
39237
39919
  }
39238
39920
  });
39239
39921
  const discardDraftMutation = useMutation({
39240
- mutationFn: () => discardDraft(collection, id),
39922
+ mutationFn: () => discardDraft(collection, id, { locale: rawItem?.locale ?? activeLocale }),
39241
39923
  onSuccess: () => {
39242
39924
  queryClient.invalidateQueries({ queryKey: [
39243
39925
  "content",
39244
39926
  collection,
39245
- id
39927
+ id,
39928
+ { locale: rawItem?.locale ?? activeLocale }
39246
39929
  ] });
39247
39930
  queryClient.invalidateQueries({ queryKey: [
39248
39931
  "revisions",
@@ -39275,12 +39958,13 @@ function ContentEditPage() {
39275
39958
  }
39276
39959
  });
39277
39960
  const scheduleMutation = useMutation({
39278
- mutationFn: (scheduledAt) => scheduleContent(collection, id, scheduledAt),
39961
+ mutationFn: (scheduledAt) => scheduleContent(collection, id, scheduledAt, { locale: rawItem?.locale ?? activeLocale }),
39279
39962
  onSuccess: () => {
39280
39963
  queryClient.invalidateQueries({ queryKey: [
39281
39964
  "content",
39282
39965
  collection,
39283
- id
39966
+ id,
39967
+ { locale: rawItem?.locale ?? activeLocale }
39284
39968
  ] });
39285
39969
  toastManager.add({
39286
39970
  title: _t3({
@@ -39308,12 +39992,13 @@ function ContentEditPage() {
39308
39992
  }
39309
39993
  });
39310
39994
  const unscheduleMutation = useMutation({
39311
- mutationFn: () => unscheduleContent(collection, id),
39995
+ mutationFn: () => unscheduleContent(collection, id, { locale: rawItem?.locale ?? activeLocale }),
39312
39996
  onSuccess: () => {
39313
39997
  queryClient.invalidateQueries({ queryKey: [
39314
39998
  "content",
39315
39999
  collection,
39316
- id
40000
+ id,
40001
+ { locale: rawItem?.locale ?? activeLocale }
39317
40002
  ] });
39318
40003
  toastManager.add({
39319
40004
  title: _t3({
@@ -39359,7 +40044,8 @@ function ContentEditPage() {
39359
40044
  params: {
39360
40045
  collection,
39361
40046
  id: result.id
39362
- }
40047
+ },
40048
+ search: { locale: result.locale }
39363
40049
  });
39364
40050
  toastManager.add({
39365
40051
  title: _t3({
@@ -39391,7 +40077,7 @@ function ContentEditPage() {
39391
40077
  }
39392
40078
  });
39393
40079
  const deleteMutation = useMutation({
39394
- mutationFn: () => deleteContent(collection, id),
40080
+ mutationFn: () => deleteContent(collection, id, { locale: rawItem?.locale ?? activeLocale }),
39395
40081
  onSuccess: () => {
39396
40082
  queryClient.invalidateQueries({ queryKey: ["content", collection] });
39397
40083
  queryClient.invalidateQueries({ queryKey: [
@@ -39402,7 +40088,7 @@ function ContentEditPage() {
39402
40088
  navigate({
39403
40089
  to: "/content/$collection",
39404
40090
  params: { collection },
39405
- search: { locale: void 0 }
40091
+ search: { locale: activeLocale }
39406
40092
  });
39407
40093
  },
39408
40094
  onError: (error) => {
@@ -39769,7 +40455,7 @@ function MarketplaceBrowsePage() {
39769
40455
  const { data: plugins } = useQuery({
39770
40456
  queryKey: ["plugins"],
39771
40457
  queryFn: async () => {
39772
- const { fetchPlugins } = await import("./plugins-BZzztFdK.js").then((n) => n.a);
40458
+ const { fetchPlugins } = await import("./plugins-G9Z9NQ7n.js").then((n) => n.a);
39773
40459
  return fetchPlugins();
39774
40460
  }
39775
40461
  });
@@ -39800,7 +40486,7 @@ function MarketplaceDetailPage() {
39800
40486
  const { data: plugins } = useQuery({
39801
40487
  queryKey: ["plugins"],
39802
40488
  queryFn: async () => {
39803
- const { fetchPlugins } = await import("./plugins-BZzztFdK.js").then((n) => n.a);
40489
+ const { fetchPlugins } = await import("./plugins-G9Z9NQ7n.js").then((n) => n.a);
39804
40490
  return fetchPlugins();
39805
40491
  }
39806
40492
  });
@@ -40283,5 +40969,5 @@ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, authProviders = EMPTY_AUTH_PRO
40283
40969
  }
40284
40970
 
40285
40971
  //#endregion
40286
- 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, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, 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 };
40972
+ 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 };
40287
40973
  //# sourceMappingURL=index.js.map