@sonicjs-cms/core 2.8.3 → 2.10.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 (87) hide show
  1. package/dist/{app-DnQ26Lho.d.cts → app-Ozl9agJG.d.cts} +1 -1
  2. package/dist/{app-DnQ26Lho.d.ts → app-Ozl9agJG.d.ts} +1 -1
  3. package/dist/{chunk-YFJJU26H.js → chunk-27AOVQTR.js} +10 -2
  4. package/dist/chunk-27AOVQTR.js.map +1 -0
  5. package/dist/{chunk-Y3VMEGY2.js → chunk-4TTMQQC7.js} +4 -4
  6. package/dist/{chunk-Y3VMEGY2.js.map → chunk-4TTMQQC7.js.map} +1 -1
  7. package/dist/{chunk-VNLR35GO.cjs → chunk-64APW3DW.cjs} +339 -2
  8. package/dist/chunk-64APW3DW.cjs.map +1 -0
  9. package/dist/{chunk-GTFMI24U.js → chunk-6O3RJV3C.js} +2 -2
  10. package/dist/{chunk-GTFMI24U.js.map → chunk-6O3RJV3C.js.map} +1 -1
  11. package/dist/{chunk-G44QUVNM.js → chunk-7JMMLHPQ.js} +337 -4
  12. package/dist/chunk-7JMMLHPQ.js.map +1 -0
  13. package/dist/chunk-CJYFSKH7.js +54 -54
  14. package/dist/chunk-CJYFSKH7.js.map +1 -1
  15. package/dist/{chunk-JDIM5AG7.cjs → chunk-EKPLKUZT.cjs} +11 -5
  16. package/dist/chunk-EKPLKUZT.cjs.map +1 -0
  17. package/dist/{chunk-MPT5PA6U.cjs → chunk-IIBRG5S5.cjs} +10 -2
  18. package/dist/chunk-IIBRG5S5.cjs.map +1 -0
  19. package/dist/{chunk-K4Q4SFJJ.cjs → chunk-IT2TC4ZD.cjs} +7 -7
  20. package/dist/{chunk-K4Q4SFJJ.cjs.map → chunk-IT2TC4ZD.cjs.map} +1 -1
  21. package/dist/{chunk-5XAI2XUF.js → chunk-IZWNIUJI.js} +11 -5
  22. package/dist/chunk-IZWNIUJI.js.map +1 -0
  23. package/dist/{chunk-CH5UHZVM.js → chunk-JTNUM7JE.js} +1218 -442
  24. package/dist/chunk-JTNUM7JE.js.map +1 -0
  25. package/dist/chunk-MNFY6DWY.cjs +54 -54
  26. package/dist/chunk-MNFY6DWY.cjs.map +1 -1
  27. package/dist/{chunk-R4WR3VTN.cjs → chunk-RCA6R6VE.cjs} +1329 -553
  28. package/dist/chunk-RCA6R6VE.cjs.map +1 -0
  29. package/dist/{chunk-HXHVU5GM.cjs → chunk-ZMVWMJ3S.cjs} +2 -2
  30. package/dist/{chunk-HXHVU5GM.cjs.map → chunk-ZMVWMJ3S.cjs.map} +1 -1
  31. package/dist/{collection-config-i8EaAF7z.d.cts → collection-config-B4PG-AaF.d.cts} +4 -2
  32. package/dist/{collection-config-i8EaAF7z.d.ts → collection-config-B4PG-AaF.d.ts} +4 -2
  33. package/dist/{filter-bar.template-Daw8ZDoq.d.cts → filter-bar.template-DlVYMk-T.d.cts} +1 -1
  34. package/dist/{filter-bar.template-Daw8ZDoq.d.ts → filter-bar.template-DlVYMk-T.d.ts} +1 -1
  35. package/dist/index.cjs +142 -141
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +8 -8
  38. package/dist/index.d.ts +8 -8
  39. package/dist/index.js +11 -10
  40. package/dist/index.js.map +1 -1
  41. package/dist/middleware.cjs +29 -29
  42. package/dist/middleware.d.cts +1 -1
  43. package/dist/middleware.d.ts +1 -1
  44. package/dist/middleware.js +3 -3
  45. package/dist/migrations-N2C2VPJU.js +4 -0
  46. package/dist/{migrations-KHWFJ2HN.js.map → migrations-N2C2VPJU.js.map} +1 -1
  47. package/dist/migrations-ONIAY6GK.cjs +13 -0
  48. package/dist/{migrations-7X4RPH7O.cjs.map → migrations-ONIAY6GK.cjs.map} +1 -1
  49. package/dist/{plugin-zvZpaiP5.d.cts → plugin-0Xogrln-.d.cts} +1 -1
  50. package/dist/{plugin-zvZpaiP5.d.ts → plugin-0Xogrln-.d.ts} +1 -1
  51. package/dist/{plugin-bootstrap-CJozpgmI.d.cts → plugin-bootstrap-WmpvYM5w.d.ts} +2 -2
  52. package/dist/{plugin-bootstrap-DU5VmuHZ.d.ts → plugin-bootstrap-fpG98Otb.d.cts} +2 -2
  53. package/dist/{plugin-manager-Baa6xXqB.d.ts → plugin-manager-Clf2gXwj.d.ts} +2 -2
  54. package/dist/{plugin-manager-vBal9Zip.d.cts → plugin-manager-GcIeb226.d.cts} +2 -2
  55. package/dist/plugins.d.cts +2 -2
  56. package/dist/plugins.d.ts +2 -2
  57. package/dist/routes.cjs +29 -29
  58. package/dist/routes.d.cts +1 -1
  59. package/dist/routes.d.ts +1 -1
  60. package/dist/routes.js +6 -6
  61. package/dist/services.cjs +44 -28
  62. package/dist/services.d.cts +29 -4
  63. package/dist/services.d.ts +29 -4
  64. package/dist/services.js +3 -3
  65. package/dist/{telemetry-UiD1i9GS.d.cts → telemetry-B9vIV4wh.d.cts} +1 -1
  66. package/dist/{telemetry-UiD1i9GS.d.ts → telemetry-B9vIV4wh.d.ts} +1 -1
  67. package/dist/templates.d.cts +1 -1
  68. package/dist/templates.d.ts +1 -1
  69. package/dist/types.d.cts +3 -3
  70. package/dist/types.d.ts +3 -3
  71. package/dist/utils.cjs +11 -11
  72. package/dist/utils.d.cts +3 -3
  73. package/dist/utils.d.ts +3 -3
  74. package/dist/utils.js +1 -1
  75. package/dist/{version-C_CXrN_T.d.cts → version-ChpccWQ1.d.cts} +1 -1
  76. package/dist/{version-C_CXrN_T.d.ts → version-ChpccWQ1.d.ts} +1 -1
  77. package/package.json +9 -3
  78. package/dist/chunk-5XAI2XUF.js.map +0 -1
  79. package/dist/chunk-CH5UHZVM.js.map +0 -1
  80. package/dist/chunk-G44QUVNM.js.map +0 -1
  81. package/dist/chunk-JDIM5AG7.cjs.map +0 -1
  82. package/dist/chunk-MPT5PA6U.cjs.map +0 -1
  83. package/dist/chunk-R4WR3VTN.cjs.map +0 -1
  84. package/dist/chunk-VNLR35GO.cjs.map +0 -1
  85. package/dist/chunk-YFJJU26H.js.map +0 -1
  86. package/dist/migrations-7X4RPH7O.cjs +0 -13
  87. package/dist/migrations-KHWFJ2HN.js +0 -4
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var chunkVNLR35GO_cjs = require('./chunk-VNLR35GO.cjs');
4
- var chunkK4Q4SFJJ_cjs = require('./chunk-K4Q4SFJJ.cjs');
5
- var chunkMPT5PA6U_cjs = require('./chunk-MPT5PA6U.cjs');
6
- var chunkHXHVU5GM_cjs = require('./chunk-HXHVU5GM.cjs');
3
+ var chunk64APW3DW_cjs = require('./chunk-64APW3DW.cjs');
4
+ var chunkIT2TC4ZD_cjs = require('./chunk-IT2TC4ZD.cjs');
5
+ var chunkIIBRG5S5_cjs = require('./chunk-IIBRG5S5.cjs');
6
+ var chunkZMVWMJ3S_cjs = require('./chunk-ZMVWMJ3S.cjs');
7
7
  var chunkLTKV7AE5_cjs = require('./chunk-LTKV7AE5.cjs');
8
8
  var chunk6FHNRRJ3_cjs = require('./chunk-6FHNRRJ3.cjs');
9
- var chunkJDIM5AG7_cjs = require('./chunk-JDIM5AG7.cjs');
9
+ var chunkEKPLKUZT_cjs = require('./chunk-EKPLKUZT.cjs');
10
10
  var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
11
11
  var chunkMNWKYY5E_cjs = require('./chunk-MNWKYY5E.cjs');
12
12
  var hono = require('hono');
@@ -121,7 +121,7 @@ apiContentCrudRoutes.get("/:id", async (c) => {
121
121
  }, 500);
122
122
  }
123
123
  });
124
- apiContentCrudRoutes.post("/", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
124
+ apiContentCrudRoutes.post("/", chunkIT2TC4ZD_cjs.requireAuth(), chunkIT2TC4ZD_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
125
125
  try {
126
126
  const db = c.env.DB;
127
127
  const user = c.get("user");
@@ -162,7 +162,7 @@ apiContentCrudRoutes.post("/", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
162
162
  now,
163
163
  now
164
164
  ).run();
165
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.api);
165
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.api);
166
166
  await cache.invalidate(`content:list:${collectionId}:*`);
167
167
  await cache.invalidate("content-filtered:*");
168
168
  const getStmt = db.prepare("SELECT * FROM content WHERE id = ?");
@@ -187,7 +187,7 @@ apiContentCrudRoutes.post("/", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
187
187
  }, 500);
188
188
  }
189
189
  });
190
- apiContentCrudRoutes.put("/:id", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
190
+ apiContentCrudRoutes.put("/:id", chunkIT2TC4ZD_cjs.requireAuth(), chunkIT2TC4ZD_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
191
191
  try {
192
192
  const id = c.req.param("id");
193
193
  const db = c.env.DB;
@@ -225,7 +225,7 @@ apiContentCrudRoutes.put("/:id", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
225
225
  WHERE id = ?
226
226
  `);
227
227
  await updateStmt.bind(...params).run();
228
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.api);
228
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.api);
229
229
  await cache.delete(cache.generateKey("content", id));
230
230
  await cache.invalidate(`content:list:${existing.collection_id}:*`);
231
231
  await cache.invalidate("content-filtered:*");
@@ -251,7 +251,7 @@ apiContentCrudRoutes.put("/:id", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
251
251
  }, 500);
252
252
  }
253
253
  });
254
- apiContentCrudRoutes.delete("/:id", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
254
+ apiContentCrudRoutes.delete("/:id", chunkIT2TC4ZD_cjs.requireAuth(), chunkIT2TC4ZD_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
255
255
  try {
256
256
  const id = c.req.param("id");
257
257
  const db = c.env.DB;
@@ -262,7 +262,7 @@ apiContentCrudRoutes.delete("/:id", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) =
262
262
  }
263
263
  const deleteStmt = db.prepare("DELETE FROM content WHERE id = ?");
264
264
  await deleteStmt.bind(id).run();
265
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.api);
265
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.api);
266
266
  await cache.delete(cache.generateKey("content", id));
267
267
  await cache.invalidate(`content:list:${existing.collection_id}:*`);
268
268
  await cache.invalidate("content-filtered:*");
@@ -287,7 +287,7 @@ apiRoutes.use("*", async (c, next) => {
287
287
  c.header("X-Response-Time", `${totalTime}ms`);
288
288
  });
289
289
  apiRoutes.use("*", async (c, next) => {
290
- const cacheEnabled = await chunkK4Q4SFJJ_cjs.isPluginActive(c.env.DB, "core-cache");
290
+ const cacheEnabled = await chunkIT2TC4ZD_cjs.isPluginActive(c.env.DB, "core-cache");
291
291
  c.set("cacheEnabled", cacheEnabled);
292
292
  await next();
293
293
  });
@@ -724,7 +724,7 @@ apiRoutes.get("/collections", async (c) => {
724
724
  try {
725
725
  const db = c.env.DB;
726
726
  const cacheEnabled = c.get("cacheEnabled");
727
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.api);
727
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.api);
728
728
  const cacheKey = cache.generateKey("collections", "all");
729
729
  if (cacheEnabled) {
730
730
  const cacheResult = await cache.getWithSource(cacheKey);
@@ -778,7 +778,7 @@ apiRoutes.get("/collections", async (c) => {
778
778
  return c.json({ error: "Failed to fetch collections" }, 500);
779
779
  }
780
780
  });
781
- apiRoutes.get("/content", chunkK4Q4SFJJ_cjs.optionalAuth(), async (c) => {
781
+ apiRoutes.get("/content", chunkIT2TC4ZD_cjs.optionalAuth(), async (c) => {
782
782
  const executionStart = Date.now();
783
783
  try {
784
784
  const db = c.env.DB;
@@ -801,13 +801,13 @@ apiRoutes.get("/content", chunkK4Q4SFJJ_cjs.optionalAuth(), async (c) => {
801
801
  });
802
802
  }
803
803
  }
804
- const filter = chunkJDIM5AG7_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
804
+ const filter = chunkEKPLKUZT_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
805
805
  const normalizedFilter = normalizePublicContentFilter(filter, c.get("user")?.role);
806
806
  if (!normalizedFilter.limit) {
807
807
  normalizedFilter.limit = 50;
808
808
  }
809
809
  normalizedFilter.limit = Math.min(normalizedFilter.limit, 1e3);
810
- const builder3 = new chunkJDIM5AG7_cjs.QueryFilterBuilder();
810
+ const builder3 = new chunkEKPLKUZT_cjs.QueryFilterBuilder();
811
811
  const queryResult = builder3.build("content", normalizedFilter);
812
812
  if (queryResult.errors.length > 0) {
813
813
  return c.json({
@@ -816,7 +816,7 @@ apiRoutes.get("/content", chunkK4Q4SFJJ_cjs.optionalAuth(), async (c) => {
816
816
  }, 400);
817
817
  }
818
818
  const cacheEnabled = c.get("cacheEnabled");
819
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.api);
819
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.api);
820
820
  const cacheKey = cache.generateKey("content-filtered", JSON.stringify({ filter: normalizedFilter, query: queryResult.sql }));
821
821
  if (cacheEnabled) {
822
822
  const cacheResult = await cache.getWithSource(cacheKey);
@@ -879,7 +879,7 @@ apiRoutes.get("/content", chunkK4Q4SFJJ_cjs.optionalAuth(), async (c) => {
879
879
  }, 500);
880
880
  }
881
881
  });
882
- apiRoutes.get("/collections/:collection/content", chunkK4Q4SFJJ_cjs.optionalAuth(), async (c) => {
882
+ apiRoutes.get("/collections/:collection/content", chunkIT2TC4ZD_cjs.optionalAuth(), async (c) => {
883
883
  const executionStart = Date.now();
884
884
  try {
885
885
  const collection = c.req.param("collection");
@@ -890,7 +890,7 @@ apiRoutes.get("/collections/:collection/content", chunkK4Q4SFJJ_cjs.optionalAuth
890
890
  if (!collectionResult) {
891
891
  return c.json({ error: "Collection not found" }, 404);
892
892
  }
893
- const filter = chunkJDIM5AG7_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
893
+ const filter = chunkEKPLKUZT_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
894
894
  const normalizedFilter = normalizePublicContentFilter(filter, c.get("user")?.role);
895
895
  if (!normalizedFilter.where) {
896
896
  normalizedFilter.where = { and: [] };
@@ -907,7 +907,7 @@ apiRoutes.get("/collections/:collection/content", chunkK4Q4SFJJ_cjs.optionalAuth
907
907
  normalizedFilter.limit = 50;
908
908
  }
909
909
  normalizedFilter.limit = Math.min(normalizedFilter.limit, 1e3);
910
- const builder3 = new chunkJDIM5AG7_cjs.QueryFilterBuilder();
910
+ const builder3 = new chunkEKPLKUZT_cjs.QueryFilterBuilder();
911
911
  const queryResult = builder3.build("content", normalizedFilter);
912
912
  if (queryResult.errors.length > 0) {
913
913
  return c.json({
@@ -916,7 +916,7 @@ apiRoutes.get("/collections/:collection/content", chunkK4Q4SFJJ_cjs.optionalAuth
916
916
  }, 400);
917
917
  }
918
918
  const cacheEnabled = c.get("cacheEnabled");
919
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.api);
919
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.api);
920
920
  const cacheKey = cache.generateKey("collection-content-filtered", `${collection}:${JSON.stringify({ filter: normalizedFilter, query: queryResult.sql })}`);
921
921
  if (cacheEnabled) {
922
922
  const cacheResult = await cache.getWithSource(cacheKey);
@@ -1028,7 +1028,7 @@ var fileValidationSchema = zod.z.object({
1028
1028
  // 50MB max
1029
1029
  });
1030
1030
  var apiMediaRoutes = new hono.Hono();
1031
- apiMediaRoutes.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
1031
+ apiMediaRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
1032
1032
  apiMediaRoutes.post("/upload", async (c) => {
1033
1033
  try {
1034
1034
  const user = c.get("user");
@@ -1772,8 +1772,8 @@ apiSystemRoutes.get("/env", (c) => {
1772
1772
  });
1773
1773
  var api_system_default = apiSystemRoutes;
1774
1774
  var adminApiRoutes = new hono.Hono();
1775
- adminApiRoutes.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
1776
- adminApiRoutes.use("*", chunkK4Q4SFJJ_cjs.requireRole(["admin", "editor"]));
1775
+ adminApiRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
1776
+ adminApiRoutes.use("*", chunkIT2TC4ZD_cjs.requireRole(["admin", "editor"]));
1777
1777
  adminApiRoutes.get("/stats", async (c) => {
1778
1778
  try {
1779
1779
  const db = c.env.DB;
@@ -2283,7 +2283,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
2283
2283
  });
2284
2284
  adminApiRoutes.get("/migrations/status", async (c) => {
2285
2285
  try {
2286
- const { MigrationService: MigrationService2 } = await import('./migrations-7X4RPH7O.cjs');
2286
+ const { MigrationService: MigrationService2 } = await import('./migrations-ONIAY6GK.cjs');
2287
2287
  const db = c.env.DB;
2288
2288
  const migrationService = new MigrationService2(db);
2289
2289
  const status = await migrationService.getMigrationStatus();
@@ -2308,7 +2308,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
2308
2308
  error: "Unauthorized. Admin access required."
2309
2309
  }, 403);
2310
2310
  }
2311
- const { MigrationService: MigrationService2 } = await import('./migrations-7X4RPH7O.cjs');
2311
+ const { MigrationService: MigrationService2 } = await import('./migrations-ONIAY6GK.cjs');
2312
2312
  const db = c.env.DB;
2313
2313
  const migrationService = new MigrationService2(db);
2314
2314
  const result = await migrationService.runPendingMigrations();
@@ -2327,7 +2327,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
2327
2327
  });
2328
2328
  adminApiRoutes.get("/migrations/validate", async (c) => {
2329
2329
  try {
2330
- const { MigrationService: MigrationService2 } = await import('./migrations-7X4RPH7O.cjs');
2330
+ const { MigrationService: MigrationService2 } = await import('./migrations-ONIAY6GK.cjs');
2331
2331
  const db = c.env.DB;
2332
2332
  const migrationService = new MigrationService2(db);
2333
2333
  const validation = await migrationService.validateSchema();
@@ -2738,7 +2738,7 @@ var JWT_SECRET_FALLBACK = "your-super-secret-jwt-key-change-in-production";
2738
2738
  async function setCsrfCookie(c) {
2739
2739
  const secret = c.env?.JWT_SECRET || JWT_SECRET_FALLBACK;
2740
2740
  const isDev = c.env?.ENVIRONMENT === "development" || !c.env?.ENVIRONMENT;
2741
- const csrfToken = await chunkK4Q4SFJJ_cjs.generateCsrfToken(secret);
2741
+ const csrfToken = await chunkIT2TC4ZD_cjs.generateCsrfToken(secret);
2742
2742
  cookie.setCookie(c, "csrf_token", csrfToken, {
2743
2743
  httpOnly: false,
2744
2744
  secure: !isDev,
@@ -2795,7 +2795,7 @@ var loginSchema = zod.z.object({
2795
2795
  });
2796
2796
  authRoutes.post(
2797
2797
  "/register",
2798
- chunkK4Q4SFJJ_cjs.rateLimit({ max: 3, windowMs: 60 * 1e3, keyPrefix: "register" }),
2798
+ chunkIT2TC4ZD_cjs.rateLimit({ max: 3, windowMs: 60 * 1e3, keyPrefix: "register" }),
2799
2799
  async (c) => {
2800
2800
  try {
2801
2801
  const db = c.env.DB;
@@ -2832,7 +2832,7 @@ authRoutes.post(
2832
2832
  if (existingUser) {
2833
2833
  return c.json({ error: "User with this email or username already exists" }, 400);
2834
2834
  }
2835
- const passwordHash = await chunkK4Q4SFJJ_cjs.AuthManager.hashPassword(password);
2835
+ const passwordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
2836
2836
  const userId = crypto.randomUUID();
2837
2837
  const now = /* @__PURE__ */ new Date();
2838
2838
  await db.prepare(`
@@ -2852,7 +2852,7 @@ authRoutes.post(
2852
2852
  now.getTime(),
2853
2853
  now.getTime()
2854
2854
  ).run();
2855
- const token = await chunkK4Q4SFJJ_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer", c.env.JWT_SECRET);
2855
+ const token = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer", c.env.JWT_SECRET);
2856
2856
  cookie.setCookie(c, "auth_token", token, {
2857
2857
  httpOnly: true,
2858
2858
  secure: true,
@@ -2886,7 +2886,7 @@ authRoutes.post(
2886
2886
  );
2887
2887
  authRoutes.post(
2888
2888
  "/login",
2889
- chunkK4Q4SFJJ_cjs.rateLimit({ max: 5, windowMs: 60 * 1e3, keyPrefix: "login" }),
2889
+ chunkIT2TC4ZD_cjs.rateLimit({ max: 5, windowMs: 60 * 1e3, keyPrefix: "login" }),
2890
2890
  async (c) => {
2891
2891
  try {
2892
2892
  const body = await c.req.json();
@@ -2897,7 +2897,7 @@ authRoutes.post(
2897
2897
  const { email, password } = validation.data;
2898
2898
  const db = c.env.DB;
2899
2899
  const normalizedEmail = email.toLowerCase();
2900
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.user);
2900
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.user);
2901
2901
  let user = await cache.get(cache.generateKey("user", `email:${normalizedEmail}`));
2902
2902
  if (!user) {
2903
2903
  user = await db.prepare("SELECT * FROM users WHERE email = ? AND is_active = 1").bind(normalizedEmail).first();
@@ -2909,19 +2909,19 @@ authRoutes.post(
2909
2909
  if (!user) {
2910
2910
  return c.json({ error: "Invalid email or password" }, 401);
2911
2911
  }
2912
- const isValidPassword = await chunkK4Q4SFJJ_cjs.AuthManager.verifyPassword(password, user.password_hash);
2912
+ const isValidPassword = await chunkIT2TC4ZD_cjs.AuthManager.verifyPassword(password, user.password_hash);
2913
2913
  if (!isValidPassword) {
2914
2914
  return c.json({ error: "Invalid email or password" }, 401);
2915
2915
  }
2916
- if (chunkK4Q4SFJJ_cjs.AuthManager.isLegacyHash(user.password_hash)) {
2916
+ if (chunkIT2TC4ZD_cjs.AuthManager.isLegacyHash(user.password_hash)) {
2917
2917
  try {
2918
- const newHash = await chunkK4Q4SFJJ_cjs.AuthManager.hashPassword(password);
2918
+ const newHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
2919
2919
  await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(newHash, Date.now(), user.id).run();
2920
2920
  } catch (rehashError) {
2921
2921
  console.error("Password rehash failed (non-fatal):", rehashError);
2922
2922
  }
2923
2923
  }
2924
- const token = await chunkK4Q4SFJJ_cjs.AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET);
2924
+ const token = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET);
2925
2925
  cookie.setCookie(c, "auth_token", token, {
2926
2926
  httpOnly: true,
2927
2927
  secure: true,
@@ -2974,7 +2974,7 @@ authRoutes.get("/logout", (c) => {
2974
2974
  clearCsrfCookie(c);
2975
2975
  return c.redirect("/auth/login?message=You have been logged out successfully");
2976
2976
  });
2977
- authRoutes.get("/me", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
2977
+ authRoutes.get("/me", chunkIT2TC4ZD_cjs.requireAuth(), async (c) => {
2978
2978
  try {
2979
2979
  const user = c.get("user");
2980
2980
  if (!user) {
@@ -2991,13 +2991,13 @@ authRoutes.get("/me", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
2991
2991
  return c.json({ error: "Failed to get user" }, 500);
2992
2992
  }
2993
2993
  });
2994
- authRoutes.post("/refresh", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
2994
+ authRoutes.post("/refresh", chunkIT2TC4ZD_cjs.requireAuth(), async (c) => {
2995
2995
  try {
2996
2996
  const user = c.get("user");
2997
2997
  if (!user) {
2998
2998
  return c.json({ error: "Not authenticated" }, 401);
2999
2999
  }
3000
- const token = await chunkK4Q4SFJJ_cjs.AuthManager.generateToken(user.userId, user.email, user.role, c.env.JWT_SECRET);
3000
+ const token = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(user.userId, user.email, user.role, c.env.JWT_SECRET);
3001
3001
  cookie.setCookie(c, "auth_token", token, {
3002
3002
  httpOnly: true,
3003
3003
  secure: true,
@@ -3014,7 +3014,7 @@ authRoutes.post("/refresh", chunkK4Q4SFJJ_cjs.requireAuth(), async (c) => {
3014
3014
  });
3015
3015
  authRoutes.post(
3016
3016
  "/register/form",
3017
- chunkK4Q4SFJJ_cjs.rateLimit({ max: 3, windowMs: 60 * 1e3, keyPrefix: "register" }),
3017
+ chunkIT2TC4ZD_cjs.rateLimit({ max: 3, windowMs: 60 * 1e3, keyPrefix: "register" }),
3018
3018
  async (c) => {
3019
3019
  try {
3020
3020
  const db = c.env.DB;
@@ -3061,7 +3061,7 @@ authRoutes.post(
3061
3061
  </div>
3062
3062
  `);
3063
3063
  }
3064
- const passwordHash = await chunkK4Q4SFJJ_cjs.AuthManager.hashPassword(password);
3064
+ const passwordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
3065
3065
  const role = isFirstUser ? "admin" : "viewer";
3066
3066
  const userId = crypto.randomUUID();
3067
3067
  const now = /* @__PURE__ */ new Date();
@@ -3081,7 +3081,7 @@ authRoutes.post(
3081
3081
  now.getTime(),
3082
3082
  now.getTime()
3083
3083
  ).run();
3084
- const token = await chunkK4Q4SFJJ_cjs.AuthManager.generateToken(userId, normalizedEmail, role, c.env.JWT_SECRET);
3084
+ const token = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(userId, normalizedEmail, role, c.env.JWT_SECRET);
3085
3085
  cookie.setCookie(c, "auth_token", token, {
3086
3086
  httpOnly: true,
3087
3087
  secure: false,
@@ -3114,7 +3114,7 @@ authRoutes.post(
3114
3114
  );
3115
3115
  authRoutes.post(
3116
3116
  "/login/form",
3117
- chunkK4Q4SFJJ_cjs.rateLimit({ max: 5, windowMs: 60 * 1e3, keyPrefix: "login" }),
3117
+ chunkIT2TC4ZD_cjs.rateLimit({ max: 5, windowMs: 60 * 1e3, keyPrefix: "login" }),
3118
3118
  async (c) => {
3119
3119
  try {
3120
3120
  const formData = await c.req.formData();
@@ -3138,7 +3138,7 @@ authRoutes.post(
3138
3138
  </div>
3139
3139
  `);
3140
3140
  }
3141
- const isValidPassword = await chunkK4Q4SFJJ_cjs.AuthManager.verifyPassword(password, user.password_hash);
3141
+ const isValidPassword = await chunkIT2TC4ZD_cjs.AuthManager.verifyPassword(password, user.password_hash);
3142
3142
  if (!isValidPassword) {
3143
3143
  return c.html(html.html`
3144
3144
  <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
@@ -3146,15 +3146,15 @@ authRoutes.post(
3146
3146
  </div>
3147
3147
  `);
3148
3148
  }
3149
- if (chunkK4Q4SFJJ_cjs.AuthManager.isLegacyHash(user.password_hash)) {
3149
+ if (chunkIT2TC4ZD_cjs.AuthManager.isLegacyHash(user.password_hash)) {
3150
3150
  try {
3151
- const newHash = await chunkK4Q4SFJJ_cjs.AuthManager.hashPassword(password);
3151
+ const newHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
3152
3152
  await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(newHash, Date.now(), user.id).run();
3153
3153
  } catch (rehashError) {
3154
3154
  console.error("Password rehash failed (non-fatal):", rehashError);
3155
3155
  }
3156
3156
  }
3157
- const token = await chunkK4Q4SFJJ_cjs.AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET);
3157
+ const token = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET);
3158
3158
  cookie.setCookie(c, "auth_token", token, {
3159
3159
  httpOnly: true,
3160
3160
  secure: false,
@@ -3196,7 +3196,7 @@ authRoutes.post(
3196
3196
  );
3197
3197
  authRoutes.post(
3198
3198
  "/seed-admin",
3199
- chunkK4Q4SFJJ_cjs.rateLimit({ max: 2, windowMs: 60 * 1e3, keyPrefix: "seed-admin" }),
3199
+ chunkIT2TC4ZD_cjs.rateLimit({ max: 2, windowMs: 60 * 1e3, keyPrefix: "seed-admin" }),
3200
3200
  async (c) => {
3201
3201
  try {
3202
3202
  const db = c.env.DB;
@@ -3218,7 +3218,7 @@ authRoutes.post(
3218
3218
  `).run();
3219
3219
  const existingAdmin = await db.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind("admin@sonicjs.com", "admin").first();
3220
3220
  if (existingAdmin) {
3221
- const passwordHash2 = await chunkK4Q4SFJJ_cjs.AuthManager.hashPassword("sonicjs!");
3221
+ const passwordHash2 = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword("sonicjs!");
3222
3222
  await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
3223
3223
  return c.json({
3224
3224
  message: "Admin user already exists (password updated)",
@@ -3230,7 +3230,7 @@ authRoutes.post(
3230
3230
  }
3231
3231
  });
3232
3232
  }
3233
- const passwordHash = await chunkK4Q4SFJJ_cjs.AuthManager.hashPassword("sonicjs!");
3233
+ const passwordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword("sonicjs!");
3234
3234
  const userId = "admin-user-id";
3235
3235
  const now = Date.now();
3236
3236
  const adminEmail = "admin@sonicjs.com".toLowerCase();
@@ -3451,7 +3451,7 @@ authRoutes.post("/accept-invitation", async (c) => {
3451
3451
  if (existingUsername) {
3452
3452
  return c.json({ error: "Username is already taken" }, 400);
3453
3453
  }
3454
- const passwordHash = await chunkK4Q4SFJJ_cjs.AuthManager.hashPassword(password);
3454
+ const passwordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
3455
3455
  const updateStmt = db.prepare(`
3456
3456
  UPDATE users SET
3457
3457
  username = ?,
@@ -3470,7 +3470,7 @@ authRoutes.post("/accept-invitation", async (c) => {
3470
3470
  Date.now(),
3471
3471
  invitedUser.id
3472
3472
  ).run();
3473
- const authToken = await chunkK4Q4SFJJ_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role, c.env.JWT_SECRET);
3473
+ const authToken = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role, c.env.JWT_SECRET);
3474
3474
  cookie.setCookie(c, "auth_token", authToken, {
3475
3475
  httpOnly: true,
3476
3476
  secure: true,
@@ -3487,7 +3487,7 @@ authRoutes.post("/accept-invitation", async (c) => {
3487
3487
  });
3488
3488
  authRoutes.post(
3489
3489
  "/request-password-reset",
3490
- chunkK4Q4SFJJ_cjs.rateLimit({ max: 3, windowMs: 15 * 60 * 1e3, keyPrefix: "password-reset" }),
3490
+ chunkIT2TC4ZD_cjs.rateLimit({ max: 3, windowMs: 15 * 60 * 1e3, keyPrefix: "password-reset" }),
3491
3491
  async (c) => {
3492
3492
  try {
3493
3493
  const formData = await c.req.formData();
@@ -3705,7 +3705,7 @@ authRoutes.post("/reset-password", async (c) => {
3705
3705
  if (Date.now() > user.password_reset_expires) {
3706
3706
  return c.json({ error: "Reset token has expired" }, 400);
3707
3707
  }
3708
- const newPasswordHash = await chunkK4Q4SFJJ_cjs.AuthManager.hashPassword(password);
3708
+ const newPasswordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
3709
3709
  try {
3710
3710
  const historyStmt = db.prepare(`
3711
3711
  INSERT INTO password_history (id, user_id, password_hash, created_at)
@@ -4286,7 +4286,7 @@ function getMDXEditorInitScript(config) {
4286
4286
  const toolbar = config?.toolbar || "full";
4287
4287
  const placeholder = config?.placeholder || "Start writing your content...";
4288
4288
  return `
4289
- // Initialize EasyMDE (Markdown Editor) for all richtext fields
4289
+ // Initialize EasyMDE (Markdown Editor) only for markdown-marked fields
4290
4290
  function initializeMDXEditor() {
4291
4291
  if (typeof EasyMDE === 'undefined') {
4292
4292
  console.warn('EasyMDE not loaded yet, retrying...');
@@ -4295,7 +4295,7 @@ function getMDXEditorInitScript(config) {
4295
4295
  }
4296
4296
 
4297
4297
  // Find all textareas that need EasyMDE
4298
- document.querySelectorAll('.richtext-container textarea').forEach((textarea) => {
4298
+ document.querySelectorAll('.richtext-container[data-editor-provider="easymde"] textarea').forEach((textarea) => {
4299
4299
  // Skip if already initialized
4300
4300
  if (textarea.dataset.mdxeditorInitialized === 'true') {
4301
4301
  return;
@@ -4394,11 +4394,11 @@ function getTinyMCEInitScript(config) {
4394
4394
  const contentCss = skin.includes("dark") ? "dark" : "default";
4395
4395
  const defaultHeight = config?.defaultHeight || 300;
4396
4396
  return `
4397
- // Initialize TinyMCE for all richtext fields
4397
+ // Initialize TinyMCE only for TinyMCE-backed richtext fields
4398
4398
  function initializeTinyMCE() {
4399
4399
  if (typeof tinymce !== 'undefined') {
4400
4400
  // Find all textareas that need TinyMCE
4401
- document.querySelectorAll('.richtext-container textarea').forEach((textarea) => {
4401
+ document.querySelectorAll('.richtext-container[data-editor-provider="tinymce"] textarea').forEach((textarea) => {
4402
4402
  // Skip if already initialized
4403
4403
  if (tinymce.get(textarea.id)) {
4404
4404
  return;
@@ -4777,6 +4777,39 @@ function getReadFieldValueScript() {
4777
4777
  window.__sonicReadFieldValueInit = true;
4778
4778
 
4779
4779
  window.sonicReadFieldValue = function(fieldWrapper) {
4780
+ const getDirectChild = (parent, selector) => {
4781
+ if (!(parent instanceof Element)) return null;
4782
+ return Array.from(parent.children).find(
4783
+ (child) => child instanceof Element && child.matches(selector),
4784
+ ) || null;
4785
+ };
4786
+ const getDirectStructuredSubfields = (host) =>
4787
+ Array.from(host.children).filter(
4788
+ (child) => child instanceof Element && child.classList.contains('structured-subfield'),
4789
+ );
4790
+ const getStructuredObjectFieldsHost = (container) => {
4791
+ const directFieldsHost = getDirectChild(container, '[data-structured-object-fields]');
4792
+ if (directFieldsHost) return directFieldsHost;
4793
+ const groupContent = getDirectChild(container, '.field-group-content');
4794
+ const nestedFieldsHost = groupContent
4795
+ ? getDirectChild(groupContent, '[data-structured-object-fields]')
4796
+ : null;
4797
+ if (nestedFieldsHost) return nestedFieldsHost;
4798
+ return getDirectChild(container, '[data-array-item-fields]') || container;
4799
+ };
4800
+ const getDirectStructuredObject = (fieldWrapper) => {
4801
+ const directObject = getDirectChild(fieldWrapper, '[data-structured-object]');
4802
+ if (directObject) return directObject;
4803
+ const formGroup = getDirectChild(fieldWrapper, '.form-group');
4804
+ return formGroup ? getDirectChild(formGroup, '[data-structured-object]') : null;
4805
+ };
4806
+ const getDirectStructuredArray = (fieldWrapper) => {
4807
+ const directArray = getDirectChild(fieldWrapper, '[data-structured-array]');
4808
+ if (directArray) return directArray;
4809
+ const formGroup = getDirectChild(fieldWrapper, '.form-group');
4810
+ return formGroup ? getDirectChild(formGroup, '[data-structured-array]') : null;
4811
+ };
4812
+
4780
4813
  const fieldType = fieldWrapper.dataset.fieldType;
4781
4814
  const select = fieldWrapper.querySelector('select');
4782
4815
  const textarea = fieldWrapper.querySelector('textarea');
@@ -4786,7 +4819,47 @@ function getReadFieldValueScript() {
4786
4819
  const nonHiddenInput = inputs.find((input) => input.type !== 'hidden' && input.type !== 'checkbox');
4787
4820
  const hiddenInput = inputs.find((input) => input.type === 'hidden');
4788
4821
 
4822
+ const readStructuredFieldsHost = (host) => {
4823
+ const fields = getDirectStructuredSubfields(host);
4824
+ if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
4825
+ return window.sonicReadFieldValue(fields[0]);
4826
+ }
4827
+ return fields.reduce((acc, subfield) => {
4828
+ const fieldName = subfield.dataset.structuredField;
4829
+ if (!fieldName || fieldName === '__value') return acc;
4830
+ acc[fieldName] = window.sonicReadFieldValue(subfield);
4831
+ return acc;
4832
+ }, {});
4833
+ };
4834
+
4835
+ const readStructuredObject = () => {
4836
+ const objectContainer = getDirectStructuredObject(fieldWrapper);
4837
+ if (!objectContainer) return null;
4838
+ const host = getStructuredObjectFieldsHost(objectContainer);
4839
+ return readStructuredFieldsHost(host);
4840
+ };
4841
+
4842
+ const readStructuredArray = () => {
4843
+ const arrayContainer = getDirectStructuredArray(fieldWrapper);
4844
+ if (!arrayContainer) return null;
4845
+ const list = arrayContainer.querySelector('[data-structured-array-list]');
4846
+ if (!list) return [];
4847
+ const items = Array.from(list.querySelectorAll(':scope > .structured-array-item'));
4848
+ return items.map((item) => {
4849
+ const host =
4850
+ item.querySelector(':scope > [data-array-item-fields]') ||
4851
+ item.querySelector('[data-array-item-fields]') ||
4852
+ item;
4853
+ return readStructuredFieldsHost(host);
4854
+ });
4855
+ };
4856
+
4789
4857
  if (fieldType === 'object' || fieldType === 'array') {
4858
+ const liveValue = fieldType === 'array' ? readStructuredArray() : readStructuredObject();
4859
+ if (liveValue !== null) {
4860
+ return liveValue;
4861
+ }
4862
+
4790
4863
  if (!hiddenInput) {
4791
4864
  return fieldType === 'array' ? [] : {};
4792
4865
  }
@@ -4835,6 +4908,33 @@ function getReadFieldValueScript() {
4835
4908
  </script>
4836
4909
  `;
4837
4910
  }
4911
+ var STRUCTURED_INDEX_TOKEN = "__INDEX__";
4912
+ var BLOCK_INDEX_TOKEN = "__BLOCK_INDEX__";
4913
+ function sanitizeStructuredGroupId(fieldName) {
4914
+ return `object-${fieldName}`.split(BLOCK_INDEX_TOKEN).map(
4915
+ (blockSegment) => blockSegment.split(STRUCTURED_INDEX_TOKEN).map(
4916
+ (segment) => segment.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
4917
+ ).join(STRUCTURED_INDEX_TOKEN)
4918
+ ).join(BLOCK_INDEX_TOKEN);
4919
+ }
4920
+ function isMarkdownEditorFieldType(fieldType) {
4921
+ return fieldType === "markdown" || fieldType === "mdxeditor" || fieldType === "easymde";
4922
+ }
4923
+ function getEditorMetadata(fieldType) {
4924
+ if (fieldType === "richtext" || fieldType === "tinymce") {
4925
+ return {
4926
+ family: "richtext",
4927
+ provider: "tinymce"
4928
+ };
4929
+ }
4930
+ if (isMarkdownEditorFieldType(fieldType)) {
4931
+ return {
4932
+ family: "markdown",
4933
+ provider: "easymde"
4934
+ };
4935
+ }
4936
+ return null;
4937
+ }
4838
4938
  function renderDynamicField(field, options = {}) {
4839
4939
  const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {}, collectionId = "", contentId = "" } = options;
4840
4940
  const opts = field.field_options || {};
@@ -4848,10 +4948,10 @@ function renderDynamicField(field, options = {}) {
4848
4948
  if (field.field_type === "quill" && !pluginStatuses.quillEnabled) {
4849
4949
  fallbackToTextarea = true;
4850
4950
  fallbackWarning = "\u26A0\uFE0F Quill Editor plugin is inactive. Using textarea fallback.";
4851
- } else if ((field.field_type === "mdxeditor" || field.field_type === "easymde" || field.field_type === "markdown") && !pluginStatuses.mdxeditorEnabled) {
4951
+ } else if (isMarkdownEditorFieldType(field.field_type) && !pluginStatuses.mdxeditorEnabled) {
4852
4952
  fallbackToTextarea = true;
4853
- fallbackWarning = "\u26A0\uFE0F EasyMDE plugin is inactive. Using textarea fallback.";
4854
- } else if (field.field_type === "tinymce" && !pluginStatuses.tinymceEnabled) {
4953
+ fallbackWarning = "\u26A0\uFE0F Markdown editor plugin is inactive. Using textarea fallback.";
4954
+ } else if ((field.field_type === "richtext" || field.field_type === "tinymce") && !pluginStatuses.tinymceEnabled) {
4855
4955
  fallbackToTextarea = true;
4856
4956
  fallbackWarning = "\u26A0\uFE0F TinyMCE plugin is inactive. Using textarea fallback.";
4857
4957
  }
@@ -4973,8 +5073,10 @@ function renderDynamicField(field, options = {}) {
4973
5073
  `;
4974
5074
  break;
4975
5075
  case "richtext":
5076
+ case "tinymce": {
5077
+ const editorMetadata = getEditorMetadata(field.field_type);
4976
5078
  fieldHTML = `
4977
- <div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}">
5079
+ <div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}" data-editor-family="${editorMetadata?.family || ""}" data-editor-provider="${editorMetadata?.provider || ""}">
4978
5080
  <textarea
4979
5081
  id="${fieldId}"
4980
5082
  name="${fieldName}"
@@ -4985,6 +5087,7 @@ function renderDynamicField(field, options = {}) {
4985
5087
  </div>
4986
5088
  `;
4987
5089
  break;
5090
+ }
4988
5091
  case "quill":
4989
5092
  fieldHTML = `
4990
5093
  <div class="quill-editor-container" data-field-id="${fieldId}">
@@ -5008,12 +5111,12 @@ function renderDynamicField(field, options = {}) {
5008
5111
  </div>
5009
5112
  `;
5010
5113
  break;
5011
- case "mdxeditor":
5012
- case "tinymce":
5013
- case "easymde":
5014
5114
  case "markdown":
5115
+ case "mdxeditor":
5116
+ case "easymde": {
5117
+ const editorMetadata = getEditorMetadata(field.field_type);
5015
5118
  fieldHTML = `
5016
- <div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}">
5119
+ <div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}" data-editor-family="${editorMetadata?.family || ""}" data-editor-provider="${editorMetadata?.provider || ""}">
5017
5120
  <textarea
5018
5121
  id="${fieldId}"
5019
5122
  name="${fieldName}"
@@ -5024,6 +5127,7 @@ function renderDynamicField(field, options = {}) {
5024
5127
  </div>
5025
5128
  `;
5026
5129
  break;
5130
+ }
5027
5131
  case "number":
5028
5132
  fieldHTML = `
5029
5133
  <input
@@ -5393,12 +5497,14 @@ function renderDynamicField(field, options = {}) {
5393
5497
 
5394
5498
  ${isMultiple ? `
5395
5499
  <div class="media-preview-grid grid grid-cols-4 gap-2 mb-2 ${mediaValues.length === 0 ? "hidden" : ""}" id="${fieldId}-preview">
5396
- ${mediaValues.map((url, idx) => `
5500
+ ${mediaValues.map(
5501
+ (url, idx) => `
5397
5502
  <div class="relative media-preview-item" data-url="${url}">
5398
5503
  ${renderMediaPreview(url, `Media ${idx + 1}`, "w-full h-24 object-cover rounded-lg border border-white/20")}
5399
5504
  <button
5400
5505
  type="button"
5401
5506
  onclick="removeMediaFromMultiple('${fieldId}', '${url}')"
5507
+ data-media-remove="true"
5402
5508
  class="absolute top-1 right-1 bg-red-600 text-white rounded-full p-1 hover:bg-red-700"
5403
5509
  ${disabled ? "disabled" : ""}
5404
5510
  >
@@ -5407,7 +5513,8 @@ function renderDynamicField(field, options = {}) {
5407
5513
  </svg>
5408
5514
  </button>
5409
5515
  </div>
5410
- `).join("")}
5516
+ `
5517
+ ).join("")}
5411
5518
  </div>
5412
5519
  ` : `
5413
5520
  <div class="media-preview ${singleValue ? "" : "hidden"}" id="${fieldId}-preview">
@@ -5431,6 +5538,7 @@ function renderDynamicField(field, options = {}) {
5431
5538
  <button
5432
5539
  type="button"
5433
5540
  onclick="clearMediaField('${fieldId}')"
5541
+ data-media-remove="true"
5434
5542
  class="inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all"
5435
5543
  ${disabled ? "disabled" : ""}
5436
5544
  >
@@ -5464,7 +5572,7 @@ function renderDynamicField(field, options = {}) {
5464
5572
  }
5465
5573
  const showLabel = field.field_type !== "boolean";
5466
5574
  return `
5467
- <div class="form-group">
5575
+ <div class="form-group" data-has-errors="${errors.length > 0 ? "true" : "false"}">
5468
5576
  ${showLabel ? `
5469
5577
  <label for="${fieldId}" class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2">
5470
5578
  ${escapeHtml3(field.field_label)}
@@ -5473,7 +5581,7 @@ function renderDynamicField(field, options = {}) {
5473
5581
  ` : ""}
5474
5582
  ${fieldHTML}
5475
5583
  ${errors.length > 0 ? `
5476
- <div class="mt-2 text-sm text-pink-600 dark:text-pink-400">
5584
+ <div class="mt-2 text-sm text-pink-600 dark:text-pink-400" data-validation-error-message>
5477
5585
  ${errors.map((error) => `<div>${escapeHtml3(error)}</div>`).join("")}
5478
5586
  </div>
5479
5587
  ` : ""}
@@ -5488,8 +5596,8 @@ function renderDynamicField(field, options = {}) {
5488
5596
  function renderFieldGroup(title, fields, collapsible = false) {
5489
5597
  const groupId = title.toLowerCase().replace(/\s+/g, "-");
5490
5598
  return `
5491
- <div class="field-group rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 mb-6">
5492
- <div class="field-group-header border-b border-zinc-950/5 dark:border-white/10 px-6 py-4 ${collapsible ? "cursor-pointer" : ""}" ${collapsible ? `onclick="toggleFieldGroup('${groupId}')"` : ""}>
5599
+ <div class="field-group rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 mb-6" data-group-id="${escapeHtml3(groupId)}">
5600
+ <div class="field-group-header border-b border-zinc-950/5 dark:border-white/10 px-6 py-4 ${collapsible ? "cursor-pointer" : ""}" ${collapsible ? `onclick="toggleFieldGroup(this)"` : ""}>
5493
5601
  <h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
5494
5602
  ${escapeHtml3(title)}
5495
5603
  ${collapsible ? `
@@ -5533,6 +5641,12 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
5533
5641
  >
5534
5642
  <input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(blockValues))}">
5535
5643
 
5644
+ <div class="flex items-center justify-between border-b border-zinc-950/5 dark:border-white/10 py-4">
5645
+ <h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">
5646
+ ${escapeHtml3(field.field_label || "Content Blocks")}
5647
+ </h3>
5648
+ </div>
5649
+
5536
5650
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
5537
5651
  <div class="flex-1">
5538
5652
  <select
@@ -5563,12 +5677,14 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
5563
5677
  `;
5564
5678
  }
5565
5679
  function renderStructuredObjectField(field, options, baseClasses, errorClasses) {
5566
- const { value = {}, pluginStatuses = {} } = options;
5680
+ const { value = {}, pluginStatuses = {}, errors = [] } = options;
5567
5681
  const opts = field.field_options || {};
5568
5682
  const properties = opts.properties && typeof opts.properties === "object" ? opts.properties : {};
5569
5683
  const fieldId = `field-${field.field_name}`;
5570
5684
  const fieldName = field.field_name;
5571
5685
  const objectValue = normalizeStructuredObjectValue(value);
5686
+ const objectLayout = opts.objectLayout || "nested";
5687
+ const useNestedLayout = objectLayout !== "flat";
5572
5688
  const subfields = Object.entries(properties).map(
5573
5689
  ([propertyName, propertyConfig]) => renderStructuredSubfield(
5574
5690
  field,
@@ -5579,11 +5695,40 @@ function renderStructuredObjectField(field, options, baseClasses, errorClasses)
5579
5695
  field.field_name
5580
5696
  )
5581
5697
  ).join("");
5698
+ const groupTitle = field.field_label || field.field_name;
5699
+ if (!useNestedLayout) {
5700
+ return `
5701
+ <div class="space-y-4" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
5702
+ <input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
5703
+ <div class="flex items-center justify-between border-b border-zinc-950/5 dark:border-white/10 py-4 first-of-type:pt-0">
5704
+ <h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">
5705
+ ${escapeHtml3(groupTitle)}
5706
+ </h3>
5707
+ </div>
5708
+ <div class="space-y-4" data-structured-object-fields>
5709
+ ${subfields}
5710
+ </div>
5711
+ </div>
5712
+ ${getStructuredFieldScript()}
5713
+ `;
5714
+ }
5715
+ const groupId = sanitizeStructuredGroupId(field.field_name);
5716
+ const isCollapsed = errors.length > 0 ? false : opts.collapsed !== false;
5582
5717
  return `
5583
- <div class="space-y-4" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
5584
- <input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
5585
- <div class="space-y-4" data-structured-object-fields>
5586
- ${subfields}
5718
+ <div class="field-group rounded-lg shadow-sm mb-6" data-group-id="${escapeHtml3(groupId)}" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
5719
+ <div class="field-group-header border-b border-zinc-950/5 dark:border-white/10 pr-6 pb-4 cursor-pointer" onclick="toggleFieldGroup(this)">
5720
+ <h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
5721
+ ${escapeHtml3(groupTitle)}
5722
+ <svg id="${groupId}-icon" class="w-5 h-5 ml-2 transform transition-transform ${isCollapsed ? "-rotate-90" : ""} text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5723
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
5724
+ </svg>
5725
+ </h3>
5726
+ </div>
5727
+ <div id="${groupId}-content" class="field-group-content px-6 py-6 space-y-4 ${isCollapsed ? "hidden" : ""}">
5728
+ <input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
5729
+ <div class="space-y-4" data-structured-object-fields>
5730
+ ${subfields}
5731
+ </div>
5587
5732
  </div>
5588
5733
  </div>
5589
5734
  ${getStructuredFieldScript()}
@@ -5654,7 +5799,7 @@ function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
5654
5799
  function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginStatuses, arrayItemTitle) {
5655
5800
  const itemFields = renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses);
5656
5801
  return `
5657
- <div class="structured-array-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-white/5 p-4 shadow-sm" data-array-index="${escapeHtml3(index)}" draggable="true">
5802
+ <div class="structured-array-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-zinc-600/5 p-4 shadow-lg shadow-zinc-950/20" data-array-index="${escapeHtml3(index)}" draggable="true">
5658
5803
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
5659
5804
  <div class="flex items-center gap-3">
5660
5805
  <div class="drag-handle cursor-move text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-400" data-action="drag-handle" title="Drag to reorder">
@@ -5667,6 +5812,11 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
5667
5812
  </div>
5668
5813
  </div>
5669
5814
  <div class="flex flex-wrap gap-2 text-xs">
5815
+ <button type="button" data-action="toggle-item" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10" aria-label="Expand item" title="Expand">
5816
+ <svg class="h-4 w-4 transition-transform -rotate-90 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" data-item-toggle-icon>
5817
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
5818
+ </svg>
5819
+ </button>
5670
5820
  <button type="button" data-action="move-up" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent dark:disabled:hover:bg-transparent" aria-label="Move item up" title="Move up">
5671
5821
  <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
5672
5822
  <path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
@@ -5685,7 +5835,7 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
5685
5835
  </button>
5686
5836
  </div>
5687
5837
  </div>
5688
- <div class="mt-4 space-y-4" data-array-item-fields>
5838
+ <div class="mt-4 space-y-4 hidden" data-array-item-fields>
5689
5839
  ${itemFields}
5690
5840
  </div>
5691
5841
  </div>
@@ -5795,7 +5945,7 @@ function normalizeBlocksValue(value, discriminator) {
5795
5945
  function renderBlockTemplate(field, block, discriminator, pluginStatuses) {
5796
5946
  return `
5797
5947
  <template data-block-template="${escapeHtml3(block.name)}">
5798
- ${renderBlockCard(field, block, discriminator, "__INDEX__", {}, pluginStatuses)}
5948
+ ${renderBlockCard(field, block, discriminator, BLOCK_INDEX_TOKEN, {}, pluginStatuses)}
5799
5949
  </template>
5800
5950
  `;
5801
5951
  }
@@ -5837,7 +5987,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
5837
5987
  `;
5838
5988
  }).join("");
5839
5989
  return `
5840
- <div class="blocks-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-white/5 p-4 shadow-sm" data-block-type="${escapeHtml3(block.name)}" data-block-discriminator="${escapeHtml3(discriminator)}" draggable="true">
5990
+ <div class="blocks-item rounded-lg border border-zinc-200 dark:border-white/10 dark:bg-zinc-600/5 p-4 shadow-lg shadow-zinc-950/20" data-block-type="${escapeHtml3(block.name)}" data-block-discriminator="${escapeHtml3(discriminator)}" draggable="true">
5841
5991
  <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
5842
5992
  <div class="flex items-start gap-3">
5843
5993
  <div class="drag-handle cursor-move text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-400" data-action="drag-handle" title="Drag to reorder">
@@ -5845,7 +5995,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
5845
5995
  <path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
5846
5996
  </svg>
5847
5997
  </div>
5848
- <div>
5998
+ <div class="cursor-pointer" data-action="toggle-block">
5849
5999
  <div class="text-sm font-semibold text-zinc-900 dark:text-white">
5850
6000
  ${escapeHtml3(block.label)}
5851
6001
  <span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-block-order-label></span>
@@ -5854,6 +6004,11 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
5854
6004
  </div>
5855
6005
  </div>
5856
6006
  <div class="flex flex-wrap gap-2 text-xs">
6007
+ <button type="button" data-action="toggle-block" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10" aria-label="Expand block" title="Expand">
6008
+ <svg class="h-4 w-4 transition-transform -rotate-90 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" data-block-toggle-icon>
6009
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
6010
+ </svg>
6011
+ </button>
5857
6012
  <button type="button" data-action="move-up" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent dark:disabled:hover:bg-transparent" aria-label="Move block up" title="Move up">
5858
6013
  <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
5859
6014
  <path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
@@ -5872,7 +6027,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
5872
6027
  </button>
5873
6028
  </div>
5874
6029
  </div>
5875
- <div class="mt-4 space-y-4">
6030
+ <div class="mt-4 space-y-4 hidden" data-block-content>
5876
6031
  ${blockFields}
5877
6032
  </div>
5878
6033
  </div>
@@ -5906,9 +6061,101 @@ function getStructuredFieldScript() {
5906
6061
 
5907
6062
  function initializeStructuredFields() {
5908
6063
  const readFieldValue = window.sonicReadFieldValue;
6064
+ const getDirectChild = (parent, selector) => {
6065
+ if (!(parent instanceof Element)) return null;
6066
+ return Array.from(parent.children).find(
6067
+ (child) => child instanceof Element && child.matches(selector),
6068
+ ) || null;
6069
+ };
6070
+ const getDirectStructuredSubfields = (host) =>
6071
+ Array.from(host.children).filter(
6072
+ (child) => child instanceof Element && child.classList.contains('structured-subfield'),
6073
+ );
6074
+ const getStructuredValueHost = (container) => {
6075
+ const directObjectHost = getDirectChild(container, '[data-structured-object-fields]');
6076
+ if (directObjectHost) return directObjectHost;
6077
+ const groupContent = getDirectChild(container, '.field-group-content');
6078
+ const nestedObjectHost = groupContent
6079
+ ? getDirectChild(groupContent, '[data-structured-object-fields]')
6080
+ : null;
6081
+ if (nestedObjectHost) return nestedObjectHost;
6082
+ return getDirectChild(container, '[data-array-item-fields]') || container;
6083
+ };
6084
+ const getCollectionScope = () => {
6085
+ const url = new URL(window.location.href);
6086
+ const collectionFromQuery = url.searchParams.get('collection');
6087
+ const form = document.getElementById('content-form');
6088
+ const collectionInput = form?.querySelector('input[name="collection_id"]');
6089
+ const collectionFromForm = collectionInput instanceof HTMLInputElement ? collectionInput.value : '';
6090
+ const collectionId = collectionFromQuery || collectionFromForm || '';
6091
+ return window.location.pathname + ':' + collectionId;
6092
+ };
6093
+
6094
+ const getArrayStateKey = (container) => {
6095
+ const fieldName = container.dataset.fieldName || 'unknown';
6096
+ return 'sonic:ui:repeaters:' + getCollectionScope() + ':' + fieldName;
6097
+ };
6098
+
6099
+ const readArrayState = (container) => {
6100
+ try {
6101
+ const raw = sessionStorage.getItem(getArrayStateKey(container));
6102
+ if (!raw) return null;
6103
+ const parsed = JSON.parse(raw);
6104
+ return Array.isArray(parsed) ? parsed : null;
6105
+ } catch {
6106
+ return null;
6107
+ }
6108
+ };
6109
+
6110
+ const writeArrayState = (container, state) => {
6111
+ try {
6112
+ sessionStorage.setItem(getArrayStateKey(container), JSON.stringify(state));
6113
+ } catch {}
6114
+ };
6115
+
6116
+ const setArrayItemExpanded = (item, isExpanded) => {
6117
+ const content = item.querySelector('[data-array-item-fields]');
6118
+ const icon = item.querySelector('[data-item-toggle-icon]');
6119
+ if (content instanceof HTMLElement) {
6120
+ content.classList.toggle('hidden', !isExpanded);
6121
+ }
6122
+ if (icon instanceof Element) {
6123
+ icon.classList.toggle('-rotate-90', !isExpanded);
6124
+ }
6125
+ };
6126
+
6127
+ const getArrayItems = (container, list) => {
6128
+ if (list) {
6129
+ return Array.from(list.querySelectorAll(':scope > .structured-array-item'));
6130
+ }
6131
+ return Array.from(
6132
+ container.querySelectorAll(':scope > [data-structured-array-list] > .structured-array-item'),
6133
+ );
6134
+ };
6135
+
6136
+ const captureArrayState = (container) => {
6137
+ return getArrayItems(container).map((item) => {
6138
+ const content = item.querySelector('[data-array-item-fields]');
6139
+ return content instanceof HTMLElement ? !content.classList.contains('hidden') : false;
6140
+ });
6141
+ };
6142
+
6143
+ const applyArrayState = (container, state) => {
6144
+ const items = getArrayItems(container);
6145
+ items.forEach((item, index) => {
6146
+ if (typeof state[index] === 'boolean') {
6147
+ setArrayItemExpanded(item, state[index]);
6148
+ }
6149
+ });
6150
+ };
6151
+
6152
+ const syncArrayState = (container) => {
6153
+ writeArrayState(container, captureArrayState(container));
6154
+ };
5909
6155
 
5910
6156
  const readStructuredValue = (container) => {
5911
- const fields = Array.from(container.querySelectorAll('.structured-subfield'));
6157
+ const fieldHost = getStructuredValueHost(container);
6158
+ const fields = getDirectStructuredSubfields(fieldHost);
5912
6159
  if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
5913
6160
  return readFieldValue(fields[0]);
5914
6161
  }
@@ -5922,111 +6169,229 @@ function getStructuredFieldScript() {
5922
6169
  };
5923
6170
 
5924
6171
  document.querySelectorAll('[data-structured-object]').forEach((container) => {
6172
+ if (container.closest('template')) {
6173
+ return;
6174
+ }
5925
6175
  if (container.dataset.structuredInitialized === 'true') {
5926
6176
  return;
5927
6177
  }
5928
- container.dataset.structuredInitialized = 'true';
5929
- const hiddenInput = container.querySelector('input[type="hidden"]');
6178
+ if (container.dataset.structuredInitializing === 'true') {
6179
+ return;
6180
+ }
6181
+ container.dataset.structuredInitializing = 'true';
6182
+ try {
6183
+ const hiddenInput = container.querySelector('input[type="hidden"]');
5930
6184
 
5931
- const updateHiddenInput = () => {
5932
- if (!hiddenInput) return;
5933
- const value = readStructuredValue(container);
5934
- hiddenInput.value = JSON.stringify(value);
5935
- };
6185
+ const updateHiddenInput = () => {
6186
+ if (!hiddenInput) return;
6187
+ const value = readStructuredValue(container);
6188
+ hiddenInput.value = JSON.stringify(value);
6189
+ };
5936
6190
 
5937
- container.addEventListener('input', updateHiddenInput);
5938
- container.addEventListener('change', updateHiddenInput);
5939
- updateHiddenInput();
6191
+ container.addEventListener('input', updateHiddenInput);
6192
+ container.addEventListener('change', updateHiddenInput);
6193
+ updateHiddenInput();
6194
+ container.dataset.structuredInitialized = 'true';
6195
+ } catch (error) {
6196
+ delete container.dataset.structuredInitialized;
6197
+ console.error('[structured-object] initialization failed', error);
6198
+ } finally {
6199
+ delete container.dataset.structuredInitializing;
6200
+ }
5940
6201
  });
5941
6202
 
5942
6203
  document.querySelectorAll('[data-structured-array]').forEach((container) => {
6204
+ if (container.closest('template')) {
6205
+ return;
6206
+ }
5943
6207
  if (container.dataset.structuredInitialized === 'true') {
5944
6208
  return;
5945
6209
  }
5946
- container.dataset.structuredInitialized = 'true';
5947
- const list = container.querySelector('[data-structured-array-list]');
5948
- const hiddenInput = container.querySelector('input[type="hidden"]');
5949
- const template = container.querySelector('template[data-structured-array-template]');
6210
+ if (container.dataset.structuredInitializing === 'true') {
6211
+ return;
6212
+ }
6213
+ container.dataset.structuredInitializing = 'true';
6214
+ try {
6215
+ const list = container.querySelector(':scope > [data-structured-array-list]');
6216
+ const hiddenInput = container.querySelector(':scope > input[type="hidden"]');
6217
+ const template = container.querySelector(':scope > template[data-structured-array-template]');
6218
+ if (
6219
+ template instanceof HTMLTemplateElement &&
6220
+ typeof template.innerHTML === 'string' &&
6221
+ template.innerHTML.trim()
6222
+ ) {
6223
+ container.__sonicStructuredArrayTemplate = template.innerHTML;
6224
+ }
5950
6225
 
5951
- const updateOrderLabels = () => {
5952
- const items = Array.from(container.querySelectorAll('.structured-array-item'));
5953
- items.forEach((item, index) => {
5954
- const label = item.querySelector('[data-array-order-label]');
5955
- if (label) {
5956
- label.textContent = '#'+ (index + 1);
6226
+ const getLiveList = () =>
6227
+ list || container.querySelector(':scope > [data-structured-array-list]');
6228
+ const getLiveHiddenInput = () =>
6229
+ hiddenInput || container.querySelector(':scope > input[type="hidden"]');
6230
+ const getTemplateHtml = () => {
6231
+ if (typeof container.__sonicStructuredArrayTemplate === 'string' &&
6232
+ container.__sonicStructuredArrayTemplate.trim()) {
6233
+ return container.__sonicStructuredArrayTemplate;
5957
6234
  }
5958
6235
 
5959
- const moveUpButton = item.querySelector('[data-action="move-up"]');
5960
- if (moveUpButton instanceof HTMLButtonElement) {
5961
- moveUpButton.disabled = index === 0;
6236
+ const liveTemplate =
6237
+ template instanceof HTMLTemplateElement
6238
+ ? template
6239
+ : container.querySelector(':scope > template[data-structured-array-template]');
6240
+ if (
6241
+ liveTemplate instanceof HTMLTemplateElement &&
6242
+ typeof liveTemplate.innerHTML === 'string' &&
6243
+ liveTemplate.innerHTML.trim()
6244
+ ) {
6245
+ container.__sonicStructuredArrayTemplate = liveTemplate.innerHTML;
6246
+ return liveTemplate.innerHTML;
5962
6247
  }
6248
+ return typeof container.__sonicStructuredArrayTemplate === 'string'
6249
+ ? container.__sonicStructuredArrayTemplate
6250
+ : '';
6251
+ };
5963
6252
 
5964
- const moveDownButton = item.querySelector('[data-action="move-down"]');
5965
- if (moveDownButton instanceof HTMLButtonElement) {
5966
- moveDownButton.disabled = index === items.length - 1;
6253
+ const updateOrderLabels = () => {
6254
+ const liveList = getLiveList();
6255
+ if (!liveList) return;
6256
+ const items = getArrayItems(container, liveList);
6257
+ items.forEach((item, index) => {
6258
+ const label = item.querySelector('[data-array-order-label]');
6259
+ if (label) {
6260
+ label.textContent = '#'+ (index + 1);
6261
+ }
6262
+
6263
+ const moveUpButton = item.querySelector('[data-action="move-up"]');
6264
+ if (moveUpButton instanceof HTMLButtonElement) {
6265
+ moveUpButton.disabled = index === 0;
6266
+ }
6267
+
6268
+ const moveDownButton = item.querySelector('[data-action="move-down"]');
6269
+ if (moveDownButton instanceof HTMLButtonElement) {
6270
+ moveDownButton.disabled = index === items.length - 1;
6271
+ }
6272
+ });
6273
+ };
6274
+
6275
+ const updateHiddenInput = () => {
6276
+ const liveHiddenInput = getLiveHiddenInput();
6277
+ const liveList = getLiveList();
6278
+ if (!liveHiddenInput || !liveList) return;
6279
+ const items = getArrayItems(container, liveList);
6280
+ const values = items.map((item) => readStructuredValue(item));
6281
+ liveHiddenInput.value = JSON.stringify(values);
6282
+ // Notify parent structured containers after non-input actions (add/remove/move)
6283
+ // so nested array mutations are persisted correctly.
6284
+ liveHiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
6285
+
6286
+ const emptyState = liveList.querySelector(':scope > [data-structured-empty]');
6287
+ if (emptyState) {
6288
+ emptyState.style.display = values.length === 0 ? 'block' : 'none';
5967
6289
  }
5968
- });
5969
- };
6290
+ updateOrderLabels();
6291
+ };
5970
6292
 
5971
- const updateHiddenInput = () => {
5972
- if (!hiddenInput || !list) return;
5973
- const items = Array.from(list.querySelectorAll('.structured-array-item'));
5974
- const values = items.map((item) => readStructuredValue(item));
5975
- hiddenInput.value = JSON.stringify(values);
6293
+ const addArrayItem = () => {
6294
+ const liveList = getLiveList();
6295
+ if (!liveList) return;
6296
+ const templateHtml = getTemplateHtml();
6297
+ if (!templateHtml) return;
6298
+ try {
6299
+ const nextIndex = getArrayItems(container, liveList).length;
6300
+ const html = templateHtml.replace(/__INDEX__/g, String(nextIndex));
6301
+ liveList.insertAdjacentHTML('beforeend', html);
6302
+ const newItem = liveList.lastElementChild;
6303
+ if (newItem instanceof HTMLElement) {
6304
+ // Ensure cloned template content can be initialized even if stale
6305
+ // data-structured-initialized attributes were copied.
6306
+ newItem
6307
+ .querySelectorAll('[data-structured-object], [data-structured-array]')
6308
+ .forEach((nestedContainer) => {
6309
+ if (nestedContainer instanceof HTMLElement) {
6310
+ delete nestedContainer.dataset.structuredInitialized;
6311
+ }
6312
+ });
6313
+ setArrayItemExpanded(newItem, true);
6314
+ }
6315
+ if (typeof initializeTinyMCE === 'function') {
6316
+ initializeTinyMCE();
6317
+ }
6318
+ if (typeof window.initializeQuillEditors === 'function') {
6319
+ window.initializeQuillEditors();
6320
+ }
6321
+ if (typeof initializeMDXEditor === 'function') {
6322
+ initializeMDXEditor();
6323
+ }
6324
+ if (typeof window.initializeStructuredFields === 'function') {
6325
+ window.initializeStructuredFields();
6326
+ }
6327
+ updateHiddenInput();
6328
+ syncArrayState(container);
6329
+ } catch (error) {
6330
+ console.error('[structured-array] add-item failed', error);
6331
+ }
6332
+ };
5976
6333
 
5977
- const emptyState = list.querySelector('[data-structured-empty]');
5978
- if (emptyState) {
5979
- emptyState.style.display = values.length === 0 ? 'block' : 'none';
6334
+ const topLevelAddButton = container.querySelector(
6335
+ ':scope > .flex.items-center.justify-between.gap-3 [data-action="add-item"]',
6336
+ );
6337
+ if (topLevelAddButton instanceof HTMLElement) {
6338
+ topLevelAddButton.addEventListener('click', (event) => {
6339
+ event.preventDefault();
6340
+ event.stopPropagation();
6341
+ addArrayItem();
6342
+ });
5980
6343
  }
5981
- updateOrderLabels();
5982
- };
5983
6344
 
5984
- if (typeof window.initializeDragSortable === 'function' && list) {
5985
- window.initializeDragSortable(list, {
5986
- itemSelector: '.structured-array-item',
5987
- handleSelector: '[data-action="drag-handle"]',
5988
- onUpdate: updateHiddenInput
5989
- });
5990
- }
6345
+ const dragList = getLiveList();
6346
+ if (typeof window.initializeDragSortable === 'function' && dragList) {
6347
+ window.initializeDragSortable(dragList, {
6348
+ itemSelector: '.structured-array-item',
6349
+ handleSelector: '[data-action="drag-handle"]',
6350
+ onUpdate: () => {
6351
+ updateHiddenInput();
6352
+ syncArrayState(container);
6353
+ }
6354
+ });
6355
+ }
5991
6356
 
5992
- container.addEventListener('click', (event) => {
6357
+ container.addEventListener('click', (event) => {
5993
6358
  const target = event.target;
5994
6359
  if (!(target instanceof Element)) return;
5995
6360
  const actionButton = target.closest('[data-action]');
5996
6361
  if (!actionButton || actionButton.hasAttribute('disabled')) return;
6362
+ const actionOwner = actionButton.closest('[data-structured-array]');
6363
+ if (actionOwner !== container) return;
5997
6364
 
5998
- const action = actionButton.getAttribute('data-action');
6365
+ const action = actionButton.getAttribute('data-action');
5999
6366
 
6000
- if (action === 'add-item') {
6001
- if (!list || !template) return;
6002
- const nextIndex = list.querySelectorAll('.structured-array-item').length;
6003
- const html = template.innerHTML.replace(/__INDEX__/g, String(nextIndex));
6004
- list.insertAdjacentHTML('beforeend', html);
6005
- if (typeof initializeTinyMCE === 'function') {
6006
- initializeTinyMCE();
6007
- }
6008
- if (typeof window.initializeQuillEditors === 'function') {
6009
- window.initializeQuillEditors();
6010
- }
6011
- if (typeof initializeMDXEditor === 'function') {
6012
- initializeMDXEditor();
6367
+ if (action === 'add-item') {
6368
+ addArrayItem();
6369
+ return;
6013
6370
  }
6014
- updateHiddenInput();
6015
- return;
6016
- }
6017
6371
 
6018
6372
  const item = actionButton.closest('.structured-array-item');
6019
- if (!item || !list) return;
6373
+ const liveList = getLiveList();
6374
+ if (!item || !liveList) return;
6375
+
6376
+ if (action === 'toggle-item') {
6377
+ const content = item.querySelector('[data-array-item-fields]');
6378
+ if (!(content instanceof HTMLElement)) return;
6379
+ setArrayItemExpanded(item, content.classList.contains('hidden'));
6380
+ syncArrayState(container);
6381
+ return;
6382
+ }
6020
6383
 
6021
6384
  if (action === 'remove-item') {
6022
6385
  if (typeof requestRepeaterDelete === 'function') {
6023
6386
  requestRepeaterDelete(() => {
6024
6387
  item.remove();
6025
6388
  updateHiddenInput();
6389
+ syncArrayState(container);
6026
6390
  });
6027
6391
  } else {
6028
6392
  item.remove();
6029
6393
  updateHiddenInput();
6394
+ syncArrayState(container);
6030
6395
  }
6031
6396
  return;
6032
6397
  }
@@ -6034,8 +6399,9 @@ function getStructuredFieldScript() {
6034
6399
  if (action === 'move-up') {
6035
6400
  const previous = item.previousElementSibling;
6036
6401
  if (previous) {
6037
- list.insertBefore(item, previous);
6402
+ liveList.insertBefore(item, previous);
6038
6403
  updateHiddenInput();
6404
+ syncArrayState(container);
6039
6405
  }
6040
6406
  return;
6041
6407
  }
@@ -6043,29 +6409,43 @@ function getStructuredFieldScript() {
6043
6409
  if (action === 'move-down') {
6044
6410
  const next = item.nextElementSibling;
6045
6411
  if (next) {
6046
- list.insertBefore(next, item);
6412
+ liveList.insertBefore(next, item);
6047
6413
  updateHiddenInput();
6414
+ syncArrayState(container);
6048
6415
  }
6049
6416
  }
6050
- });
6417
+ });
6051
6418
 
6052
- container.addEventListener('input', (event) => {
6419
+ container.addEventListener('input', (event) => {
6053
6420
  const target = event.target;
6054
6421
  if (!(target instanceof Element)) return;
6055
6422
  if (target.closest('[data-structured-array-list]')) {
6056
6423
  updateHiddenInput();
6057
6424
  }
6058
- });
6425
+ });
6059
6426
 
6060
- container.addEventListener('change', (event) => {
6427
+ container.addEventListener('change', (event) => {
6061
6428
  const target = event.target;
6062
6429
  if (!(target instanceof Element)) return;
6063
6430
  if (target.closest('[data-structured-array-list]')) {
6064
6431
  updateHiddenInput();
6065
6432
  }
6066
- });
6433
+ });
6067
6434
 
6068
- updateHiddenInput();
6435
+ updateHiddenInput();
6436
+ const savedArrayState = readArrayState(container);
6437
+ if (savedArrayState) {
6438
+ applyArrayState(container, savedArrayState);
6439
+ } else {
6440
+ syncArrayState(container);
6441
+ }
6442
+ container.dataset.structuredInitialized = 'true';
6443
+ } catch (error) {
6444
+ delete container.dataset.structuredInitialized;
6445
+ console.error('[structured-array] initialization failed', error);
6446
+ } finally {
6447
+ delete container.dataset.structuredInitializing;
6448
+ }
6069
6449
  });
6070
6450
  }
6071
6451
 
@@ -6080,7 +6460,10 @@ function getStructuredFieldScript() {
6080
6460
  document.addEventListener('htmx:afterSwap', function() {
6081
6461
  setTimeout(initializeStructuredFields, 50);
6082
6462
  });
6083
- } else if (typeof window.initializeStructuredFields === 'function') {
6463
+ } else if (
6464
+ typeof window.initializeStructuredFields === 'function' &&
6465
+ document.readyState !== 'loading'
6466
+ ) {
6084
6467
  window.initializeStructuredFields();
6085
6468
  }
6086
6469
  </script>
@@ -6092,6 +6475,68 @@ function getBlocksFieldScript() {
6092
6475
  <script>
6093
6476
  if (!window.__sonicBlocksFieldInit) {
6094
6477
  window.__sonicBlocksFieldInit = true;
6478
+ const getCollectionScope = () => {
6479
+ const url = new URL(window.location.href);
6480
+ const collectionFromQuery = url.searchParams.get('collection');
6481
+ const form = document.getElementById('content-form');
6482
+ const collectionInput = form?.querySelector('input[name="collection_id"]');
6483
+ const collectionFromForm = collectionInput instanceof HTMLInputElement ? collectionInput.value : '';
6484
+ const collectionId = collectionFromQuery || collectionFromForm || '';
6485
+ return window.location.pathname + ':' + collectionId;
6486
+ };
6487
+
6488
+ const getBlocksStateKey = (container) => {
6489
+ const fieldName = container.dataset.fieldName || 'unknown';
6490
+ return 'sonic:ui:blocks:' + getCollectionScope() + ':' + fieldName;
6491
+ };
6492
+
6493
+ const readBlocksState = (container) => {
6494
+ try {
6495
+ const raw = sessionStorage.getItem(getBlocksStateKey(container));
6496
+ if (!raw) return null;
6497
+ const parsed = JSON.parse(raw);
6498
+ return Array.isArray(parsed) ? parsed : null;
6499
+ } catch {
6500
+ return null;
6501
+ }
6502
+ };
6503
+
6504
+ const writeBlocksState = (container, state) => {
6505
+ try {
6506
+ sessionStorage.setItem(getBlocksStateKey(container), JSON.stringify(state));
6507
+ } catch {}
6508
+ };
6509
+
6510
+ const setBlockExpanded = (item, isExpanded) => {
6511
+ const content = item.querySelector('[data-block-content]');
6512
+ const icon = item.querySelector('[data-block-toggle-icon]');
6513
+ if (content instanceof HTMLElement) {
6514
+ content.classList.toggle('hidden', !isExpanded);
6515
+ }
6516
+ if (icon instanceof Element) {
6517
+ icon.classList.toggle('-rotate-90', !isExpanded);
6518
+ }
6519
+ };
6520
+
6521
+ const captureBlocksState = (container) => {
6522
+ return Array.from(container.querySelectorAll('.blocks-item')).map((item) => {
6523
+ const content = item.querySelector('[data-block-content]');
6524
+ return content instanceof HTMLElement ? !content.classList.contains('hidden') : false;
6525
+ });
6526
+ };
6527
+
6528
+ const applyBlocksState = (container, state) => {
6529
+ const items = Array.from(container.querySelectorAll('.blocks-item'));
6530
+ items.forEach((item, index) => {
6531
+ if (typeof state[index] === 'boolean') {
6532
+ setBlockExpanded(item, state[index]);
6533
+ }
6534
+ });
6535
+ };
6536
+
6537
+ const syncBlocksState = (container) => {
6538
+ writeBlocksState(container, captureBlocksState(container));
6539
+ };
6095
6540
 
6096
6541
  function initializeBlocksFields() {
6097
6542
  document.querySelectorAll('.blocks-field').forEach((container) => {
@@ -6179,7 +6624,10 @@ function getBlocksFieldScript() {
6179
6624
  window.initializeDragSortable(list, {
6180
6625
  itemSelector: '.blocks-item',
6181
6626
  handleSelector: '[data-action="drag-handle"]',
6182
- onUpdate: updateHiddenInput
6627
+ onUpdate: () => {
6628
+ updateHiddenInput();
6629
+ syncBlocksState(container);
6630
+ }
6183
6631
  });
6184
6632
  }
6185
6633
 
@@ -6201,8 +6649,12 @@ function getBlocksFieldScript() {
6201
6649
  if (!template) return;
6202
6650
 
6203
6651
  const nextIndex = list.querySelectorAll('.blocks-item').length;
6204
- const html = template.innerHTML.replace(/__INDEX__/g, String(nextIndex));
6652
+ const html = template.innerHTML.replace(/__BLOCK_INDEX__/g, String(nextIndex));
6205
6653
  list.insertAdjacentHTML('beforeend', html);
6654
+ const newItem = list.lastElementChild;
6655
+ if (newItem instanceof HTMLElement) {
6656
+ setBlockExpanded(newItem, true);
6657
+ }
6206
6658
  if (typeSelect) {
6207
6659
  typeSelect.value = '';
6208
6660
  }
@@ -6211,21 +6663,32 @@ function getBlocksFieldScript() {
6211
6663
  window.initializeStructuredFields();
6212
6664
  }
6213
6665
  updateHiddenInput();
6666
+ syncBlocksState(container);
6214
6667
  return;
6215
6668
  }
6216
6669
 
6217
6670
  const item = actionButton.closest('.blocks-item');
6218
6671
  if (!item || !list) return;
6219
6672
 
6673
+ if (action === 'toggle-block') {
6674
+ const content = item.querySelector('[data-block-content]');
6675
+ if (!(content instanceof HTMLElement)) return;
6676
+ setBlockExpanded(item, content.classList.contains('hidden'));
6677
+ syncBlocksState(container);
6678
+ return;
6679
+ }
6680
+
6220
6681
  if (action === 'remove-block') {
6221
6682
  if (typeof requestRepeaterDelete === 'function') {
6222
6683
  requestRepeaterDelete(() => {
6223
6684
  item.remove();
6224
6685
  updateHiddenInput();
6686
+ syncBlocksState(container);
6225
6687
  }, 'block');
6226
6688
  } else {
6227
6689
  item.remove();
6228
6690
  updateHiddenInput();
6691
+ syncBlocksState(container);
6229
6692
  }
6230
6693
  return;
6231
6694
  }
@@ -6235,6 +6698,7 @@ function getBlocksFieldScript() {
6235
6698
  if (previous) {
6236
6699
  list.insertBefore(item, previous);
6237
6700
  updateHiddenInput();
6701
+ syncBlocksState(container);
6238
6702
  }
6239
6703
  return;
6240
6704
  }
@@ -6244,6 +6708,7 @@ function getBlocksFieldScript() {
6244
6708
  if (next) {
6245
6709
  list.insertBefore(next, item);
6246
6710
  updateHiddenInput();
6711
+ syncBlocksState(container);
6247
6712
  }
6248
6713
  }
6249
6714
  });
@@ -6265,6 +6730,12 @@ function getBlocksFieldScript() {
6265
6730
  });
6266
6731
 
6267
6732
  updateHiddenInput();
6733
+ const savedBlocksState = readBlocksState(container);
6734
+ if (savedBlocksState) {
6735
+ applyBlocksState(container, savedBlocksState);
6736
+ } else {
6737
+ syncBlocksState(container);
6738
+ }
6268
6739
  });
6269
6740
  }
6270
6741
 
@@ -6301,6 +6772,7 @@ chunkLTKV7AE5_cjs.init_admin_layout_catalyst_template();
6301
6772
  function renderContentFormPage(data) {
6302
6773
  const isEdit = data.isEdit || !!data.id;
6303
6774
  const title = isEdit ? `Edit: ${data.title || "Content"}` : `New ${data.collection.display_name}`;
6775
+ const hasValidationErrors = Boolean(data.validationErrors && Object.keys(data.validationErrors).length > 0);
6304
6776
  const backUrl = data.referrerParams ? `/admin/content?${data.referrerParams}` : `/admin/content?collection=${data.collection.id}`;
6305
6777
  const coreFields = data.fields.filter((f) => ["title", "slug", "content"].includes(f.field_name));
6306
6778
  const contentFields = data.fields.filter((f) => !["title", "slug", "content"].includes(f.field_name) && !f.field_name.startsWith("meta_"));
@@ -6389,6 +6861,7 @@ function renderContentFormPage(data) {
6389
6861
  ${isEdit ? `hx-put="/admin/content/${data.id}"` : `hx-post="/admin/content"`}
6390
6862
  hx-target="#form-messages"
6391
6863
  hx-encoding="multipart/form-data"
6864
+ data-has-validation-errors="${hasValidationErrors ? "true" : "false"}"
6392
6865
  class="space-y-6"
6393
6866
  >
6394
6867
  <input type="hidden" name="collection_id" value="${data.collection.id}">
@@ -6669,39 +7142,456 @@ function renderContentFormPage(data) {
6669
7142
 
6670
7143
  <!-- Dynamic Field Scripts -->
6671
7144
  <script>
7145
+ const contentFormCollectionId = ${JSON.stringify(data.collection.id)};
7146
+
7147
+ function getFieldGroupScope() {
7148
+ const url = new URL(window.location.href);
7149
+ const urlCollectionId = url.searchParams.get('collection');
7150
+ const effectiveCollectionId = urlCollectionId || contentFormCollectionId || '';
7151
+ return window.location.pathname + ':' + effectiveCollectionId;
7152
+ }
7153
+
7154
+ function getItemPosition(itemSelector, item) {
7155
+ if (!(item instanceof Element)) return -1;
7156
+ const parent = item.parentElement;
7157
+ if (!parent) return -1;
7158
+ return Array.from(parent.querySelectorAll(':scope > ' + itemSelector)).indexOf(item);
7159
+ }
7160
+
7161
+ function stripIndexedFieldPrefix(fullFieldName, prefix) {
7162
+ if (!fullFieldName || !prefix || !fullFieldName.startsWith(prefix)) {
7163
+ return fullFieldName;
7164
+ }
7165
+
7166
+ const remainder = fullFieldName.slice(prefix.length);
7167
+ const indexMatch = remainder.match(/^(\\d+)(-|__)(.*)$/);
7168
+ if (!indexMatch) {
7169
+ return fullFieldName;
7170
+ }
7171
+
7172
+ return indexMatch[3];
7173
+ }
7174
+
7175
+ function getFieldGroupStorageKey(groupOrId) {
7176
+ const defaultGroupId = typeof groupOrId === 'string' ? groupOrId : (groupOrId?.getAttribute('data-group-id') || 'unknown');
7177
+ const group = typeof groupOrId === 'string'
7178
+ ? document.querySelector('.field-group[data-group-id="' + defaultGroupId + '"]')
7179
+ : groupOrId;
7180
+
7181
+ const scopePrefix = 'sonic:ui:objects:' + getFieldGroupScope() + ':';
7182
+ if (!(group instanceof Element)) {
7183
+ return scopePrefix + defaultGroupId;
7184
+ }
7185
+
7186
+ const fullFieldName = group.getAttribute('data-field-name') || '';
7187
+
7188
+ const blocksField = group.closest('.blocks-field');
7189
+ const blockItem = group.closest('.blocks-item');
7190
+ if (blocksField instanceof Element && blockItem instanceof Element) {
7191
+ const blocksFieldName = blocksField.getAttribute('data-field-name') || 'unknown';
7192
+ const blockPosition = getItemPosition('.blocks-item', blockItem);
7193
+ const relativePath = stripIndexedFieldPrefix(fullFieldName, 'block-' + blocksFieldName + '-') || defaultGroupId;
7194
+ return scopePrefix + 'blocks:' + blocksFieldName + ':' + blockPosition + ':' + relativePath;
7195
+ }
7196
+
7197
+ const arrayField = group.closest('[data-structured-array][data-field-name]');
7198
+ const arrayItem = group.closest('.structured-array-item');
7199
+ if (arrayField instanceof Element && arrayItem instanceof Element) {
7200
+ const arrayFieldName = arrayField.getAttribute('data-field-name') || 'unknown';
7201
+ const itemPosition = getItemPosition('.structured-array-item', arrayItem);
7202
+ const relativePath = stripIndexedFieldPrefix(fullFieldName, 'array-' + arrayFieldName + '-') || defaultGroupId;
7203
+ return scopePrefix + 'repeaters:' + arrayFieldName + ':' + itemPosition + ':' + relativePath;
7204
+ }
7205
+
7206
+ return scopePrefix + defaultGroupId;
7207
+ }
7208
+
7209
+ function loadFieldGroupState(group) {
7210
+ try {
7211
+ const value = sessionStorage.getItem(getFieldGroupStorageKey(group));
7212
+ if (value === '1') return true;
7213
+ if (value === '0') return false;
7214
+ } catch {}
7215
+ return null;
7216
+ }
7217
+
7218
+ function saveFieldGroupState(group, isCollapsed) {
7219
+ try {
7220
+ sessionStorage.setItem(getFieldGroupStorageKey(group), isCollapsed ? '1' : '0');
7221
+ } catch {}
7222
+ }
7223
+
7224
+ function resolveFieldGroupElements(groupOrId) {
7225
+ let group = null;
7226
+
7227
+ if (groupOrId instanceof Element) {
7228
+ group = groupOrId.classList.contains('field-group')
7229
+ ? groupOrId
7230
+ : groupOrId.closest('.field-group[data-group-id]');
7231
+ } else if (typeof groupOrId === 'string' && groupOrId) {
7232
+ group = document.querySelector('.field-group[data-group-id="' + groupOrId + '"]');
7233
+ }
7234
+
7235
+ let content = null;
7236
+ let icon = null;
7237
+
7238
+ if (group instanceof Element) {
7239
+ content = group.querySelector(':scope > .field-group-content');
7240
+ icon = group.querySelector(':scope > .field-group-header svg[id$="-icon"]');
7241
+ }
7242
+
7243
+ // Legacy fallback for any existing calls still passing string IDs.
7244
+ if (!(content instanceof HTMLElement) && typeof groupOrId === 'string') {
7245
+ content = document.getElementById(groupOrId + '-content');
7246
+ }
7247
+ if (!(icon instanceof Element) && typeof groupOrId === 'string') {
7248
+ icon = document.getElementById(groupOrId + '-icon');
7249
+ }
7250
+
7251
+ if (!(group instanceof Element) && content instanceof Element) {
7252
+ group = content.closest('.field-group[data-group-id]');
7253
+ }
7254
+
7255
+ return { group, content, icon };
7256
+ }
7257
+
7258
+ function applyFieldGroupState(groupOrId, isCollapsed) {
7259
+ const { content, icon } = resolveFieldGroupElements(groupOrId);
7260
+ if (!(content instanceof HTMLElement) || !(icon instanceof Element)) return;
7261
+ content.classList.toggle('hidden', isCollapsed);
7262
+ icon.classList.toggle('-rotate-90', isCollapsed);
7263
+ }
7264
+
7265
+ function restoreFieldGroupStates() {
7266
+ document.querySelectorAll('.field-group[data-group-id]').forEach((group) => {
7267
+ const savedState = loadFieldGroupState(group);
7268
+ if (savedState === null) return;
7269
+ applyFieldGroupState(group, savedState);
7270
+ });
7271
+ }
7272
+
7273
+ function persistAllFieldGroupStates() {
7274
+ document.querySelectorAll('.field-group[data-group-id]').forEach((group) => {
7275
+ const { content } = resolveFieldGroupElements(group);
7276
+ if (!(content instanceof HTMLElement)) return;
7277
+ saveFieldGroupState(group, content.classList.contains('hidden'));
7278
+ });
7279
+ }
7280
+
7281
+ function setValidationHeaderIndicator(container) {
7282
+ if (!(container instanceof Element)) return;
7283
+ let header = null;
7284
+ let markerTarget = null;
7285
+
7286
+ if (container.classList.contains('field-group')) {
7287
+ header = container.querySelector(':scope > .field-group-header');
7288
+ markerTarget = container.querySelector(':scope > .field-group-header h3');
7289
+ } else if (container.classList.contains('structured-array-item')) {
7290
+ header = container.querySelector('[data-action="toggle-item"]');
7291
+ markerTarget = header;
7292
+ } else if (container.classList.contains('blocks-item')) {
7293
+ header = container.querySelector('[data-action="toggle-block"]');
7294
+ markerTarget = header;
7295
+ }
7296
+
7297
+ if (!(header instanceof HTMLElement)) return;
7298
+ if (!(markerTarget instanceof HTMLElement)) {
7299
+ markerTarget = header;
7300
+ }
7301
+
7302
+ header.dataset.validationHeaderError = 'true';
7303
+ header.classList.add('text-pink-700', 'dark:text-pink-300');
7304
+
7305
+ if (!markerTarget.querySelector('[data-validation-indicator]')) {
7306
+ const marker = document.createElement('span');
7307
+ marker.setAttribute('data-validation-indicator', 'true');
7308
+ marker.className = 'ml-2 inline-block h-2 w-2 rounded-full bg-pink-500 align-middle';
7309
+ marker.setAttribute('aria-hidden', 'true');
7310
+ markerTarget.appendChild(marker);
7311
+ }
7312
+ }
7313
+
7314
+ function clearValidationIndicators() {
7315
+ document.querySelectorAll('[data-validation-header-error="true"]').forEach((el) => {
7316
+ if (!(el instanceof HTMLElement)) return;
7317
+ delete el.dataset.validationHeaderError;
7318
+ el.classList.remove('text-pink-700', 'dark:text-pink-300');
7319
+ });
7320
+
7321
+ document.querySelectorAll('[data-validation-indicator]').forEach((el) => el.remove());
7322
+ }
7323
+
7324
+ function expandContainerForValidation(container) {
7325
+ if (!(container instanceof Element)) return;
7326
+
7327
+ if (container.classList.contains('field-group')) {
7328
+ applyFieldGroupState(container, false);
7329
+ return;
7330
+ }
7331
+
7332
+ if (container.classList.contains('structured-array-item')) {
7333
+ const content = container.querySelector('[data-array-item-fields]');
7334
+ const icon = container.querySelector('[data-item-toggle-icon]');
7335
+ if (content instanceof HTMLElement) {
7336
+ content.classList.remove('hidden');
7337
+ }
7338
+ if (icon instanceof Element) {
7339
+ icon.classList.remove('-rotate-90');
7340
+ }
7341
+ return;
7342
+ }
7343
+
7344
+ if (container.classList.contains('blocks-item')) {
7345
+ const content = container.querySelector('[data-block-content]');
7346
+ const icon = container.querySelector('[data-block-toggle-icon]');
7347
+ if (content instanceof HTMLElement) {
7348
+ content.classList.remove('hidden');
7349
+ }
7350
+ if (icon instanceof Element) {
7351
+ icon.classList.remove('-rotate-90');
7352
+ }
7353
+ }
7354
+ }
7355
+
7356
+ function walkErrorContainers(node, expand) {
7357
+ if (!(node instanceof Element)) return;
7358
+ const visited = new Set();
7359
+ let cursor = node;
7360
+ while (cursor) {
7361
+ const candidates = [
7362
+ cursor.closest('.structured-array-item'),
7363
+ cursor.closest('.blocks-item'),
7364
+ cursor.closest('.field-group[data-group-id]')
7365
+ ].filter((c) => c instanceof Element && !visited.has(c));
7366
+
7367
+ if (candidates.length === 0) break;
7368
+
7369
+ // Pick nearest ancestor container to preserve "first-error path only".
7370
+ let nearest = candidates[0];
7371
+ let bestDistance = Number.MAX_SAFE_INTEGER;
7372
+ for (const candidate of candidates) {
7373
+ let distance = 0;
7374
+ let walker = cursor;
7375
+ while (walker && walker !== candidate) {
7376
+ walker = walker.parentElement;
7377
+ distance += 1;
7378
+ }
7379
+ if (distance < bestDistance) {
7380
+ bestDistance = distance;
7381
+ nearest = candidate;
7382
+ }
7383
+ }
7384
+
7385
+ visited.add(nearest);
7386
+ setValidationHeaderIndicator(nearest);
7387
+ if (expand) {
7388
+ expandContainerForValidation(nearest);
7389
+ }
7390
+ cursor = nearest.parentElement;
7391
+ }
7392
+ }
7393
+
7394
+ function getFocusableTargetFromErrorGroup(group) {
7395
+ if (!(group instanceof Element)) return null;
7396
+ return (
7397
+ group.querySelector('input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"]') ||
7398
+ group.querySelector('button:not([disabled])')
7399
+ );
7400
+ }
7401
+
7402
+ function revealServerValidationErrors() {
7403
+ clearValidationIndicators();
7404
+
7405
+ const errorGroups = Array.from(document.querySelectorAll('.form-group[data-has-errors="true"]'));
7406
+ if (errorGroups.length === 0) return;
7407
+
7408
+ // Add indicators for all errored sections, expand only first-error path.
7409
+ errorGroups.forEach((group, index) => {
7410
+ walkErrorContainers(group, index === 0);
7411
+ });
7412
+
7413
+ const firstTarget = getFocusableTargetFromErrorGroup(errorGroups[0]);
7414
+ if (firstTarget instanceof HTMLElement) {
7415
+ firstTarget.scrollIntoView({ behavior: 'smooth', block: 'center' });
7416
+ firstTarget.focus({ preventScroll: true });
7417
+ }
7418
+ }
7419
+
7420
+ function revealNativeValidationErrors(form) {
7421
+ if (!(form instanceof HTMLFormElement)) return;
7422
+ clearValidationIndicators();
7423
+
7424
+ const invalidControls = Array.from(form.querySelectorAll(':invalid'));
7425
+ if (invalidControls.length === 0) return;
7426
+
7427
+ invalidControls.forEach((control, index) => {
7428
+ walkErrorContainers(control, index === 0);
7429
+ });
7430
+
7431
+ const first = invalidControls[0];
7432
+ if (first instanceof HTMLElement) {
7433
+ first.scrollIntoView({ behavior: 'smooth', block: 'center' });
7434
+ first.focus({ preventScroll: true });
7435
+ }
7436
+ }
7437
+
6672
7438
  // Field group toggle
6673
- function toggleFieldGroup(groupId) {
6674
- const content = document.getElementById(groupId + '-content');
6675
- const icon = document.getElementById(groupId + '-icon');
6676
-
6677
- if (content.classList.contains('hidden')) {
6678
- content.classList.remove('hidden');
6679
- icon.classList.remove('rotate-[-90deg]');
6680
- } else {
6681
- content.classList.add('hidden');
6682
- icon.classList.add('rotate-[-90deg]');
7439
+ function toggleFieldGroup(groupOrTrigger) {
7440
+ const { group, content } = resolveFieldGroupElements(groupOrTrigger);
7441
+ if (!(group instanceof Element)) return;
7442
+ if (!(content instanceof HTMLElement)) return;
7443
+
7444
+ const isCollapsed = !content.classList.contains('hidden');
7445
+ applyFieldGroupState(group, isCollapsed);
7446
+ saveFieldGroupState(group, isCollapsed);
7447
+ }
7448
+
7449
+ if (document.readyState === 'loading') {
7450
+ document.addEventListener('DOMContentLoaded', () => {
7451
+ restoreFieldGroupStates();
7452
+ const form = document.getElementById('content-form');
7453
+ if (form?.getAttribute('data-has-validation-errors') === 'true') {
7454
+ revealServerValidationErrors();
7455
+ }
7456
+ });
7457
+ } else {
7458
+ restoreFieldGroupStates();
7459
+ const form = document.getElementById('content-form');
7460
+ if (form?.getAttribute('data-has-validation-errors') === 'true') {
7461
+ revealServerValidationErrors();
6683
7462
  }
6684
7463
  }
6685
7464
 
7465
+ document.addEventListener('htmx:afterSwap', function() {
7466
+ setTimeout(() => {
7467
+ restoreFieldGroupStates();
7468
+ const form = document.getElementById('content-form');
7469
+ if (form?.getAttribute('data-has-validation-errors') === 'true') {
7470
+ revealServerValidationErrors();
7471
+ }
7472
+ }, 50);
7473
+ });
7474
+
7475
+ const contentFormEl = document.getElementById('content-form');
7476
+ if (contentFormEl instanceof HTMLFormElement) {
7477
+ contentFormEl.addEventListener('submit', () => {
7478
+ persistAllFieldGroupStates();
7479
+ }, true);
7480
+ }
7481
+
7482
+ window.addEventListener('beforeunload', () => {
7483
+ persistAllFieldGroupStates();
7484
+ });
7485
+
7486
+ document.addEventListener('visibilitychange', () => {
7487
+ if (document.visibilityState === 'hidden') {
7488
+ persistAllFieldGroupStates();
7489
+ }
7490
+ });
7491
+
7492
+ let pendingNativeValidationReveal = false;
7493
+ document.addEventListener('invalid', function(event) {
7494
+ const target = event.target;
7495
+ if (!(target instanceof Element)) return;
7496
+ const form = target.closest('form');
7497
+ if (!(form instanceof HTMLFormElement)) return;
7498
+
7499
+ if (pendingNativeValidationReveal) return;
7500
+ pendingNativeValidationReveal = true;
7501
+
7502
+ // Expand only first invalid path synchronously so the browser can focus it
7503
+ // and avoid "invalid form control is not focusable" errors.
7504
+ walkErrorContainers(target, true);
7505
+
7506
+ setTimeout(() => {
7507
+ pendingNativeValidationReveal = false;
7508
+ revealNativeValidationErrors(form);
7509
+ }, 0);
7510
+ }, true);
7511
+
6686
7512
  // Media field functions
6687
- let currentMediaFieldId = null;
7513
+ function notifyFieldChange(input) {
7514
+ if (!input) return;
7515
+ input.dispatchEvent(new Event('input', { bubbles: true }));
7516
+ input.dispatchEvent(new Event('change', { bubbles: true }));
7517
+ }
7518
+
7519
+ function getActiveMediaModal() {
7520
+ const modal = document.getElementById('media-selector-modal');
7521
+ return modal instanceof HTMLElement ? modal : null;
7522
+ }
7523
+
7524
+ function getMediaFieldElements(fieldId) {
7525
+ if (!fieldId) {
7526
+ return {
7527
+ fieldId: '',
7528
+ hiddenInput: null,
7529
+ preview: null,
7530
+ mediaField: null,
7531
+ actionsDiv: null,
7532
+ };
7533
+ }
7534
+
7535
+ const hiddenInput = document.getElementById(fieldId);
7536
+ const preview = document.getElementById(fieldId + '-preview');
7537
+ const mediaField = hiddenInput?.closest('.media-field-container') || null;
7538
+ const actionsDiv = mediaField?.querySelector('.media-actions') || null;
7539
+
7540
+ return {
7541
+ fieldId,
7542
+ hiddenInput,
7543
+ preview,
7544
+ mediaField,
7545
+ actionsDiv,
7546
+ };
7547
+ }
7548
+
7549
+ function getActiveMediaTarget() {
7550
+ const modal = getActiveMediaModal();
7551
+ const fieldId = modal?.dataset.targetFieldId || '';
7552
+ return {
7553
+ modal,
7554
+ originalValue: modal?.dataset.originalValue || '',
7555
+ ...getMediaFieldElements(fieldId),
7556
+ };
7557
+ }
7558
+
7559
+ function ensureSingleMediaRemoveButton(fieldId, actionsDiv) {
7560
+ if (!(actionsDiv instanceof HTMLElement)) return;
7561
+ const existingRemoveButton = actionsDiv.querySelector('[data-media-remove="true"]');
7562
+ if (existingRemoveButton) return;
7563
+
7564
+ const removeBtn = document.createElement('button');
7565
+ removeBtn.type = 'button';
7566
+ removeBtn.setAttribute('data-media-remove', 'true');
7567
+ removeBtn.onclick = () => clearMediaField(fieldId);
7568
+ removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
7569
+ removeBtn.textContent = 'Remove';
7570
+ actionsDiv.appendChild(removeBtn);
7571
+ }
6688
7572
 
6689
7573
  function openMediaSelector(fieldId) {
6690
- currentMediaFieldId = fieldId;
7574
+ const existingModal = getActiveMediaModal();
7575
+ if (existingModal) {
7576
+ existingModal.remove();
7577
+ }
7578
+
6691
7579
  // Store the original value in case user cancels
6692
- const originalValue = document.getElementById(fieldId)?.value || '';
7580
+ const originalValue = getMediaFieldElements(fieldId).hiddenInput?.value || '';
6693
7581
 
6694
7582
  // Open media library modal
6695
7583
  const modal = document.createElement('div');
6696
7584
  modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50';
6697
7585
  modal.id = 'media-selector-modal';
7586
+ modal.dataset.targetFieldId = fieldId;
7587
+ modal.dataset.originalValue = originalValue;
6698
7588
  modal.innerHTML = \`
6699
7589
  <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-xl ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
6700
7590
  <h3 class="text-lg font-semibold text-zinc-950 dark:text-white mb-4">Select Media</h3>
6701
7591
  <div id="media-grid-container" hx-get="/admin/media/selector" hx-trigger="load"></div>
6702
7592
  <div class="mt-4 flex justify-end space-x-2">
6703
7593
  <button
6704
- onclick="cancelMediaSelection('\${fieldId}', '\${originalValue}')"
7594
+ onclick="cancelMediaSelection()"
6705
7595
  class="rounded-lg bg-white dark:bg-zinc-800 px-4 py-2 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
6706
7596
  Cancel
6707
7597
  </button>
@@ -6721,23 +7611,23 @@ function renderContentFormPage(data) {
6721
7611
  }
6722
7612
 
6723
7613
  function closeMediaSelector() {
6724
- const modal = document.getElementById('media-selector-modal');
7614
+ const modal = getActiveMediaModal();
6725
7615
  if (modal) {
6726
7616
  modal.remove();
6727
7617
  }
6728
- currentMediaFieldId = null;
6729
7618
  }
6730
7619
 
6731
- function cancelMediaSelection(fieldId, originalValue) {
7620
+ function cancelMediaSelection() {
7621
+ const { hiddenInput, preview, originalValue } = getActiveMediaTarget();
7622
+
6732
7623
  // Restore original value
6733
- const hiddenInput = document.getElementById(fieldId);
6734
7624
  if (hiddenInput) {
6735
7625
  hiddenInput.value = originalValue;
7626
+ notifyFieldChange(hiddenInput);
6736
7627
  }
6737
7628
 
6738
7629
  // If original value was empty, hide the preview and show select button
6739
7630
  if (!originalValue) {
6740
- const preview = document.getElementById(fieldId + '-preview');
6741
7631
  if (preview) {
6742
7632
  preview.classList.add('hidden');
6743
7633
  }
@@ -6748,11 +7638,11 @@ function renderContentFormPage(data) {
6748
7638
  }
6749
7639
 
6750
7640
  function clearMediaField(fieldId) {
6751
- const hiddenInput = document.getElementById(fieldId);
6752
- const preview = document.getElementById(fieldId + '-preview');
7641
+ const { hiddenInput, preview, actionsDiv } = getMediaFieldElements(fieldId);
6753
7642
 
6754
7643
  if (hiddenInput) {
6755
7644
  hiddenInput.value = '';
7645
+ notifyFieldChange(hiddenInput);
6756
7646
  }
6757
7647
 
6758
7648
  if (preview) {
@@ -6762,25 +7652,34 @@ function renderContentFormPage(data) {
6762
7652
  }
6763
7653
  preview.classList.add('hidden');
6764
7654
  }
7655
+
7656
+ const removeButton = actionsDiv?.querySelector('[data-media-remove="true"]');
7657
+ if (removeButton) {
7658
+ removeButton.remove();
7659
+ }
6765
7660
  }
6766
7661
 
6767
7662
  // Global function to remove a single media from multiple selection
6768
7663
  window.removeMediaFromMultiple = function(fieldId, urlToRemove) {
6769
- const hiddenInput = document.getElementById(fieldId);
7664
+ const { hiddenInput, preview } = getMediaFieldElements(fieldId);
6770
7665
  if (!hiddenInput) return;
6771
7666
 
6772
7667
  const values = hiddenInput.value.split(',').filter(url => url !== urlToRemove);
6773
7668
  hiddenInput.value = values.join(',');
7669
+ notifyFieldChange(hiddenInput);
6774
7670
 
6775
7671
  // Remove preview item
6776
- const previewItem = document.querySelector(\`[data-url="\${urlToRemove}"]\`);
7672
+ const previewItem =
7673
+ preview &&
7674
+ Array.from(preview.querySelectorAll('[data-url]')).find(
7675
+ (item) => item.getAttribute('data-url') === urlToRemove,
7676
+ );
6777
7677
  if (previewItem) {
6778
7678
  previewItem.remove();
6779
7679
  }
6780
7680
 
6781
7681
  // Hide preview grid if empty
6782
7682
  if (values.length === 0) {
6783
- const preview = document.getElementById(fieldId + '-preview');
6784
7683
  if (preview) {
6785
7684
  preview.classList.add('hidden');
6786
7685
  }
@@ -6789,39 +7688,24 @@ function renderContentFormPage(data) {
6789
7688
 
6790
7689
  // Global function called by media selector buttons
6791
7690
  window.selectMediaFile = function(mediaId, mediaUrl, filename) {
6792
- if (!currentMediaFieldId) {
7691
+ const { fieldId, hiddenInput, preview, actionsDiv } = getActiveMediaTarget();
7692
+ if (!fieldId || !hiddenInput) {
6793
7693
  console.error('No field ID set for media selection');
6794
7694
  return;
6795
7695
  }
6796
7696
 
6797
- const fieldId = currentMediaFieldId;
6798
-
6799
7697
  // Set the hidden input value to the media URL (not ID)
6800
- const hiddenInput = document.getElementById(fieldId);
6801
- if (hiddenInput) {
6802
- hiddenInput.value = mediaUrl;
6803
- }
7698
+ hiddenInput.value = mediaUrl;
7699
+ notifyFieldChange(hiddenInput);
6804
7700
 
6805
7701
  // Update the preview
6806
- const preview = document.getElementById(fieldId + '-preview');
6807
7702
  if (preview) {
6808
7703
  preview.innerHTML = \`<img src="\${mediaUrl}" alt="\${filename}" class="w-32 h-32 object-cover rounded-lg border border-white/20">\`;
6809
7704
  preview.classList.remove('hidden');
6810
7705
  }
6811
7706
 
6812
7707
  // Show the remove button by finding the media actions container and updating it
6813
- const mediaField = hiddenInput?.closest('.media-field-container');
6814
- if (mediaField) {
6815
- const actionsDiv = mediaField.querySelector('.media-actions');
6816
- if (actionsDiv && !actionsDiv.querySelector('button:has-text("Remove")')) {
6817
- const removeBtn = document.createElement('button');
6818
- removeBtn.type = 'button';
6819
- removeBtn.onclick = () => clearMediaField(fieldId);
6820
- removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
6821
- removeBtn.textContent = 'Remove';
6822
- actionsDiv.appendChild(removeBtn);
6823
- }
6824
- }
7708
+ ensureSingleMediaRemoveButton(fieldId, actionsDiv);
6825
7709
 
6826
7710
  // DON'T close the modal - let user click OK button
6827
7711
  // Visual feedback: highlight the selected item
@@ -6835,7 +7719,9 @@ function renderContentFormPage(data) {
6835
7719
  };
6836
7720
 
6837
7721
  function setMediaField(fieldId, mediaUrl) {
6838
- document.getElementById(fieldId).value = mediaUrl;
7722
+ const hiddenInput = document.getElementById(fieldId);
7723
+ hiddenInput.value = mediaUrl;
7724
+ notifyFieldChange(hiddenInput);
6839
7725
  const preview = document.getElementById(fieldId + '-preview');
6840
7726
  preview.innerHTML = \`<img src="\${mediaUrl}" alt="Selected media" class="w-32 h-32 object-cover rounded-lg ring-1 ring-zinc-950/10 dark:ring-white/10">\`;
6841
7727
  preview.classList.remove('hidden');
@@ -8242,15 +9128,49 @@ function renderContentListPage(data) {
8242
9128
  return chunkLTKV7AE5_cjs.renderAdminLayoutCatalyst(layoutData);
8243
9129
  }
8244
9130
 
9131
+ // src/routes/admin-content-field-types.ts
9132
+ function resolveSchemaFieldType(fieldConfig) {
9133
+ if (fieldConfig.type === "slug" || fieldConfig.format === "slug") {
9134
+ return "slug";
9135
+ }
9136
+ if (fieldConfig.type && fieldConfig.type !== "string") {
9137
+ return fieldConfig.type;
9138
+ }
9139
+ if (fieldConfig.format === "richtext") {
9140
+ return "richtext";
9141
+ }
9142
+ if (fieldConfig.format === "media") {
9143
+ return "media";
9144
+ }
9145
+ if (fieldConfig.format === "date-time") {
9146
+ return "date";
9147
+ }
9148
+ if (Array.isArray(fieldConfig.enum)) {
9149
+ return "select";
9150
+ }
9151
+ return fieldConfig.type || "string";
9152
+ }
9153
+ function buildSchemaFieldOptions(fieldConfig) {
9154
+ const fieldOptions = { ...fieldConfig };
9155
+ const resolvedFieldType = resolveSchemaFieldType(fieldConfig);
9156
+ if (resolvedFieldType === "select" && Array.isArray(fieldConfig.enum)) {
9157
+ fieldOptions.options = fieldConfig.enum.map((value, index) => ({
9158
+ value,
9159
+ label: fieldConfig.enumLabels?.[index] || value
9160
+ }));
9161
+ }
9162
+ return fieldOptions;
9163
+ }
9164
+
8245
9165
  // src/routes/admin-content.ts
8246
9166
  var adminContentRoutes = new hono.Hono();
8247
9167
  function parseFieldValue(field, formData, options = {}) {
8248
9168
  const { skipValidation = false } = options;
8249
9169
  const value = formData.get(field.field_name);
8250
9170
  const errors = [];
8251
- const blocksConfig = chunkJDIM5AG7_cjs.getBlocksFieldConfig(field.field_options);
9171
+ const blocksConfig = chunkEKPLKUZT_cjs.getBlocksFieldConfig(field.field_options);
8252
9172
  if (blocksConfig) {
8253
- const parsed = chunkJDIM5AG7_cjs.parseBlocksValue(value, blocksConfig);
9173
+ const parsed = chunkEKPLKUZT_cjs.parseBlocksValue(value, blocksConfig);
8254
9174
  if (!skipValidation && field.is_required && parsed.value.length === 0) {
8255
9175
  parsed.errors.push(`${field.field_label} is required`);
8256
9176
  }
@@ -8360,9 +9280,9 @@ function extractFieldData(fields, formData, options = {}) {
8360
9280
  }
8361
9281
  return { data, errors };
8362
9282
  }
8363
- adminContentRoutes.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
9283
+ adminContentRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
8364
9284
  async function getCollectionFields(db, collectionId) {
8365
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.collection);
9285
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.collection);
8366
9286
  return cache.getOrSet(
8367
9287
  cache.generateKey("fields", collectionId),
8368
9288
  async () => {
@@ -8374,17 +9294,11 @@ async function getCollectionFields(db, collectionId) {
8374
9294
  if (schema && schema.properties) {
8375
9295
  let fieldOrder = 0;
8376
9296
  return Object.entries(schema.properties).map(([fieldName, fieldConfig]) => {
8377
- let fieldOptions = { ...fieldConfig };
8378
- if (fieldConfig.type === "select" && fieldConfig.enum) {
8379
- fieldOptions.options = fieldConfig.enum.map((value, index) => ({
8380
- value,
8381
- label: fieldConfig.enumLabels?.[index] || value
8382
- }));
8383
- }
9297
+ const fieldOptions = buildSchemaFieldOptions(fieldConfig);
8384
9298
  return {
8385
9299
  id: `schema-${fieldName}`,
8386
9300
  field_name: fieldName,
8387
- field_type: fieldConfig.type || "string",
9301
+ field_type: resolveSchemaFieldType(fieldConfig),
8388
9302
  field_label: fieldConfig.title || fieldName,
8389
9303
  field_options: fieldOptions,
8390
9304
  field_order: fieldOrder++,
@@ -8417,7 +9331,7 @@ async function getCollectionFields(db, collectionId) {
8417
9331
  );
8418
9332
  }
8419
9333
  async function getCollection(db, collectionId) {
8420
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.collection);
9334
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.collection);
8421
9335
  return cache.getOrSet(
8422
9336
  cache.generateKey("collection", collectionId),
8423
9337
  async () => {
@@ -8642,21 +9556,21 @@ adminContentRoutes.get("/new", async (c) => {
8642
9556
  const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
8643
9557
  let tinymceSettings;
8644
9558
  if (tinymceEnabled) {
8645
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
9559
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
8646
9560
  const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
8647
9561
  tinymceSettings = tinymcePlugin2?.settings;
8648
9562
  }
8649
9563
  const quillEnabled = await isPluginActive2(db, "quill-editor");
8650
9564
  let quillSettings;
8651
9565
  if (quillEnabled) {
8652
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
9566
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
8653
9567
  const quillPlugin = await pluginService.getPlugin("quill-editor");
8654
9568
  quillSettings = quillPlugin?.settings;
8655
9569
  }
8656
9570
  const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
8657
9571
  let mdxeditorSettings;
8658
9572
  if (mdxeditorEnabled) {
8659
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
9573
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
8660
9574
  const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
8661
9575
  mdxeditorSettings = mdxeditorPlugin?.settings;
8662
9576
  }
@@ -8706,7 +9620,7 @@ adminContentRoutes.get("/:id/edit", async (c) => {
8706
9620
  const db = c.env.DB;
8707
9621
  const url = new URL(c.req.url);
8708
9622
  const referrerParams = url.searchParams.get("ref") || "";
8709
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.content);
9623
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.content);
8710
9624
  const content = await cache.getOrSet(
8711
9625
  cache.generateKey("content", id),
8712
9626
  async () => {
@@ -8747,21 +9661,21 @@ adminContentRoutes.get("/:id/edit", async (c) => {
8747
9661
  const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
8748
9662
  let tinymceSettings;
8749
9663
  if (tinymceEnabled) {
8750
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
9664
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
8751
9665
  const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
8752
9666
  tinymceSettings = tinymcePlugin2?.settings;
8753
9667
  }
8754
9668
  const quillEnabled = await isPluginActive2(db, "quill-editor");
8755
9669
  let quillSettings;
8756
9670
  if (quillEnabled) {
8757
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
9671
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
8758
9672
  const quillPlugin = await pluginService.getPlugin("quill-editor");
8759
9673
  quillSettings = quillPlugin?.settings;
8760
9674
  }
8761
9675
  const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
8762
9676
  let mdxeditorSettings;
8763
9677
  if (mdxeditorEnabled) {
8764
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
9678
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
8765
9679
  const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
8766
9680
  mdxeditorSettings = mdxeditorPlugin?.settings;
8767
9681
  }
@@ -8882,7 +9796,7 @@ adminContentRoutes.post("/", async (c) => {
8882
9796
  now,
8883
9797
  now
8884
9798
  ).run();
8885
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.content);
9799
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.content);
8886
9800
  await cache.invalidate(`content:list:${collectionId}:*`);
8887
9801
  const versionStmt = db.prepare(`
8888
9802
  INSERT INTO content_versions (id, content_id, version, data, author_id, created_at)
@@ -9001,7 +9915,7 @@ adminContentRoutes.put("/:id", async (c) => {
9001
9915
  now,
9002
9916
  id
9003
9917
  ).run();
9004
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.content);
9918
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.content);
9005
9919
  await cache.delete(cache.generateKey("content", id));
9006
9920
  await cache.invalidate(`content:list:${existingContent.collection_id}:*`);
9007
9921
  const existingData = JSON.parse(existingContent.data || "{}");
@@ -9056,7 +9970,7 @@ adminContentRoutes.put("/:id", async (c) => {
9056
9970
  `);
9057
9971
  }
9058
9972
  });
9059
- adminContentRoutes.post("/preview", chunkK4Q4SFJJ_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
9973
+ adminContentRoutes.post("/preview", chunkIT2TC4ZD_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
9060
9974
  try {
9061
9975
  const formData = await c.req.formData();
9062
9976
  const collectionId = formData.get("collection_id");
@@ -9278,7 +10192,7 @@ adminContentRoutes.post("/bulk-action", async (c) => {
9278
10192
  } else {
9279
10193
  return c.json({ success: false, error: "Invalid action" });
9280
10194
  }
9281
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.content);
10195
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.content);
9282
10196
  for (const contentId of ids) {
9283
10197
  await cache.delete(cache.generateKey("content", contentId));
9284
10198
  }
@@ -9306,7 +10220,7 @@ adminContentRoutes.delete("/:id", async (c) => {
9306
10220
  WHERE id = ?
9307
10221
  `);
9308
10222
  await deleteStmt.bind(now, id).run();
9309
- const cache = chunkVNLR35GO_cjs.getCacheService(chunkVNLR35GO_cjs.CACHE_CONFIGS.content);
10223
+ const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.content);
9310
10224
  await cache.delete(cache.generateKey("content", id));
9311
10225
  await cache.invalidate("content:list:*");
9312
10226
  return c.html(`
@@ -9434,7 +10348,7 @@ adminContentRoutes.post("/:id/restore/:version", async (c) => {
9434
10348
  return c.json({ success: false, error: "Failed to restore version" });
9435
10349
  }
9436
10350
  });
9437
- adminContentRoutes.get("/:id/version/:version/preview", chunkK4Q4SFJJ_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
10351
+ adminContentRoutes.get("/:id/version/:version/preview", chunkIT2TC4ZD_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
9438
10352
  try {
9439
10353
  const id = c.req.param("id");
9440
10354
  const version = parseInt(c.req.param("version") || "0");
@@ -11401,7 +12315,14 @@ function renderUsersListPage(data) {
11401
12315
 
11402
12316
  // src/routes/admin-users.ts
11403
12317
  var userRoutes = new hono.Hono();
11404
- userRoutes.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
12318
+ userRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
12319
+ userRoutes.use("/users/*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
12320
+ userRoutes.use("/users", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
12321
+ userRoutes.use("/invite-user", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
12322
+ userRoutes.use("/resend-invitation/*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
12323
+ userRoutes.use("/cancel-invitation/*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
12324
+ userRoutes.use("/activity-logs", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
12325
+ userRoutes.use("/activity-logs/*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
11405
12326
  userRoutes.get("/", (c) => {
11406
12327
  return c.redirect("/admin/dashboard");
11407
12328
  });
@@ -11556,7 +12477,7 @@ userRoutes.put("/profile", async (c) => {
11556
12477
  Date.now(),
11557
12478
  user.userId
11558
12479
  ).run();
11559
- await chunkK4Q4SFJJ_cjs.logActivity(
12480
+ await chunkIT2TC4ZD_cjs.logActivity(
11560
12481
  db,
11561
12482
  user.userId,
11562
12483
  "profile.update",
@@ -11619,7 +12540,7 @@ userRoutes.post("/profile/avatar", async (c) => {
11619
12540
  SELECT first_name, last_name FROM users WHERE id = ?
11620
12541
  `);
11621
12542
  const userData = await userStmt.bind(user.userId).first();
11622
- await chunkK4Q4SFJJ_cjs.logActivity(
12543
+ await chunkIT2TC4ZD_cjs.logActivity(
11623
12544
  db,
11624
12545
  user.userId,
11625
12546
  "profile.avatar_update",
@@ -11690,7 +12611,7 @@ userRoutes.post("/profile/password", async (c) => {
11690
12611
  dismissible: true
11691
12612
  }));
11692
12613
  }
11693
- const validPassword = await chunkK4Q4SFJJ_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
12614
+ const validPassword = await chunkIT2TC4ZD_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
11694
12615
  if (!validPassword) {
11695
12616
  return c.html(renderAlert2({
11696
12617
  type: "error",
@@ -11698,7 +12619,7 @@ userRoutes.post("/profile/password", async (c) => {
11698
12619
  dismissible: true
11699
12620
  }));
11700
12621
  }
11701
- const newPasswordHash = await chunkK4Q4SFJJ_cjs.AuthManager.hashPassword(newPassword);
12622
+ const newPasswordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(newPassword);
11702
12623
  const historyStmt = db.prepare(`
11703
12624
  INSERT INTO password_history (id, user_id, password_hash, created_at)
11704
12625
  VALUES (?, ?, ?, ?)
@@ -11714,7 +12635,7 @@ userRoutes.post("/profile/password", async (c) => {
11714
12635
  WHERE id = ?
11715
12636
  `);
11716
12637
  await updateStmt.bind(newPasswordHash, Date.now(), user.userId).run();
11717
- await chunkK4Q4SFJJ_cjs.logActivity(
12638
+ await chunkIT2TC4ZD_cjs.logActivity(
11718
12639
  db,
11719
12640
  user.userId,
11720
12641
  "profile.password_change",
@@ -11781,7 +12702,7 @@ userRoutes.get("/users", async (c) => {
11781
12702
  `);
11782
12703
  const countResult = await countStmt.bind(...params).first();
11783
12704
  const totalUsers = countResult?.total || 0;
11784
- await chunkK4Q4SFJJ_cjs.logActivity(
12705
+ await chunkIT2TC4ZD_cjs.logActivity(
11785
12706
  db,
11786
12707
  user.userId,
11787
12708
  "users.list_view",
@@ -11889,7 +12810,9 @@ userRoutes.post("/users/new", async (c) => {
11889
12810
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
11890
12811
  const phone = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
11891
12812
  const bio = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
11892
- const role = formData.get("role")?.toString() || "viewer";
12813
+ const roleInput = formData.get("role")?.toString() || "viewer";
12814
+ const validRoles = ["admin", "editor", "author", "viewer"];
12815
+ const role = validRoles.includes(roleInput) ? roleInput : "viewer";
11893
12816
  const password = formData.get("password")?.toString() || "";
11894
12817
  const confirmPassword = formData.get("confirm_password")?.toString() || "";
11895
12818
  const isActive = formData.get("is_active") === "1";
@@ -11935,7 +12858,7 @@ userRoutes.post("/users/new", async (c) => {
11935
12858
  dismissible: true
11936
12859
  }));
11937
12860
  }
11938
- const passwordHash = await chunkK4Q4SFJJ_cjs.AuthManager.hashPassword(password);
12861
+ const passwordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
11939
12862
  const userId = crypto.randomUUID();
11940
12863
  const createStmt = db.prepare(`
11941
12864
  INSERT INTO users (
@@ -11958,7 +12881,7 @@ userRoutes.post("/users/new", async (c) => {
11958
12881
  Date.now(),
11959
12882
  Date.now()
11960
12883
  ).run();
11961
- await chunkK4Q4SFJJ_cjs.logActivity(
12884
+ await chunkIT2TC4ZD_cjs.logActivity(
11962
12885
  db,
11963
12886
  user.userId,
11964
12887
  "user!.create",
@@ -11996,7 +12919,7 @@ userRoutes.get("/users/:id", async (c) => {
11996
12919
  if (!userRecord) {
11997
12920
  return c.json({ error: "User not found" }, 404);
11998
12921
  }
11999
- await chunkK4Q4SFJJ_cjs.logActivity(
12922
+ await chunkIT2TC4ZD_cjs.logActivity(
12000
12923
  db,
12001
12924
  user.userId,
12002
12925
  "user!.view",
@@ -12109,7 +13032,9 @@ userRoutes.put("/users/:id", async (c) => {
12109
13032
  const username = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("username")?.toString());
12110
13033
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
12111
13034
  const phone = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
12112
- const role = formData.get("role")?.toString() || "viewer";
13035
+ const roleInput = formData.get("role")?.toString() || "viewer";
13036
+ const validRoles = ["admin", "editor", "author", "viewer"];
13037
+ const role = validRoles.includes(roleInput) ? roleInput : "viewer";
12113
13038
  const isActive = formData.get("is_active") === "1";
12114
13039
  const emailVerified = formData.get("email_verified") === "1";
12115
13040
  const profileDisplayName = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("profile_display_name")?.toString()) || null;
@@ -12221,7 +13146,7 @@ userRoutes.put("/users/:id", async (c) => {
12221
13146
  ).run();
12222
13147
  }
12223
13148
  }
12224
- await chunkK4Q4SFJJ_cjs.logActivity(
13149
+ await chunkIT2TC4ZD_cjs.logActivity(
12225
13150
  db,
12226
13151
  user.userId,
12227
13152
  "user.update",
@@ -12266,7 +13191,7 @@ userRoutes.post("/users/:id/toggle", async (c) => {
12266
13191
  UPDATE users SET is_active = ?, updated_at = ? WHERE id = ?
12267
13192
  `);
12268
13193
  await toggleStmt.bind(active ? 1 : 0, Date.now(), userId).run();
12269
- await chunkK4Q4SFJJ_cjs.logActivity(
13194
+ await chunkIT2TC4ZD_cjs.logActivity(
12270
13195
  db,
12271
13196
  user.userId,
12272
13197
  active ? "user.activate" : "user.deactivate",
@@ -12307,7 +13232,7 @@ userRoutes.delete("/users/:id", async (c) => {
12307
13232
  DELETE FROM users WHERE id = ?
12308
13233
  `);
12309
13234
  await deleteStmt.bind(userId).run();
12310
- await chunkK4Q4SFJJ_cjs.logActivity(
13235
+ await chunkIT2TC4ZD_cjs.logActivity(
12311
13236
  db,
12312
13237
  user.userId,
12313
13238
  "user!.hard_delete",
@@ -12326,7 +13251,7 @@ userRoutes.delete("/users/:id", async (c) => {
12326
13251
  UPDATE users SET is_active = 0, updated_at = ? WHERE id = ?
12327
13252
  `);
12328
13253
  await deleteStmt.bind(Date.now(), userId).run();
12329
- await chunkK4Q4SFJJ_cjs.logActivity(
13254
+ await chunkIT2TC4ZD_cjs.logActivity(
12330
13255
  db,
12331
13256
  user.userId,
12332
13257
  "user!.soft_delete",
@@ -12392,7 +13317,7 @@ userRoutes.post("/invite-user", async (c) => {
12392
13317
  Date.now(),
12393
13318
  Date.now()
12394
13319
  ).run();
12395
- await chunkK4Q4SFJJ_cjs.logActivity(
13320
+ await chunkIT2TC4ZD_cjs.logActivity(
12396
13321
  db,
12397
13322
  user.userId,
12398
13323
  "user!.invite_sent",
@@ -12449,7 +13374,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
12449
13374
  Date.now(),
12450
13375
  userId
12451
13376
  ).run();
12452
- await chunkK4Q4SFJJ_cjs.logActivity(
13377
+ await chunkIT2TC4ZD_cjs.logActivity(
12453
13378
  db,
12454
13379
  user.userId,
12455
13380
  "user!.invitation_resent",
@@ -12485,7 +13410,7 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
12485
13410
  }
12486
13411
  const deleteStmt = db.prepare(`DELETE FROM users WHERE id = ?`);
12487
13412
  await deleteStmt.bind(userId).run();
12488
- await chunkK4Q4SFJJ_cjs.logActivity(
13413
+ await chunkIT2TC4ZD_cjs.logActivity(
12489
13414
  db,
12490
13415
  user.userId,
12491
13416
  "user!.invitation_cancelled",
@@ -12568,7 +13493,7 @@ userRoutes.get("/activity-logs", async (c) => {
12568
13493
  ...log,
12569
13494
  details: log.details ? JSON.parse(log.details) : null
12570
13495
  }));
12571
- await chunkK4Q4SFJJ_cjs.logActivity(
13496
+ await chunkIT2TC4ZD_cjs.logActivity(
12572
13497
  db,
12573
13498
  user.userId,
12574
13499
  "activity.logs_viewed",
@@ -12675,7 +13600,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
12675
13600
  csvRows.push(row.join(","));
12676
13601
  }
12677
13602
  const csvContent = csvRows.join("\n");
12678
- await chunkK4Q4SFJJ_cjs.logActivity(
13603
+ await chunkIT2TC4ZD_cjs.logActivity(
12679
13604
  db,
12680
13605
  user.userId,
12681
13606
  "activity.logs_exported",
@@ -14014,7 +14939,7 @@ var fileValidationSchema2 = zod.z.object({
14014
14939
  // 50MB max
14015
14940
  });
14016
14941
  var adminMediaRoutes = new hono.Hono();
14017
- adminMediaRoutes.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
14942
+ adminMediaRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
14018
14943
  adminMediaRoutes.get("/", async (c) => {
14019
14944
  try {
14020
14945
  const user = c.get("user");
@@ -14600,7 +15525,7 @@ adminMediaRoutes.put("/:id", async (c) => {
14600
15525
  `);
14601
15526
  }
14602
15527
  });
14603
- adminMediaRoutes.delete("/cleanup", chunkK4Q4SFJJ_cjs.requireRole("admin"), async (c) => {
15528
+ adminMediaRoutes.delete("/cleanup", chunkIT2TC4ZD_cjs.requireRole("admin"), async (c) => {
14604
15529
  try {
14605
15530
  const db = c.env.DB;
14606
15531
  const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
@@ -16823,7 +17748,7 @@ function renderEmailSettingsContent(plugin, settings) {
16823
17748
 
16824
17749
  // src/routes/admin-plugins.ts
16825
17750
  var adminPluginRoutes = new hono.Hono();
16826
- adminPluginRoutes.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
17751
+ adminPluginRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
16827
17752
  var AVAILABLE_PLUGINS = [
16828
17753
  {
16829
17754
  id: "third-party-faq",
@@ -16950,7 +17875,7 @@ adminPluginRoutes.get("/", async (c) => {
16950
17875
  if (user?.role !== "admin") {
16951
17876
  return c.text("Access denied", 403);
16952
17877
  }
16953
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
17878
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
16954
17879
  let installedPlugins = [];
16955
17880
  let stats = { total: 0, active: 0, inactive: 0, errors: 0, uninstalled: 0 };
16956
17881
  try {
@@ -17026,7 +17951,7 @@ adminPluginRoutes.get("/:id", async (c) => {
17026
17951
  if (user?.role !== "admin") {
17027
17952
  return c.redirect("/admin/plugins");
17028
17953
  }
17029
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
17954
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
17030
17955
  const plugin = await pluginService.getPlugin(pluginId);
17031
17956
  if (!plugin) {
17032
17957
  return c.text("Plugin not found", 404);
@@ -17110,7 +18035,7 @@ adminPluginRoutes.post("/:id/activate", async (c) => {
17110
18035
  if (user?.role !== "admin") {
17111
18036
  return c.json({ error: "Access denied" }, 403);
17112
18037
  }
17113
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
18038
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
17114
18039
  await pluginService.activatePlugin(pluginId);
17115
18040
  return c.json({ success: true });
17116
18041
  } catch (error) {
@@ -17127,7 +18052,7 @@ adminPluginRoutes.post("/:id/deactivate", async (c) => {
17127
18052
  if (user?.role !== "admin") {
17128
18053
  return c.json({ error: "Access denied" }, 403);
17129
18054
  }
17130
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
18055
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
17131
18056
  await pluginService.deactivatePlugin(pluginId);
17132
18057
  return c.json({ success: true });
17133
18058
  } catch (error) {
@@ -17144,7 +18069,7 @@ adminPluginRoutes.post("/install", async (c) => {
17144
18069
  return c.json({ error: "Access denied" }, 403);
17145
18070
  }
17146
18071
  const body = await c.req.json();
17147
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
18072
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
17148
18073
  if (body.name === "faq-plugin") {
17149
18074
  const faqPlugin = await pluginService.installPlugin({
17150
18075
  id: "third-party-faq",
@@ -17414,7 +18339,7 @@ adminPluginRoutes.post("/:id/uninstall", async (c) => {
17414
18339
  if (user?.role !== "admin") {
17415
18340
  return c.json({ error: "Access denied" }, 403);
17416
18341
  }
17417
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
18342
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
17418
18343
  await pluginService.uninstallPlugin(pluginId);
17419
18344
  return c.json({ success: true });
17420
18345
  } catch (error) {
@@ -17432,8 +18357,20 @@ adminPluginRoutes.post("/:id/settings", async (c) => {
17432
18357
  return c.json({ error: "Access denied" }, 403);
17433
18358
  }
17434
18359
  const settings = await c.req.json();
17435
- const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
18360
+ const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
17436
18361
  await pluginService.updatePluginSettings(pluginId, settings);
18362
+ if (pluginId === "core-auth") {
18363
+ try {
18364
+ const cacheKv = c.env.CACHE_KV;
18365
+ if (cacheKv) {
18366
+ await cacheKv.delete("auth:settings");
18367
+ await cacheKv.delete("auth:registration-enabled");
18368
+ console.log("[AuthSettings] Cache cleared after updating authentication settings");
18369
+ }
18370
+ } catch (cacheError) {
18371
+ console.error("[AuthSettings] Failed to clear cache:", cacheError);
18372
+ }
18373
+ }
17437
18374
  return c.json({ success: true });
17438
18375
  } catch (error) {
17439
18376
  console.error("Error updating plugin settings:", error);
@@ -18228,11 +19165,11 @@ function renderLogConfigPage(data) {
18228
19165
 
18229
19166
  // src/routes/admin-logs.ts
18230
19167
  var adminLogsRoutes = new hono.Hono();
18231
- adminLogsRoutes.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
19168
+ adminLogsRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
18232
19169
  adminLogsRoutes.get("/", async (c) => {
18233
19170
  try {
18234
19171
  const user = c.get("user");
18235
- const logger = chunkVNLR35GO_cjs.getLogger(c.env.DB);
19172
+ const logger = chunk64APW3DW_cjs.getLogger(c.env.DB);
18236
19173
  const query = c.req.query();
18237
19174
  const page = parseInt(query.page || "1");
18238
19175
  const limit = parseInt(query.limit || "50");
@@ -18312,7 +19249,7 @@ adminLogsRoutes.get("/:id", async (c) => {
18312
19249
  try {
18313
19250
  const id = c.req.param("id");
18314
19251
  const user = c.get("user");
18315
- const logger = chunkVNLR35GO_cjs.getLogger(c.env.DB);
19252
+ const logger = chunk64APW3DW_cjs.getLogger(c.env.DB);
18316
19253
  const { logs } = await logger.getLogs({
18317
19254
  limit: 1,
18318
19255
  offset: 0,
@@ -18349,7 +19286,7 @@ adminLogsRoutes.get("/:id", async (c) => {
18349
19286
  adminLogsRoutes.get("/config", async (c) => {
18350
19287
  try {
18351
19288
  const user = c.get("user");
18352
- const logger = chunkVNLR35GO_cjs.getLogger(c.env.DB);
19289
+ const logger = chunk64APW3DW_cjs.getLogger(c.env.DB);
18353
19290
  const configs = await logger.getAllConfigs();
18354
19291
  const pageData = {
18355
19292
  configs,
@@ -18373,7 +19310,7 @@ adminLogsRoutes.post("/config/:category", async (c) => {
18373
19310
  const level = formData.get("level");
18374
19311
  const retention = parseInt(formData.get("retention"));
18375
19312
  const maxSize = parseInt(formData.get("max_size"));
18376
- const logger = chunkVNLR35GO_cjs.getLogger(c.env.DB);
19313
+ const logger = chunk64APW3DW_cjs.getLogger(c.env.DB);
18377
19314
  await logger.updateConfig(category, {
18378
19315
  enabled,
18379
19316
  level,
@@ -18402,7 +19339,7 @@ adminLogsRoutes.get("/export", async (c) => {
18402
19339
  const category = query.category;
18403
19340
  const startDate = query.start_date;
18404
19341
  const endDate = query.end_date;
18405
- const logger = chunkVNLR35GO_cjs.getLogger(c.env.DB);
19342
+ const logger = chunk64APW3DW_cjs.getLogger(c.env.DB);
18406
19343
  const filter = {
18407
19344
  limit: 1e4,
18408
19345
  // Export up to 10k logs
@@ -18483,7 +19420,7 @@ adminLogsRoutes.post("/cleanup", async (c) => {
18483
19420
  error: "Unauthorized. Admin access required."
18484
19421
  }, 403);
18485
19422
  }
18486
- const logger = chunkVNLR35GO_cjs.getLogger(c.env.DB);
19423
+ const logger = chunk64APW3DW_cjs.getLogger(c.env.DB);
18487
19424
  await logger.cleanupByRetention();
18488
19425
  return c.html(html.html`
18489
19426
  <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
@@ -18505,7 +19442,7 @@ adminLogsRoutes.post("/search", async (c) => {
18505
19442
  const search = formData.get("search");
18506
19443
  const level = formData.get("level");
18507
19444
  const category = formData.get("category");
18508
- const logger = chunkVNLR35GO_cjs.getLogger(c.env.DB);
19445
+ const logger = chunk64APW3DW_cjs.getLogger(c.env.DB);
18509
19446
  const filter = {
18510
19447
  limit: 20,
18511
19448
  offset: 0,
@@ -20556,9 +21493,9 @@ function renderStorageUsage(databaseSizeBytes, mediaSizeBytes) {
20556
21493
  }
20557
21494
 
20558
21495
  // src/routes/admin-dashboard.ts
20559
- var VERSION = chunkJDIM5AG7_cjs.getCoreVersion();
21496
+ var VERSION = chunkEKPLKUZT_cjs.getCoreVersion();
20560
21497
  var router = new hono.Hono();
20561
- router.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
21498
+ router.use("*", chunkIT2TC4ZD_cjs.requireAuth());
20562
21499
  router.get("/", async (c) => {
20563
21500
  const user = c.get("user");
20564
21501
  try {
@@ -20782,6 +21719,20 @@ router.get("/system-status", async (c) => {
20782
21719
  }
20783
21720
  });
20784
21721
 
21722
+ // src/routes/admin-collections-field-types.ts
21723
+ function isMarkdownEditorType(fieldType) {
21724
+ return fieldType === "markdown" || fieldType === "mdxeditor" || fieldType === "easymde";
21725
+ }
21726
+ function normalizeFieldType(fieldType) {
21727
+ if (isMarkdownEditorType(fieldType)) {
21728
+ return "markdown";
21729
+ }
21730
+ if (fieldType === "tinymce") {
21731
+ return "richtext";
21732
+ }
21733
+ return fieldType;
21734
+ }
21735
+
20785
21736
  // src/templates/pages/admin-collections-list.template.ts
20786
21737
  chunkLTKV7AE5_cjs.init_admin_layout_catalyst_template();
20787
21738
 
@@ -21268,7 +22219,9 @@ function getFieldTypeBadge(fieldType) {
21268
22219
  "slug": "URL Slug",
21269
22220
  "richtext": "Rich Text (TinyMCE)",
21270
22221
  "quill": "Rich Text (Quill)",
21271
- "mdxeditor": "EasyMDX",
22222
+ "markdown": "Markdown",
22223
+ "mdxeditor": "Markdown",
22224
+ "easymde": "Markdown",
21272
22225
  "number": "Number",
21273
22226
  "boolean": "Boolean",
21274
22227
  "date": "Date",
@@ -21281,7 +22234,9 @@ function getFieldTypeBadge(fieldType) {
21281
22234
  "slug": "bg-sky-500/10 dark:bg-sky-400/10 text-sky-700 dark:text-sky-300 ring-sky-500/20 dark:ring-sky-400/20",
21282
22235
  "richtext": "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 ring-purple-500/20 dark:ring-purple-400/20",
21283
22236
  "quill": "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 ring-purple-500/20 dark:ring-purple-400/20",
22237
+ "markdown": "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 ring-purple-500/20 dark:ring-purple-400/20",
21284
22238
  "mdxeditor": "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 ring-purple-500/20 dark:ring-purple-400/20",
22239
+ "easymde": "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 ring-purple-500/20 dark:ring-purple-400/20",
21285
22240
  "number": "bg-green-500/10 dark:bg-green-400/10 text-green-700 dark:text-green-300 ring-green-500/20 dark:ring-green-400/20",
21286
22241
  "boolean": "bg-amber-500/10 dark:bg-amber-400/10 text-amber-700 dark:text-amber-300 ring-amber-500/20 dark:ring-amber-400/20",
21287
22242
  "date": "bg-cyan-500/10 dark:bg-cyan-400/10 text-cyan-700 dark:text-cyan-300 ring-cyan-500/20 dark:ring-cyan-400/20",
@@ -21762,7 +22717,7 @@ function renderCollectionFormPage(data) {
21762
22717
  <option value="slug">URL Slug</option>
21763
22718
  ${data.editorPlugins?.tinymce ? '<option value="richtext">Rich Text (TinyMCE)</option>' : ""}
21764
22719
  ${data.editorPlugins?.quill ? '<option value="quill">Rich Text (Quill)</option>' : ""}
21765
- ${data.editorPlugins?.easyMdx ? '<option value="mdxeditor">EasyMDX</option>' : ""}
22720
+ ${data.editorPlugins?.easyMdx ? '<option value="markdown">Markdown</option>' : ""}
21766
22721
  <option value="number">Number</option>
21767
22722
  <option value="boolean">Boolean</option>
21768
22723
  <option value="date">Date</option>
@@ -21987,7 +22942,7 @@ function renderCollectionFormPage(data) {
21987
22942
  // Check if it's a schema field with field_options that might indicate the actual type
21988
22943
  if (field.field_options && typeof field.field_options === 'object') {
21989
22944
  // Only convert to richtext if type is explicitly 'string' and format is richtext
21990
- // Don't convert if it's already a specific editor type like 'mdxeditor', 'quill', etc.
22945
+ // Don't convert if it's already a specific editor type like 'markdown', 'quill', etc.
21991
22946
  if (field.field_options.format === 'richtext' && uiFieldType === 'string') {
21992
22947
  uiFieldType = 'richtext';
21993
22948
  }
@@ -22008,6 +22963,12 @@ function renderCollectionFormPage(data) {
22008
22963
  uiFieldType = typeMapping[uiFieldType];
22009
22964
  }
22010
22965
 
22966
+ if (uiFieldType === 'mdxeditor' || uiFieldType === 'easymde') {
22967
+ uiFieldType = 'markdown';
22968
+ } else if (uiFieldType === 'tinymce') {
22969
+ uiFieldType = 'richtext';
22970
+ }
22971
+
22011
22972
  // Log all available options
22012
22973
  const availableOptions = Array.from(fieldTypeSelect.options).map(opt => ({ value: opt.value, text: opt.text }));
22013
22974
  console.log('Available dropdown options:', availableOptions);
@@ -22094,7 +23055,7 @@ function renderCollectionFormPage(data) {
22094
23055
 
22095
23056
  console.log('[Edit Field] Showing options for field type:', fieldType, '(original:', field.field_type, ')');
22096
23057
 
22097
- if (['select', 'radio', 'media', 'richtext', 'reference'].includes(fieldType)) {
23058
+ if (['select', 'radio', 'media', 'richtext', 'markdown', 'reference'].includes(fieldType)) {
22098
23059
  optionsContainer.classList.remove('hidden');
22099
23060
 
22100
23061
  // Set help text based on type
@@ -22111,6 +23072,9 @@ function renderCollectionFormPage(data) {
22111
23072
  case 'richtext':
22112
23073
  helpText.textContent = 'Full-featured WYSIWYG text editor with formatting options';
22113
23074
  break;
23075
+ case 'markdown':
23076
+ helpText.textContent = 'Markdown editor with live preview powered by the EasyMDE plugin';
23077
+ break;
22114
23078
  case 'reference':
22115
23079
  helpText.textContent = 'Link to content from other collections';
22116
23080
  break;
@@ -22251,7 +23215,7 @@ function renderCollectionFormPage(data) {
22251
23215
  const fieldNameInput = document.getElementById('modal-field-name');
22252
23216
 
22253
23217
  // Show/hide options based on field type
22254
- if (['select', 'radio', 'media', 'richtext', 'guid', 'reference'].includes(this.value)) {
23218
+ if (['select', 'radio', 'media', 'richtext', 'markdown', 'guid', 'reference'].includes(this.value)) {
22255
23219
  optionsContainer.classList.remove('hidden');
22256
23220
 
22257
23221
  // Set default options and help text based on type
@@ -22272,6 +23236,10 @@ function renderCollectionFormPage(data) {
22272
23236
  fieldOptions.value = '{"toolbar": "full", "height": 400}';
22273
23237
  helpText.textContent = 'Full-featured WYSIWYG text editor with formatting options';
22274
23238
  break;
23239
+ case 'markdown':
23240
+ fieldOptions.value = '{"toolbar": "full", "height": 400}';
23241
+ helpText.textContent = 'Markdown editor with live preview powered by the EasyMDE plugin';
23242
+ break;
22275
23243
  case 'reference':
22276
23244
  fieldOptions.value = '{"collection": ["pages", "posts"]}';
22277
23245
  helpText.textContent = 'Link to content from other collections';
@@ -22346,7 +23314,10 @@ function renderCollectionFormPage(data) {
22346
23314
 
22347
23315
  // src/routes/admin-collections.ts
22348
23316
  var adminCollectionsRoutes = new hono.Hono();
22349
- adminCollectionsRoutes.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
23317
+ adminCollectionsRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
23318
+ adminCollectionsRoutes.post("*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
23319
+ adminCollectionsRoutes.put("*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
23320
+ adminCollectionsRoutes.delete("*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
22350
23321
  adminCollectionsRoutes.get("/", async (c) => {
22351
23322
  try {
22352
23323
  const user = c.get("user");
@@ -22832,11 +23803,12 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
22832
23803
  searchable: isSearchable,
22833
23804
  ...parsedOptions
22834
23805
  };
22835
- if (fieldType === "richtext") {
23806
+ const normalizedFieldType = normalizeFieldType(fieldType);
23807
+ if (normalizedFieldType === "richtext") {
22836
23808
  fieldConfig.format = "richtext";
22837
- } else if (fieldType === "date") {
23809
+ } else if (normalizedFieldType === "date") {
22838
23810
  fieldConfig.format = "date-time";
22839
- } else if (fieldType === "select") {
23811
+ } else if (normalizedFieldType === "select") {
22840
23812
  fieldConfig.enum = parsedOptions.options || [];
22841
23813
  } else if (fieldType === "radio") {
22842
23814
  fieldConfig.type = "radio";
@@ -22845,20 +23817,14 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
22845
23817
  }
22846
23818
  } else if (fieldType === "media") {
22847
23819
  fieldConfig.format = "media";
22848
- } else if (fieldType === "slug") {
23820
+ } else if (normalizedFieldType === "slug") {
22849
23821
  fieldConfig.type = "slug";
22850
23822
  fieldConfig.format = "slug";
22851
- } else if (fieldType === "quill") {
23823
+ } else if (normalizedFieldType === "quill") {
22852
23824
  fieldConfig.type = "quill";
22853
- } else if (fieldType === "mdxeditor") {
22854
- fieldConfig.type = "mdxeditor";
22855
- } else if (fieldType === "tinymce") {
22856
- fieldConfig.type = "tinymce";
22857
- } else if (fieldType === "easymde") {
22858
- fieldConfig.type = "easymde";
22859
- } else if (fieldType === "markdown") {
23825
+ } else if (normalizedFieldType === "markdown") {
22860
23826
  fieldConfig.type = "markdown";
22861
- } else if (fieldType === "reference") {
23827
+ } else if (normalizedFieldType === "reference") {
22862
23828
  fieldConfig.type = "reference";
22863
23829
  }
22864
23830
  schema.properties[fieldName] = fieldConfig;
@@ -24546,7 +25512,7 @@ function renderDatabaseToolsSettings(settings) {
24546
25512
 
24547
25513
  // src/routes/admin-settings.ts
24548
25514
  var adminSettingsRoutes = new hono.Hono();
24549
- adminSettingsRoutes.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
25515
+ adminSettingsRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
24550
25516
  function getMockSettings(user) {
24551
25517
  return {
24552
25518
  general: {
@@ -24611,7 +25577,7 @@ adminSettingsRoutes.get("/", (c) => {
24611
25577
  adminSettingsRoutes.get("/general", async (c) => {
24612
25578
  const user = c.get("user");
24613
25579
  const db = c.env.DB;
24614
- const settingsService = new chunkVNLR35GO_cjs.SettingsService(db);
25580
+ const settingsService = new chunk64APW3DW_cjs.SettingsService(db);
24615
25581
  const generalSettings = await settingsService.getGeneralSettings(user?.email);
24616
25582
  const mockSettings = getMockSettings(user);
24617
25583
  mockSettings.general = generalSettings;
@@ -24714,7 +25680,7 @@ adminSettingsRoutes.get("/database-tools", (c) => {
24714
25680
  adminSettingsRoutes.get("/api/migrations/status", async (c) => {
24715
25681
  try {
24716
25682
  const db = c.env.DB;
24717
- const migrationService = new chunkHXHVU5GM_cjs.MigrationService(db);
25683
+ const migrationService = new chunkZMVWMJ3S_cjs.MigrationService(db);
24718
25684
  const status = await migrationService.getMigrationStatus();
24719
25685
  return c.json({
24720
25686
  success: true,
@@ -24738,7 +25704,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
24738
25704
  }, 403);
24739
25705
  }
24740
25706
  const db = c.env.DB;
24741
- const migrationService = new chunkHXHVU5GM_cjs.MigrationService(db);
25707
+ const migrationService = new chunkZMVWMJ3S_cjs.MigrationService(db);
24742
25708
  const result = await migrationService.runPendingMigrations();
24743
25709
  return c.json({
24744
25710
  success: result.success,
@@ -24756,7 +25722,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
24756
25722
  adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
24757
25723
  try {
24758
25724
  const db = c.env.DB;
24759
- const migrationService = new chunkHXHVU5GM_cjs.MigrationService(db);
25725
+ const migrationService = new chunkZMVWMJ3S_cjs.MigrationService(db);
24760
25726
  const validation = await migrationService.validateSchema();
24761
25727
  return c.json({
24762
25728
  success: true,
@@ -24925,7 +25891,7 @@ adminSettingsRoutes.post("/general", async (c) => {
24925
25891
  }
24926
25892
  const formData = await c.req.formData();
24927
25893
  const db = c.env.DB;
24928
- const settingsService = new chunkVNLR35GO_cjs.SettingsService(db);
25894
+ const settingsService = new chunk64APW3DW_cjs.SettingsService(db);
24929
25895
  const settings = {
24930
25896
  siteName: formData.get("siteName"),
24931
25897
  siteDescription: formData.get("siteDescription"),
@@ -26646,7 +27612,7 @@ function renderFormCreatePage(data) {
26646
27612
 
26647
27613
  // src/routes/admin-forms.ts
26648
27614
  var adminFormsRoutes = new hono.Hono();
26649
- adminFormsRoutes.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
27615
+ adminFormsRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
26650
27616
  adminFormsRoutes.get("/", async (c) => {
26651
27617
  try {
26652
27618
  const user = c.get("user");
@@ -27463,6 +28429,33 @@ var public_forms_default = publicFormsRoutes;
27463
28429
 
27464
28430
  // src/templates/pages/admin-api-reference.template.ts
27465
28431
  chunkLTKV7AE5_cjs.init_admin_layout_catalyst_template();
28432
+ function renderAuthBadge(auth) {
28433
+ if (auth === true) {
28434
+ return `
28435
+ <span class="shrink-0 inline-flex items-center gap-x-1 rounded-md bg-amber-50 dark:bg-amber-500/10 px-2 py-1 text-xs font-medium text-amber-700 dark:text-amber-300 ring-1 ring-inset ring-amber-700/10 dark:ring-amber-400/20">
28436
+ <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
28437
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
28438
+ </svg>
28439
+ Auth
28440
+ </span>`;
28441
+ }
28442
+ if (auth === false) {
28443
+ return `
28444
+ <span class="shrink-0 inline-flex items-center gap-x-1 rounded-md bg-lime-50 dark:bg-lime-500/10 px-2 py-1 text-xs font-medium text-lime-700 dark:text-lime-300 ring-1 ring-inset ring-lime-700/10 dark:ring-lime-400/20">
28445
+ <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
28446
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
28447
+ </svg>
28448
+ Public
28449
+ </span>`;
28450
+ }
28451
+ return `
28452
+ <span class="shrink-0 inline-flex items-center gap-x-1 rounded-md bg-zinc-50 dark:bg-zinc-500/10 px-2 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400 ring-1 ring-inset ring-zinc-500/10 dark:ring-zinc-400/20">
28453
+ <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
28454
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
28455
+ </svg>
28456
+ Unknown
28457
+ </span>`;
28458
+ }
27466
28459
  function renderAPIReferencePage(data) {
27467
28460
  const endpointsByCategory = data.endpoints.reduce((acc, endpoint) => {
27468
28461
  if (!acc[endpoint.category]) {
@@ -27471,40 +28464,18 @@ function renderAPIReferencePage(data) {
27471
28464
  acc[endpoint.category].push(endpoint);
27472
28465
  return acc;
27473
28466
  }, {});
27474
- const categoryInfo = {
27475
- "Auth": {
27476
- title: "Authentication",
27477
- description: "User authentication and authorization endpoints",
27478
- icon: "\u{1F510}"
27479
- },
27480
- "Content": {
27481
- title: "Content Management",
27482
- description: "Content creation, retrieval, and management",
27483
- icon: "\u{1F4DD}"
27484
- },
27485
- "Media": {
27486
- title: "Media Management",
27487
- description: "File upload, storage, and media operations",
27488
- icon: "\u{1F5BC}\uFE0F"
27489
- },
27490
- "Admin": {
27491
- title: "Admin Interface",
27492
- description: "Administrative panel and management features",
27493
- icon: "\u2699\uFE0F"
27494
- },
27495
- "System": {
27496
- title: "System",
27497
- description: "Health checks and system information",
27498
- icon: "\u{1F527}"
27499
- }
27500
- };
28467
+ const categories = Object.keys(endpointsByCategory);
28468
+ const totalEndpoints = data.endpoints.length;
28469
+ const publicEndpoints = data.endpoints.filter((e) => e.authentication === false).length;
28470
+ const protectedEndpoints = data.endpoints.filter((e) => e.authentication === true).length;
28471
+ const undocumentedCount = data.endpoints.filter((e) => e.documented === false).length;
27501
28472
  const pageContent = `
27502
28473
  <div class="space-y-6">
27503
28474
  <!-- Header -->
27504
28475
  <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between">
27505
28476
  <div>
27506
28477
  <h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">API Reference</h1>
27507
- <p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">Complete documentation of all available API endpoints</p>
28478
+ <p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">Auto-discovered documentation of all registered API endpoints</p>
27508
28479
  </div>
27509
28480
  <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
27510
28481
  <a href="/api" target="_blank" class="inline-flex items-center justify-center gap-x-1.5 rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm">
@@ -27517,29 +28488,35 @@ function renderAPIReferencePage(data) {
27517
28488
  </div>
27518
28489
 
27519
28490
  <!-- Stats -->
27520
- <dl class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
28491
+ <dl class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5">
27521
28492
  <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-6 py-5">
27522
28493
  <dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Total Endpoints</dt>
27523
28494
  <dd class="mt-2 flex items-baseline gap-x-2">
27524
- <span class="text-4xl font-semibold tracking-tight text-zinc-950 dark:text-white">${data.endpoints.length}</span>
28495
+ <span class="text-4xl font-semibold tracking-tight text-zinc-950 dark:text-white">${totalEndpoints}</span>
27525
28496
  </dd>
27526
28497
  </div>
27527
28498
  <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-6 py-5">
27528
28499
  <dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Public Endpoints</dt>
27529
28500
  <dd class="mt-2 flex items-baseline gap-x-2">
27530
- <span class="text-4xl font-semibold tracking-tight text-lime-600 dark:text-lime-400">${data.endpoints.filter((e) => !e.authentication).length}</span>
28501
+ <span class="text-4xl font-semibold tracking-tight text-lime-600 dark:text-lime-400">${publicEndpoints}</span>
27531
28502
  </dd>
27532
28503
  </div>
27533
28504
  <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-6 py-5">
27534
28505
  <dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Protected Endpoints</dt>
27535
28506
  <dd class="mt-2 flex items-baseline gap-x-2">
27536
- <span class="text-4xl font-semibold tracking-tight text-amber-600 dark:text-amber-400">${data.endpoints.filter((e) => e.authentication).length}</span>
28507
+ <span class="text-4xl font-semibold tracking-tight text-amber-600 dark:text-amber-400">${protectedEndpoints}</span>
27537
28508
  </dd>
27538
28509
  </div>
27539
28510
  <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-6 py-5">
27540
28511
  <dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Categories</dt>
27541
28512
  <dd class="mt-2 flex items-baseline gap-x-2">
27542
- <span class="text-4xl font-semibold tracking-tight text-cyan-600 dark:text-cyan-400">${Object.keys(endpointsByCategory).length}</span>
28513
+ <span class="text-4xl font-semibold tracking-tight text-cyan-600 dark:text-cyan-400">${categories.length}</span>
28514
+ </dd>
28515
+ </div>
28516
+ <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-6 py-5">
28517
+ <dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Undocumented</dt>
28518
+ <dd class="mt-2 flex items-baseline gap-x-2">
28519
+ <span class="text-4xl font-semibold tracking-tight ${undocumentedCount > 0 ? "text-zinc-400 dark:text-zinc-500" : "text-lime-600 dark:text-lime-400"}">${undocumentedCount}</span>
27543
28520
  </dd>
27544
28521
  </div>
27545
28522
  </dl>
@@ -27591,9 +28568,11 @@ function renderAPIReferencePage(data) {
27591
28568
  class="col-start-1 row-start-1 w-full appearance-none rounded-lg bg-white dark:bg-zinc-800 py-2 pl-3 pr-8 text-sm text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-zinc-950/10 dark:outline-white/10 *:bg-white dark:*:bg-zinc-800 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-zinc-950 dark:focus:outline-white min-w-[200px]"
27592
28569
  >
27593
28570
  <option value="">All Categories</option>
27594
- ${Object.keys(categoryInfo).map((category) => `
27595
- <option value="${category}">${categoryInfo[category].title}</option>
27596
- `).join("")}
28571
+ ${categories.map((category) => {
28572
+ const info = chunk64APW3DW_cjs.CATEGORY_INFO[category];
28573
+ const title = info ? info.title : category;
28574
+ return `<option value="${category}">${title}</option>`;
28575
+ }).join("\n ")}
27597
28576
  </select>
27598
28577
  <svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-zinc-500 dark:text-zinc-400 sm:size-4">
27599
28578
  <path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
@@ -27607,7 +28586,7 @@ function renderAPIReferencePage(data) {
27607
28586
  <!-- API Categories -->
27608
28587
  <div class="space-y-6">
27609
28588
  ${Object.entries(endpointsByCategory).map(([category, endpoints]) => {
27610
- const info = categoryInfo[category] || { title: category, description: "", icon: "\u{1F4CB}" };
28589
+ const info = chunk64APW3DW_cjs.CATEGORY_INFO[category] || { title: category, description: "", icon: "&#x1f4cb;" };
27611
28590
  return `
27612
28591
  <div class="api-category" data-category="${category}">
27613
28592
  <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 overflow-hidden">
@@ -27641,23 +28620,14 @@ function renderAPIReferencePage(data) {
27641
28620
  <div class="flex-1 min-w-0">
27642
28621
  <div class="flex items-center gap-x-2 mb-2">
27643
28622
  <code class="text-zinc-950 dark:text-white text-sm font-mono font-medium break-all">${endpoint.path}</code>
27644
- ${endpoint.authentication ? `
27645
- <span class="shrink-0 inline-flex items-center gap-x-1 rounded-md bg-amber-50 dark:bg-amber-500/10 px-2 py-1 text-xs font-medium text-amber-700 dark:text-amber-300 ring-1 ring-inset ring-amber-700/10 dark:ring-amber-400/20">
27646
- <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
27647
- <path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
27648
- </svg>
27649
- Auth
27650
- </span>
27651
- ` : `
27652
- <span class="shrink-0 inline-flex items-center gap-x-1 rounded-md bg-lime-50 dark:bg-lime-500/10 px-2 py-1 text-xs font-medium text-lime-700 dark:text-lime-300 ring-1 ring-inset ring-lime-700/10 dark:ring-lime-400/20">
27653
- <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
27654
- <path stroke-linecap="round" stroke-linejoin="round" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
27655
- </svg>
27656
- Public
28623
+ ${renderAuthBadge(endpoint.authentication)}
28624
+ ${endpoint.documented === false ? `
28625
+ <span class="shrink-0 inline-flex items-center rounded-md bg-zinc-50 dark:bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-400 dark:text-zinc-500 ring-1 ring-inset ring-zinc-200 dark:ring-zinc-700">
28626
+ Auto-discovered
27657
28627
  </span>
27658
- `}
28628
+ ` : ""}
27659
28629
  </div>
27660
- <p class="text-zinc-600 dark:text-zinc-400 text-sm leading-6">${endpoint.description}</p>
28630
+ <p class="text-zinc-600 dark:text-zinc-400 text-sm leading-6">${endpoint.description || '<em class="text-zinc-400 dark:text-zinc-500">No description available</em>'}</p>
27661
28631
  </div>
27662
28632
  </div>
27663
28633
  </div>
@@ -27735,8 +28705,8 @@ function renderAPIReferencePage(data) {
27735
28705
  const path = endpoint.dataset.path.toLowerCase();
27736
28706
  const description = endpoint.dataset.description.toLowerCase();
27737
28707
 
27738
- const matchesSearch = !searchTerm ||
27739
- path.includes(searchTerm) ||
28708
+ const matchesSearch = !searchTerm ||
28709
+ path.includes(searchTerm) ||
27740
28710
  description.includes(searchTerm);
27741
28711
  const matchesMethod = !selectedMethod || method === selectedMethod;
27742
28712
 
@@ -27793,210 +28763,16 @@ function renderAPIReferencePage(data) {
27793
28763
  }
27794
28764
 
27795
28765
  // src/routes/admin-api-reference.ts
27796
- var VERSION2 = chunkJDIM5AG7_cjs.getCoreVersion();
28766
+ var VERSION2 = chunkEKPLKUZT_cjs.getCoreVersion();
27797
28767
  var router2 = new hono.Hono();
27798
- router2.use("*", chunkK4Q4SFJJ_cjs.requireAuth());
27799
- var apiEndpoints = [
27800
- // Auth endpoints
27801
- {
27802
- method: "POST",
27803
- path: "/auth/login",
27804
- description: "Authenticate user with email and password",
27805
- authentication: false,
27806
- category: "Auth"
27807
- },
27808
- {
27809
- method: "POST",
27810
- path: "/auth/register",
27811
- description: "Register a new user account",
27812
- authentication: false,
27813
- category: "Auth"
27814
- },
27815
- {
27816
- method: "POST",
27817
- path: "/auth/logout",
27818
- description: "Log out the current user and invalidate session",
27819
- authentication: true,
27820
- category: "Auth"
27821
- },
27822
- {
27823
- method: "GET",
27824
- path: "/auth/me",
27825
- description: "Get current authenticated user information",
27826
- authentication: true,
27827
- category: "Auth"
27828
- },
27829
- {
27830
- method: "POST",
27831
- path: "/auth/refresh",
27832
- description: "Refresh authentication token",
27833
- authentication: true,
27834
- category: "Auth"
27835
- },
27836
- // Content endpoints
27837
- {
27838
- method: "GET",
27839
- path: "/api/collections",
27840
- description: "List all available collections",
27841
- authentication: false,
27842
- category: "Content"
27843
- },
27844
- {
27845
- method: "GET",
27846
- path: "/api/collections/:collection/content",
27847
- description: "Get all content items from a specific collection",
27848
- authentication: false,
27849
- category: "Content"
27850
- },
27851
- {
27852
- method: "GET",
27853
- path: "/api/content/:id",
27854
- description: "Get a specific content item by ID",
27855
- authentication: false,
27856
- category: "Content"
27857
- },
27858
- {
27859
- method: "POST",
27860
- path: "/api/content",
27861
- description: "Create a new content item",
27862
- authentication: true,
27863
- category: "Content"
27864
- },
27865
- {
27866
- method: "PUT",
27867
- path: "/api/content/:id",
27868
- description: "Update an existing content item",
27869
- authentication: true,
27870
- category: "Content"
27871
- },
27872
- {
27873
- method: "DELETE",
27874
- path: "/api/content/:id",
27875
- description: "Delete a content item",
27876
- authentication: true,
27877
- category: "Content"
27878
- },
27879
- // Media endpoints
27880
- {
27881
- method: "GET",
27882
- path: "/api/media",
27883
- description: "List all media files with pagination",
27884
- authentication: false,
27885
- category: "Media"
27886
- },
27887
- {
27888
- method: "GET",
27889
- path: "/api/media/:id",
27890
- description: "Get a specific media file by ID",
27891
- authentication: false,
27892
- category: "Media"
27893
- },
27894
- {
27895
- method: "POST",
27896
- path: "/api/media/upload",
27897
- description: "Upload a new media file to R2 storage",
27898
- authentication: true,
27899
- category: "Media"
27900
- },
27901
- {
27902
- method: "DELETE",
27903
- path: "/api/media/:id",
27904
- description: "Delete a media file from storage",
27905
- authentication: true,
27906
- category: "Media"
27907
- },
27908
- // Admin endpoints
27909
- {
27910
- method: "GET",
27911
- path: "/admin/api/stats",
27912
- description: "Get dashboard statistics (collections, content, media, users)",
27913
- authentication: true,
27914
- category: "Admin"
27915
- },
27916
- {
27917
- method: "GET",
27918
- path: "/admin/api/storage",
27919
- description: "Get storage usage information",
27920
- authentication: true,
27921
- category: "Admin"
27922
- },
27923
- {
27924
- method: "GET",
27925
- path: "/admin/api/activity",
27926
- description: "Get recent activity logs",
27927
- authentication: true,
27928
- category: "Admin"
27929
- },
27930
- {
27931
- method: "GET",
27932
- path: "/admin/api/collections",
27933
- description: "List all collections with field counts",
27934
- authentication: true,
27935
- category: "Admin"
27936
- },
27937
- {
27938
- method: "POST",
27939
- path: "/admin/api/collections",
27940
- description: "Create a new collection",
27941
- authentication: true,
27942
- category: "Admin"
27943
- },
27944
- {
27945
- method: "PATCH",
27946
- path: "/admin/api/collections/:id",
27947
- description: "Update an existing collection",
27948
- authentication: true,
27949
- category: "Admin"
27950
- },
27951
- {
27952
- method: "DELETE",
27953
- path: "/admin/api/collections/:id",
27954
- description: "Delete a collection (must be empty)",
27955
- authentication: true,
27956
- category: "Admin"
27957
- },
27958
- {
27959
- method: "GET",
27960
- path: "/admin/api/migrations/status",
27961
- description: "Get database migration status",
27962
- authentication: true,
27963
- category: "Admin"
27964
- },
27965
- {
27966
- method: "POST",
27967
- path: "/admin/api/migrations/run",
27968
- description: "Run pending database migrations",
27969
- authentication: true,
27970
- category: "Admin"
27971
- },
27972
- // System endpoints
27973
- {
27974
- method: "GET",
27975
- path: "/health",
27976
- description: "Health check endpoint for monitoring",
27977
- authentication: false,
27978
- category: "System"
27979
- },
27980
- {
27981
- method: "GET",
27982
- path: "/api/health",
27983
- description: "API health check with schema information",
27984
- authentication: false,
27985
- category: "System"
27986
- },
27987
- {
27988
- method: "GET",
27989
- path: "/api",
27990
- description: "API root - returns API information and OpenAPI spec",
27991
- authentication: false,
27992
- category: "System"
27993
- }
27994
- ];
28768
+ router2.use("*", chunkIT2TC4ZD_cjs.requireAuth());
27995
28769
  router2.get("/", async (c) => {
27996
28770
  const user = c.get("user");
27997
28771
  try {
28772
+ const app2 = chunk64APW3DW_cjs.getAppInstance();
28773
+ const endpoints = chunk64APW3DW_cjs.buildRouteList(app2);
27998
28774
  const pageData = {
27999
- endpoints: apiEndpoints,
28775
+ endpoints,
28000
28776
  user: user ? {
28001
28777
  name: user.email.split("@")[0] || user.email,
28002
28778
  email: user.email,
@@ -28076,5 +28852,5 @@ exports.router = router;
28076
28852
  exports.router2 = router2;
28077
28853
  exports.test_cleanup_default = test_cleanup_default;
28078
28854
  exports.userRoutes = userRoutes;
28079
- //# sourceMappingURL=chunk-R4WR3VTN.cjs.map
28080
- //# sourceMappingURL=chunk-R4WR3VTN.cjs.map
28855
+ //# sourceMappingURL=chunk-RCA6R6VE.cjs.map
28856
+ //# sourceMappingURL=chunk-RCA6R6VE.cjs.map