@sonicjs-cms/core 2.5.0 → 2.6.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 (92) hide show
  1. package/dist/{app-Db0AfT5F.d.cts → app-DV27cjPy.d.cts} +1 -1
  2. package/dist/{app-Db0AfT5F.d.ts → app-DV27cjPy.d.ts} +1 -1
  3. package/dist/{chunk-YIXSSJWD.cjs → chunk-63K7XXRX.cjs} +5 -5
  4. package/dist/{chunk-YIXSSJWD.cjs.map → chunk-63K7XXRX.cjs.map} +1 -1
  5. package/dist/{chunk-BHNDALCA.js → chunk-7DL5SPPX.js} +6 -4
  6. package/dist/chunk-7DL5SPPX.js.map +1 -0
  7. package/dist/{chunk-AZLU3ROK.cjs → chunk-BZC4FYW7.cjs} +4 -4
  8. package/dist/chunk-BZC4FYW7.cjs.map +1 -0
  9. package/dist/chunk-CLIH2T74.js +403 -0
  10. package/dist/chunk-CLIH2T74.js.map +1 -0
  11. package/dist/{chunk-VEL7QRYI.js → chunk-EVZOVYLO.js} +9 -2
  12. package/dist/chunk-EVZOVYLO.js.map +1 -0
  13. package/dist/{chunk-TJTWRO4G.js → chunk-EYWR6UA2.js} +4 -4
  14. package/dist/{chunk-TJTWRO4G.js.map → chunk-EYWR6UA2.js.map} +1 -1
  15. package/dist/{chunk-3YUHXWSG.js → chunk-F332TENF.js} +3 -3
  16. package/dist/{chunk-3YUHXWSG.js.map → chunk-F332TENF.js.map} +1 -1
  17. package/dist/{chunk-OJZ45OJD.js → chunk-F6GZURXJ.js} +2243 -539
  18. package/dist/chunk-F6GZURXJ.js.map +1 -0
  19. package/dist/{chunk-I4V3VZWF.cjs → chunk-IIRVZSP2.cjs} +9 -2
  20. package/dist/chunk-IIRVZSP2.cjs.map +1 -0
  21. package/dist/{chunk-V5LBQN3I.js → chunk-KA2PDJNB.js} +4 -4
  22. package/dist/chunk-KA2PDJNB.js.map +1 -0
  23. package/dist/{chunk-AVPUX57O.js → chunk-KAOWRIFD.js} +3 -3
  24. package/dist/{chunk-AVPUX57O.js.map → chunk-KAOWRIFD.js.map} +1 -1
  25. package/dist/{chunk-ILZ3DP4I.cjs → chunk-MPT5PA6U.cjs} +24 -2
  26. package/dist/chunk-MPT5PA6U.cjs.map +1 -0
  27. package/dist/{chunk-UAQL2VWX.cjs → chunk-N7TDLOUE.cjs} +2406 -703
  28. package/dist/chunk-N7TDLOUE.cjs.map +1 -0
  29. package/dist/{chunk-YYV3XQOQ.cjs → chunk-T3YIKW2A.cjs} +7 -7
  30. package/dist/{chunk-YYV3XQOQ.cjs.map → chunk-T3YIKW2A.cjs.map} +1 -1
  31. package/dist/{chunk-LWG2MWDA.cjs → chunk-Y72M3MVX.cjs} +4 -4
  32. package/dist/{chunk-LWG2MWDA.cjs.map → chunk-Y72M3MVX.cjs.map} +1 -1
  33. package/dist/{chunk-SGAG6FD3.js → chunk-YFJJU26H.js} +24 -2
  34. package/dist/chunk-YFJJU26H.js.map +1 -0
  35. package/dist/chunk-YHW27CBV.cjs +406 -0
  36. package/dist/chunk-YHW27CBV.cjs.map +1 -0
  37. package/dist/{chunk-ZWV3EBZ7.cjs → chunk-YMTTGHEK.cjs} +6 -4
  38. package/dist/chunk-YMTTGHEK.cjs.map +1 -0
  39. package/dist/{collection-config-B6gMPunn.d.cts → collection-config-BF95LgQb.d.cts} +1 -1
  40. package/dist/{collection-config-B6gMPunn.d.ts → collection-config-BF95LgQb.d.ts} +1 -1
  41. package/dist/index.cjs +2156 -300
  42. package/dist/index.cjs.map +1 -1
  43. package/dist/index.d.cts +503 -8
  44. package/dist/index.d.ts +503 -8
  45. package/dist/index.js +1893 -44
  46. package/dist/index.js.map +1 -1
  47. package/dist/middleware.cjs +24 -24
  48. package/dist/middleware.d.cts +1 -1
  49. package/dist/middleware.d.ts +1 -1
  50. package/dist/middleware.js +3 -3
  51. package/dist/migrations-QNYAWQLB.cjs +13 -0
  52. package/dist/{migrations-NIEUFG44.cjs.map → migrations-QNYAWQLB.cjs.map} +1 -1
  53. package/dist/migrations-R6NQBKQV.js +4 -0
  54. package/dist/{migrations-TGZKJKV4.js.map → migrations-R6NQBKQV.js.map} +1 -1
  55. package/dist/{plugin-bootstrap-dYhD9fQR.d.ts → plugin-bootstrap-CB-xaBfK.d.ts} +2 -2
  56. package/dist/{plugin-bootstrap-SHsdjE6X.d.cts → plugin-bootstrap-U-cw9jn3.d.cts} +2 -2
  57. package/dist/plugins.cjs +11 -11
  58. package/dist/plugins.js +2 -2
  59. package/dist/routes.cjs +27 -27
  60. package/dist/routes.d.cts +1 -1
  61. package/dist/routes.d.ts +1 -1
  62. package/dist/routes.js +7 -7
  63. package/dist/services.cjs +16 -16
  64. package/dist/services.d.cts +2 -2
  65. package/dist/services.d.ts +2 -2
  66. package/dist/services.js +2 -2
  67. package/dist/templates.cjs +17 -17
  68. package/dist/templates.js +2 -2
  69. package/dist/types.d.cts +1 -1
  70. package/dist/types.d.ts +1 -1
  71. package/dist/utils.cjs +14 -14
  72. package/dist/utils.d.cts +1 -1
  73. package/dist/utils.d.ts +1 -1
  74. package/dist/utils.js +1 -1
  75. package/migrations/029_ai_search_plugin.sql +45 -0
  76. package/package.json +4 -2
  77. package/dist/chunk-AI2JJIJX.cjs +0 -211
  78. package/dist/chunk-AI2JJIJX.cjs.map +0 -1
  79. package/dist/chunk-AZLU3ROK.cjs.map +0 -1
  80. package/dist/chunk-BHNDALCA.js.map +0 -1
  81. package/dist/chunk-I4V3VZWF.cjs.map +0 -1
  82. package/dist/chunk-ILZ3DP4I.cjs.map +0 -1
  83. package/dist/chunk-OJZ45OJD.js.map +0 -1
  84. package/dist/chunk-QDBNW7KQ.js +0 -209
  85. package/dist/chunk-QDBNW7KQ.js.map +0 -1
  86. package/dist/chunk-SGAG6FD3.js.map +0 -1
  87. package/dist/chunk-UAQL2VWX.cjs.map +0 -1
  88. package/dist/chunk-V5LBQN3I.js.map +0 -1
  89. package/dist/chunk-VEL7QRYI.js.map +0 -1
  90. package/dist/chunk-ZWV3EBZ7.cjs.map +0 -1
  91. package/dist/migrations-NIEUFG44.cjs +0 -13
  92. package/dist/migrations-TGZKJKV4.js +0 -4
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var chunk7FOAMNTI_cjs = require('./chunk-7FOAMNTI.cjs');
4
- var chunkYYV3XQOQ_cjs = require('./chunk-YYV3XQOQ.cjs');
5
- var chunkILZ3DP4I_cjs = require('./chunk-ILZ3DP4I.cjs');
6
- var chunkI4V3VZWF_cjs = require('./chunk-I4V3VZWF.cjs');
7
- var chunkAZLU3ROK_cjs = require('./chunk-AZLU3ROK.cjs');
8
- var chunkAI2JJIJX_cjs = require('./chunk-AI2JJIJX.cjs');
9
- var chunkZWV3EBZ7_cjs = require('./chunk-ZWV3EBZ7.cjs');
4
+ var chunkT3YIKW2A_cjs = require('./chunk-T3YIKW2A.cjs');
5
+ var chunkMPT5PA6U_cjs = require('./chunk-MPT5PA6U.cjs');
6
+ var chunkIIRVZSP2_cjs = require('./chunk-IIRVZSP2.cjs');
7
+ var chunkBZC4FYW7_cjs = require('./chunk-BZC4FYW7.cjs');
8
+ var chunkYHW27CBV_cjs = require('./chunk-YHW27CBV.cjs');
9
+ var chunkYMTTGHEK_cjs = require('./chunk-YMTTGHEK.cjs');
10
10
  var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
11
11
  var hono = require('hono');
12
12
  var cors = require('hono/cors');
@@ -76,7 +76,7 @@ apiContentCrudRoutes.get("/:id", async (c) => {
76
76
  }, 500);
77
77
  }
78
78
  });
79
- apiContentCrudRoutes.post("/", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
79
+ apiContentCrudRoutes.post("/", chunkT3YIKW2A_cjs.requireAuth(), async (c) => {
80
80
  try {
81
81
  const db = c.env.DB;
82
82
  const user = c.get("user");
@@ -142,7 +142,7 @@ apiContentCrudRoutes.post("/", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
142
142
  }, 500);
143
143
  }
144
144
  });
145
- apiContentCrudRoutes.put("/:id", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
145
+ apiContentCrudRoutes.put("/:id", chunkT3YIKW2A_cjs.requireAuth(), async (c) => {
146
146
  try {
147
147
  const id = c.req.param("id");
148
148
  const db = c.env.DB;
@@ -206,7 +206,7 @@ apiContentCrudRoutes.put("/:id", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
206
206
  }, 500);
207
207
  }
208
208
  });
209
- apiContentCrudRoutes.delete("/:id", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
209
+ apiContentCrudRoutes.delete("/:id", chunkT3YIKW2A_cjs.requireAuth(), async (c) => {
210
210
  try {
211
211
  const id = c.req.param("id");
212
212
  const db = c.env.DB;
@@ -242,7 +242,7 @@ apiRoutes.use("*", async (c, next) => {
242
242
  c.header("X-Response-Time", `${totalTime}ms`);
243
243
  });
244
244
  apiRoutes.use("*", async (c, next) => {
245
- const cacheEnabled = await chunkYYV3XQOQ_cjs.isPluginActive(c.env.DB, "core-cache");
245
+ const cacheEnabled = await chunkT3YIKW2A_cjs.isPluginActive(c.env.DB, "core-cache");
246
246
  c.set("cacheEnabled", cacheEnabled);
247
247
  await next();
248
248
  });
@@ -367,12 +367,12 @@ apiRoutes.get("/content", async (c) => {
367
367
  });
368
368
  }
369
369
  }
370
- const filter = chunkZWV3EBZ7_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
370
+ const filter = chunkYMTTGHEK_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
371
371
  if (!filter.limit) {
372
372
  filter.limit = 50;
373
373
  }
374
374
  filter.limit = Math.min(filter.limit, 1e3);
375
- const builder3 = new chunkZWV3EBZ7_cjs.QueryFilterBuilder();
375
+ const builder3 = new chunkYMTTGHEK_cjs.QueryFilterBuilder();
376
376
  const queryResult = builder3.build("content", filter);
377
377
  if (queryResult.errors.length > 0) {
378
378
  return c.json({
@@ -459,7 +459,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
459
459
  if (!collectionResult) {
460
460
  return c.json({ error: "Collection not found" }, 404);
461
461
  }
462
- const filter = chunkZWV3EBZ7_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
462
+ const filter = chunkYMTTGHEK_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
463
463
  if (!filter.where) {
464
464
  filter.where = { and: [] };
465
465
  }
@@ -475,7 +475,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
475
475
  filter.limit = 50;
476
476
  }
477
477
  filter.limit = Math.min(filter.limit, 1e3);
478
- const builder3 = new chunkZWV3EBZ7_cjs.QueryFilterBuilder();
478
+ const builder3 = new chunkYMTTGHEK_cjs.QueryFilterBuilder();
479
479
  const queryResult = builder3.build("content", filter);
480
480
  if (queryResult.errors.length > 0) {
481
481
  return c.json({
@@ -600,7 +600,7 @@ var fileValidationSchema = zod.z.object({
600
600
  // 50MB max
601
601
  });
602
602
  var apiMediaRoutes = new hono.Hono();
603
- apiMediaRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
603
+ apiMediaRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
604
604
  apiMediaRoutes.post("/upload", async (c) => {
605
605
  try {
606
606
  const user = c.get("user");
@@ -1344,8 +1344,8 @@ apiSystemRoutes.get("/env", (c) => {
1344
1344
  });
1345
1345
  var api_system_default = apiSystemRoutes;
1346
1346
  var adminApiRoutes = new hono.Hono();
1347
- adminApiRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
1348
- adminApiRoutes.use("*", chunkYYV3XQOQ_cjs.requireRole(["admin", "editor"]));
1347
+ adminApiRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
1348
+ adminApiRoutes.use("*", chunkT3YIKW2A_cjs.requireRole(["admin", "editor"]));
1349
1349
  adminApiRoutes.get("/stats", async (c) => {
1350
1350
  try {
1351
1351
  const db = c.env.DB;
@@ -1585,6 +1585,107 @@ adminApiRoutes.get("/collections/:id", async (c) => {
1585
1585
  return c.json({ error: "Failed to fetch collection" }, 500);
1586
1586
  }
1587
1587
  });
1588
+ adminApiRoutes.get("/references", async (c) => {
1589
+ try {
1590
+ const db = c.env.DB;
1591
+ const url = new URL(c.req.url);
1592
+ const collectionParams = url.searchParams.getAll("collection").flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
1593
+ const search = c.req.query("search") || "";
1594
+ const id = c.req.query("id") || "";
1595
+ const limit = Math.min(Number.parseInt(c.req.query("limit") || "20", 10) || 20, 100);
1596
+ if (collectionParams.length === 0) {
1597
+ return c.json({ error: "Collection is required" }, 400);
1598
+ }
1599
+ const placeholders = collectionParams.map(() => "?").join(", ");
1600
+ const collectionStmt = db.prepare(`
1601
+ SELECT id, name, display_name
1602
+ FROM collections
1603
+ WHERE id IN (${placeholders}) OR name IN (${placeholders})
1604
+ `);
1605
+ const collectionResults = await collectionStmt.bind(...collectionParams, ...collectionParams).all();
1606
+ const collections = collectionResults.results || [];
1607
+ if (collections.length === 0) {
1608
+ return c.json({ error: "Collection not found" }, 404);
1609
+ }
1610
+ const collectionById = Object.fromEntries(
1611
+ collections.map((entry) => [
1612
+ entry.id,
1613
+ {
1614
+ id: entry.id,
1615
+ name: entry.name,
1616
+ display_name: entry.display_name
1617
+ }
1618
+ ])
1619
+ );
1620
+ const collectionIds = collections.map((entry) => entry.id);
1621
+ if (id) {
1622
+ const idPlaceholders = collectionIds.map(() => "?").join(", ");
1623
+ const itemStmt = db.prepare(`
1624
+ SELECT id, title, slug, collection_id
1625
+ FROM content
1626
+ WHERE id = ? AND collection_id IN (${idPlaceholders})
1627
+ LIMIT 1
1628
+ `);
1629
+ const item = await itemStmt.bind(id, ...collectionIds).first();
1630
+ if (!item) {
1631
+ return c.json({ error: "Reference not found" }, 404);
1632
+ }
1633
+ return c.json({
1634
+ data: {
1635
+ id: item.id,
1636
+ title: item.title,
1637
+ slug: item.slug,
1638
+ collection: collectionById[item.collection_id]
1639
+ }
1640
+ });
1641
+ }
1642
+ let stmt;
1643
+ let results;
1644
+ const listPlaceholders = collectionIds.map(() => "?").join(", ");
1645
+ const statusFilterValues = ["published"];
1646
+ const statusClause = ` AND status IN (${statusFilterValues.map(() => "?").join(", ")})`;
1647
+ if (search) {
1648
+ const searchParam = `%${search}%`;
1649
+ stmt = db.prepare(`
1650
+ SELECT id, title, slug, status, updated_at, collection_id
1651
+ FROM content
1652
+ WHERE collection_id IN (${listPlaceholders})
1653
+ AND (title LIKE ? OR slug LIKE ?)
1654
+ ${statusClause}
1655
+ ORDER BY updated_at DESC
1656
+ LIMIT ?
1657
+ `);
1658
+ const queryResults = await stmt.bind(...collectionIds, searchParam, searchParam, ...statusFilterValues, limit).all();
1659
+ results = queryResults.results;
1660
+ } else {
1661
+ stmt = db.prepare(`
1662
+ SELECT id, title, slug, status, updated_at, collection_id
1663
+ FROM content
1664
+ WHERE collection_id IN (${listPlaceholders})
1665
+ ${statusClause}
1666
+ ORDER BY updated_at DESC
1667
+ LIMIT ?
1668
+ `);
1669
+ const queryResults = await stmt.bind(...collectionIds, ...statusFilterValues, limit).all();
1670
+ results = queryResults.results;
1671
+ }
1672
+ const items = (results || []).map((row) => ({
1673
+ id: row.id,
1674
+ title: row.title,
1675
+ slug: row.slug,
1676
+ status: row.status,
1677
+ updated_at: row.updated_at ? Number(row.updated_at) : null,
1678
+ collection: collectionById[row.collection_id]
1679
+ }));
1680
+ return c.json({
1681
+ data: items,
1682
+ count: items.length
1683
+ });
1684
+ } catch (error) {
1685
+ console.error("Error fetching reference options:", error);
1686
+ return c.json({ error: "Failed to fetch references" }, 500);
1687
+ }
1688
+ });
1588
1689
  adminApiRoutes.post("/collections", async (c) => {
1589
1690
  try {
1590
1691
  const contentType = c.req.header("Content-Type");
@@ -1754,7 +1855,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
1754
1855
  });
1755
1856
  adminApiRoutes.get("/migrations/status", async (c) => {
1756
1857
  try {
1757
- const { MigrationService: MigrationService2 } = await import('./migrations-NIEUFG44.cjs');
1858
+ const { MigrationService: MigrationService2 } = await import('./migrations-QNYAWQLB.cjs');
1758
1859
  const db = c.env.DB;
1759
1860
  const migrationService = new MigrationService2(db);
1760
1861
  const status = await migrationService.getMigrationStatus();
@@ -1779,7 +1880,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
1779
1880
  error: "Unauthorized. Admin access required."
1780
1881
  }, 403);
1781
1882
  }
1782
- const { MigrationService: MigrationService2 } = await import('./migrations-NIEUFG44.cjs');
1883
+ const { MigrationService: MigrationService2 } = await import('./migrations-QNYAWQLB.cjs');
1783
1884
  const db = c.env.DB;
1784
1885
  const migrationService = new MigrationService2(db);
1785
1886
  const result = await migrationService.runPendingMigrations();
@@ -1798,7 +1899,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
1798
1899
  });
1799
1900
  adminApiRoutes.get("/migrations/validate", async (c) => {
1800
1901
  try {
1801
- const { MigrationService: MigrationService2 } = await import('./migrations-NIEUFG44.cjs');
1902
+ const { MigrationService: MigrationService2 } = await import('./migrations-QNYAWQLB.cjs');
1802
1903
  const db = c.env.DB;
1803
1904
  const migrationService = new MigrationService2(db);
1804
1905
  const validation = await migrationService.validateSchema();
@@ -1825,7 +1926,7 @@ function renderLoginPage(data, demoLoginActive = false) {
1825
1926
  <meta charset="UTF-8">
1826
1927
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
1827
1928
  <title>Login - SonicJS AI</title>
1828
- <link rel="icon" type="image/x-icon" href="https://demo.sonicjs.com/images/favicon.ico">
1929
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
1829
1930
  <script src="https://unpkg.com/htmx.org@2.0.3"></script>
1830
1931
  <script src="https://cdn.tailwindcss.com"></script>
1831
1932
  <script>
@@ -1873,8 +1974,8 @@ function renderLoginPage(data, demoLoginActive = false) {
1873
1974
  <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
1874
1975
  <div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
1875
1976
  <!-- Alerts -->
1876
- ${data.error ? `<div class="mb-6">${chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
1877
- ${data.message ? `<div class="mb-6">${chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.message })}</div>` : ""}
1977
+ ${data.error ? `<div class="mb-6">${chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
1978
+ ${data.message ? `<div class="mb-6">${chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.message })}</div>` : ""}
1878
1979
 
1879
1980
  <!-- Form Response (HTMX target) -->
1880
1981
  <div id="form-response" class="mb-6"></div>
@@ -2002,7 +2103,7 @@ function renderRegisterPage(data) {
2002
2103
  <meta charset="UTF-8">
2003
2104
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2004
2105
  <title>Register - SonicJS AI</title>
2005
- <link rel="icon" type="image/x-icon" href="https://demo.sonicjs.com/images/favicon.ico">
2106
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg">
2006
2107
  <script src="https://unpkg.com/htmx.org@2.0.3"></script>
2007
2108
  <script src="https://cdn.tailwindcss.com"></script>
2008
2109
  <script>
@@ -2025,42 +2126,20 @@ function renderRegisterPage(data) {
2025
2126
  <div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
2026
2127
  <!-- Logo Section -->
2027
2128
  <div class="sm:mx-auto sm:w-full sm:max-w-md text-center">
2028
- <div class="mx-auto w-64 mb-8">
2029
- <svg class="w-full h-auto" viewBox="380 1300 2250 400" aria-hidden="true">
2030
- <path fill="#F1F2F2" d="M476.851,1404.673h168.536c4.714,0,8.695-1.618,11.944-4.866c3.241-3.241,4.866-7.222,4.866-11.943 c0-2.357-0.443-4.569-1.327-6.636c-0.885-2.06-2.067-3.829-3.539-5.308c-1.479-1.472-3.249-2.654-5.308-3.538 c-2.067-0.885-4.279-1.327-6.635-1.327H476.851c-20.057,0-37.158,7.154-51.313,21.454c-14.155,14.308-21.233,31.483-21.233,51.534 c0,20.058,7.078,37.234,21.233,51.534c14.155,14.308,31.255,21.454,51.313,21.454h112.357c10.907,0,20.196,3.837,27.868,11.502 c7.666,7.672,11.502,16.885,11.502,27.646c0,10.769-3.836,19.982-11.502,27.647c-7.672,7.673-16.961,11.502-27.868,11.502H421.115 c-4.721,0-8.702,1.624-11.944,4.865c-3.248,3.249-4.866,7.23-4.866,11.944c0,3.248,0.733,6.123,2.212,8.626 c1.472,2.509,3.462,4.499,5.971,5.972c2.502,1.472,5.378,2.212,8.626,2.212h168.094c20.052,0,37.227-7.078,51.534-21.234 c14.3-14.155,21.454-31.331,21.454-51.534c0-20.196-7.154-37.379-21.454-51.534c-14.308-14.156-31.483-21.234-51.534-21.234 H476.851c-10.616,0-19.76-3.905-27.426-11.721c-7.672-7.811-11.501-17.101-11.501-27.87c0-10.761,3.829-19.975,11.501-27.647 C457.091,1408.508,466.235,1404.673,476.851,1404.673z"></path>
2031
- <path fill="#F1F2F2" d="M974.78,1398.211c-5.016,6.574-10.034,13.146-15.048,19.721c-1.828,2.398-3.657,4.796-5.487,7.194 c1.994,1.719,3.958,3.51,5.873,5.424c18.724,18.731,28.089,41.216,28.089,67.459c0,26.251-9.366,48.658-28.089,67.237 c-18.731,18.579-41.215,27.868-67.459,27.868c-9.848,0-19.156-1.308-27.923-3.923l-4.185,3.354 c-8.587,6.885-17.154,13.796-25.725,20.702c17.52,8.967,36.86,13.487,58.054,13.487c35.533,0,65.91-12.608,91.124-37.821 c25.214-25.215,37.821-55.584,37.821-91.125c0-35.534-12.607-65.911-37.821-91.126 C981.004,1403.663,977.926,1400.854,974.78,1398.211z"></path>
2032
- <path fill="#F1F2F2" d="M1364.644,1439.619c-4.72,0-8.702,1.624-11.943,4.865c-3.249,3.249-4.866,7.23-4.866,11.944v138.014 l-167.651-211.003c-0.297-0.586-0.74-1.03-1.327-1.326c-4.721-4.714-10.249-7.742-16.588-9.069 c-6.346-1.326-12.608-0.732-18.801,1.77c-6.192,2.509-11.059,6.49-14.598,11.944c-3.539,5.46-5.308,11.577-5.308,18.357v208.348 c0,4.721,1.618,8.703,4.866,11.944c3.241,3.241,7.222,4.865,11.943,4.865c2.945,0,5.751-0.738,8.405-2.211 c2.654-1.472,4.713-3.463,6.193-5.971c1.473-2.503,2.212-5.378,2.212-8.627v-205.251l166.325,209.675 c2.06,2.654,4.423,4.865,7.078,6.635c5.308,3.829,11.349,5.75,18.137,5.75c5.308,0,10.464-1.182,15.482-3.538 c3.539-1.769,6.56-4.127,9.069-7.078c2.502-2.945,4.491-6.338,5.971-10.175c1.473-3.829,2.212-7.664,2.212-11.501v-141.552 c0-4.714-1.624-8.695-4.865-11.944C1373.339,1441.243,1369.359,1439.619,1364.644,1439.619z"></path>
2033
- <path fill="#F1F2F2" d="M1508.406,1432.983c-2.654-1.472-5.46-2.212-8.404-2.212c-4.721,0-8.703,1.7-11.944,5.087 c-3.249,3.395-4.865,7.3-4.865,11.723v163.228c0,4.721,1.616,8.702,4.865,11.943c3.241,3.249,7.223,4.866,11.944,4.866 c2.944,0,5.751-0.732,8.404-2.212c2.655-1.472,4.714-3.539,6.193-6.194c1.473-2.654,2.213-5.453,2.213-8.404V1447.58 c0-2.945-0.74-5.75-2.213-8.405C1513.12,1436.522,1511.06,1434.462,1508.406,1432.983z"></path>
2034
- <path fill="#F1F2F2" d="M1499.78,1367.957c-4.575,0-8.481,1.625-11.722,4.866c-3.249,3.249-4.865,7.23-4.865,11.943 c0,2.951,0.732,5.75,2.212,8.405c1.472,2.654,3.463,4.721,5.971,6.193c2.503,1.479,5.378,2.212,8.627,2.212 c4.423,0,8.328-1.618,11.721-4.865c3.387-3.243,5.088-7.224,5.088-11.944c0-4.713-1.701-8.694-5.088-11.943 C1508.33,1369.582,1504.349,1367.957,1499.78,1367.957z"></path>
2035
- <path fill="#F1F2F2" d="M1859.627,1369.727H1747.27c-35.388,0-65.69,12.607-90.904,37.821 c-25.213,25.215-37.82,55.591-37.82,91.125c0,35.54,12.607,65.911,37.82,91.125c25.215,25.215,55.516,37.821,90.904,37.821h56.178 c4.714,0,8.695-1.618,11.944-4.866c3.241-3.241,4.865-7.222,4.865-11.943c0-4.714-1.624-8.695-4.865-11.943 c-3.249-3.243-7.23-4.866-11.944-4.866h-56.178c-26.251,0-48.659-9.359-67.237-28.09c-18.579-18.723-27.868-41.207-27.868-67.459 c0-26.243,9.29-48.659,27.868-67.237c18.579-18.579,40.987-27.868,67.237-27.868h112.357c4.714,0,8.696-1.693,11.944-5.087 c3.241-3.387,4.865-7.368,4.865-11.943c0-4.569-1.624-8.475-4.865-11.723C1868.322,1371.351,1864.341,1369.727,1859.627,1369.727z "></path>
2036
- <path fill="#06b6d4" d="M2219.256,1371.054h-112.357c-4.423,0-8.336,1.624-11.723,4.865c-3.393,3.249-5.087,7.23-5.087,11.944 c0,4.721,1.694,8.702,5.087,11.943c3.387,3.249,7.3,4.866,11.723,4.866h95.547v95.105c0,26.251-9.365,48.659-28.088,67.237 c-18.731,18.579-41.215,27.868-67.459,27.868c-26.251,0-48.659-9.289-67.237-27.868c-18.579-18.579-27.868-40.987-27.868-67.237 c0-4.713-1.701-8.771-5.088-12.165c-3.393-3.387-7.374-5.087-11.943-5.087c-4.575,0-8.481,1.7-11.722,5.087 c-3.249,3.393-4.865,7.451-4.865,12.165c0,35.388,12.607,65.69,37.82,90.904c25.215,25.213,55.584,37.82,91.126,37.82 c35.532,0,65.91-12.607,91.125-37.82c25.214-25.215,37.82-55.516,37.82-90.904v-111.915c0-4.714-1.624-8.695-4.865-11.944 C2227.951,1372.678,2223.971,1371.054,2219.256,1371.054z"></path>
2037
- <path fill="#06b6d4" d="M2574.24,1502.875c-14.306-14.156-31.483-21.234-51.533-21.234H2410.35 c-10.617,0-19.762-3.829-27.426-11.501c-7.672-7.664-11.501-16.954-11.501-27.868c0-10.907,3.829-20.196,11.501-27.868 c7.664-7.664,16.809-11.501,27.426-11.501h112.357c4.714,0,8.695-1.617,11.944-4.866c3.241-3.241,4.865-7.222,4.865-11.943 c0-4.714-1.624-8.695-4.865-11.944c-3.249-3.241-7.23-4.865-11.944-4.865H2410.35c-20.058,0-37.158,7.154-51.313,21.454 c-14.156,14.308-21.232,31.483-21.232,51.534c0,20.058,7.077,37.234,21.232,51.534c14.156,14.308,31.255,21.454,51.313,21.454 h112.357c7.078,0,13.637,1.77,19.684,5.308c6.042,3.539,10.838,8.336,14.377,14.377c3.538,6.047,5.307,12.607,5.307,19.685 c0,10.616-3.835,19.76-11.501,27.425c-7.672,7.673-16.961,11.502-27.868,11.502h-168.094c-4.721,0-8.703,1.7-11.944,5.087 c-3.249,3.393-4.865,7.374-4.865,11.943c0,4.576,1.616,8.481,4.865,11.723c3.241,3.249,7.223,4.866,11.944,4.866h168.094 c20.051,0,37.227-7.078,51.533-21.234c14.302-14.155,21.454-31.331,21.454-51.534 C2595.695,1534.213,2588.542,1517.03,2574.24,1502.875z"></path>
2038
- <path fill="#06b6d4" d="M854.024,1585.195l20.001-16.028c16.616-13.507,33.04-27.265,50.086-40.251 c1.13-0.861,2.9-1.686,2.003-3.516c-0.843-1.716-2.481-2.302-4.484-2.123c-8.514,0.765-17.016-0.538-25.537-0.353 c-1.124,0.024-2.768,0.221-3.163-1.25c-0.371-1.369,1.088-2.063,1.919-2.894c6.26-6.242,12.574-12.43,18.816-18.691 c9.303-9.327,18.565-18.714,27.851-28.066c1.848-1.859,3.701-3.713,5.549-5.572c2.655-2.661,5.309-5.315,7.958-7.982 c0.574-0.579,1.259-1.141,1.246-1.94c-0.004-0.257-0.078-0.538-0.254-0.853c-0.556-0.981-1.441-1.1-2.469-0.957 c-0.658,0.096-1.315,0.185-1.973,0.275c-3.844,0.538-7.689,1.076-11.533,1.608c-3.641,0.505-7.281,1.02-10.922,1.529 c-4.162,0.582-8.324,1.158-12.486,1.748c-1.142,0.161-2.409,1.662-3.354,0.508c-0.419-0.508-0.431-1.028-0.251-1.531 c0.269-0.741,0.957-1.441,1.387-2.021c3.414-4.58,6.882-9.124,10.356-13.662c1.74-2.272,3.48-4.544,5.214-6.822 c4.682-6.141,9.369-12.281,14.051-18.422c0.09-0.119,0.181-0.237,0.271-0.355c6.848-8.98,13.7-17.958,20.553-26.936 c0.488-0.64,0.977-1.28,1.465-1.92c2.159-2.828,4.315-5.658,6.476-8.486c4.197-5.501,8.454-10.954,12.67-16.442 c0.263-0.347,0.538-0.718,0.717-1.106c0.269-0.586,0.299-1.196-0.335-1.776c-0.825-0.753-1.8-0.15-2.595,0.419 c-0.67,0.472-1.333,0.957-1.955,1.489c-2.206,1.889-4.401,3.797-6.595,5.698c-3.958,3.438-7.922,6.876-11.976,10.194 c-2.443,2.003-4.865,4.028-7.301,6.038c-18.689-10.581-39.53-15.906-62.549-15.906c-35.54,0-65.911,12.607-91.125,37.82 c-25.214,25.215-37.821,55.592-37.821,91.126c0,35.54,12.607,65.91,37.821,91.125c4.146,4.146,8.445,7.916,12.87,11.381 c-9.015,11.14-18.036,22.277-27.034,33.429c-1.208,1.489-3.755,3.151-2.745,4.891c0.078,0.144,0.173,0.281,0.305,0.425 c1.321,1.429,3.492-1.303,4.933-2.457c6.673-5.333,13.333-10.685,19.982-16.042c3.707-2.984,7.417-5.965,11.124-8.952 c1.474-1.188,2.951-2.373,4.425-3.561c6.41-5.164,12.816-10.333,19.238-15.481L854.024,1585.195z M797.552,1498.009 c0-26.243,9.29-48.728,27.868-67.459c18.579-18.723,40.987-28.089,67.238-28.089c12.273,0,23.712,2.075,34.34,6.171 c-3.37,2.905-6.734,5.816-10.069,8.762c-6.075,5.351-12.365,10.469-18.667,15.564c-4.179,3.378-8.371,6.744-12.514,10.164 c-7.54,6.23-15.037,12.52-22.529,18.804c-7.091,5.955-14.182,11.904-21.19,17.949c-1.136,0.974-3.055,1.907-2.135,3.94 c0.831,1.836,2.774,1.417,4.341,1.578l12.145-0.599l14.151-0.698c1.031-0.102,2.192-0.257,2.89,0.632 c0.034,0.044,0.073,0.078,0.106,0.127c1.017,1.561-0.67,2.105-1.387,2.942c-6.308,7.318-12.616,14.637-18.978,21.907 c-8.161,9.339-16.353,18.649-24.544,27.958c-2.146,2.433-4.275,4.879-6.422,7.312c-1.034,1.172-2.129,2.272-1.238,3.922 c0.933,1.728,2.685,1.752,4.323,1.602c4.134-0.367,8.263-0.489,12.396-0.492c0.242,0,0.485-0.005,0.728-0.004 c2.711,0.009,5.422,0.068,8.134,0.145c2.582,0.074,5.166,0.165,7.752,0.249c0.275,1.62-0.879,2.356-1.62,3.259 c-1.333,1.626-2.667,3.247-4,4.867c-4.315,5.252-8.62,10.514-12.928,15.772c-3.562-2.725-7.007-5.733-10.324-9.051 C806.842,1546.667,797.552,1524.26,797.552,1498.009z"></path>
2129
+ <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-lg bg-white">
2130
+ <svg class="h-7 w-7 text-zinc-950" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2131
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
2039
2132
  </svg>
2040
2133
  </div>
2041
- <h2 class="mt-6 text-xl font-medium text-white">${data.isSetup ? "Welcome to SonicJS" : "Create Account"}</h2>
2042
- ${data.isSetup ? `<p class="mt-2 text-sm text-zinc-400">Create your admin account to get started.</p>` : `<p class="mt-2 text-sm text-zinc-400">Create your account and get started</p>`}
2134
+ <h1 class="mt-6 text-3xl font-semibold tracking-tight text-white">SonicJS AI</h1>
2135
+ <p class="mt-2 text-sm text-zinc-400">Create your account and get started</p>
2043
2136
  </div>
2044
2137
 
2045
2138
  <!-- Form Container -->
2046
2139
  <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
2047
2140
  <div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
2048
- <!-- Setup Banner -->
2049
- ${data.isSetup ? `
2050
- <div class="mb-6 rounded-lg bg-blue-500/10 p-4 ring-1 ring-blue-500/20">
2051
- <div class="flex items-start gap-x-3">
2052
- <svg class="h-5 w-5 text-blue-400 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2053
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
2054
- </svg>
2055
- <div class="flex-1">
2056
- <p class="text-sm font-medium text-blue-300">First-time Setup</p>
2057
- <p class="text-sm text-blue-400/80 mt-1">This account will be the administrator with full access to manage your SonicJS installation.</p>
2058
- </div>
2059
- </div>
2060
- </div>
2061
- ` : ""}
2062
2141
  <!-- Alerts -->
2063
- ${data.error ? `<div class="mb-6">${chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
2142
+ ${data.error ? `<div class="mb-6">${chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
2064
2143
 
2065
2144
  <!-- Form -->
2066
2145
  <form
@@ -2173,7 +2252,6 @@ function renderRegisterPage(data) {
2173
2252
  </html>
2174
2253
  `;
2175
2254
  }
2176
- var adminExistsCache = null;
2177
2255
  async function isRegistrationEnabled(db) {
2178
2256
  try {
2179
2257
  const plugin = await db.prepare("SELECT settings FROM plugins WHERE id = ?").bind("core-auth").first();
@@ -2195,21 +2273,6 @@ async function isFirstUserRegistration(db) {
2195
2273
  return false;
2196
2274
  }
2197
2275
  }
2198
- async function checkAdminUserExists(db) {
2199
- if (adminExistsCache !== null) {
2200
- return adminExistsCache;
2201
- }
2202
- try {
2203
- const result = await db.prepare("SELECT id FROM users WHERE role = ?").bind("admin").first();
2204
- adminExistsCache = !!result;
2205
- return adminExistsCache;
2206
- } catch {
2207
- return false;
2208
- }
2209
- }
2210
- function setAdminExists() {
2211
- adminExistsCache = true;
2212
- }
2213
2276
  var baseRegistrationSchema = zod.z.object({
2214
2277
  email: zod.z.string().email("Valid email is required"),
2215
2278
  password: zod.z.string().min(8, "Password must be at least 8 characters"),
@@ -2271,11 +2334,8 @@ authRoutes.get("/register", async (c) => {
2271
2334
  }
2272
2335
  }
2273
2336
  const error = c.req.query("error");
2274
- const isSetup = c.req.query("setup") === "true";
2275
2337
  const pageData = {
2276
- error: error || void 0,
2277
- isSetup: isSetup && isFirstUser
2278
- // Only show setup message if truly first user
2338
+ error: error || void 0
2279
2339
  };
2280
2340
  return c.html(renderRegisterPage(pageData));
2281
2341
  });
@@ -2321,7 +2381,7 @@ authRoutes.post(
2321
2381
  if (existingUser) {
2322
2382
  return c.json({ error: "User with this email or username already exists" }, 400);
2323
2383
  }
2324
- const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
2384
+ const passwordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(password);
2325
2385
  const userId = crypto.randomUUID();
2326
2386
  const now = /* @__PURE__ */ new Date();
2327
2387
  await db.prepare(`
@@ -2341,7 +2401,7 @@ authRoutes.post(
2341
2401
  now.getTime(),
2342
2402
  now.getTime()
2343
2403
  ).run();
2344
- const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer");
2404
+ const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer");
2345
2405
  cookie.setCookie(c, "auth_token", token, {
2346
2406
  httpOnly: true,
2347
2407
  secure: true,
@@ -2394,11 +2454,11 @@ authRoutes.post("/login", async (c) => {
2394
2454
  if (!user) {
2395
2455
  return c.json({ error: "Invalid email or password" }, 401);
2396
2456
  }
2397
- const isValidPassword = await chunkYYV3XQOQ_cjs.AuthManager.verifyPassword(password, user.password_hash);
2457
+ const isValidPassword = await chunkT3YIKW2A_cjs.AuthManager.verifyPassword(password, user.password_hash);
2398
2458
  if (!isValidPassword) {
2399
2459
  return c.json({ error: "Invalid email or password" }, 401);
2400
2460
  }
2401
- const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2461
+ const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2402
2462
  cookie.setCookie(c, "auth_token", token, {
2403
2463
  httpOnly: true,
2404
2464
  secure: true,
@@ -2447,7 +2507,7 @@ authRoutes.get("/logout", (c) => {
2447
2507
  });
2448
2508
  return c.redirect("/auth/login?message=You have been logged out successfully");
2449
2509
  });
2450
- authRoutes.get("/me", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
2510
+ authRoutes.get("/me", chunkT3YIKW2A_cjs.requireAuth(), async (c) => {
2451
2511
  try {
2452
2512
  const user = c.get("user");
2453
2513
  if (!user) {
@@ -2464,13 +2524,13 @@ authRoutes.get("/me", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
2464
2524
  return c.json({ error: "Failed to get user" }, 500);
2465
2525
  }
2466
2526
  });
2467
- authRoutes.post("/refresh", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
2527
+ authRoutes.post("/refresh", chunkT3YIKW2A_cjs.requireAuth(), async (c) => {
2468
2528
  try {
2469
2529
  const user = c.get("user");
2470
2530
  if (!user) {
2471
2531
  return c.json({ error: "Not authenticated" }, 401);
2472
2532
  }
2473
- const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(user.userId, user.email, user.role);
2533
+ const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(user.userId, user.email, user.role);
2474
2534
  cookie.setCookie(c, "auth_token", token, {
2475
2535
  httpOnly: true,
2476
2536
  secure: true,
@@ -2530,7 +2590,7 @@ authRoutes.post("/register/form", async (c) => {
2530
2590
  </div>
2531
2591
  `);
2532
2592
  }
2533
- const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
2593
+ const passwordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(password);
2534
2594
  const role = isFirstUser ? "admin" : "viewer";
2535
2595
  const userId = crypto.randomUUID();
2536
2596
  const now = /* @__PURE__ */ new Date();
@@ -2550,10 +2610,7 @@ authRoutes.post("/register/form", async (c) => {
2550
2610
  now.getTime(),
2551
2611
  now.getTime()
2552
2612
  ).run();
2553
- if (isFirstUser) {
2554
- setAdminExists();
2555
- }
2556
- const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
2613
+ const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
2557
2614
  cookie.setCookie(c, "auth_token", token, {
2558
2615
  httpOnly: true,
2559
2616
  secure: false,
@@ -2605,7 +2662,7 @@ authRoutes.post("/login/form", async (c) => {
2605
2662
  </div>
2606
2663
  `);
2607
2664
  }
2608
- const isValidPassword = await chunkYYV3XQOQ_cjs.AuthManager.verifyPassword(password, user.password_hash);
2665
+ const isValidPassword = await chunkT3YIKW2A_cjs.AuthManager.verifyPassword(password, user.password_hash);
2609
2666
  if (!isValidPassword) {
2610
2667
  return c.html(html.html`
2611
2668
  <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
@@ -2613,7 +2670,7 @@ authRoutes.post("/login/form", async (c) => {
2613
2670
  </div>
2614
2671
  `);
2615
2672
  }
2616
- const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2673
+ const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2617
2674
  cookie.setCookie(c, "auth_token", token, {
2618
2675
  httpOnly: true,
2619
2676
  secure: false,
@@ -2672,9 +2729,8 @@ authRoutes.post("/seed-admin", async (c) => {
2672
2729
  `).run();
2673
2730
  const existingAdmin = await db.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind("admin@sonicjs.com", "admin").first();
2674
2731
  if (existingAdmin) {
2675
- const passwordHash2 = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword("sonicjs!");
2732
+ const passwordHash2 = await chunkT3YIKW2A_cjs.AuthManager.hashPassword("sonicjs!");
2676
2733
  await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
2677
- setAdminExists();
2678
2734
  return c.json({
2679
2735
  message: "Admin user already exists (password updated)",
2680
2736
  user: {
@@ -2685,7 +2741,7 @@ authRoutes.post("/seed-admin", async (c) => {
2685
2741
  }
2686
2742
  });
2687
2743
  }
2688
- const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword("sonicjs!");
2744
+ const passwordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword("sonicjs!");
2689
2745
  const userId = "admin-user-id";
2690
2746
  const now = Date.now();
2691
2747
  const adminEmail = "admin@sonicjs.com".toLowerCase();
@@ -2705,7 +2761,6 @@ authRoutes.post("/seed-admin", async (c) => {
2705
2761
  now,
2706
2762
  now
2707
2763
  ).run();
2708
- setAdminExists();
2709
2764
  return c.json({
2710
2765
  message: "Admin user created successfully",
2711
2766
  user: {
@@ -2906,7 +2961,7 @@ authRoutes.post("/accept-invitation", async (c) => {
2906
2961
  if (existingUsername) {
2907
2962
  return c.json({ error: "Username is already taken" }, 400);
2908
2963
  }
2909
- const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
2964
+ const passwordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(password);
2910
2965
  const updateStmt = db.prepare(`
2911
2966
  UPDATE users SET
2912
2967
  username = ?,
@@ -2925,7 +2980,7 @@ authRoutes.post("/accept-invitation", async (c) => {
2925
2980
  Date.now(),
2926
2981
  invitedUser.id
2927
2982
  ).run();
2928
- const authToken = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role);
2983
+ const authToken = await chunkT3YIKW2A_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role);
2929
2984
  cookie.setCookie(c, "auth_token", authToken, {
2930
2985
  httpOnly: true,
2931
2986
  secure: true,
@@ -3155,7 +3210,7 @@ authRoutes.post("/reset-password", async (c) => {
3155
3210
  if (Date.now() > user.password_reset_expires) {
3156
3211
  return c.json({ error: "Reset token has expired" }, 400);
3157
3212
  }
3158
- const newPasswordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
3213
+ const newPasswordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(password);
3159
3214
  try {
3160
3215
  const historyStmt = db.prepare(`
3161
3216
  INSERT INTO password_history (id, user_id, password_hash, created_at)
@@ -3413,9 +3468,166 @@ app.post("/test-cleanup/content", async (c) => {
3413
3468
  var test_cleanup_default = app;
3414
3469
 
3415
3470
  // src/templates/pages/admin-content-form.template.ts
3416
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
3471
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
3472
+
3473
+ // src/templates/components/drag-sortable.template.ts
3474
+ function getDragSortableScript() {
3475
+ return `
3476
+ <script>
3477
+ if (!window.__sonicDragSortableInit) {
3478
+ window.__sonicDragSortableInit = true;
3479
+
3480
+ window.initializeDragSortable = function(container, options) {
3481
+ if (!container || container.dataset.dragSortableInit === 'true') {
3482
+ return;
3483
+ }
3484
+
3485
+ container.dataset.dragSortableInit = 'true';
3486
+ const itemSelector = options && options.itemSelector ? options.itemSelector : '.sortable-item';
3487
+ const handleSelector = options && options.handleSelector ? options.handleSelector : '[data-action="drag-handle"]';
3488
+ const onUpdate = options && typeof options.onUpdate === 'function' ? options.onUpdate : function() {};
3489
+ let activeDragItem = null;
3490
+
3491
+ const getDragAfterElement = function(list, y) {
3492
+ const items = Array.from(list.querySelectorAll(itemSelector + ':not(.is-dragging)'));
3493
+ let closest = { offset: Number.NEGATIVE_INFINITY, element: null };
3494
+ items.forEach(function(item) {
3495
+ const box = item.getBoundingClientRect();
3496
+ const offset = y - box.top - box.height / 2;
3497
+ if (offset < 0 && offset > closest.offset) {
3498
+ closest = { offset: offset, element: item };
3499
+ }
3500
+ });
3501
+ return closest.element;
3502
+ };
3503
+
3504
+ const activateDragItem = function(event) {
3505
+ const target = event.target;
3506
+ if (!(target instanceof Element)) return;
3507
+ const handle = target.closest(handleSelector);
3508
+ if (!handle) return;
3509
+ const item = handle.closest(itemSelector);
3510
+ if (!item) return;
3511
+ activeDragItem = item;
3512
+ };
3513
+
3514
+ const clearActiveDragItem = function() {
3515
+ activeDragItem = null;
3516
+ };
3517
+
3518
+ container.addEventListener('pointerdown', activateDragItem);
3519
+ container.addEventListener('mousedown', activateDragItem);
3520
+ container.addEventListener('pointerup', clearActiveDragItem);
3521
+ container.addEventListener('mouseup', clearActiveDragItem);
3522
+
3523
+ container.addEventListener('dragstart', function(event) {
3524
+ const target = event.target;
3525
+ if (!(target instanceof Element)) return;
3526
+ const item = target.closest(itemSelector);
3527
+ if (!item || item !== activeDragItem) {
3528
+ event.preventDefault();
3529
+ return;
3530
+ }
3531
+ item.classList.add('is-dragging');
3532
+ if (event.dataTransfer) {
3533
+ event.dataTransfer.setData('text/plain', '');
3534
+ }
3535
+ });
3536
+
3537
+ container.addEventListener('dragend', function(event) {
3538
+ const target = event.target;
3539
+ if (target instanceof Element) {
3540
+ const item = target.closest(itemSelector);
3541
+ if (item) {
3542
+ item.classList.remove('is-dragging');
3543
+ }
3544
+ }
3545
+ activeDragItem = null;
3546
+ onUpdate();
3547
+ });
3548
+
3549
+ container.addEventListener('dragover', function(event) {
3550
+ event.preventDefault();
3551
+ const dragging = container.querySelector(itemSelector + '.is-dragging');
3552
+ if (!dragging) return;
3553
+ const afterElement = getDragAfterElement(container, event.clientY);
3554
+ if (afterElement === null) {
3555
+ container.appendChild(dragging);
3556
+ } else {
3557
+ container.insertBefore(dragging, afterElement);
3558
+ }
3559
+ });
3560
+
3561
+ container.addEventListener('drop', function() {
3562
+ onUpdate();
3563
+ });
3564
+ };
3565
+ }
3566
+ </script>
3567
+ `;
3568
+ }
3417
3569
 
3418
3570
  // src/templates/components/dynamic-field.template.ts
3571
+ function getReadFieldValueScript() {
3572
+ return `
3573
+ <script>
3574
+ if (!window.__sonicReadFieldValueInit) {
3575
+ window.__sonicReadFieldValueInit = true;
3576
+
3577
+ window.sonicReadFieldValue = function(fieldWrapper) {
3578
+ const fieldType = fieldWrapper.dataset.fieldType;
3579
+ const select = fieldWrapper.querySelector('select');
3580
+ const textarea = fieldWrapper.querySelector('textarea');
3581
+ const inputs = Array.from(fieldWrapper.querySelectorAll('input'));
3582
+ const checkbox = inputs.find((input) => input.type === 'checkbox');
3583
+ const nonHiddenInput = inputs.find((input) => input.type !== 'hidden' && input.type !== 'checkbox');
3584
+ const hiddenInput = inputs.find((input) => input.type === 'hidden');
3585
+
3586
+ if (fieldType === 'object' || fieldType === 'array') {
3587
+ if (!hiddenInput) {
3588
+ return fieldType === 'array' ? [] : {};
3589
+ }
3590
+ const rawValue = hiddenInput.value || '';
3591
+ if (!rawValue.trim()) {
3592
+ return fieldType === 'array' ? [] : {};
3593
+ }
3594
+ try {
3595
+ return JSON.parse(rawValue);
3596
+ } catch {
3597
+ return fieldType === 'array' ? [] : {};
3598
+ }
3599
+ }
3600
+
3601
+ if (fieldType === 'boolean' && checkbox) {
3602
+ return checkbox.checked;
3603
+ }
3604
+
3605
+ if (select) {
3606
+ if (select.multiple) {
3607
+ return Array.from(select.selectedOptions).map((option) => option.value);
3608
+ }
3609
+ return select.value;
3610
+ }
3611
+
3612
+ if (fieldType === 'quill' || fieldType === 'media') {
3613
+ return hiddenInput ? hiddenInput.value : '';
3614
+ }
3615
+
3616
+ const textSource = textarea || nonHiddenInput || hiddenInput;
3617
+ if (!textSource) {
3618
+ return '';
3619
+ }
3620
+
3621
+ if (fieldType === 'number') {
3622
+ return textSource.value === '' ? null : Number(textSource.value);
3623
+ }
3624
+
3625
+ return textSource.value;
3626
+ };
3627
+ }
3628
+ </script>
3629
+ `;
3630
+ }
3419
3631
  function renderDynamicField(field, options = {}) {
3420
3632
  const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {}, collectionId = "", contentId = "" } = options;
3421
3633
  const opts = field.field_options || {};
@@ -3871,43 +4083,124 @@ function renderDynamicField(field, options = {}) {
3871
4083
  ` : ""}
3872
4084
  `;
3873
4085
  break;
4086
+ case "reference":
4087
+ let referenceCollections = [];
4088
+ if (Array.isArray(opts.collection)) {
4089
+ referenceCollections = opts.collection.filter(Boolean);
4090
+ } else if (typeof opts.collection === "string" && opts.collection) {
4091
+ referenceCollections = [opts.collection];
4092
+ }
4093
+ const referenceCollectionsAttr = referenceCollections.join(",");
4094
+ const hasReferenceCollection = referenceCollections.length > 0;
4095
+ const hasReferenceValue = Boolean(value);
4096
+ fieldHTML = `
4097
+ <div class="reference-field-container space-y-3" data-reference-field data-field-name="${escapeHtml2(fieldName)}" data-reference-collection="${escapeHtml2(referenceCollections[0] || "")}" data-reference-collections="${escapeHtml2(referenceCollectionsAttr)}">
4098
+ <input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml2(value)}">
4099
+ <div class="rounded-lg border border-zinc-200 bg-white/60 px-3 py-2 text-sm text-zinc-600 dark:border-white/10 dark:bg-white/5 dark:text-zinc-300" data-reference-display>
4100
+ ${hasReferenceCollection ? hasReferenceValue ? "Loading selection..." : "No reference selected." : "Reference collection not configured."}
4101
+ </div>
4102
+ <div class="flex flex-wrap gap-2">
4103
+ <button
4104
+ type="button"
4105
+ onclick="openReferenceSelector('${fieldId}')"
4106
+ class="inline-flex items-center justify-center rounded-lg bg-zinc-900 px-3 py-2 text-sm font-semibold text-white hover:bg-zinc-800 dark:bg-white/10 dark:hover:bg-white/20"
4107
+ ${hasReferenceCollection ? "" : "disabled"}
4108
+ >
4109
+ Select reference
4110
+ </button>
4111
+ <button
4112
+ type="button"
4113
+ onclick="clearReferenceField('${fieldId}')"
4114
+ class="inline-flex items-center justify-center rounded-lg border border-zinc-200 px-3 py-2 text-sm font-semibold text-zinc-700 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-200 dark:hover:bg-white/10"
4115
+ data-reference-clear
4116
+ ${hasReferenceValue ? "" : "disabled"}
4117
+ >
4118
+ Remove
4119
+ </button>
4120
+ </div>
4121
+ </div>
4122
+ `;
4123
+ break;
3874
4124
  case "media":
4125
+ const isMultiple = opts.multiple === true;
4126
+ const mediaValues = isMultiple && value ? Array.isArray(value) ? value : String(value).split(",").filter(Boolean) : [];
4127
+ const singleValue = !isMultiple ? value : "";
4128
+ const isVideoUrl = (url) => {
4129
+ const videoExtensions = [".mp4", ".webm", ".ogg", ".mov", ".avi"];
4130
+ return videoExtensions.some((ext) => url.toLowerCase().endsWith(ext));
4131
+ };
4132
+ const renderMediaPreview = (url, alt, classes) => {
4133
+ if (isVideoUrl(url)) {
4134
+ return `<video src="${url}" class="${classes}" muted></video>`;
4135
+ }
4136
+ return `<img src="${url}" alt="${alt}" class="${classes}">`;
4137
+ };
3875
4138
  fieldHTML = `
3876
4139
  <div class="media-field-container">
3877
- <input type="hidden" id="${fieldId}" name="${fieldName}" value="${value}">
3878
- <div class="media-preview ${value ? "" : "hidden"}" id="${fieldId}-preview">
3879
- ${value ? `<img src="${value}" alt="Selected media" class="w-32 h-32 object-cover rounded-lg border border-white/20">` : ""}
3880
- </div>
4140
+ <input type="hidden" id="${fieldId}" name="${fieldName}" value="${isMultiple ? mediaValues.join(",") : singleValue}" data-multiple="${isMultiple}">
4141
+
4142
+ ${isMultiple ? `
4143
+ <div class="media-preview-grid grid grid-cols-4 gap-2 mb-2 ${mediaValues.length === 0 ? "hidden" : ""}" id="${fieldId}-preview">
4144
+ ${mediaValues.map((url, idx) => `
4145
+ <div class="relative media-preview-item" data-url="${url}">
4146
+ ${renderMediaPreview(url, `Media ${idx + 1}`, "w-full h-24 object-cover rounded-lg border border-white/20")}
4147
+ <button
4148
+ type="button"
4149
+ onclick="removeMediaFromMultiple('${fieldId}', '${url}')"
4150
+ class="absolute top-1 right-1 bg-red-600 text-white rounded-full p-1 hover:bg-red-700"
4151
+ ${disabled ? "disabled" : ""}
4152
+ >
4153
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
4154
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
4155
+ </svg>
4156
+ </button>
4157
+ </div>
4158
+ `).join("")}
4159
+ </div>
4160
+ ` : `
4161
+ <div class="media-preview ${singleValue ? "" : "hidden"}" id="${fieldId}-preview">
4162
+ ${singleValue ? renderMediaPreview(singleValue, "Selected media", "w-32 h-32 object-cover rounded-lg border border-white/20") : ""}
4163
+ </div>
4164
+ `}
4165
+
3881
4166
  <div class="media-actions mt-2 space-x-2">
3882
4167
  <button
3883
4168
  type="button"
3884
- onclick="openMediaSelector('${fieldId}')"
4169
+ onclick="openMediaSelector('${fieldId}', ${isMultiple})"
3885
4170
  class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all"
3886
4171
  ${disabled ? "disabled" : ""}
3887
4172
  >
3888
4173
  <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3889
4174
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
3890
4175
  </svg>
3891
- Select Media
4176
+ ${isMultiple ? "Select Media (Multiple)" : "Select Media"}
3892
4177
  </button>
3893
- ${value ? `
4178
+ ${(isMultiple ? mediaValues.length > 0 : singleValue) ? `
3894
4179
  <button
3895
4180
  type="button"
3896
4181
  onclick="clearMediaField('${fieldId}')"
3897
4182
  class="inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all"
3898
4183
  ${disabled ? "disabled" : ""}
3899
4184
  >
3900
- Remove
4185
+ ${isMultiple ? "Clear All" : "Remove"}
3901
4186
  </button>
3902
4187
  ` : ""}
3903
4188
  </div>
3904
4189
  </div>
3905
4190
  `;
3906
4191
  break;
4192
+ case "object":
4193
+ return renderStructuredObjectField(field, options2);
4194
+ case "array":
4195
+ const itemsConfig = opts.items && typeof opts.items === "object" ? opts.items : {};
4196
+ if (itemsConfig.blocks && typeof itemsConfig.blocks === "object") {
4197
+ return renderBlocksField(field, options2, baseClasses, errorClasses);
4198
+ }
4199
+ return renderStructuredArrayField(field, options2);
3907
4200
  default:
3908
4201
  fieldHTML = `
3909
- <input
3910
- type="text"
4202
+ <input
4203
+ type="text"
3911
4204
  id="${fieldId}"
3912
4205
  name="${fieldName}"
3913
4206
  value="${escapeHtml2(value)}"
@@ -3957,77 +4250,825 @@ function renderFieldGroup(title, fields, collapsible = false) {
3957
4250
  </div>
3958
4251
  `;
3959
4252
  }
3960
- function escapeHtml2(text) {
3961
- if (typeof text !== "string") return String(text || "");
3962
- return text.replace(/[&<>"']/g, (char) => ({
3963
- "&": "&amp;",
3964
- "<": "&lt;",
3965
- ">": "&gt;",
3966
- '"': "&quot;",
3967
- "'": "&#39;"
3968
- })[char] || char);
3969
- }
4253
+ function renderBlocksField(field, options, baseClasses, errorClasses) {
4254
+ const { value = [], pluginStatuses = {} } = options;
4255
+ const opts = field.field_options || {};
4256
+ const itemsConfig = opts.items && typeof opts.items === "object" ? opts.items : {};
4257
+ const blocks = normalizeBlockDefinitions(itemsConfig.blocks);
4258
+ const discriminator = typeof itemsConfig.discriminator === "string" && itemsConfig.discriminator ? itemsConfig.discriminator : "blockType";
4259
+ const blockValues = normalizeBlocksValue(value, discriminator);
4260
+ const fieldId = `field-${field.field_name}`;
4261
+ const fieldName = field.field_name;
4262
+ const emptyState = blockValues.length === 0 ? `
4263
+ <div class="rounded-lg border border-dashed border-zinc-200 dark:border-white/10 px-4 py-6 text-center text-sm text-zinc-500 dark:text-zinc-400" data-blocks-empty>
4264
+ No blocks yet. Add your first block to get started.
4265
+ </div>
4266
+ ` : "";
4267
+ const blockOptions = blocks.map((block) => `<option value="${escapeHtml2(block.name)}">${escapeHtml2(block.label)}</option>`).join("");
4268
+ const blockItems = blockValues.map(
4269
+ (blockValue, index) => renderBlockItem(field, blockValue, blocks, discriminator, index, pluginStatuses)
4270
+ ).join("");
4271
+ const templates = blocks.map((block) => renderBlockTemplate(field, block, discriminator, pluginStatuses)).join("");
4272
+ return `
4273
+ <div
4274
+ class="blocks-field space-y-4"
4275
+ data-blocks='${escapeHtml2(JSON.stringify(blocks))}'
4276
+ data-blocks-discriminator="${escapeHtml2(discriminator)}"
4277
+ data-field-name="${escapeHtml2(fieldName)}"
4278
+ >
4279
+ <input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml2(JSON.stringify(blockValues))}">
3970
4280
 
3971
- // src/plugins/available/tinymce-plugin/index.ts
3972
- var builder = chunkAI2JJIJX_cjs.PluginBuilder.create({
3973
- name: "tinymce-plugin",
3974
- version: "1.0.0",
3975
- description: "Powerful WYSIWYG rich text editor for content creation"
3976
- });
3977
- builder.metadata({
3978
- author: {
3979
- name: "SonicJS Team",
3980
- email: "team@sonicjs.com"
3981
- },
3982
- license: "MIT",
3983
- compatibility: "^2.0.0"
3984
- });
3985
- builder.lifecycle({
3986
- activate: async () => {
3987
- console.info("\u2705 TinyMCE plugin activated");
3988
- },
3989
- deactivate: async () => {
3990
- console.info("\u274C TinyMCE plugin deactivated");
3991
- }
3992
- });
3993
- builder.build();
3994
- function getTinyMCEScript(apiKey = "no-api-key") {
3995
- return `<script src="https://cdn.tiny.cloud/1/${apiKey}/tinymce/6/tinymce.min.js" referrerpolicy="origin"></script>`;
4281
+ <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
4282
+ <div class="flex-1">
4283
+ <select
4284
+ class="${baseClasses} ${errorClasses}"
4285
+ data-role="block-type-select"
4286
+ >
4287
+ <option value="">Choose a block...</option>
4288
+ ${blockOptions}
4289
+ </select>
4290
+ </div>
4291
+ <button
4292
+ type="button"
4293
+ data-action="add-block"
4294
+ class="inline-flex items-center justify-center rounded-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white hover:bg-zinc-800 dark:bg-white/10 dark:hover:bg-white/20"
4295
+ >
4296
+ Add Block
4297
+ </button>
4298
+ </div>
4299
+
4300
+ <div class="space-y-4" data-blocks-list>
4301
+ ${blockItems || emptyState}
4302
+ </div>
4303
+
4304
+ ${templates}
4305
+ </div>
4306
+ ${getDragSortableScript()}
4307
+ ${getBlocksFieldScript()}
4308
+ `;
3996
4309
  }
3997
- function getTinyMCEInitScript(config) {
3998
- const skin = config?.skin || "oxide-dark";
3999
- const contentCss = skin.includes("dark") ? "dark" : "default";
4000
- const defaultHeight = config?.defaultHeight || 300;
4310
+ function renderStructuredObjectField(field, options, baseClasses, errorClasses) {
4311
+ const { value = {}, pluginStatuses = {} } = options;
4312
+ const opts = field.field_options || {};
4313
+ const properties = opts.properties && typeof opts.properties === "object" ? opts.properties : {};
4314
+ const fieldId = `field-${field.field_name}`;
4315
+ const fieldName = field.field_name;
4316
+ const objectValue = normalizeStructuredObjectValue(value);
4317
+ const subfields = Object.entries(properties).map(
4318
+ ([propertyName, propertyConfig]) => renderStructuredSubfield(
4319
+ field,
4320
+ propertyName,
4321
+ propertyConfig,
4322
+ objectValue,
4323
+ pluginStatuses,
4324
+ field.field_name
4325
+ )
4326
+ ).join("");
4001
4327
  return `
4002
- // Initialize TinyMCE for all richtext fields
4003
- function initializeTinyMCE() {
4004
- if (typeof tinymce !== 'undefined') {
4005
- // Find all textareas that need TinyMCE
4006
- document.querySelectorAll('.richtext-container textarea').forEach((textarea) => {
4007
- // Skip if already initialized
4008
- if (tinymce.get(textarea.id)) {
4009
- return;
4010
- }
4328
+ <div class="space-y-4" data-structured-object data-field-name="${escapeHtml2(fieldName)}">
4329
+ <input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml2(JSON.stringify(objectValue))}">
4330
+ <div class="space-y-4" data-structured-object-fields>
4331
+ ${subfields}
4332
+ </div>
4333
+ </div>
4334
+ ${getStructuredFieldScript()}
4335
+ `;
4336
+ }
4337
+ function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
4338
+ const { value = [], pluginStatuses = {} } = options;
4339
+ const opts = field.field_options || {};
4340
+ const itemsConfig = opts.items && typeof opts.items === "object" ? opts.items : {};
4341
+ const fieldId = `field-${field.field_name}`;
4342
+ const fieldName = field.field_name;
4343
+ const arrayValue = normalizeStructuredArrayValue(value);
4344
+ const items = arrayValue.map(
4345
+ (itemValue, index) => renderStructuredArrayItem(field, itemsConfig, String(index), itemValue, pluginStatuses)
4346
+ ).join("");
4347
+ const emptyState = arrayValue.length === 0 ? `
4348
+ <div class="rounded-lg border border-dashed border-zinc-200 dark:border-white/10 px-4 py-6 text-center text-sm text-zinc-500 dark:text-zinc-400" data-structured-empty>
4349
+ No items yet. Add the first item to get started.
4350
+ </div>
4351
+ ` : "";
4352
+ return `
4353
+ <div class="space-y-4" data-structured-array data-field-name="${escapeHtml2(fieldName)}">
4354
+ <input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml2(JSON.stringify(arrayValue))}">
4011
4355
 
4012
- // Get configuration from data attributes
4013
- const container = textarea.closest('.richtext-container');
4014
- const height = container?.dataset.height || ${defaultHeight};
4015
- const toolbar = container?.dataset.toolbar || 'full';
4356
+ <div class="flex items-center justify-between gap-3">
4357
+ <div class="text-sm text-zinc-500 dark:text-zinc-400">
4358
+ ${escapeHtml2(opts.itemLabel || "Items")}
4359
+ </div>
4360
+ <button
4361
+ type="button"
4362
+ data-action="add-item"
4363
+ class="inline-flex items-center justify-center rounded-lg bg-zinc-900 px-3 py-2 text-sm font-semibold text-white hover:bg-zinc-800 dark:bg-white/10 dark:hover:bg-white/20"
4364
+ >
4365
+ Add item
4366
+ </button>
4367
+ </div>
4016
4368
 
4017
- tinymce.init({
4018
- selector: '#' + textarea.id,
4019
- skin: '${skin}',
4020
- content_css: '${contentCss}',
4021
- height: parseInt(height),
4022
- menubar: false,
4023
- plugins: [
4024
- 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
4025
- 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
4026
- 'insertdatetime', 'media', 'table', 'help', 'wordcount'
4027
- ],
4028
- toolbar: toolbar === 'simple'
4029
- ? 'bold italic underline | bullist numlist | link'
4030
- : toolbar === 'minimal'
4369
+ <div class="space-y-4" data-structured-array-list>
4370
+ ${items || emptyState}
4371
+ </div>
4372
+
4373
+ <template data-structured-array-template>
4374
+ ${renderStructuredArrayItem(field, itemsConfig, "__INDEX__", {}, pluginStatuses)}
4375
+ </template>
4376
+ </div>
4377
+ ${getDragSortableScript()}
4378
+ ${getStructuredFieldScript()}
4379
+ `;
4380
+ }
4381
+ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginStatuses) {
4382
+ const itemFields = renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses);
4383
+ return `
4384
+ <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="${escapeHtml2(index)}" draggable="true">
4385
+ <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
4386
+ <div class="flex items-center gap-3">
4387
+ <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">
4388
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
4389
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
4390
+ </svg>
4391
+ </div>
4392
+ <div class="text-sm font-semibold text-zinc-900 dark:text-white">
4393
+ Item <span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-array-order-label></span>
4394
+ </div>
4395
+ </div>
4396
+ <div class="flex flex-wrap gap-2 text-xs">
4397
+ <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">
4398
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
4399
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
4400
+ </svg>
4401
+ </button>
4402
+ <button type="button" data-action="move-down" 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 down" title="Move down">
4403
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
4404
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 18l4-4m-4 4l-4-4m4 4V6"/>
4405
+ </svg>
4406
+ </button>
4407
+ <button type="button" data-action="remove-item" class="inline-flex items-center gap-x-1 px-2.5 py-1.5 text-xs font-medium text-pink-700 dark:text-pink-300 hover:bg-pink-50 dark:hover:bg-pink-900/20 rounded-lg transition-colors">
4408
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
4409
+ <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 0 00-7.5 0"/>
4410
+ </svg>
4411
+ Delete item
4412
+ </button>
4413
+ </div>
4414
+ </div>
4415
+ <div class="mt-4 space-y-4" data-array-item-fields>
4416
+ ${itemFields}
4417
+ </div>
4418
+ </div>
4419
+ `;
4420
+ }
4421
+ function renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses) {
4422
+ const itemType = itemConfig?.type || "string";
4423
+ if (itemType === "object" && itemConfig?.properties && typeof itemConfig.properties === "object") {
4424
+ const fieldPrefix = `array-${field.field_name}-${index}`;
4425
+ return Object.entries(itemConfig.properties).map(
4426
+ ([propertyName, propertyConfig]) => renderStructuredSubfield(
4427
+ field,
4428
+ propertyName,
4429
+ propertyConfig,
4430
+ itemValue || {},
4431
+ pluginStatuses,
4432
+ fieldPrefix
4433
+ )
4434
+ ).join("");
4435
+ }
4436
+ const normalizedField = normalizeBlockField(itemConfig, "Item");
4437
+ const fieldValue = itemValue ?? normalizedField.defaultValue ?? "";
4438
+ const fieldDefinition = {
4439
+ id: `array-${field.field_name}-${index}-value`,
4440
+ field_name: `array-${field.field_name}-${index}-value`,
4441
+ field_type: normalizedField.type,
4442
+ field_label: normalizedField.label,
4443
+ field_options: normalizedField.options,
4444
+ is_required: normalizedField.required};
4445
+ return `
4446
+ <div class="structured-subfield" data-structured-field="__value" data-field-type="${escapeHtml2(normalizedField.type)}">
4447
+ ${renderDynamicField(fieldDefinition, { value: fieldValue, pluginStatuses })}
4448
+ </div>
4449
+ `;
4450
+ }
4451
+ function renderStructuredSubfield(field, propertyName, propertyConfig, objectValue, pluginStatuses, fieldPrefix) {
4452
+ const normalizedField = normalizeBlockField(propertyConfig, propertyName);
4453
+ const fieldValue = objectValue?.[propertyName] ?? normalizedField.defaultValue ?? "";
4454
+ const fieldDefinition = {
4455
+ field_name: `${fieldPrefix}__${propertyName}`,
4456
+ field_type: normalizedField.type,
4457
+ field_label: normalizedField.label,
4458
+ field_options: normalizedField.options,
4459
+ is_required: normalizedField.required};
4460
+ return `
4461
+ <div class="structured-subfield" data-structured-field="${escapeHtml2(propertyName)}" data-field-type="${escapeHtml2(normalizedField.type)}">
4462
+ ${renderDynamicField(fieldDefinition, { value: fieldValue, pluginStatuses })}
4463
+ </div>
4464
+ `;
4465
+ }
4466
+ function normalizeStructuredObjectValue(value) {
4467
+ if (!value) return {};
4468
+ if (typeof value === "string") {
4469
+ try {
4470
+ const parsed = JSON.parse(value);
4471
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
4472
+ } catch {
4473
+ return {};
4474
+ }
4475
+ }
4476
+ if (typeof value === "object" && !Array.isArray(value)) return value;
4477
+ return {};
4478
+ }
4479
+ function normalizeStructuredArrayValue(value) {
4480
+ if (!value) return [];
4481
+ if (Array.isArray(value)) return value;
4482
+ if (typeof value === "string") {
4483
+ try {
4484
+ const parsed = JSON.parse(value);
4485
+ return Array.isArray(parsed) ? parsed : [];
4486
+ } catch {
4487
+ return [];
4488
+ }
4489
+ }
4490
+ return [];
4491
+ }
4492
+ function normalizeBlockDefinitions(rawBlocks) {
4493
+ if (!rawBlocks || typeof rawBlocks !== "object") return [];
4494
+ return Object.entries(rawBlocks).filter(([name, block]) => typeof name === "string" && block && typeof block === "object").map(([name, block]) => ({
4495
+ name,
4496
+ label: block.label || name,
4497
+ description: block.description,
4498
+ properties: block.properties && typeof block.properties === "object" ? block.properties : {}
4499
+ }));
4500
+ }
4501
+ function normalizeBlocksValue(value, discriminator) {
4502
+ const normalizeItem = (item) => {
4503
+ if (!item || typeof item !== "object") return null;
4504
+ if (item[discriminator]) return item;
4505
+ if (item.blockType && item.data && typeof item.data === "object") {
4506
+ return { [discriminator]: item.blockType, ...item.data };
4507
+ }
4508
+ return item;
4509
+ };
4510
+ const fromArray = (items) => items.map(normalizeItem).filter((item) => item && typeof item === "object");
4511
+ if (Array.isArray(value)) return fromArray(value);
4512
+ if (typeof value === "string" && value.trim()) {
4513
+ try {
4514
+ const parsed = JSON.parse(value);
4515
+ return Array.isArray(parsed) ? fromArray(parsed) : [];
4516
+ } catch {
4517
+ return [];
4518
+ }
4519
+ }
4520
+ return [];
4521
+ }
4522
+ function renderBlockTemplate(field, block, discriminator, pluginStatuses) {
4523
+ return `
4524
+ <template data-block-template="${escapeHtml2(block.name)}">
4525
+ ${renderBlockCard(field, block, discriminator, "__INDEX__", {}, pluginStatuses)}
4526
+ </template>
4527
+ `;
4528
+ }
4529
+ function renderBlockItem(field, blockValue, blocks, discriminator, index, pluginStatuses) {
4530
+ const blockType = blockValue?.[discriminator] || blockValue?.blockType;
4531
+ const blockDefinition = blocks.find((block) => block.name === blockType);
4532
+ if (!blockDefinition) {
4533
+ return `
4534
+ <div class="rounded-lg border border-amber-200 bg-amber-50/50 px-4 py-3 text-sm text-amber-700 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200" data-block-raw="${escapeHtml2(JSON.stringify(blockValue || {}))}">
4535
+ Unknown block type: <strong>${escapeHtml2(String(blockType || "unknown"))}</strong>. This block will be preserved as-is.
4536
+ </div>
4537
+ `;
4538
+ }
4539
+ const data = blockValue && typeof blockValue === "object" ? Object.fromEntries(Object.entries(blockValue).filter(([key]) => key !== discriminator)) : {};
4540
+ return renderBlockCard(field, blockDefinition, discriminator, String(index), data, pluginStatuses);
4541
+ }
4542
+ function renderBlockCard(field, block, discriminator, index, data, pluginStatuses) {
4543
+ const blockFields = Object.entries(block.properties).map(([fieldName, fieldConfig]) => {
4544
+ if (fieldConfig?.type === "array" && fieldConfig?.items?.blocks) {
4545
+ return `
4546
+ <div class="rounded-lg border border-dashed border-amber-200 bg-amber-50/50 px-4 py-3 text-xs text-amber-700 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200">
4547
+ Nested blocks are not supported yet for "${escapeHtml2(fieldName)}".
4548
+ </div>
4549
+ `;
4550
+ }
4551
+ const normalizedField = normalizeBlockField(fieldConfig, fieldName);
4552
+ const fieldValue = data?.[fieldName] ?? normalizedField.defaultValue ?? "";
4553
+ const fieldDefinition = {
4554
+ id: `block-${field.field_name}-${index}-${fieldName}`,
4555
+ field_name: `block-${field.field_name}-${index}-${fieldName}`,
4556
+ field_type: normalizedField.type,
4557
+ field_label: normalizedField.label,
4558
+ field_options: normalizedField.options,
4559
+ is_required: normalizedField.required};
4560
+ return `
4561
+ <div class="blocks-subfield" data-block-field="${escapeHtml2(fieldName)}" data-field-type="${escapeHtml2(normalizedField.type)}">
4562
+ ${renderDynamicField(fieldDefinition, { value: fieldValue, pluginStatuses })}
4563
+ </div>
4564
+ `;
4565
+ }).join("");
4566
+ return `
4567
+ <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="${escapeHtml2(block.name)}" data-block-discriminator="${escapeHtml2(discriminator)}" draggable="true">
4568
+ <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
4569
+ <div class="flex items-start gap-3">
4570
+ <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">
4571
+ <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
4572
+ <path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
4573
+ </svg>
4574
+ </div>
4575
+ <div>
4576
+ <div class="text-sm font-semibold text-zinc-900 dark:text-white">
4577
+ ${escapeHtml2(block.label)}
4578
+ <span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-block-order-label></span>
4579
+ </div>
4580
+ ${block.description ? `<p class="text-xs text-zinc-500 dark:text-zinc-400">${escapeHtml2(block.description)}</p>` : ""}
4581
+ </div>
4582
+ </div>
4583
+ <div class="flex flex-wrap gap-2 text-xs">
4584
+ <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">
4585
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
4586
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
4587
+ </svg>
4588
+ </button>
4589
+ <button type="button" data-action="move-down" 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 down" title="Move down">
4590
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
4591
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 18l4-4m-4 4l-4-4m4 4V6"/>
4592
+ </svg>
4593
+ </button>
4594
+ <button type="button" data-action="remove-block" class="inline-flex items-center gap-x-1 px-2.5 py-1.5 text-xs font-medium text-pink-700 dark:text-pink-300 hover:bg-pink-50 dark:hover:bg-pink-900/20 rounded-lg transition-colors">
4595
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
4596
+ <path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/>
4597
+ </svg>
4598
+ Delete block
4599
+ </button>
4600
+ </div>
4601
+ </div>
4602
+ <div class="mt-4 space-y-4">
4603
+ ${blockFields}
4604
+ </div>
4605
+ </div>
4606
+ `;
4607
+ }
4608
+ function normalizeBlockField(fieldConfig, fieldName) {
4609
+ const type = fieldConfig?.type || "text";
4610
+ const label = fieldConfig?.title || fieldName;
4611
+ const required = fieldConfig?.required === true;
4612
+ const options = { ...fieldConfig };
4613
+ if (type === "select" && Array.isArray(fieldConfig?.enum)) {
4614
+ options.options = fieldConfig.enum.map((value, index) => ({
4615
+ value,
4616
+ label: fieldConfig.enumLabels?.[index] || value
4617
+ }));
4618
+ }
4619
+ return {
4620
+ type,
4621
+ label,
4622
+ required,
4623
+ defaultValue: fieldConfig?.default,
4624
+ options
4625
+ };
4626
+ }
4627
+ function getStructuredFieldScript() {
4628
+ return `
4629
+ ${getReadFieldValueScript()}
4630
+ <script>
4631
+ if (!window.__sonicStructuredFieldInit) {
4632
+ window.__sonicStructuredFieldInit = true;
4633
+
4634
+ function initializeStructuredFields() {
4635
+ const readFieldValue = window.sonicReadFieldValue;
4636
+
4637
+ const readStructuredValue = (container) => {
4638
+ const fields = Array.from(container.querySelectorAll('.structured-subfield'));
4639
+ if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
4640
+ return readFieldValue(fields[0]);
4641
+ }
4642
+
4643
+ return fields.reduce((acc, fieldWrapper) => {
4644
+ const fieldName = fieldWrapper.dataset.structuredField;
4645
+ if (!fieldName || fieldName === '__value') return acc;
4646
+ acc[fieldName] = readFieldValue(fieldWrapper);
4647
+ return acc;
4648
+ }, {});
4649
+ };
4650
+
4651
+ document.querySelectorAll('[data-structured-object]').forEach((container) => {
4652
+ if (container.dataset.structuredInitialized === 'true') {
4653
+ return;
4654
+ }
4655
+ container.dataset.structuredInitialized = 'true';
4656
+ const hiddenInput = container.querySelector('input[type="hidden"]');
4657
+
4658
+ const updateHiddenInput = () => {
4659
+ if (!hiddenInput) return;
4660
+ const value = readStructuredValue(container);
4661
+ hiddenInput.value = JSON.stringify(value);
4662
+ };
4663
+
4664
+ container.addEventListener('input', updateHiddenInput);
4665
+ container.addEventListener('change', updateHiddenInput);
4666
+ updateHiddenInput();
4667
+ });
4668
+
4669
+ document.querySelectorAll('[data-structured-array]').forEach((container) => {
4670
+ if (container.dataset.structuredInitialized === 'true') {
4671
+ return;
4672
+ }
4673
+ container.dataset.structuredInitialized = 'true';
4674
+ const list = container.querySelector('[data-structured-array-list]');
4675
+ const hiddenInput = container.querySelector('input[type="hidden"]');
4676
+ const template = container.querySelector('template[data-structured-array-template]');
4677
+
4678
+ const updateOrderLabels = () => {
4679
+ const items = Array.from(container.querySelectorAll('.structured-array-item'));
4680
+ items.forEach((item, index) => {
4681
+ const label = item.querySelector('[data-array-order-label]');
4682
+ if (label) {
4683
+ label.textContent = '#'+ (index + 1);
4684
+ }
4685
+
4686
+ const moveUpButton = item.querySelector('[data-action="move-up"]');
4687
+ if (moveUpButton instanceof HTMLButtonElement) {
4688
+ moveUpButton.disabled = index === 0;
4689
+ }
4690
+
4691
+ const moveDownButton = item.querySelector('[data-action="move-down"]');
4692
+ if (moveDownButton instanceof HTMLButtonElement) {
4693
+ moveDownButton.disabled = index === items.length - 1;
4694
+ }
4695
+ });
4696
+ };
4697
+
4698
+ const updateHiddenInput = () => {
4699
+ if (!hiddenInput || !list) return;
4700
+ const items = Array.from(list.querySelectorAll('.structured-array-item'));
4701
+ const values = items.map((item) => readStructuredValue(item));
4702
+ hiddenInput.value = JSON.stringify(values);
4703
+
4704
+ const emptyState = list.querySelector('[data-structured-empty]');
4705
+ if (emptyState) {
4706
+ emptyState.style.display = values.length === 0 ? 'block' : 'none';
4707
+ }
4708
+ updateOrderLabels();
4709
+ };
4710
+
4711
+ if (typeof window.initializeDragSortable === 'function' && list) {
4712
+ window.initializeDragSortable(list, {
4713
+ itemSelector: '.structured-array-item',
4714
+ handleSelector: '[data-action="drag-handle"]',
4715
+ onUpdate: updateHiddenInput
4716
+ });
4717
+ }
4718
+
4719
+ container.addEventListener('click', (event) => {
4720
+ const target = event.target;
4721
+ if (!(target instanceof Element)) return;
4722
+ const actionButton = target.closest('[data-action]');
4723
+ if (!actionButton || actionButton.hasAttribute('disabled')) return;
4724
+
4725
+ const action = actionButton.getAttribute('data-action');
4726
+
4727
+ if (action === 'add-item') {
4728
+ if (!list || !template) return;
4729
+ const nextIndex = list.querySelectorAll('.structured-array-item').length;
4730
+ const html = template.innerHTML.replace(/__INDEX__/g, String(nextIndex));
4731
+ list.insertAdjacentHTML('beforeend', html);
4732
+ if (typeof initializeTinyMCE === 'function') {
4733
+ initializeTinyMCE();
4734
+ }
4735
+ if (typeof window.initializeQuillEditors === 'function') {
4736
+ window.initializeQuillEditors();
4737
+ }
4738
+ if (typeof initializeMDXEditor === 'function') {
4739
+ initializeMDXEditor();
4740
+ }
4741
+ updateHiddenInput();
4742
+ return;
4743
+ }
4744
+
4745
+ const item = actionButton.closest('.structured-array-item');
4746
+ if (!item || !list) return;
4747
+
4748
+ if (action === 'remove-item') {
4749
+ item.remove();
4750
+ updateHiddenInput();
4751
+ return;
4752
+ }
4753
+
4754
+ if (action === 'move-up') {
4755
+ const previous = item.previousElementSibling;
4756
+ if (previous) {
4757
+ list.insertBefore(item, previous);
4758
+ updateHiddenInput();
4759
+ }
4760
+ return;
4761
+ }
4762
+
4763
+ if (action === 'move-down') {
4764
+ const next = item.nextElementSibling;
4765
+ if (next) {
4766
+ list.insertBefore(next, item);
4767
+ updateHiddenInput();
4768
+ }
4769
+ }
4770
+ });
4771
+
4772
+ container.addEventListener('input', (event) => {
4773
+ const target = event.target;
4774
+ if (!(target instanceof Element)) return;
4775
+ if (target.closest('[data-structured-array-list]')) {
4776
+ updateHiddenInput();
4777
+ }
4778
+ });
4779
+
4780
+ container.addEventListener('change', (event) => {
4781
+ const target = event.target;
4782
+ if (!(target instanceof Element)) return;
4783
+ if (target.closest('[data-structured-array-list]')) {
4784
+ updateHiddenInput();
4785
+ }
4786
+ });
4787
+
4788
+ updateHiddenInput();
4789
+ });
4790
+ }
4791
+
4792
+ window.initializeStructuredFields = initializeStructuredFields;
4793
+
4794
+ if (document.readyState === 'loading') {
4795
+ document.addEventListener('DOMContentLoaded', initializeStructuredFields);
4796
+ } else {
4797
+ initializeStructuredFields();
4798
+ }
4799
+
4800
+ document.addEventListener('htmx:afterSwap', function() {
4801
+ setTimeout(initializeStructuredFields, 50);
4802
+ });
4803
+ } else if (typeof window.initializeStructuredFields === 'function') {
4804
+ window.initializeStructuredFields();
4805
+ }
4806
+ </script>
4807
+ `;
4808
+ }
4809
+ function getBlocksFieldScript() {
4810
+ return `
4811
+ ${getReadFieldValueScript()}
4812
+ <script>
4813
+ if (!window.__sonicBlocksFieldInit) {
4814
+ window.__sonicBlocksFieldInit = true;
4815
+
4816
+ function initializeBlocksFields() {
4817
+ document.querySelectorAll('.blocks-field').forEach((container) => {
4818
+ if (container.dataset.blocksInitialized === 'true') {
4819
+ return;
4820
+ }
4821
+
4822
+ container.dataset.blocksInitialized = 'true';
4823
+ const list = container.querySelector('[data-blocks-list]');
4824
+ const hiddenInput = container.querySelector('input[type="hidden"]');
4825
+ const typeSelect = container.querySelector('[data-role="block-type-select"]');
4826
+ const discriminator = container.dataset.blocksDiscriminator || 'blockType';
4827
+
4828
+ const updateOrderLabels = () => {
4829
+ const items = Array.from(container.querySelectorAll('.blocks-item'));
4830
+ items.forEach((item, index) => {
4831
+ const label = item.querySelector('[data-block-order-label]');
4832
+ if (label) {
4833
+ label.textContent = '#'+ (index + 1);
4834
+ }
4835
+
4836
+ const moveUpButton = item.querySelector('[data-action="move-up"]');
4837
+ if (moveUpButton instanceof HTMLButtonElement) {
4838
+ moveUpButton.disabled = index === 0;
4839
+ }
4840
+
4841
+ const moveDownButton = item.querySelector('[data-action="move-down"]');
4842
+ if (moveDownButton instanceof HTMLButtonElement) {
4843
+ moveDownButton.disabled = index === items.length - 1;
4844
+ }
4845
+ });
4846
+ };
4847
+
4848
+ const readFieldValue = window.sonicReadFieldValue;
4849
+
4850
+ const readBlockItem = (item) => {
4851
+ if (item.dataset.blockRaw) {
4852
+ try {
4853
+ return JSON.parse(item.dataset.blockRaw);
4854
+ } catch (error) {
4855
+ return {};
4856
+ }
4857
+ }
4858
+
4859
+ const blockType = item.dataset.blockType;
4860
+ const data = {};
4861
+
4862
+ item.querySelectorAll('.blocks-subfield').forEach((fieldWrapper) => {
4863
+ const fieldName = fieldWrapper.dataset.blockField;
4864
+ if (!fieldName) {
4865
+ return;
4866
+ }
4867
+ data[fieldName] = readFieldValue(fieldWrapper);
4868
+ });
4869
+
4870
+ return { [discriminator]: blockType, ...data };
4871
+ };
4872
+
4873
+ const updateHiddenInput = () => {
4874
+ if (!hiddenInput || !list) return;
4875
+ const items = Array.from(list.querySelectorAll('.blocks-item, [data-block-raw]'));
4876
+ const blocksData = items.map((item) => readBlockItem(item));
4877
+ hiddenInput.value = JSON.stringify(blocksData);
4878
+
4879
+ const emptyState = list.querySelector('[data-blocks-empty]');
4880
+ if (emptyState) {
4881
+ emptyState.style.display = blocksData.length === 0 ? 'block' : 'none';
4882
+ }
4883
+ updateOrderLabels();
4884
+ };
4885
+
4886
+ const initializeEditors = () => {
4887
+ if (typeof initializeTinyMCE === 'function') {
4888
+ initializeTinyMCE();
4889
+ }
4890
+ if (typeof window.initializeQuillEditors === 'function') {
4891
+ window.initializeQuillEditors();
4892
+ }
4893
+ if (typeof initializeMDXEditor === 'function') {
4894
+ initializeMDXEditor();
4895
+ }
4896
+ };
4897
+
4898
+ if (typeof window.initializeDragSortable === 'function' && list) {
4899
+ window.initializeDragSortable(list, {
4900
+ itemSelector: '.blocks-item',
4901
+ handleSelector: '[data-action="drag-handle"]',
4902
+ onUpdate: updateHiddenInput
4903
+ });
4904
+ }
4905
+
4906
+ container.addEventListener('click', (event) => {
4907
+ const target = event.target;
4908
+ if (!(target instanceof Element)) return;
4909
+ const actionButton = target.closest('[data-action]');
4910
+ if (!actionButton) return;
4911
+
4912
+ if (actionButton.hasAttribute('disabled')) {
4913
+ return;
4914
+ }
4915
+
4916
+ const action = actionButton.getAttribute('data-action');
4917
+ if (action === 'add-block') {
4918
+ const blockType = typeSelect ? typeSelect.value : '';
4919
+ if (!blockType || !list) return;
4920
+ const template = container.querySelector('template[data-block-template="' + blockType + '"]');
4921
+ if (!template) return;
4922
+
4923
+ const nextIndex = list.querySelectorAll('.blocks-item').length;
4924
+ const html = template.innerHTML.replace(/__INDEX__/g, String(nextIndex));
4925
+ list.insertAdjacentHTML('beforeend', html);
4926
+ if (typeSelect) {
4927
+ typeSelect.value = '';
4928
+ }
4929
+ initializeEditors();
4930
+ if (typeof window.initializeStructuredFields === 'function') {
4931
+ window.initializeStructuredFields();
4932
+ }
4933
+ updateHiddenInput();
4934
+ return;
4935
+ }
4936
+
4937
+ const item = actionButton.closest('.blocks-item');
4938
+ if (!item || !list) return;
4939
+
4940
+ if (action === 'remove-block') {
4941
+ item.remove();
4942
+ updateHiddenInput();
4943
+ return;
4944
+ }
4945
+
4946
+ if (action === 'move-up') {
4947
+ const previous = item.previousElementSibling;
4948
+ if (previous) {
4949
+ list.insertBefore(item, previous);
4950
+ updateHiddenInput();
4951
+ }
4952
+ return;
4953
+ }
4954
+
4955
+ if (action === 'move-down') {
4956
+ const next = item.nextElementSibling;
4957
+ if (next) {
4958
+ list.insertBefore(next, item);
4959
+ updateHiddenInput();
4960
+ }
4961
+ }
4962
+ });
4963
+
4964
+ container.addEventListener('input', (event) => {
4965
+ const target = event.target;
4966
+ if (!(target instanceof Element)) return;
4967
+ if (target.closest('[data-blocks-list]')) {
4968
+ updateHiddenInput();
4969
+ }
4970
+ });
4971
+
4972
+ container.addEventListener('change', (event) => {
4973
+ const target = event.target;
4974
+ if (!(target instanceof Element)) return;
4975
+ if (target.closest('[data-blocks-list]')) {
4976
+ updateHiddenInput();
4977
+ }
4978
+ });
4979
+
4980
+ updateHiddenInput();
4981
+ });
4982
+ }
4983
+
4984
+ window.initializeBlocksFields = initializeBlocksFields;
4985
+
4986
+ if (document.readyState === 'loading') {
4987
+ document.addEventListener('DOMContentLoaded', initializeBlocksFields);
4988
+ } else {
4989
+ initializeBlocksFields();
4990
+ }
4991
+
4992
+ document.addEventListener('htmx:afterSwap', function() {
4993
+ setTimeout(initializeBlocksFields, 50);
4994
+ });
4995
+ } else if (typeof window.initializeBlocksFields === 'function') {
4996
+ window.initializeBlocksFields();
4997
+ }
4998
+ </script>
4999
+ `;
5000
+ }
5001
+ function escapeHtml2(text) {
5002
+ if (typeof text !== "string") return String(text || "");
5003
+ return text.replace(/[&<>"']/g, (char) => ({
5004
+ "&": "&amp;",
5005
+ "<": "&lt;",
5006
+ ">": "&gt;",
5007
+ '"': "&quot;",
5008
+ "'": "&#39;"
5009
+ })[char] || char);
5010
+ }
5011
+
5012
+ // src/plugins/available/tinymce-plugin/index.ts
5013
+ var builder = chunkYHW27CBV_cjs.PluginBuilder.create({
5014
+ name: "tinymce-plugin",
5015
+ version: "1.0.0",
5016
+ description: "Powerful WYSIWYG rich text editor for content creation"
5017
+ });
5018
+ builder.metadata({
5019
+ author: {
5020
+ name: "SonicJS Team",
5021
+ email: "team@sonicjs.com"
5022
+ },
5023
+ license: "MIT",
5024
+ compatibility: "^2.0.0"
5025
+ });
5026
+ builder.lifecycle({
5027
+ activate: async () => {
5028
+ console.info("\u2705 TinyMCE plugin activated");
5029
+ },
5030
+ deactivate: async () => {
5031
+ console.info("\u274C TinyMCE plugin deactivated");
5032
+ }
5033
+ });
5034
+ builder.build();
5035
+ function getTinyMCEScript(apiKey = "no-api-key") {
5036
+ return `<script src="https://cdn.tiny.cloud/1/${apiKey}/tinymce/6/tinymce.min.js" referrerpolicy="origin"></script>`;
5037
+ }
5038
+ function getTinyMCEInitScript(config) {
5039
+ const skin = config?.skin || "oxide-dark";
5040
+ const contentCss = skin.includes("dark") ? "dark" : "default";
5041
+ const defaultHeight = config?.defaultHeight || 300;
5042
+ return `
5043
+ // Initialize TinyMCE for all richtext fields
5044
+ function initializeTinyMCE() {
5045
+ if (typeof tinymce !== 'undefined') {
5046
+ // Find all textareas that need TinyMCE
5047
+ document.querySelectorAll('.richtext-container textarea').forEach((textarea) => {
5048
+ // Skip if already initialized
5049
+ if (tinymce.get(textarea.id)) {
5050
+ return;
5051
+ }
5052
+
5053
+ // Get configuration from data attributes
5054
+ const container = textarea.closest('.richtext-container');
5055
+ const height = container?.dataset.height || ${defaultHeight};
5056
+ const toolbar = container?.dataset.toolbar || 'full';
5057
+
5058
+ tinymce.init({
5059
+ selector: '#' + textarea.id,
5060
+ skin: '${skin}',
5061
+ content_css: '${contentCss}',
5062
+ height: parseInt(height),
5063
+ menubar: false,
5064
+ plugins: [
5065
+ 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
5066
+ 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
5067
+ 'insertdatetime', 'media', 'table', 'help', 'wordcount'
5068
+ ],
5069
+ toolbar: toolbar === 'simple'
5070
+ ? 'bold italic underline | bullist numlist | link'
5071
+ : toolbar === 'minimal'
4031
5072
  ? 'bold italic | link'
4032
5073
  : 'undo redo | blocks | bold italic forecolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help',
4033
5074
  content_style: 'body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; font-size: 14px }'
@@ -4252,7 +5293,7 @@ function getQuillCDN(version = "2.0.2") {
4252
5293
  `;
4253
5294
  }
4254
5295
  function createQuillEditorPlugin() {
4255
- const builder3 = chunkAI2JJIJX_cjs.PluginBuilder.create({
5296
+ const builder3 = chunkYHW27CBV_cjs.PluginBuilder.create({
4256
5297
  name: "quill-editor",
4257
5298
  version: "1.0.0",
4258
5299
  description: "Quill rich text editor integration for SonicJS"
@@ -4278,7 +5319,7 @@ function createQuillEditorPlugin() {
4278
5319
  createQuillEditorPlugin();
4279
5320
 
4280
5321
  // src/plugins/available/easy-mdx/index.ts
4281
- var builder2 = chunkAI2JJIJX_cjs.PluginBuilder.create({
5322
+ var builder2 = chunkYHW27CBV_cjs.PluginBuilder.create({
4282
5323
  name: "easy-mdx",
4283
5324
  version: "1.0.0",
4284
5325
  description: "Lightweight markdown editor with live preview"
@@ -4569,8 +5610,8 @@ function renderContentFormPage(data) {
4569
5610
  <!-- Form Content -->
4570
5611
  <div class="px-6 py-6">
4571
5612
  <div id="form-messages">
4572
- ${data.error ? chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
4573
- ${data.success ? chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
5613
+ ${data.error ? chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
5614
+ ${data.success ? chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
4574
5615
  </div>
4575
5616
 
4576
5617
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
@@ -4805,7 +5846,7 @@ function renderContentFormPage(data) {
4805
5846
  </div>
4806
5847
 
4807
5848
  <!-- Confirmation Dialogs -->
4808
- ${chunkAZLU3ROK_cjs.renderConfirmationDialog({
5849
+ ${chunkBZC4FYW7_cjs.renderConfirmationDialog({
4809
5850
  id: "duplicate-content-confirm",
4810
5851
  title: "Duplicate Content",
4811
5852
  message: "Create a copy of this content?",
@@ -4816,7 +5857,7 @@ function renderContentFormPage(data) {
4816
5857
  onConfirm: "performDuplicateContent()"
4817
5858
  })}
4818
5859
 
4819
- ${chunkAZLU3ROK_cjs.renderConfirmationDialog({
5860
+ ${chunkBZC4FYW7_cjs.renderConfirmationDialog({
4820
5861
  id: "delete-content-confirm",
4821
5862
  title: "Delete Content",
4822
5863
  message: "Are you sure you want to delete this content? This action cannot be undone.",
@@ -4827,7 +5868,7 @@ function renderContentFormPage(data) {
4827
5868
  onConfirm: `performDeleteContent('${data.id}')`
4828
5869
  })}
4829
5870
 
4830
- ${chunkAZLU3ROK_cjs.getConfirmationDialogScript()}
5871
+ ${chunkBZC4FYW7_cjs.getConfirmationDialogScript()}
4831
5872
 
4832
5873
  ${data.tinymceEnabled ? getTinyMCEScript(data.tinymceSettings?.apiKey) : "<!-- TinyMCE plugin not active -->"}
4833
5874
 
@@ -4898,86 +5939,386 @@ function renderContentFormPage(data) {
4898
5939
  currentMediaFieldId = null;
4899
5940
  }
4900
5941
 
4901
- function cancelMediaSelection(fieldId, originalValue) {
4902
- // Restore original value
4903
- const hiddenInput = document.getElementById(fieldId);
4904
- if (hiddenInput) {
4905
- hiddenInput.value = originalValue;
4906
- }
5942
+ function cancelMediaSelection(fieldId, originalValue) {
5943
+ // Restore original value
5944
+ const hiddenInput = document.getElementById(fieldId);
5945
+ if (hiddenInput) {
5946
+ hiddenInput.value = originalValue;
5947
+ }
5948
+
5949
+ // If original value was empty, hide the preview and show select button
5950
+ if (!originalValue) {
5951
+ const preview = document.getElementById(fieldId + '-preview');
5952
+ if (preview) {
5953
+ preview.classList.add('hidden');
5954
+ }
5955
+ }
5956
+
5957
+ // Close modal
5958
+ closeMediaSelector();
5959
+ }
5960
+
5961
+ function clearMediaField(fieldId) {
5962
+ const hiddenInput = document.getElementById(fieldId);
5963
+ const preview = document.getElementById(fieldId + '-preview');
5964
+
5965
+ if (hiddenInput) {
5966
+ hiddenInput.value = '';
5967
+ }
5968
+
5969
+ if (preview) {
5970
+ // Clear all children if it's a grid, or hide it
5971
+ if (preview.classList.contains('media-preview-grid')) {
5972
+ preview.innerHTML = '';
5973
+ }
5974
+ preview.classList.add('hidden');
5975
+ }
5976
+ }
5977
+
5978
+ // Global function to remove a single media from multiple selection
5979
+ window.removeMediaFromMultiple = function(fieldId, urlToRemove) {
5980
+ const hiddenInput = document.getElementById(fieldId);
5981
+ if (!hiddenInput) return;
5982
+
5983
+ const values = hiddenInput.value.split(',').filter(url => url !== urlToRemove);
5984
+ hiddenInput.value = values.join(',');
5985
+
5986
+ // Remove preview item
5987
+ const previewItem = document.querySelector(\`[data-url="\${urlToRemove}"]\`);
5988
+ if (previewItem) {
5989
+ previewItem.remove();
5990
+ }
5991
+
5992
+ // Hide preview grid if empty
5993
+ if (values.length === 0) {
5994
+ const preview = document.getElementById(fieldId + '-preview');
5995
+ if (preview) {
5996
+ preview.classList.add('hidden');
5997
+ }
5998
+ }
5999
+ };
6000
+
6001
+ // Global function called by media selector buttons
6002
+ window.selectMediaFile = function(mediaId, mediaUrl, filename) {
6003
+ if (!currentMediaFieldId) {
6004
+ console.error('No field ID set for media selection');
6005
+ return;
6006
+ }
6007
+
6008
+ const fieldId = currentMediaFieldId;
6009
+
6010
+ // Set the hidden input value to the media URL (not ID)
6011
+ const hiddenInput = document.getElementById(fieldId);
6012
+ if (hiddenInput) {
6013
+ hiddenInput.value = mediaUrl;
6014
+ }
6015
+
6016
+ // Update the preview
6017
+ const preview = document.getElementById(fieldId + '-preview');
6018
+ if (preview) {
6019
+ preview.innerHTML = \`<img src="\${mediaUrl}" alt="\${filename}" class="w-32 h-32 object-cover rounded-lg border border-white/20">\`;
6020
+ preview.classList.remove('hidden');
6021
+ }
6022
+
6023
+ // Show the remove button by finding the media actions container and updating it
6024
+ const mediaField = hiddenInput?.closest('.media-field-container');
6025
+ if (mediaField) {
6026
+ const actionsDiv = mediaField.querySelector('.media-actions');
6027
+ if (actionsDiv && !actionsDiv.querySelector('button:has-text("Remove")')) {
6028
+ const removeBtn = document.createElement('button');
6029
+ removeBtn.type = 'button';
6030
+ removeBtn.onclick = () => clearMediaField(fieldId);
6031
+ removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
6032
+ removeBtn.textContent = 'Remove';
6033
+ actionsDiv.appendChild(removeBtn);
6034
+ }
6035
+ }
6036
+
6037
+ // DON'T close the modal - let user click OK button
6038
+ // Visual feedback: highlight the selected item
6039
+ document.querySelectorAll('#media-selector-grid [data-media-id]').forEach(el => {
6040
+ el.classList.remove('ring-2', 'ring-lime-500', 'dark:ring-lime-400');
6041
+ });
6042
+ const selectedItem = document.querySelector(\`#media-selector-grid [data-media-id="\${mediaId}"]\`);
6043
+ if (selectedItem) {
6044
+ selectedItem.classList.add('ring-2', 'ring-lime-500', 'dark:ring-lime-400');
6045
+ }
6046
+ };
6047
+
6048
+ function setMediaField(fieldId, mediaUrl) {
6049
+ document.getElementById(fieldId).value = mediaUrl;
6050
+ const preview = document.getElementById(fieldId + '-preview');
6051
+ 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">\`;
6052
+ preview.classList.remove('hidden');
6053
+
6054
+ // Close modal
6055
+ document.querySelector('.fixed.inset-0')?.remove();
6056
+ }
6057
+
6058
+ // Reference field functions
6059
+ let currentReferenceFieldId = null;
6060
+ let referenceSearchTimeout = null;
6061
+
6062
+ function getReferenceContainer(fieldId) {
6063
+ const input = document.getElementById(fieldId);
6064
+ return input ? input.closest('[data-reference-field]') : null;
6065
+ }
6066
+
6067
+ function getReferenceCollections(container) {
6068
+ if (!container) return [];
6069
+ const rawCollections = container.dataset.referenceCollections || '';
6070
+ const collections = rawCollections
6071
+ .split(',')
6072
+ .map((value) => value.trim())
6073
+ .filter(Boolean);
6074
+ if (collections.length > 0) {
6075
+ return collections;
6076
+ }
6077
+ const singleCollection = container.dataset.referenceCollection;
6078
+ return singleCollection ? [singleCollection] : [];
6079
+ }
6080
+
6081
+ async function fetchReferenceItems(collections, search = '', limit = 20) {
6082
+ const params = new URLSearchParams({ limit: String(limit) });
6083
+ collections.forEach((collection) => params.append('collection', collection));
6084
+ if (search) {
6085
+ params.set('search', search);
6086
+ }
6087
+ const response = await fetch('/admin/api/references?' + params.toString());
6088
+ if (!response.ok) {
6089
+ throw new Error('Failed to load references');
6090
+ }
6091
+ const data = await response.json();
6092
+ return data?.data || [];
6093
+ }
6094
+
6095
+ async function fetchReferenceById(collections, id) {
6096
+ if (!id) return null;
6097
+ const params = new URLSearchParams({ id });
6098
+ collections.forEach((collection) => params.append('collection', collection));
6099
+ const response = await fetch('/admin/api/references?' + params.toString());
6100
+ if (!response.ok) {
6101
+ return null;
6102
+ }
6103
+ const data = await response.json();
6104
+ return data?.data || null;
6105
+ }
6106
+
6107
+ function renderReferenceDisplay(container, item, fallbackMessage = 'No reference selected.') {
6108
+ const display = container.querySelector('[data-reference-display]');
6109
+ const removeButton = container.querySelector('[data-reference-clear]');
6110
+ if (!display) return;
6111
+
6112
+ display.innerHTML = '';
6113
+
6114
+ if (!item) {
6115
+ display.textContent = fallbackMessage;
6116
+ if (removeButton) {
6117
+ removeButton.disabled = true;
6118
+ }
6119
+ return;
6120
+ }
6121
+
6122
+ const title = item.title || item.slug || item.id || 'Untitled';
6123
+ const titleEl = document.createElement('div');
6124
+ titleEl.className = 'font-medium text-zinc-900 dark:text-white';
6125
+ titleEl.textContent = title;
6126
+
6127
+ display.appendChild(titleEl);
6128
+
6129
+ const metaRow = document.createElement('div');
6130
+ metaRow.className = 'mt-1 flex flex-wrap items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400';
6131
+
6132
+ if (item.collection?.display_name || item.collection?.name) {
6133
+ const collectionLabel = document.createElement('span');
6134
+ collectionLabel.className = 'inline-flex items-center rounded-full bg-zinc-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-zinc-600 dark:bg-white/10 dark:text-zinc-200';
6135
+ collectionLabel.textContent = item.collection.display_name || item.collection.name;
6136
+ metaRow.appendChild(collectionLabel);
6137
+ }
6138
+
6139
+ if (item.slug) {
6140
+ const slugEl = document.createElement('span');
6141
+ slugEl.textContent = item.slug;
6142
+ metaRow.appendChild(slugEl);
6143
+ }
6144
+
6145
+ if (metaRow.childElementCount > 0) {
6146
+ display.appendChild(metaRow);
6147
+ }
6148
+
6149
+ if (removeButton) {
6150
+ removeButton.disabled = false;
6151
+ }
6152
+ }
6153
+
6154
+ function updateReferenceField(fieldId, item) {
6155
+ const input = document.getElementById(fieldId);
6156
+ const container = getReferenceContainer(fieldId);
6157
+ if (!input || !container) return;
6158
+
6159
+ input.value = item?.id || '';
6160
+ renderReferenceDisplay(container, item, 'No reference selected.');
6161
+ input.dispatchEvent(new Event('input', { bubbles: true }));
6162
+ input.dispatchEvent(new Event('change', { bubbles: true }));
6163
+ }
6164
+
6165
+ function clearReferenceField(fieldId) {
6166
+ updateReferenceField(fieldId, null);
6167
+ }
6168
+
6169
+ function closeReferenceSelector() {
6170
+ const modal = document.getElementById('reference-selector-modal');
6171
+ if (modal) {
6172
+ modal.remove();
6173
+ }
6174
+ currentReferenceFieldId = null;
6175
+ }
6176
+
6177
+ function openReferenceSelector(fieldId) {
6178
+ const container = getReferenceContainer(fieldId);
6179
+ const collections = getReferenceCollections(container);
6180
+ if (!container || collections.length === 0) {
6181
+ console.error('Reference collection is missing for field', fieldId);
6182
+ return;
6183
+ }
6184
+
6185
+ currentReferenceFieldId = fieldId;
6186
+
6187
+ const modal = document.createElement('div');
6188
+ modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50';
6189
+ modal.id = 'reference-selector-modal';
6190
+ modal.innerHTML = \`
6191
+ <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-3xl max-h-[90vh] overflow-y-auto">
6192
+ <div class="flex items-center justify-between gap-3">
6193
+ <h3 class="text-lg font-semibold text-zinc-950 dark:text-white">Select Reference</h3>
6194
+ <button
6195
+ type="button"
6196
+ onclick="closeReferenceSelector()"
6197
+ class="rounded-md text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"
6198
+ aria-label="Close"
6199
+ >
6200
+ \u2715
6201
+ </button>
6202
+ </div>
6203
+ <div class="mt-4">
6204
+ <input
6205
+ type="search"
6206
+ id="reference-search-input"
6207
+ placeholder="Search by title or slug..."
6208
+ class="w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 dark:border-white/10 dark:bg-zinc-900 dark:text-white"
6209
+ >
6210
+ </div>
6211
+ <div id="reference-results" class="mt-4 space-y-2"></div>
6212
+ <div class="mt-4 flex justify-end">
6213
+ <button
6214
+ type="button"
6215
+ onclick="closeReferenceSelector()"
6216
+ class="rounded-lg bg-zinc-950 dark:bg-white px-4 py-2 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors"
6217
+ >
6218
+ Close
6219
+ </button>
6220
+ </div>
6221
+ </div>
6222
+ \`;
6223
+
6224
+ document.body.appendChild(modal);
4907
6225
 
4908
- // If original value was empty, hide the preview and show select button
4909
- if (!originalValue) {
4910
- const preview = document.getElementById(fieldId + '-preview');
4911
- if (preview) {
4912
- preview.classList.add('hidden');
6226
+ const resultsContainer = modal.querySelector('#reference-results');
6227
+ const searchInput = modal.querySelector('#reference-search-input');
6228
+
6229
+ const renderResults = (items) => {
6230
+ resultsContainer.innerHTML = '';
6231
+ if (!items || items.length === 0) {
6232
+ resultsContainer.innerHTML = '<div class="rounded-lg border border-dashed border-zinc-200 p-4 text-sm text-zinc-500 dark:border-white/10 dark:text-zinc-400">No items found.</div>';
6233
+ return;
4913
6234
  }
4914
- }
4915
6235
 
4916
- // Close modal
4917
- closeMediaSelector();
4918
- }
6236
+ const selectedId = document.getElementById(fieldId)?.value;
4919
6237
 
4920
- function clearMediaField(fieldId) {
4921
- document.getElementById(fieldId).value = '';
4922
- document.getElementById(fieldId + '-preview').classList.add('hidden');
4923
- }
6238
+ items.forEach((item) => {
6239
+ const button = document.createElement('button');
6240
+ button.type = 'button';
6241
+ button.className = 'w-full text-left rounded-lg border border-zinc-200 px-4 py-3 text-sm text-zinc-700 hover:bg-zinc-50 dark:border-white/10 dark:text-zinc-200 dark:hover:bg-white/5';
6242
+ if (item.id === selectedId) {
6243
+ button.classList.add('ring-2', 'ring-cyan-500', 'dark:ring-cyan-400');
6244
+ }
4924
6245
 
4925
- // Global function called by media selector buttons
4926
- window.selectMediaFile = function(mediaId, mediaUrl, filename) {
4927
- if (!currentMediaFieldId) {
4928
- console.error('No field ID set for media selection');
4929
- return;
4930
- }
6246
+ const title = item.title || item.slug || item.id || 'Untitled';
6247
+ const titleEl = document.createElement('div');
6248
+ titleEl.className = 'font-medium text-zinc-900 dark:text-white';
6249
+ titleEl.textContent = title;
4931
6250
 
4932
- const fieldId = currentMediaFieldId;
6251
+ button.appendChild(titleEl);
4933
6252
 
4934
- // Set the hidden input value to the media URL (not ID)
4935
- const hiddenInput = document.getElementById(fieldId);
4936
- if (hiddenInput) {
4937
- hiddenInput.value = mediaUrl;
4938
- }
6253
+ const metaRow = document.createElement('div');
6254
+ metaRow.className = 'mt-1 flex flex-wrap items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400';
4939
6255
 
4940
- // Update the preview
4941
- const preview = document.getElementById(fieldId + '-preview');
4942
- if (preview) {
4943
- preview.innerHTML = \`<img src="\${mediaUrl}" alt="\${filename}" class="w-32 h-32 object-cover rounded-lg border border-white/20">\`;
4944
- preview.classList.remove('hidden');
4945
- }
6256
+ if (item.collection?.display_name || item.collection?.name) {
6257
+ const collectionLabel = document.createElement('span');
6258
+ collectionLabel.className = 'inline-flex items-center rounded-full bg-zinc-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-zinc-600 dark:bg-white/10 dark:text-zinc-200';
6259
+ collectionLabel.textContent = item.collection.display_name || item.collection.name;
6260
+ metaRow.appendChild(collectionLabel);
6261
+ }
4946
6262
 
4947
- // Show the remove button by finding the media actions container and updating it
4948
- const mediaField = hiddenInput?.closest('.media-field-container');
4949
- if (mediaField) {
4950
- const actionsDiv = mediaField.querySelector('.media-actions');
4951
- if (actionsDiv && !actionsDiv.querySelector('button:has-text("Remove")')) {
4952
- const removeBtn = document.createElement('button');
4953
- removeBtn.type = 'button';
4954
- removeBtn.onclick = () => clearMediaField(fieldId);
4955
- removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
4956
- removeBtn.textContent = 'Remove';
4957
- actionsDiv.appendChild(removeBtn);
6263
+ if (item.slug) {
6264
+ const slugEl = document.createElement('span');
6265
+ slugEl.textContent = item.slug;
6266
+ metaRow.appendChild(slugEl);
6267
+ }
6268
+
6269
+ if (metaRow.childElementCount > 0) {
6270
+ button.appendChild(metaRow);
6271
+ }
6272
+
6273
+ button.addEventListener('click', () => {
6274
+ updateReferenceField(fieldId, item);
6275
+ closeReferenceSelector();
6276
+ });
6277
+
6278
+ resultsContainer.appendChild(button);
6279
+ });
6280
+ };
6281
+
6282
+ const loadResults = async (searchValue = '') => {
6283
+ try {
6284
+ const items = await fetchReferenceItems(collections, searchValue);
6285
+ renderResults(items);
6286
+ } catch (error) {
6287
+ resultsContainer.innerHTML = '<div class="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-700 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200">Failed to load references.</div>';
4958
6288
  }
4959
- }
6289
+ };
4960
6290
 
4961
- // DON'T close the modal - let user click OK button
4962
- // Visual feedback: highlight the selected item
4963
- document.querySelectorAll('#media-selector-grid [data-media-id]').forEach(el => {
4964
- el.classList.remove('ring-2', 'ring-lime-500', 'dark:ring-lime-400');
6291
+ loadResults();
6292
+
6293
+ searchInput.addEventListener('input', () => {
6294
+ if (referenceSearchTimeout) {
6295
+ clearTimeout(referenceSearchTimeout);
6296
+ }
6297
+ referenceSearchTimeout = setTimeout(() => {
6298
+ loadResults(searchInput.value.trim());
6299
+ }, 250);
4965
6300
  });
4966
- const selectedItem = document.querySelector(\`#media-selector-grid [data-media-id="\${mediaId}"]\`);
4967
- if (selectedItem) {
4968
- selectedItem.classList.add('ring-2', 'ring-lime-500', 'dark:ring-lime-400');
4969
- }
4970
- };
6301
+ }
4971
6302
 
4972
- function setMediaField(fieldId, mediaUrl) {
4973
- document.getElementById(fieldId).value = mediaUrl;
4974
- const preview = document.getElementById(fieldId + '-preview');
4975
- 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">\`;
4976
- preview.classList.remove('hidden');
6303
+ document.addEventListener('DOMContentLoaded', () => {
6304
+ document.querySelectorAll('[data-reference-field]').forEach(async (container) => {
6305
+ const input = container.querySelector('input[type="hidden"]');
6306
+ const collections = getReferenceCollections(container);
6307
+ if (!input || collections.length === 0) return;
4977
6308
 
4978
- // Close modal
4979
- document.querySelector('.fixed.inset-0')?.remove();
4980
- }
6309
+ if (!input.value) {
6310
+ renderReferenceDisplay(container, null, 'No reference selected.');
6311
+ return;
6312
+ }
6313
+
6314
+ const item = await fetchReferenceById(collections, input.value);
6315
+ if (item) {
6316
+ renderReferenceDisplay(container, item);
6317
+ } else {
6318
+ renderReferenceDisplay(container, null, 'Reference not found.');
6319
+ }
6320
+ });
6321
+ });
4981
6322
 
4982
6323
  // Custom select options
4983
6324
  function addCustomOption(input, selectId) {
@@ -5132,11 +6473,11 @@ function renderContentFormPage(data) {
5132
6473
  content: pageContent,
5133
6474
  version: data.version
5134
6475
  };
5135
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
6476
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
5136
6477
  }
5137
6478
 
5138
6479
  // src/templates/pages/admin-content-list.template.ts
5139
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
6480
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
5140
6481
  function renderContentListPage(data) {
5141
6482
  const urlParams = new URLSearchParams();
5142
6483
  if (data.modelName && data.modelName !== "all") urlParams.set("model", data.modelName);
@@ -5144,7 +6485,7 @@ function renderContentListPage(data) {
5144
6485
  if (data.search) urlParams.set("search", data.search);
5145
6486
  if (data.page && data.page !== 1) urlParams.set("page", data.page.toString());
5146
6487
  const currentParams = urlParams.toString();
5147
- const hasActiveFilters = data.modelName !== "all" || data.status !== "all" || !!data.search;
6488
+ data.modelName !== "all" || data.status !== "all" || !!data.search;
5148
6489
  const filterBarData = {
5149
6490
  filters: [
5150
6491
  {
@@ -5174,6 +6515,11 @@ function renderContentListPage(data) {
5174
6515
  }
5175
6516
  ],
5176
6517
  actions: [
6518
+ {
6519
+ label: "Advanced Search",
6520
+ className: "btn-primary",
6521
+ onclick: "openAdvancedSearch()"
6522
+ },
5177
6523
  {
5178
6524
  label: "Refresh",
5179
6525
  className: "btn-secondary",
@@ -5325,12 +6671,57 @@ function renderContentListPage(data) {
5325
6671
  <div class="relative bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 rounded-xl">
5326
6672
  <div class="px-6 py-5">
5327
6673
  <div class="flex items-center justify-between">
5328
- <div class="flex items-center space-x-4">
5329
- <!-- Search Input -->
6674
+ <div class="flex items-center space-x-4 flex-1">
6675
+ <!-- Model Filter -->
6676
+ <div>
6677
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Model</label>
6678
+ <div class="grid grid-cols-1">
6679
+ <select
6680
+ name="model"
6681
+ onchange="updateContentFilters('model', this.value)"
6682
+ class="col-start-1 row-start-1 w-full appearance-none rounded-lg bg-white/5 dark:bg-white/5 py-2 pl-3 pr-8 text-sm text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-cyan-500/30 dark:outline-cyan-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-cyan-500 dark:focus-visible:outline-cyan-400 min-w-40"
6683
+ >
6684
+ <option value="all" ${data.modelName === "all" ? "selected" : ""}>All Models</option>
6685
+ ${data.models.map((model) => `
6686
+ <option value="${model.name}" ${data.modelName === model.name ? "selected" : ""}>
6687
+ ${model.displayName}
6688
+ </option>
6689
+ `).join("")}
6690
+ </select>
6691
+ <svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-cyan-600 dark:text-cyan-400 sm:size-4">
6692
+ <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" />
6693
+ </svg>
6694
+ </div>
6695
+ </div>
6696
+
6697
+ <!-- Status Filter -->
5330
6698
  <div>
6699
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Status</label>
6700
+ <div class="grid grid-cols-1">
6701
+ <select
6702
+ name="status"
6703
+ onchange="updateContentFilters('status', this.value)"
6704
+ class="col-start-1 row-start-1 w-full appearance-none rounded-lg bg-white/5 dark:bg-white/5 py-2 pl-3 pr-8 text-sm text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-cyan-500/30 dark:outline-cyan-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-cyan-500 dark:focus-visible:outline-cyan-400 min-w-40"
6705
+ >
6706
+ <option value="all" ${data.status === "all" ? "selected" : ""}>All Status</option>
6707
+ <option value="draft" ${data.status === "draft" ? "selected" : ""}>Draft</option>
6708
+ <option value="review" ${data.status === "review" ? "selected" : ""}>Under Review</option>
6709
+ <option value="scheduled" ${data.status === "scheduled" ? "selected" : ""}>Scheduled</option>
6710
+ <option value="published" ${data.status === "published" ? "selected" : ""}>Published</option>
6711
+ <option value="archived" ${data.status === "archived" ? "selected" : ""}>Archived</option>
6712
+ <option value="deleted" ${data.status === "deleted" ? "selected" : ""}>Deleted</option>
6713
+ </select>
6714
+ <svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-cyan-600 dark:text-cyan-400 sm:size-4">
6715
+ <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" />
6716
+ </svg>
6717
+ </div>
6718
+ </div>
6719
+
6720
+ <!-- Search Input -->
6721
+ <div class="flex-1 max-w-md">
5331
6722
  <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search</label>
5332
6723
  <form onsubmit="performContentSearch(event)" class="flex items-center space-x-2">
5333
- <div class="relative group">
6724
+ <div class="relative group flex-1">
5334
6725
  <input
5335
6726
  type="text"
5336
6727
  name="search"
@@ -5338,7 +6729,7 @@ function renderContentListPage(data) {
5338
6729
  value="${data.search || ""}"
5339
6730
  oninput="toggleContentClearButton()"
5340
6731
  placeholder="Search content..."
5341
- class="rounded-full bg-white/90 dark:bg-zinc-800/90 backdrop-blur-sm px-4 py-2.5 pl-11 pr-10 text-sm w-72 text-zinc-950 dark:text-white border-2 border-cyan-200/50 dark:border-cyan-700/50 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:border-cyan-500 dark:focus:border-cyan-400 focus:bg-white dark:focus:bg-zinc-800 focus:shadow-lg focus:shadow-cyan-500/20 dark:focus:shadow-cyan-400/20 transition-all duration-300"
6732
+ class="w-full rounded-full bg-white/90 dark:bg-zinc-800/90 backdrop-blur-sm px-4 py-2.5 pl-11 pr-10 text-sm text-zinc-950 dark:text-white border-2 border-cyan-200/50 dark:border-cyan-700/50 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:border-cyan-500 dark:focus:border-cyan-400 focus:bg-white dark:focus:bg-zinc-800 focus:shadow-lg focus:shadow-cyan-500/20 dark:focus:shadow-cyan-400/20 transition-all duration-300"
5342
6733
  >
5343
6734
  <div class="absolute left-3.5 top-2.5 flex items-center justify-center w-5 h-5 rounded-full bg-gradient-to-br from-cyan-400 to-blue-500 dark:from-cyan-300 dark:to-blue-400 opacity-90 group-focus-within:opacity-100 transition-opacity">
5344
6735
  <svg class="h-3 w-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
@@ -5410,57 +6801,6 @@ function renderContentListPage(data) {
5410
6801
  }
5411
6802
  </script>
5412
6803
  </div>
5413
-
5414
- ${filterBarData.filters.map((filter) => {
5415
- const selectedOption = filter.options.find((opt) => opt.selected);
5416
- const selectedColor = selectedOption?.color || "cyan";
5417
- const colorMap = {
5418
- "cyan": "bg-cyan-400 dark:bg-cyan-400",
5419
- "lime": "bg-lime-400 dark:bg-lime-400",
5420
- "pink": "bg-pink-400 dark:bg-pink-400",
5421
- "purple": "bg-purple-400 dark:bg-purple-400",
5422
- "amber": "bg-amber-400 dark:bg-amber-400",
5423
- "zinc": "bg-zinc-400 dark:bg-zinc-400"
5424
- };
5425
- return `
5426
- <div>
5427
- <label class="block text-sm/6 font-medium text-zinc-950 dark:text-white">${filter.label}</label>
5428
- <div class="mt-2 grid grid-cols-1">
5429
- <div class="col-start-1 row-start-1 flex items-center gap-3 pl-3 pr-8 pointer-events-none">
5430
- ${filter.name === "status" ? `<span class="inline-block size-2 shrink-0 rounded-full border border-transparent ${colorMap[selectedColor]}"></span>` : ""}
5431
- </div>
5432
- <select
5433
- name="${filter.name}"
5434
- onchange="updateContentFilters('${filter.name}', this.value)"
5435
- class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white/5 dark:bg-white/5 py-1.5 ${filter.name === "status" ? "pl-8" : "pl-3"} pr-8 text-base text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-cyan-500/30 dark:outline-cyan-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-cyan-500 dark:focus-visible:outline-cyan-400 sm:text-sm/6 min-w-48"
5436
- >
5437
- ${filter.options.map((opt) => `
5438
- <option value="${opt.value}" ${opt.selected ? "selected" : ""}>${opt.label}</option>
5439
- `).join("")}
5440
- </select>
5441
- <svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-cyan-600 dark:text-cyan-400 sm:size-4">
5442
- <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" />
5443
- </svg>
5444
- </div>
5445
- </div>
5446
- `;
5447
- }).join("")}
5448
-
5449
- <!-- Clear Filters Button -->
5450
- ${hasActiveFilters ? `
5451
- <div>
5452
- <label class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2">&nbsp;</label>
5453
- <button
5454
- onclick="clearAllFilters()"
5455
- class="inline-flex items-center gap-x-1.5 px-3 py-2 bg-pink-50 dark:bg-pink-500/10 text-pink-700 dark:text-pink-300 text-sm font-medium rounded-md ring-1 ring-inset ring-pink-600/20 dark:ring-pink-500/20 hover:bg-pink-100 dark:hover:bg-pink-500/20 transition-colors"
5456
- >
5457
- <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
5458
- <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
5459
- </svg>
5460
- Clear Filters
5461
- </button>
5462
- </div>
5463
- ` : ""}
5464
6804
  </div>
5465
6805
  <div class="flex items-center gap-x-3">
5466
6806
  <span class="text-sm/6 font-medium text-zinc-700 dark:text-zinc-300 px-3 py-1.5 rounded-full bg-white/60 dark:bg-zinc-800/60 backdrop-blur-sm">${data.totalItems} ${data.totalItems === 1 ? "item" : "items"}</span>
@@ -5541,8 +6881,8 @@ function renderContentListPage(data) {
5541
6881
 
5542
6882
  <!-- Content List -->
5543
6883
  <div id="content-list">
5544
- ${chunkAZLU3ROK_cjs.renderTable(tableData)}
5545
- ${chunkAZLU3ROK_cjs.renderPagination(paginationData)}
6884
+ ${chunkBZC4FYW7_cjs.renderTable(tableData)}
6885
+ ${chunkBZC4FYW7_cjs.renderPagination(paginationData)}
5546
6886
  </div>
5547
6887
 
5548
6888
  </div>
@@ -5721,49 +7061,335 @@ function renderContentListPage(data) {
5721
7061
  } else {
5722
7062
  alert('Error: ' + (data.error || 'Unknown error'));
5723
7063
  }
5724
- })
5725
- .catch(err => {
5726
- console.error('Bulk action error:', err);
5727
- alert('Failed to perform bulk action');
5728
- })
5729
- .finally(() => {
5730
- // Clear context
5731
- currentBulkAction = null;
5732
- currentSelectedIds = [];
7064
+ })
7065
+ .catch(err => {
7066
+ console.error('Bulk action error:', err);
7067
+ alert('Failed to perform bulk action');
7068
+ })
7069
+ .finally(() => {
7070
+ // Clear context
7071
+ currentBulkAction = null;
7072
+ currentSelectedIds = [];
7073
+ });
7074
+ }
7075
+
7076
+ // Helper to get action text for display
7077
+ function getActionText(action) {
7078
+ const actionCount = currentSelectedIds.length;
7079
+ switch(action) {
7080
+ case 'publish':
7081
+ return \`publish \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
7082
+ case 'draft':
7083
+ return \`move \${actionCount} item\${actionCount > 1 ? 's' : ''} to draft\`;
7084
+ case 'delete':
7085
+ return \`delete \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
7086
+ default:
7087
+ return \`perform action on \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
7088
+ }
7089
+ }
7090
+
7091
+ </script>
7092
+
7093
+ <!-- Confirmation Dialog for Bulk Actions -->
7094
+ ${chunkBZC4FYW7_cjs.renderConfirmationDialog({
7095
+ id: "bulk-action-confirm",
7096
+ title: "Confirm Bulk Action",
7097
+ message: "Are you sure you want to perform this action? This operation will affect multiple items.",
7098
+ confirmText: "Confirm",
7099
+ cancelText: "Cancel",
7100
+ confirmClass: "bg-blue-500 hover:bg-blue-400",
7101
+ iconColor: "blue",
7102
+ onConfirm: "executeBulkAction()"
7103
+ })}
7104
+
7105
+ <!-- Confirmation Dialog Script -->
7106
+ ${chunkBZC4FYW7_cjs.getConfirmationDialogScript()}
7107
+
7108
+ <!-- Advanced Search Modal -->
7109
+ <div id="advancedSearchModal" class="hidden fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
7110
+ <div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
7111
+ <!-- Background overlay -->
7112
+ <div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" onclick="closeAdvancedSearch()"></div>
7113
+
7114
+ <!-- Modal panel -->
7115
+ <div class="inline-block align-bottom bg-white dark:bg-zinc-900 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
7116
+ <div class="bg-white dark:bg-zinc-900 px-4 pt-5 pb-4 sm:p-6">
7117
+ <!-- Header -->
7118
+ <div class="flex items-center justify-between mb-4">
7119
+ <h3 class="text-lg font-semibold text-zinc-950 dark:text-white" id="modal-title">
7120
+ \u{1F50D} Advanced Search
7121
+ </h3>
7122
+ <button onclick="closeAdvancedSearch()" class="text-zinc-400 hover:text-zinc-500 dark:hover:text-zinc-300">
7123
+ <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
7124
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
7125
+ </svg>
7126
+ </button>
7127
+ </div>
7128
+
7129
+ <!-- Search Form -->
7130
+ <form id="advancedSearchForm" class="space-y-4">
7131
+ <!-- Search Input -->
7132
+ <div>
7133
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search Query</label>
7134
+ <div class="relative">
7135
+ <input
7136
+ type="text"
7137
+ id="searchQuery"
7138
+ name="query"
7139
+ placeholder="Enter your search query..."
7140
+ class="w-full rounded-lg bg-white dark:bg-white/5 px-4 py-3 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 focus:ring-2 focus:ring-indigo-500"
7141
+ autocomplete="off"
7142
+ />
7143
+ <div id="searchSuggestions" class="hidden absolute z-10 w-full mt-1 bg-white dark:bg-zinc-800 rounded-lg shadow-lg border border-zinc-200 dark:border-zinc-700 max-h-60 overflow-y-auto"></div>
7144
+ </div>
7145
+ </div>
7146
+
7147
+ <!-- Mode Toggle -->
7148
+ <div>
7149
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search Mode</label>
7150
+ <div class="flex gap-4">
7151
+ <label class="flex items-center">
7152
+ <input type="radio" name="mode" value="ai" checked class="mr-2">
7153
+ <span class="text-sm text-zinc-950 dark:text-white">\u{1F916} AI Search (Semantic)</span>
7154
+ </label>
7155
+ <label class="flex items-center">
7156
+ <input type="radio" name="mode" value="keyword" class="mr-2">
7157
+ <span class="text-sm text-zinc-950 dark:text-white">\u{1F524} Keyword Search</span>
7158
+ </label>
7159
+ </div>
7160
+ </div>
7161
+
7162
+ <!-- Filters -->
7163
+ <div class="border-t border-zinc-200 dark:border-zinc-800 pt-4">
7164
+ <h4 class="text-sm font-semibold text-zinc-950 dark:text-white mb-3">Filters</h4>
7165
+
7166
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
7167
+ <!-- Collection Filter -->
7168
+ <div>
7169
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Collections</label>
7170
+ <select
7171
+ id="filterCollections"
7172
+ name="collections"
7173
+ multiple
7174
+ class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10"
7175
+ size="4"
7176
+ >
7177
+ <option value="">All Collections</option>
7178
+ ${data.models.map(
7179
+ (model) => `
7180
+ <option value="${model.name}">${model.displayName}</option>
7181
+ `
7182
+ ).join("")}
7183
+ </select>
7184
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">Hold Ctrl/Cmd to select multiple</p>
7185
+ </div>
7186
+
7187
+ <!-- Status Filter -->
7188
+ <div>
7189
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Status</label>
7190
+ <select
7191
+ id="filterStatus"
7192
+ name="status"
7193
+ multiple
7194
+ class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10"
7195
+ size="4"
7196
+ >
7197
+ <option value="published">Published</option>
7198
+ <option value="draft">Draft</option>
7199
+ <option value="review">Under Review</option>
7200
+ <option value="scheduled">Scheduled</option>
7201
+ <option value="archived">Archived</option>
7202
+ </select>
7203
+ </div>
7204
+ </div>
7205
+ </div>
7206
+
7207
+ <!-- Actions -->
7208
+ <div class="flex items-center justify-end gap-3 pt-4 border-t border-zinc-200 dark:border-zinc-800">
7209
+ <button
7210
+ type="button"
7211
+ onclick="closeAdvancedSearch()"
7212
+ class="inline-flex items-center justify-center 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"
7213
+ >
7214
+ Cancel
7215
+ </button>
7216
+ <button
7217
+ type="submit"
7218
+ class="inline-flex items-center justify-center rounded-lg bg-indigo-600 text-white px-6 py-2.5 text-sm font-semibold hover:bg-indigo-500 shadow-sm"
7219
+ >
7220
+ Search
7221
+ </button>
7222
+ </div>
7223
+ </form>
7224
+ </div>
7225
+
7226
+ <!-- Results Area -->
7227
+ <div id="searchResults" class="hidden px-4 pb-4 sm:px-6">
7228
+ <div class="border-t border-zinc-200 dark:border-zinc-800 pt-4">
7229
+ <div id="searchResultsContent" class="space-y-3"></div>
7230
+ <div id="searchResultsPagination" class="mt-4 flex items-center justify-between"></div>
7231
+ </div>
7232
+ </div>
7233
+ </div>
7234
+ </div>
7235
+ </div>
7236
+
7237
+ <script>
7238
+ // Open modal
7239
+ function openAdvancedSearch() {
7240
+ document.getElementById('advancedSearchModal').classList.remove('hidden');
7241
+ document.getElementById('searchQuery').focus();
7242
+ }
7243
+
7244
+ // Close modal
7245
+ function closeAdvancedSearch() {
7246
+ document.getElementById('advancedSearchModal').classList.add('hidden');
7247
+ document.getElementById('searchResults').classList.add('hidden');
7248
+ }
7249
+
7250
+ // Autocomplete
7251
+ let autocompleteTimeout;
7252
+ const searchQueryInput = document.getElementById('searchQuery');
7253
+ if (searchQueryInput) {
7254
+ searchQueryInput.addEventListener('input', (e) => {
7255
+ const query = e.target.value.trim();
7256
+ const suggestionsDiv = document.getElementById('searchSuggestions');
7257
+
7258
+ clearTimeout(autocompleteTimeout);
7259
+
7260
+ if (query.length < 2) {
7261
+ suggestionsDiv.classList.add('hidden');
7262
+ return;
7263
+ }
7264
+
7265
+ autocompleteTimeout = setTimeout(async () => {
7266
+ try {
7267
+ const res = await fetch(\`/api/search/suggest?q=\${encodeURIComponent(query)}\`);
7268
+ const { data } = await res.json();
7269
+
7270
+ if (data && data.length > 0) {
7271
+ suggestionsDiv.innerHTML = data.map(s => \`
7272
+ <div class="px-4 py-2 hover:bg-zinc-100 dark:hover:bg-zinc-700 cursor-pointer" onclick="selectSuggestion('\${s.replace(/'/g, "\\'")}')">\${s}</div>
7273
+ \`).join('');
7274
+ suggestionsDiv.classList.remove('hidden');
7275
+ } else {
7276
+ suggestionsDiv.classList.add('hidden');
7277
+ }
7278
+ } catch (error) {
7279
+ console.error('Autocomplete error:', error);
7280
+ }
7281
+ }, 300);
7282
+ });
7283
+ }
7284
+
7285
+ function selectSuggestion(suggestion) {
7286
+ document.getElementById('searchQuery').value = suggestion;
7287
+ document.getElementById('searchSuggestions').classList.add('hidden');
7288
+ }
7289
+
7290
+ // Hide suggestions when clicking outside
7291
+ document.addEventListener('click', (e) => {
7292
+ const suggestionsDiv = document.getElementById('searchSuggestions');
7293
+ if (!e.target.closest('#searchQuery') && !e.target.closest('#searchSuggestions')) {
7294
+ suggestionsDiv.classList.add('hidden');
7295
+ }
7296
+ });
7297
+
7298
+ // Form submission
7299
+ const advancedSearchForm = document.getElementById('advancedSearchForm');
7300
+ if (advancedSearchForm) {
7301
+ advancedSearchForm.addEventListener('submit', async (e) => {
7302
+ e.preventDefault();
7303
+
7304
+ const formData = new FormData(e.target);
7305
+ const query = formData.get('query');
7306
+ const mode = formData.get('mode') || 'ai';
7307
+
7308
+ // Build filters
7309
+ const filters = {};
7310
+
7311
+ const collections = Array.from(formData.getAll('collections')).filter(c => c !== '');
7312
+ if (collections.length > 0) {
7313
+ // Need to convert collection names to IDs - for now, pass names
7314
+ filters.collections = collections;
7315
+ }
7316
+
7317
+ const status = Array.from(formData.getAll('status'));
7318
+ if (status.length > 0) {
7319
+ filters.status = status;
7320
+ }
7321
+
7322
+ const dateStart = formData.get('date_start');
7323
+ const dateEnd = formData.get('date_end');
7324
+ if (dateStart || dateEnd) {
7325
+ filters.dateRange = {
7326
+ start: dateStart ? new Date(dateStart) : null,
7327
+ end: dateEnd ? new Date(dateEnd) : null,
7328
+ field: 'created_at'
7329
+ };
7330
+ }
7331
+
7332
+ // Execute search
7333
+ try {
7334
+ const res = await fetch('/api/search', {
7335
+ method: 'POST',
7336
+ headers: {'Content-Type': 'application/json'},
7337
+ body: JSON.stringify({
7338
+ query,
7339
+ mode,
7340
+ filters,
7341
+ limit: 20
7342
+ })
7343
+ });
7344
+
7345
+ const { data } = await res.json();
7346
+
7347
+ if (data && data.results) {
7348
+ displaySearchResults(data);
7349
+ }
7350
+ } catch (error) {
7351
+ console.error('Search error:', error);
7352
+ alert('Search failed. Please try again.');
7353
+ }
5733
7354
  });
5734
7355
  }
5735
7356
 
5736
- // Helper to get action text for display
5737
- function getActionText(action) {
5738
- const actionCount = currentSelectedIds.length;
5739
- switch(action) {
5740
- case 'publish':
5741
- return \`publish \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
5742
- case 'draft':
5743
- return \`move \${actionCount} item\${actionCount > 1 ? 's' : ''} to draft\`;
5744
- case 'delete':
5745
- return \`delete \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
5746
- default:
5747
- return \`perform action on \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
7357
+ function displaySearchResults(searchData) {
7358
+ const resultsDiv = document.getElementById('searchResultsContent');
7359
+ const resultsSection = document.getElementById('searchResults');
7360
+
7361
+ if (searchData.results.length === 0) {
7362
+ resultsDiv.innerHTML = '<p class="text-sm text-zinc-500 dark:text-zinc-400">No results found.</p>';
7363
+ } else {
7364
+ resultsDiv.innerHTML = searchData.results.map(result => \`
7365
+ <div class="p-4 rounded-lg border border-zinc-200 dark:border-zinc-800 hover:bg-zinc-50 dark:hover:bg-zinc-800">
7366
+ <div class="flex items-start justify-between">
7367
+ <div class="flex-1">
7368
+ <h4 class="text-sm font-semibold text-zinc-950 dark:text-white mb-1">
7369
+ <a href="/admin/content/\${result.id}/edit" class="hover:text-indigo-600 dark:hover:text-indigo-400">\${result.title || 'Untitled'}</a>
7370
+ </h4>
7371
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mb-2">
7372
+ \${result.collection_name} \u2022 \${new Date(result.created_at).toLocaleDateString()}
7373
+ \${result.relevance_score ? \` \u2022 Relevance: \${(result.relevance_score * 100).toFixed(0)}%\` : ''}
7374
+ </p>
7375
+ \${result.snippet ? \`<p class="text-sm text-zinc-600 dark:text-zinc-400">\${result.snippet}</p>\` : ''}
7376
+ </div>
7377
+ <div class="ml-4">
7378
+ <span class="px-2 py-1 text-xs rounded-full \${result.status === 'published' ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300' : 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300'}">\${result.status}</span>
7379
+ </div>
7380
+ </div>
7381
+ </div>
7382
+ \`).join('');
5748
7383
  }
7384
+
7385
+ resultsSection.classList.remove('hidden');
7386
+ resultsSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
5749
7387
  }
5750
7388
 
7389
+ // Make functions globally available
7390
+ window.openAdvancedSearch = openAdvancedSearch;
7391
+ window.closeAdvancedSearch = closeAdvancedSearch;
5751
7392
  </script>
5752
-
5753
- <!-- Confirmation Dialog for Bulk Actions -->
5754
- ${chunkAZLU3ROK_cjs.renderConfirmationDialog({
5755
- id: "bulk-action-confirm",
5756
- title: "Confirm Bulk Action",
5757
- message: "Are you sure you want to perform this action? This operation will affect multiple items.",
5758
- confirmText: "Confirm",
5759
- cancelText: "Cancel",
5760
- confirmClass: "bg-blue-500 hover:bg-blue-400",
5761
- iconColor: "blue",
5762
- onConfirm: "executeBulkAction()"
5763
- })}
5764
-
5765
- <!-- Confirmation Dialog Script -->
5766
- ${chunkAZLU3ROK_cjs.getConfirmationDialogScript()}
5767
7393
  `;
5768
7394
  const layoutData = {
5769
7395
  title: "Content Management",
@@ -5773,7 +7399,7 @@ function renderContentListPage(data) {
5773
7399
  version: data.version,
5774
7400
  content: pageContent
5775
7401
  };
5776
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
7402
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
5777
7403
  }
5778
7404
 
5779
7405
  // src/templates/components/version-history.template.ts
@@ -5967,7 +7593,123 @@ async function isPluginActive2(db, pluginId) {
5967
7593
 
5968
7594
  // src/routes/admin-content.ts
5969
7595
  var adminContentRoutes = new hono.Hono();
5970
- adminContentRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
7596
+ function parseFieldValue(field, formData, options = {}) {
7597
+ const { skipValidation = false } = options;
7598
+ const value = formData.get(field.field_name);
7599
+ const errors = [];
7600
+ const blocksConfig = chunkYMTTGHEK_cjs.getBlocksFieldConfig(field.field_options);
7601
+ if (blocksConfig) {
7602
+ const parsed = chunkYMTTGHEK_cjs.parseBlocksValue(value, blocksConfig);
7603
+ if (!skipValidation && field.is_required && parsed.value.length === 0) {
7604
+ parsed.errors.push(`${field.field_label} is required`);
7605
+ }
7606
+ return { value: parsed.value, errors: parsed.errors };
7607
+ }
7608
+ if (!skipValidation && field.is_required && (!value || value.toString().trim() === "")) {
7609
+ return { value: null, errors: [`${field.field_label} is required`] };
7610
+ }
7611
+ switch (field.field_type) {
7612
+ case "number":
7613
+ if (value && isNaN(Number(value))) {
7614
+ if (!skipValidation) {
7615
+ errors.push(`${field.field_label} must be a valid number`);
7616
+ }
7617
+ return { value: null, errors };
7618
+ }
7619
+ return { value: value ? Number(value) : null, errors: [] };
7620
+ case "boolean":
7621
+ const submitted = formData.get(`${field.field_name}_submitted`);
7622
+ return { value: submitted ? value === "true" : false, errors: [] };
7623
+ case "select":
7624
+ if (field.field_options?.multiple) {
7625
+ return { value: formData.getAll(`${field.field_name}[]`), errors: [] };
7626
+ }
7627
+ return { value, errors: [] };
7628
+ case "array": {
7629
+ if (!value || value.toString().trim() === "") {
7630
+ if (!skipValidation && field.is_required) {
7631
+ errors.push(`${field.field_label} is required`);
7632
+ }
7633
+ return { value: [], errors };
7634
+ }
7635
+ try {
7636
+ const parsed = JSON.parse(value.toString());
7637
+ if (!Array.isArray(parsed)) {
7638
+ if (!skipValidation) {
7639
+ errors.push(`${field.field_label} must be a JSON array`);
7640
+ }
7641
+ return { value: [], errors };
7642
+ }
7643
+ if (!skipValidation && field.is_required && parsed.length === 0) {
7644
+ errors.push(`${field.field_label} is required`);
7645
+ }
7646
+ return { value: parsed, errors };
7647
+ } catch {
7648
+ if (!skipValidation) {
7649
+ errors.push(`${field.field_label} must be valid JSON`);
7650
+ }
7651
+ return { value: [], errors };
7652
+ }
7653
+ }
7654
+ case "object": {
7655
+ if (!value || value.toString().trim() === "") {
7656
+ if (!skipValidation && field.is_required) {
7657
+ errors.push(`${field.field_label} is required`);
7658
+ }
7659
+ return { value: {}, errors };
7660
+ }
7661
+ try {
7662
+ const parsed = JSON.parse(value.toString());
7663
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
7664
+ if (!skipValidation) {
7665
+ errors.push(`${field.field_label} must be a JSON object`);
7666
+ }
7667
+ return { value: {}, errors };
7668
+ }
7669
+ if (!skipValidation && field.is_required && Object.keys(parsed).length === 0) {
7670
+ errors.push(`${field.field_label} is required`);
7671
+ }
7672
+ return { value: parsed, errors };
7673
+ } catch {
7674
+ if (!skipValidation) {
7675
+ errors.push(`${field.field_label} must be valid JSON`);
7676
+ }
7677
+ return { value: {}, errors };
7678
+ }
7679
+ }
7680
+ case "json": {
7681
+ if (!value || value.toString().trim() === "") {
7682
+ if (!skipValidation && field.is_required) {
7683
+ errors.push(`${field.field_label} is required`);
7684
+ }
7685
+ return { value: null, errors };
7686
+ }
7687
+ try {
7688
+ return { value: JSON.parse(value.toString()), errors: [] };
7689
+ } catch {
7690
+ if (!skipValidation) {
7691
+ errors.push(`${field.field_label} must be valid JSON`);
7692
+ }
7693
+ return { value: null, errors };
7694
+ }
7695
+ }
7696
+ default:
7697
+ return { value, errors: [] };
7698
+ }
7699
+ }
7700
+ function extractFieldData(fields, formData, options = {}) {
7701
+ const data = {};
7702
+ const errors = {};
7703
+ for (const field of fields) {
7704
+ const result = parseFieldValue(field, formData, options);
7705
+ data[field.field_name] = result.value;
7706
+ if (result.errors.length > 0) {
7707
+ errors[field.field_name] = result.errors;
7708
+ }
7709
+ }
7710
+ return { data, errors };
7711
+ }
7712
+ adminContentRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
5971
7713
  async function getCollectionFields(db, collectionId) {
5972
7714
  const cache = chunk7FOAMNTI_cjs.getCacheService(chunk7FOAMNTI_cjs.CACHE_CONFIGS.collection);
5973
7715
  return cache.getOrSet(
@@ -6249,21 +7991,21 @@ adminContentRoutes.get("/new", async (c) => {
6249
7991
  const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
6250
7992
  let tinymceSettings;
6251
7993
  if (tinymceEnabled) {
6252
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
7994
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
6253
7995
  const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
6254
7996
  tinymceSettings = tinymcePlugin2?.settings;
6255
7997
  }
6256
7998
  const quillEnabled = await isPluginActive2(db, "quill-editor");
6257
7999
  let quillSettings;
6258
8000
  if (quillEnabled) {
6259
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
8001
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
6260
8002
  const quillPlugin = await pluginService.getPlugin("quill-editor");
6261
8003
  quillSettings = quillPlugin?.settings;
6262
8004
  }
6263
8005
  const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
6264
8006
  let mdxeditorSettings;
6265
8007
  if (mdxeditorEnabled) {
6266
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
8008
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
6267
8009
  const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
6268
8010
  mdxeditorSettings = mdxeditorPlugin?.settings;
6269
8011
  }
@@ -6354,21 +8096,21 @@ adminContentRoutes.get("/:id/edit", async (c) => {
6354
8096
  const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
6355
8097
  let tinymceSettings;
6356
8098
  if (tinymceEnabled) {
6357
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
8099
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
6358
8100
  const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
6359
8101
  tinymceSettings = tinymcePlugin2?.settings;
6360
8102
  }
6361
8103
  const quillEnabled = await isPluginActive2(db, "quill-editor");
6362
8104
  let quillSettings;
6363
8105
  if (quillEnabled) {
6364
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
8106
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
6365
8107
  const quillPlugin = await pluginService.getPlugin("quill-editor");
6366
8108
  quillSettings = quillPlugin?.settings;
6367
8109
  }
6368
8110
  const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
6369
8111
  let mdxeditorSettings;
6370
8112
  if (mdxeditorEnabled) {
6371
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
8113
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
6372
8114
  const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
6373
8115
  mdxeditorSettings = mdxeditorPlugin?.settings;
6374
8116
  }
@@ -6440,109 +8182,7 @@ adminContentRoutes.post("/", async (c) => {
6440
8182
  `);
6441
8183
  }
6442
8184
  const fields = await getCollectionFields(db, collectionId);
6443
- const data = {};
6444
- const errors = {};
6445
- for (const field of fields) {
6446
- const value = formData.get(field.field_name);
6447
- const blocksConfig = chunkZWV3EBZ7_cjs.getBlocksFieldConfig(field.field_options);
6448
- if (blocksConfig) {
6449
- const parsed = chunkZWV3EBZ7_cjs.parseBlocksValue(value, blocksConfig);
6450
- if (field.is_required && parsed.value.length === 0) {
6451
- parsed.errors.push(`${field.field_label} is required`);
6452
- }
6453
- if (parsed.errors.length > 0) {
6454
- errors[field.field_name] = parsed.errors;
6455
- }
6456
- data[field.field_name] = parsed.value;
6457
- continue;
6458
- }
6459
- if (field.is_required && (!value || value.toString().trim() === "")) {
6460
- errors[field.field_name] = [`${field.field_label} is required`];
6461
- continue;
6462
- }
6463
- switch (field.field_type) {
6464
- case "number":
6465
- if (value && isNaN(Number(value))) {
6466
- errors[field.field_name] = [`${field.field_label} must be a valid number`];
6467
- } else {
6468
- data[field.field_name] = value ? Number(value) : null;
6469
- }
6470
- break;
6471
- case "boolean":
6472
- data[field.field_name] = formData.get(`${field.field_name}_submitted`) ? value === "true" : false;
6473
- break;
6474
- case "select":
6475
- if (field.field_options?.multiple) {
6476
- data[field.field_name] = formData.getAll(`${field.field_name}[]`);
6477
- } else {
6478
- data[field.field_name] = value;
6479
- }
6480
- break;
6481
- case "array": {
6482
- if (!value || value.toString().trim() === "") {
6483
- data[field.field_name] = [];
6484
- if (field.is_required) {
6485
- errors[field.field_name] = [`${field.field_label} is required`];
6486
- }
6487
- break;
6488
- }
6489
- try {
6490
- const parsed = JSON.parse(value.toString());
6491
- if (!Array.isArray(parsed)) {
6492
- errors[field.field_name] = [`${field.field_label} must be a JSON array`];
6493
- } else {
6494
- if (field.is_required && parsed.length === 0) {
6495
- errors[field.field_name] = [`${field.field_label} is required`];
6496
- }
6497
- data[field.field_name] = parsed;
6498
- }
6499
- } catch {
6500
- errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6501
- }
6502
- break;
6503
- }
6504
- case "object": {
6505
- if (!value || value.toString().trim() === "") {
6506
- data[field.field_name] = {};
6507
- if (field.is_required) {
6508
- errors[field.field_name] = [`${field.field_label} is required`];
6509
- }
6510
- break;
6511
- }
6512
- try {
6513
- const parsed = JSON.parse(value.toString());
6514
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
6515
- errors[field.field_name] = [`${field.field_label} must be a JSON object`];
6516
- } else {
6517
- if (field.is_required && Object.keys(parsed).length === 0) {
6518
- errors[field.field_name] = [`${field.field_label} is required`];
6519
- }
6520
- data[field.field_name] = parsed;
6521
- }
6522
- } catch {
6523
- errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6524
- }
6525
- break;
6526
- }
6527
- case "json": {
6528
- if (!value || value.toString().trim() === "") {
6529
- data[field.field_name] = null;
6530
- if (field.is_required) {
6531
- errors[field.field_name] = [`${field.field_label} is required`];
6532
- }
6533
- break;
6534
- }
6535
- try {
6536
- data[field.field_name] = JSON.parse(value.toString());
6537
- } catch {
6538
- errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6539
- }
6540
- break;
6541
- }
6542
- default:
6543
- data[field.field_name] = value;
6544
- }
6545
- }
8185
+ const { data, errors } = extractFieldData(fields, formData);
6546
8186
  if (Object.keys(errors).length > 0) {
6547
8187
  const formDataWithErrors = {
6548
8188
  collection,
@@ -6659,109 +8299,7 @@ adminContentRoutes.put("/:id", async (c) => {
6659
8299
  `);
6660
8300
  }
6661
8301
  const fields = await getCollectionFields(db, existingContent.collection_id);
6662
- const data = {};
6663
- const errors = {};
6664
- for (const field of fields) {
6665
- const value = formData.get(field.field_name);
6666
- const blocksConfig = chunkZWV3EBZ7_cjs.getBlocksFieldConfig(field.field_options);
6667
- if (blocksConfig) {
6668
- const parsed = chunkZWV3EBZ7_cjs.parseBlocksValue(value, blocksConfig);
6669
- if (field.is_required && parsed.value.length === 0) {
6670
- parsed.errors.push(`${field.field_label} is required`);
6671
- }
6672
- if (parsed.errors.length > 0) {
6673
- errors[field.field_name] = parsed.errors;
6674
- }
6675
- data[field.field_name] = parsed.value;
6676
- continue;
6677
- }
6678
- if (field.is_required && (!value || value.toString().trim() === "")) {
6679
- errors[field.field_name] = [`${field.field_label} is required`];
6680
- continue;
6681
- }
6682
- switch (field.field_type) {
6683
- case "number":
6684
- if (value && isNaN(Number(value))) {
6685
- errors[field.field_name] = [`${field.field_label} must be a valid number`];
6686
- } else {
6687
- data[field.field_name] = value ? Number(value) : null;
6688
- }
6689
- break;
6690
- case "boolean":
6691
- data[field.field_name] = formData.get(`${field.field_name}_submitted`) ? value === "true" : false;
6692
- break;
6693
- case "select":
6694
- if (field.field_options?.multiple) {
6695
- data[field.field_name] = formData.getAll(`${field.field_name}[]`);
6696
- } else {
6697
- data[field.field_name] = value;
6698
- }
6699
- break;
6700
- case "array": {
6701
- if (!value || value.toString().trim() === "") {
6702
- data[field.field_name] = [];
6703
- if (field.is_required) {
6704
- errors[field.field_name] = [`${field.field_label} is required`];
6705
- }
6706
- break;
6707
- }
6708
- try {
6709
- const parsed = JSON.parse(value.toString());
6710
- if (!Array.isArray(parsed)) {
6711
- errors[field.field_name] = [`${field.field_label} must be a JSON array`];
6712
- } else {
6713
- if (field.is_required && parsed.length === 0) {
6714
- errors[field.field_name] = [`${field.field_label} is required`];
6715
- }
6716
- data[field.field_name] = parsed;
6717
- }
6718
- } catch {
6719
- errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6720
- }
6721
- break;
6722
- }
6723
- case "object": {
6724
- if (!value || value.toString().trim() === "") {
6725
- data[field.field_name] = {};
6726
- if (field.is_required) {
6727
- errors[field.field_name] = [`${field.field_label} is required`];
6728
- }
6729
- break;
6730
- }
6731
- try {
6732
- const parsed = JSON.parse(value.toString());
6733
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
6734
- errors[field.field_name] = [`${field.field_label} must be a JSON object`];
6735
- } else {
6736
- if (field.is_required && Object.keys(parsed).length === 0) {
6737
- errors[field.field_name] = [`${field.field_label} is required`];
6738
- }
6739
- data[field.field_name] = parsed;
6740
- }
6741
- } catch {
6742
- errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6743
- }
6744
- break;
6745
- }
6746
- case "json": {
6747
- if (!value || value.toString().trim() === "") {
6748
- data[field.field_name] = null;
6749
- if (field.is_required) {
6750
- errors[field.field_name] = [`${field.field_label} is required`];
6751
- }
6752
- break;
6753
- }
6754
- try {
6755
- data[field.field_name] = JSON.parse(value.toString());
6756
- } catch {
6757
- errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6758
- }
6759
- break;
6760
- }
6761
- default:
6762
- data[field.field_name] = value;
6763
- }
6764
- }
8302
+ const { data, errors } = extractFieldData(fields, formData);
6765
8303
  if (Object.keys(errors).length > 0) {
6766
8304
  const formDataWithErrors = {
6767
8305
  id,
@@ -6874,33 +8412,7 @@ adminContentRoutes.post("/preview", async (c) => {
6874
8412
  return c.html("<p>Collection not found</p>");
6875
8413
  }
6876
8414
  const fields = await getCollectionFields(db, collectionId);
6877
- const data = {};
6878
- for (const field of fields) {
6879
- const value = formData.get(field.field_name);
6880
- const blocksConfig = chunkZWV3EBZ7_cjs.getBlocksFieldConfig(field.field_options);
6881
- if (blocksConfig) {
6882
- const parsed = chunkZWV3EBZ7_cjs.parseBlocksValue(value, blocksConfig);
6883
- data[field.field_name] = parsed.value;
6884
- continue;
6885
- }
6886
- switch (field.field_type) {
6887
- case "number":
6888
- data[field.field_name] = value ? Number(value) : null;
6889
- break;
6890
- case "boolean":
6891
- data[field.field_name] = value === "true";
6892
- break;
6893
- case "select":
6894
- if (field.field_options?.multiple) {
6895
- data[field.field_name] = formData.getAll(`${field.field_name}[]`);
6896
- } else {
6897
- data[field.field_name] = value;
6898
- }
6899
- break;
6900
- default:
6901
- data[field.field_name] = value;
6902
- }
6903
- }
8415
+ const { data } = extractFieldData(fields, formData, { skipValidation: true });
6904
8416
  const previewHTML = `
6905
8417
  <!DOCTYPE html>
6906
8418
  <html lang="en">
@@ -7328,7 +8840,7 @@ ${JSON.stringify(data, null, 2)}
7328
8840
  var admin_content_default = adminContentRoutes;
7329
8841
 
7330
8842
  // src/templates/pages/admin-profile.template.ts
7331
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
8843
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
7332
8844
  function renderAvatarImage(avatarUrl, firstName, lastName) {
7333
8845
  return `<div id="avatar-image-container" class="w-24 h-24 rounded-full mx-auto mb-4 overflow-hidden bg-gradient-to-br from-cyan-400 to-purple-400 flex items-center justify-center ring-4 ring-zinc-950/5 dark:ring-white/10">
7334
8846
  ${avatarUrl ? `<img src="${avatarUrl}" alt="Profile picture" class="w-full h-full object-cover">` : `<span class="text-2xl font-bold text-white">${firstName.charAt(0)}${lastName.charAt(0)}</span>`}
@@ -7348,8 +8860,8 @@ function renderProfilePage(data) {
7348
8860
  </div>
7349
8861
 
7350
8862
  <!-- Alert Messages -->
7351
- ${data.error ? chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
7352
- ${data.success ? chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
8863
+ ${data.error ? chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
8864
+ ${data.success ? chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
7353
8865
 
7354
8866
  <!-- Profile Form -->
7355
8867
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
@@ -7736,7 +9248,7 @@ function renderProfilePage(data) {
7736
9248
  version: data.version,
7737
9249
  content: pageContent
7738
9250
  };
7739
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
9251
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
7740
9252
  }
7741
9253
 
7742
9254
  // src/templates/components/alert.template.ts
@@ -8019,7 +9531,7 @@ function renderActivityLogsPage(data) {
8019
9531
  user: data.user,
8020
9532
  content: pageContent
8021
9533
  };
8022
- return chunkAZLU3ROK_cjs.renderAdminLayout(layoutData);
9534
+ return chunkBZC4FYW7_cjs.renderAdminLayout(layoutData);
8023
9535
  }
8024
9536
  function getActionBadgeClass(action) {
8025
9537
  if (action.includes("login") || action.includes("logout")) {
@@ -8039,7 +9551,7 @@ function formatAction(action) {
8039
9551
  }
8040
9552
 
8041
9553
  // src/templates/pages/admin-user-edit.template.ts
8042
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
9554
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
8043
9555
 
8044
9556
  // src/templates/components/confirmation-dialog.template.ts
8045
9557
  function renderConfirmationDialog2(options) {
@@ -8160,8 +9672,8 @@ function renderUserEditPage(data) {
8160
9672
 
8161
9673
  <!-- Alert Messages -->
8162
9674
  <div id="form-messages">
8163
- ${data.error ? chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
8164
- ${data.success ? chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
9675
+ ${data.error ? chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
9676
+ ${data.success ? chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
8165
9677
  </div>
8166
9678
 
8167
9679
  <!-- User Edit Form -->
@@ -8180,7 +9692,7 @@ function renderUserEditPage(data) {
8180
9692
  <input
8181
9693
  type="text"
8182
9694
  name="first_name"
8183
- value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.firstName || "")}"
9695
+ value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.firstName || "")}"
8184
9696
  required
8185
9697
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8186
9698
  />
@@ -8191,7 +9703,7 @@ function renderUserEditPage(data) {
8191
9703
  <input
8192
9704
  type="text"
8193
9705
  name="last_name"
8194
- value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.lastName || "")}"
9706
+ value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.lastName || "")}"
8195
9707
  required
8196
9708
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8197
9709
  />
@@ -8202,7 +9714,7 @@ function renderUserEditPage(data) {
8202
9714
  <input
8203
9715
  type="text"
8204
9716
  name="username"
8205
- value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.username || "")}"
9717
+ value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.username || "")}"
8206
9718
  required
8207
9719
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8208
9720
  />
@@ -8213,7 +9725,7 @@ function renderUserEditPage(data) {
8213
9725
  <input
8214
9726
  type="email"
8215
9727
  name="email"
8216
- value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.email || "")}"
9728
+ value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.email || "")}"
8217
9729
  required
8218
9730
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8219
9731
  />
@@ -8224,7 +9736,7 @@ function renderUserEditPage(data) {
8224
9736
  <input
8225
9737
  type="tel"
8226
9738
  name="phone"
8227
- value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.phone || "")}"
9739
+ value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.phone || "")}"
8228
9740
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8229
9741
  />
8230
9742
  </div>
@@ -8238,7 +9750,7 @@ function renderUserEditPage(data) {
8238
9750
  class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white/5 dark:bg-white/5 py-1.5 pl-3 pr-8 text-base text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-zinc-500/30 dark:outline-zinc-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-zinc-500 dark:focus-visible:outline-zinc-400 sm:text-sm/6"
8239
9751
  >
8240
9752
  ${data.roles.map((role) => `
8241
- <option value="${chunkZWV3EBZ7_cjs.escapeHtml(role.value)}" ${data.userToEdit.role === role.value ? "selected" : ""}>${chunkZWV3EBZ7_cjs.escapeHtml(role.label)}</option>
9753
+ <option value="${chunkYMTTGHEK_cjs.escapeHtml(role.value)}" ${data.userToEdit.role === role.value ? "selected" : ""}>${chunkYMTTGHEK_cjs.escapeHtml(role.label)}</option>
8242
9754
  `).join("")}
8243
9755
  </select>
8244
9756
  <svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-zinc-600 dark:text-zinc-400 sm:size-4">
@@ -8247,14 +9759,87 @@ function renderUserEditPage(data) {
8247
9759
  </div>
8248
9760
  </div>
8249
9761
  </div>
9762
+ </div>
9763
+
9764
+ <!-- Profile Information -->
9765
+ <div class="mb-8">
9766
+ <h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Profile Information</h3>
9767
+ <p class="text-sm text-zinc-500 dark:text-zinc-400 mb-4">Extended profile data for this user</p>
9768
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
9769
+ <div>
9770
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Display Name</label>
9771
+ <input
9772
+ type="text"
9773
+ name="profile_display_name"
9774
+ value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.displayName || "")}"
9775
+ placeholder="Public display name"
9776
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
9777
+ />
9778
+ </div>
9779
+
9780
+ <div>
9781
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Company</label>
9782
+ <input
9783
+ type="text"
9784
+ name="profile_company"
9785
+ value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.company || "")}"
9786
+ placeholder="Company or organization"
9787
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
9788
+ />
9789
+ </div>
9790
+
9791
+ <div>
9792
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Job Title</label>
9793
+ <input
9794
+ type="text"
9795
+ name="profile_job_title"
9796
+ value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.jobTitle || "")}"
9797
+ placeholder="Job title or role"
9798
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
9799
+ />
9800
+ </div>
9801
+
9802
+ <div>
9803
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Website</label>
9804
+ <input
9805
+ type="url"
9806
+ name="profile_website"
9807
+ value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.website || "")}"
9808
+ placeholder="https://example.com"
9809
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
9810
+ />
9811
+ </div>
9812
+
9813
+ <div>
9814
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Location</label>
9815
+ <input
9816
+ type="text"
9817
+ name="profile_location"
9818
+ value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.location || "")}"
9819
+ placeholder="City, Country"
9820
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
9821
+ />
9822
+ </div>
9823
+
9824
+ <div>
9825
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Date of Birth</label>
9826
+ <input
9827
+ type="date"
9828
+ name="profile_date_of_birth"
9829
+ value="${data.userToEdit.profile?.dateOfBirth ? new Date(data.userToEdit.profile.dateOfBirth).toISOString().split("T")[0] : ""}"
9830
+ class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
9831
+ />
9832
+ </div>
9833
+ </div>
8250
9834
 
8251
9835
  <div class="mt-6">
8252
9836
  <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Bio</label>
8253
9837
  <textarea
8254
- name="bio"
9838
+ name="profile_bio"
8255
9839
  rows="3"
9840
+ placeholder="Short bio or description"
8256
9841
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8257
- >${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.bio || "")}</textarea>
9842
+ >${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.bio || "")}</textarea>
8258
9843
  </div>
8259
9844
  </div>
8260
9845
 
@@ -8454,11 +10039,11 @@ function renderUserEditPage(data) {
8454
10039
  user: data.user,
8455
10040
  content: pageContent
8456
10041
  };
8457
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
10042
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
8458
10043
  }
8459
10044
 
8460
10045
  // src/templates/pages/admin-user-new.template.ts
8461
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
10046
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
8462
10047
  function renderUserNewPage(data) {
8463
10048
  const pageContent = `
8464
10049
  <div>
@@ -8497,8 +10082,8 @@ function renderUserNewPage(data) {
8497
10082
 
8498
10083
  <!-- Alert Messages -->
8499
10084
  <div id="form-messages">
8500
- ${data.error ? chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
8501
- ${data.success ? chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
10085
+ ${data.error ? chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
10086
+ ${data.success ? chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
8502
10087
  </div>
8503
10088
 
8504
10089
  <!-- User New Form -->
@@ -8742,11 +10327,11 @@ function renderUserNewPage(data) {
8742
10327
  user: data.user,
8743
10328
  content: pageContent
8744
10329
  };
8745
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
10330
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
8746
10331
  }
8747
10332
 
8748
10333
  // src/templates/pages/admin-users-list.template.ts
8749
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
10334
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
8750
10335
  function renderUsersListPage(data) {
8751
10336
  const columns = [
8752
10337
  {
@@ -8897,8 +10482,8 @@ function renderUsersListPage(data) {
8897
10482
  </div>
8898
10483
 
8899
10484
  <!-- Alert Messages -->
8900
- ${data.error ? chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
8901
- ${data.success ? chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
10485
+ ${data.error ? chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
10486
+ ${data.success ? chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
8902
10487
 
8903
10488
  <!-- Stats -->
8904
10489
  <div class="mb-6">
@@ -9075,10 +10660,10 @@ function renderUsersListPage(data) {
9075
10660
  </div>
9076
10661
 
9077
10662
  <!-- Users Table -->
9078
- ${chunkAZLU3ROK_cjs.renderTable(tableData)}
10663
+ ${chunkBZC4FYW7_cjs.renderTable(tableData)}
9079
10664
 
9080
10665
  <!-- Pagination -->
9081
- ${data.pagination ? chunkAZLU3ROK_cjs.renderPagination(data.pagination) : ""}
10666
+ ${data.pagination ? chunkBZC4FYW7_cjs.renderPagination(data.pagination) : ""}
9082
10667
  </div>
9083
10668
 
9084
10669
  <script>
@@ -9149,12 +10734,12 @@ function renderUsersListPage(data) {
9149
10734
  version: data.version,
9150
10735
  content: pageContent
9151
10736
  };
9152
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
10737
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
9153
10738
  }
9154
10739
 
9155
10740
  // src/routes/admin-users.ts
9156
10741
  var userRoutes = new hono.Hono();
9157
- userRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
10742
+ userRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
9158
10743
  userRoutes.get("/", (c) => {
9159
10744
  return c.redirect("/admin/dashboard");
9160
10745
  });
@@ -9253,12 +10838,12 @@ userRoutes.put("/profile", async (c) => {
9253
10838
  const db = c.env.DB;
9254
10839
  try {
9255
10840
  const formData = await c.req.formData();
9256
- const firstName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("first_name")?.toString());
9257
- const lastName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9258
- const username = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("username")?.toString());
10841
+ const firstName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("first_name")?.toString());
10842
+ const lastName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("last_name")?.toString());
10843
+ const username = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("username")?.toString());
9259
10844
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9260
- const phone = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9261
- const bio = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
10845
+ const phone = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
10846
+ const bio = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9262
10847
  const timezone = formData.get("timezone")?.toString() || "UTC";
9263
10848
  const language = formData.get("language")?.toString() || "en";
9264
10849
  const emailNotifications = formData.get("email_notifications") === "1";
@@ -9309,7 +10894,7 @@ userRoutes.put("/profile", async (c) => {
9309
10894
  Date.now(),
9310
10895
  user.userId
9311
10896
  ).run();
9312
- await chunkYYV3XQOQ_cjs.logActivity(
10897
+ await chunkT3YIKW2A_cjs.logActivity(
9313
10898
  db,
9314
10899
  user.userId,
9315
10900
  "profile.update",
@@ -9372,7 +10957,7 @@ userRoutes.post("/profile/avatar", async (c) => {
9372
10957
  SELECT first_name, last_name FROM users WHERE id = ?
9373
10958
  `);
9374
10959
  const userData = await userStmt.bind(user.userId).first();
9375
- await chunkYYV3XQOQ_cjs.logActivity(
10960
+ await chunkT3YIKW2A_cjs.logActivity(
9376
10961
  db,
9377
10962
  user.userId,
9378
10963
  "profile.avatar_update",
@@ -9443,7 +11028,7 @@ userRoutes.post("/profile/password", async (c) => {
9443
11028
  dismissible: true
9444
11029
  }));
9445
11030
  }
9446
- const validPassword = await chunkYYV3XQOQ_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
11031
+ const validPassword = await chunkT3YIKW2A_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
9447
11032
  if (!validPassword) {
9448
11033
  return c.html(renderAlert2({
9449
11034
  type: "error",
@@ -9451,7 +11036,7 @@ userRoutes.post("/profile/password", async (c) => {
9451
11036
  dismissible: true
9452
11037
  }));
9453
11038
  }
9454
- const newPasswordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(newPassword);
11039
+ const newPasswordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(newPassword);
9455
11040
  const historyStmt = db.prepare(`
9456
11041
  INSERT INTO password_history (id, user_id, password_hash, created_at)
9457
11042
  VALUES (?, ?, ?, ?)
@@ -9467,7 +11052,7 @@ userRoutes.post("/profile/password", async (c) => {
9467
11052
  WHERE id = ?
9468
11053
  `);
9469
11054
  await updateStmt.bind(newPasswordHash, Date.now(), user.userId).run();
9470
- await chunkYYV3XQOQ_cjs.logActivity(
11055
+ await chunkT3YIKW2A_cjs.logActivity(
9471
11056
  db,
9472
11057
  user.userId,
9473
11058
  "profile.password_change",
@@ -9534,7 +11119,7 @@ userRoutes.get("/users", async (c) => {
9534
11119
  `);
9535
11120
  const countResult = await countStmt.bind(...params).first();
9536
11121
  const totalUsers = countResult?.total || 0;
9537
- await chunkYYV3XQOQ_cjs.logActivity(
11122
+ await chunkT3YIKW2A_cjs.logActivity(
9538
11123
  db,
9539
11124
  user.userId,
9540
11125
  "users.list_view",
@@ -9636,12 +11221,12 @@ userRoutes.post("/users/new", async (c) => {
9636
11221
  const user = c.get("user");
9637
11222
  try {
9638
11223
  const formData = await c.req.formData();
9639
- const firstName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("first_name")?.toString());
9640
- const lastName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9641
- const username = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("username")?.toString());
11224
+ const firstName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("first_name")?.toString());
11225
+ const lastName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("last_name")?.toString());
11226
+ const username = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("username")?.toString());
9642
11227
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9643
- const phone = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9644
- const bio = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
11228
+ const phone = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
11229
+ const bio = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9645
11230
  const role = formData.get("role")?.toString() || "viewer";
9646
11231
  const password = formData.get("password")?.toString() || "";
9647
11232
  const confirmPassword = formData.get("confirm_password")?.toString() || "";
@@ -9688,7 +11273,7 @@ userRoutes.post("/users/new", async (c) => {
9688
11273
  dismissible: true
9689
11274
  }));
9690
11275
  }
9691
- const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
11276
+ const passwordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(password);
9692
11277
  const userId = crypto.randomUUID();
9693
11278
  const createStmt = db.prepare(`
9694
11279
  INSERT INTO users (
@@ -9711,7 +11296,7 @@ userRoutes.post("/users/new", async (c) => {
9711
11296
  Date.now(),
9712
11297
  Date.now()
9713
11298
  ).run();
9714
- await chunkYYV3XQOQ_cjs.logActivity(
11299
+ await chunkT3YIKW2A_cjs.logActivity(
9715
11300
  db,
9716
11301
  user.userId,
9717
11302
  "user!.create",
@@ -9749,7 +11334,7 @@ userRoutes.get("/users/:id", async (c) => {
9749
11334
  if (!userRecord) {
9750
11335
  return c.json({ error: "User not found" }, 404);
9751
11336
  }
9752
- await chunkYYV3XQOQ_cjs.logActivity(
11337
+ await chunkT3YIKW2A_cjs.logActivity(
9753
11338
  db,
9754
11339
  user.userId,
9755
11340
  "user!.view",
@@ -9788,7 +11373,7 @@ userRoutes.get("/users/:id/edit", async (c) => {
9788
11373
  const userId = c.req.param("id");
9789
11374
  try {
9790
11375
  const userStmt = db.prepare(`
9791
- SELECT id, email, username, first_name, last_name, phone, bio, avatar_url,
11376
+ SELECT id, email, username, first_name, last_name, phone, avatar_url,
9792
11377
  role, is_active, email_verified, two_factor_enabled, created_at, last_login_at
9793
11378
  FROM users
9794
11379
  WHERE id = ?
@@ -9801,6 +11386,21 @@ userRoutes.get("/users/:id/edit", async (c) => {
9801
11386
  dismissible: true
9802
11387
  }), 404);
9803
11388
  }
11389
+ const profileStmt = db.prepare(`
11390
+ SELECT display_name, bio, company, job_title, website, location, date_of_birth
11391
+ FROM user_profiles
11392
+ WHERE user_id = ?
11393
+ `);
11394
+ const profileData = await profileStmt.bind(userId).first();
11395
+ const profile = profileData ? {
11396
+ displayName: profileData.display_name,
11397
+ bio: profileData.bio,
11398
+ company: profileData.company,
11399
+ jobTitle: profileData.job_title,
11400
+ website: profileData.website,
11401
+ location: profileData.location,
11402
+ dateOfBirth: profileData.date_of_birth
11403
+ } : void 0;
9804
11404
  const editData = {
9805
11405
  id: userToEdit.id,
9806
11406
  email: userToEdit.email,
@@ -9808,14 +11408,14 @@ userRoutes.get("/users/:id/edit", async (c) => {
9808
11408
  firstName: userToEdit.first_name || "",
9809
11409
  lastName: userToEdit.last_name || "",
9810
11410
  phone: userToEdit.phone,
9811
- bio: userToEdit.bio,
9812
11411
  avatarUrl: userToEdit.avatar_url,
9813
11412
  role: userToEdit.role,
9814
11413
  isActive: Boolean(userToEdit.is_active),
9815
11414
  emailVerified: Boolean(userToEdit.email_verified),
9816
11415
  twoFactorEnabled: Boolean(userToEdit.two_factor_enabled),
9817
11416
  createdAt: userToEdit.created_at,
9818
- lastLoginAt: userToEdit.last_login_at
11417
+ lastLoginAt: userToEdit.last_login_at,
11418
+ profile
9819
11419
  };
9820
11420
  const pageData = {
9821
11421
  userToEdit: editData,
@@ -9831,7 +11431,7 @@ userRoutes.get("/users/:id/edit", async (c) => {
9831
11431
  console.error("User edit page error:", error);
9832
11432
  return c.html(renderAlert2({
9833
11433
  type: "error",
9834
- message: "Failed to load user!. Please try again.",
11434
+ message: "Failed to load user. Please try again.",
9835
11435
  dismissible: true
9836
11436
  }), 500);
9837
11437
  }
@@ -9842,15 +11442,22 @@ userRoutes.put("/users/:id", async (c) => {
9842
11442
  const userId = c.req.param("id");
9843
11443
  try {
9844
11444
  const formData = await c.req.formData();
9845
- const firstName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("first_name")?.toString());
9846
- const lastName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9847
- const username = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("username")?.toString());
11445
+ const firstName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("first_name")?.toString());
11446
+ const lastName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("last_name")?.toString());
11447
+ const username = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("username")?.toString());
9848
11448
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9849
- const phone = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9850
- const bio = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
11449
+ const phone = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9851
11450
  const role = formData.get("role")?.toString() || "viewer";
9852
11451
  const isActive = formData.get("is_active") === "1";
9853
11452
  const emailVerified = formData.get("email_verified") === "1";
11453
+ const profileDisplayName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("profile_display_name")?.toString()) || null;
11454
+ const profileBio = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("profile_bio")?.toString()) || null;
11455
+ const profileCompany = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("profile_company")?.toString()) || null;
11456
+ const profileJobTitle = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("profile_job_title")?.toString()) || null;
11457
+ const profileWebsite = formData.get("profile_website")?.toString()?.trim() || null;
11458
+ const profileLocation = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("profile_location")?.toString()) || null;
11459
+ const profileDateOfBirthStr = formData.get("profile_date_of_birth")?.toString()?.trim() || null;
11460
+ const profileDateOfBirth = profileDateOfBirthStr ? new Date(profileDateOfBirthStr).getTime() : null;
9854
11461
  if (!firstName || !lastName || !username || !email) {
9855
11462
  return c.html(renderAlert2({
9856
11463
  type: "error",
@@ -9866,6 +11473,17 @@ userRoutes.put("/users/:id", async (c) => {
9866
11473
  dismissible: true
9867
11474
  }));
9868
11475
  }
11476
+ if (profileWebsite) {
11477
+ try {
11478
+ new URL(profileWebsite);
11479
+ } catch {
11480
+ return c.html(renderAlert2({
11481
+ type: "error",
11482
+ message: "Please enter a valid website URL.",
11483
+ dismissible: true
11484
+ }));
11485
+ }
11486
+ }
9869
11487
  const checkStmt = db.prepare(`
9870
11488
  SELECT id FROM users
9871
11489
  WHERE (username = ? OR email = ?) AND id != ?
@@ -9874,14 +11492,14 @@ userRoutes.put("/users/:id", async (c) => {
9874
11492
  if (existingUser) {
9875
11493
  return c.html(renderAlert2({
9876
11494
  type: "error",
9877
- message: "Username or email is already taken by another user!.",
11495
+ message: "Username or email is already taken by another user.",
9878
11496
  dismissible: true
9879
11497
  }));
9880
11498
  }
9881
11499
  const updateStmt = db.prepare(`
9882
11500
  UPDATE users SET
9883
11501
  first_name = ?, last_name = ?, username = ?, email = ?,
9884
- phone = ?, bio = ?, role = ?, is_active = ?, email_verified = ?,
11502
+ phone = ?, role = ?, is_active = ?, email_verified = ?,
9885
11503
  updated_at = ?
9886
11504
  WHERE id = ?
9887
11505
  `);
@@ -9891,20 +11509,63 @@ userRoutes.put("/users/:id", async (c) => {
9891
11509
  username,
9892
11510
  email,
9893
11511
  phone,
9894
- bio,
9895
11512
  role,
9896
11513
  isActive ? 1 : 0,
9897
11514
  emailVerified ? 1 : 0,
9898
11515
  Date.now(),
9899
11516
  userId
9900
11517
  ).run();
9901
- await chunkYYV3XQOQ_cjs.logActivity(
11518
+ const hasProfileData = profileDisplayName || profileBio || profileCompany || profileJobTitle || profileWebsite || profileLocation || profileDateOfBirth;
11519
+ if (hasProfileData) {
11520
+ const now = Date.now();
11521
+ const profileCheckStmt = db.prepare(`SELECT id FROM user_profiles WHERE user_id = ?`);
11522
+ const existingProfile = await profileCheckStmt.bind(userId).first();
11523
+ if (existingProfile) {
11524
+ const updateProfileStmt = db.prepare(`
11525
+ UPDATE user_profiles SET
11526
+ display_name = ?, bio = ?, company = ?, job_title = ?,
11527
+ website = ?, location = ?, date_of_birth = ?, updated_at = ?
11528
+ WHERE user_id = ?
11529
+ `);
11530
+ await updateProfileStmt.bind(
11531
+ profileDisplayName,
11532
+ profileBio,
11533
+ profileCompany,
11534
+ profileJobTitle,
11535
+ profileWebsite,
11536
+ profileLocation,
11537
+ profileDateOfBirth,
11538
+ now,
11539
+ userId
11540
+ ).run();
11541
+ } else {
11542
+ const profileId = `profile_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
11543
+ const insertProfileStmt = db.prepare(`
11544
+ INSERT INTO user_profiles (id, user_id, display_name, bio, company, job_title, website, location, date_of_birth, created_at, updated_at)
11545
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
11546
+ `);
11547
+ await insertProfileStmt.bind(
11548
+ profileId,
11549
+ userId,
11550
+ profileDisplayName,
11551
+ profileBio,
11552
+ profileCompany,
11553
+ profileJobTitle,
11554
+ profileWebsite,
11555
+ profileLocation,
11556
+ profileDateOfBirth,
11557
+ now,
11558
+ now
11559
+ ).run();
11560
+ }
11561
+ }
11562
+ await chunkT3YIKW2A_cjs.logActivity(
9902
11563
  db,
9903
11564
  user.userId,
9904
- "user!.update",
11565
+ "user.update",
9905
11566
  "users",
9906
11567
  userId,
9907
- { fields: ["first_name", "last_name", "username", "email", "phone", "bio", "role", "is_active", "email_verified"] },
11568
+ { fields: ["first_name", "last_name", "username", "email", "phone", "role", "is_active", "email_verified", "profile"] },
9908
11569
  c.req.header("x-forwarded-for") || c.req.header("cf-connecting-ip"),
9909
11570
  c.req.header("user-agent")
9910
11571
  );
@@ -9917,7 +11578,7 @@ userRoutes.put("/users/:id", async (c) => {
9917
11578
  console.error("User update error:", error);
9918
11579
  return c.html(renderAlert2({
9919
11580
  type: "error",
9920
- message: "Failed to update user!. Please try again.",
11581
+ message: "Failed to update user. Please try again.",
9921
11582
  dismissible: true
9922
11583
  }));
9923
11584
  }
@@ -9943,7 +11604,7 @@ userRoutes.post("/users/:id/toggle", async (c) => {
9943
11604
  UPDATE users SET is_active = ?, updated_at = ? WHERE id = ?
9944
11605
  `);
9945
11606
  await toggleStmt.bind(active ? 1 : 0, Date.now(), userId).run();
9946
- await chunkYYV3XQOQ_cjs.logActivity(
11607
+ await chunkT3YIKW2A_cjs.logActivity(
9947
11608
  db,
9948
11609
  user.userId,
9949
11610
  active ? "user.activate" : "user.deactivate",
@@ -9984,7 +11645,7 @@ userRoutes.delete("/users/:id", async (c) => {
9984
11645
  DELETE FROM users WHERE id = ?
9985
11646
  `);
9986
11647
  await deleteStmt.bind(userId).run();
9987
- await chunkYYV3XQOQ_cjs.logActivity(
11648
+ await chunkT3YIKW2A_cjs.logActivity(
9988
11649
  db,
9989
11650
  user.userId,
9990
11651
  "user!.hard_delete",
@@ -10003,7 +11664,7 @@ userRoutes.delete("/users/:id", async (c) => {
10003
11664
  UPDATE users SET is_active = 0, updated_at = ? WHERE id = ?
10004
11665
  `);
10005
11666
  await deleteStmt.bind(Date.now(), userId).run();
10006
- await chunkYYV3XQOQ_cjs.logActivity(
11667
+ await chunkT3YIKW2A_cjs.logActivity(
10007
11668
  db,
10008
11669
  user.userId,
10009
11670
  "user!.soft_delete",
@@ -10030,8 +11691,8 @@ userRoutes.post("/invite-user", async (c) => {
10030
11691
  const formData = await c.req.formData();
10031
11692
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
10032
11693
  const role = formData.get("role")?.toString()?.trim() || "viewer";
10033
- const firstName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("first_name")?.toString());
10034
- const lastName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("last_name")?.toString());
11694
+ const firstName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("first_name")?.toString());
11695
+ const lastName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("last_name")?.toString());
10035
11696
  if (!email || !firstName || !lastName) {
10036
11697
  return c.json({ error: "Email, first name, and last name are required" }, 400);
10037
11698
  }
@@ -10069,7 +11730,7 @@ userRoutes.post("/invite-user", async (c) => {
10069
11730
  Date.now(),
10070
11731
  Date.now()
10071
11732
  ).run();
10072
- await chunkYYV3XQOQ_cjs.logActivity(
11733
+ await chunkT3YIKW2A_cjs.logActivity(
10073
11734
  db,
10074
11735
  user.userId,
10075
11736
  "user!.invite_sent",
@@ -10126,7 +11787,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
10126
11787
  Date.now(),
10127
11788
  userId
10128
11789
  ).run();
10129
- await chunkYYV3XQOQ_cjs.logActivity(
11790
+ await chunkT3YIKW2A_cjs.logActivity(
10130
11791
  db,
10131
11792
  user.userId,
10132
11793
  "user!.invitation_resent",
@@ -10162,7 +11823,7 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
10162
11823
  }
10163
11824
  const deleteStmt = db.prepare(`DELETE FROM users WHERE id = ?`);
10164
11825
  await deleteStmt.bind(userId).run();
10165
- await chunkYYV3XQOQ_cjs.logActivity(
11826
+ await chunkT3YIKW2A_cjs.logActivity(
10166
11827
  db,
10167
11828
  user.userId,
10168
11829
  "user!.invitation_cancelled",
@@ -10245,7 +11906,7 @@ userRoutes.get("/activity-logs", async (c) => {
10245
11906
  ...log,
10246
11907
  details: log.details ? JSON.parse(log.details) : null
10247
11908
  }));
10248
- await chunkYYV3XQOQ_cjs.logActivity(
11909
+ await chunkT3YIKW2A_cjs.logActivity(
10249
11910
  db,
10250
11911
  user.userId,
10251
11912
  "activity.logs_viewed",
@@ -10352,7 +12013,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
10352
12013
  csvRows.push(row.join(","));
10353
12014
  }
10354
12015
  const csvContent = csvRows.join("\n");
10355
- await chunkYYV3XQOQ_cjs.logActivity(
12016
+ await chunkT3YIKW2A_cjs.logActivity(
10356
12017
  db,
10357
12018
  user.userId,
10358
12019
  "activity.logs_exported",
@@ -10570,7 +12231,7 @@ function getFileIcon(mimeType) {
10570
12231
  }
10571
12232
 
10572
12233
  // src/templates/pages/admin-media-library.template.ts
10573
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
12234
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
10574
12235
  function renderMediaLibraryPage(data) {
10575
12236
  const pageContent = `
10576
12237
  <div>
@@ -11505,7 +13166,7 @@ function renderMediaLibraryPage(data) {
11505
13166
  version: data.version,
11506
13167
  content: pageContent
11507
13168
  };
11508
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
13169
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
11509
13170
  }
11510
13171
 
11511
13172
  // src/templates/components/media-file-details.template.ts
@@ -11691,7 +13352,7 @@ var fileValidationSchema2 = zod.z.object({
11691
13352
  // 50MB max
11692
13353
  });
11693
13354
  var adminMediaRoutes = new hono.Hono();
11694
- adminMediaRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
13355
+ adminMediaRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
11695
13356
  adminMediaRoutes.get("/", async (c) => {
11696
13357
  try {
11697
13358
  const user = c.get("user");
@@ -12277,7 +13938,7 @@ adminMediaRoutes.put("/:id", async (c) => {
12277
13938
  `);
12278
13939
  }
12279
13940
  });
12280
- adminMediaRoutes.delete("/cleanup", chunkYYV3XQOQ_cjs.requireRole("admin"), async (c) => {
13941
+ adminMediaRoutes.delete("/cleanup", chunkT3YIKW2A_cjs.requireRole("admin"), async (c) => {
12281
13942
  try {
12282
13943
  const db = c.env.DB;
12283
13944
  const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
@@ -12527,7 +14188,7 @@ function formatFileSize(bytes) {
12527
14188
  }
12528
14189
 
12529
14190
  // src/templates/pages/admin-plugins-list.template.ts
12530
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
14191
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
12531
14192
  function renderPluginsListPage(data) {
12532
14193
  const categories = [
12533
14194
  { value: "content", label: "Content Management" },
@@ -13007,7 +14668,7 @@ function renderPluginsListPage(data) {
13007
14668
  version: data.version,
13008
14669
  content: pageContent
13009
14670
  };
13010
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
14671
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
13011
14672
  }
13012
14673
  function renderPluginCard(plugin) {
13013
14674
  const statusColors = {
@@ -13662,7 +15323,7 @@ function renderPluginSettingsPage(data) {
13662
15323
  user,
13663
15324
  content: pageContent
13664
15325
  };
13665
- return chunkAZLU3ROK_cjs.renderAdminLayout(layoutData);
15326
+ return chunkBZC4FYW7_cjs.renderAdminLayout(layoutData);
13666
15327
  }
13667
15328
  function renderStatusBadge(status) {
13668
15329
  const statusColors = {
@@ -14003,7 +15664,7 @@ function formatTimestamp(timestamp) {
14003
15664
 
14004
15665
  // src/routes/admin-plugins.ts
14005
15666
  var adminPluginRoutes = new hono.Hono();
14006
- adminPluginRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
15667
+ adminPluginRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
14007
15668
  var AVAILABLE_PLUGINS = [
14008
15669
  {
14009
15670
  id: "third-party-faq",
@@ -14108,6 +15769,19 @@ var AVAILABLE_PLUGINS = [
14108
15769
  permissions: [],
14109
15770
  dependencies: [],
14110
15771
  is_core: true
15772
+ },
15773
+ {
15774
+ id: "ai-search",
15775
+ name: "ai-search-plugin",
15776
+ display_name: "AI Search",
15777
+ description: "Advanced search with Cloudflare AI Search. Full-text search, semantic search, and advanced filtering across all content collections.",
15778
+ version: "1.0.0",
15779
+ author: "SonicJS Team",
15780
+ category: "search",
15781
+ icon: "\u{1F50D}",
15782
+ permissions: [],
15783
+ dependencies: [],
15784
+ is_core: true
14111
15785
  }
14112
15786
  ];
14113
15787
  adminPluginRoutes.get("/", async (c) => {
@@ -14117,7 +15791,7 @@ adminPluginRoutes.get("/", async (c) => {
14117
15791
  if (user?.role !== "admin") {
14118
15792
  return c.text("Access denied", 403);
14119
15793
  }
14120
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
15794
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
14121
15795
  let installedPlugins = [];
14122
15796
  let stats = { total: 0, active: 0, inactive: 0, errors: 0, uninstalled: 0 };
14123
15797
  try {
@@ -14186,10 +15860,13 @@ adminPluginRoutes.get("/:id", async (c) => {
14186
15860
  const user = c.get("user");
14187
15861
  const db = c.env.DB;
14188
15862
  const pluginId = c.req.param("id");
15863
+ if (pluginId === "ai-search") {
15864
+ return c.text("", 404);
15865
+ }
14189
15866
  if (user?.role !== "admin") {
14190
15867
  return c.redirect("/admin/plugins");
14191
15868
  }
14192
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
15869
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
14193
15870
  const plugin = await pluginService.getPlugin(pluginId);
14194
15871
  if (!plugin) {
14195
15872
  return c.text("Plugin not found", 404);
@@ -14243,7 +15920,7 @@ adminPluginRoutes.post("/:id/activate", async (c) => {
14243
15920
  if (user?.role !== "admin") {
14244
15921
  return c.json({ error: "Access denied" }, 403);
14245
15922
  }
14246
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
15923
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
14247
15924
  await pluginService.activatePlugin(pluginId);
14248
15925
  return c.json({ success: true });
14249
15926
  } catch (error) {
@@ -14260,7 +15937,7 @@ adminPluginRoutes.post("/:id/deactivate", async (c) => {
14260
15937
  if (user?.role !== "admin") {
14261
15938
  return c.json({ error: "Access denied" }, 403);
14262
15939
  }
14263
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
15940
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
14264
15941
  await pluginService.deactivatePlugin(pluginId);
14265
15942
  return c.json({ success: true });
14266
15943
  } catch (error) {
@@ -14277,7 +15954,7 @@ adminPluginRoutes.post("/install", async (c) => {
14277
15954
  return c.json({ error: "Access denied" }, 403);
14278
15955
  }
14279
15956
  const body = await c.req.json();
14280
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
15957
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
14281
15958
  if (body.name === "faq-plugin") {
14282
15959
  const faqPlugin = await pluginService.installPlugin({
14283
15960
  id: "third-party-faq",
@@ -14478,6 +16155,33 @@ adminPluginRoutes.post("/install", async (c) => {
14478
16155
  });
14479
16156
  return c.json({ success: true, plugin: easyMdxPlugin2 });
14480
16157
  }
16158
+ if (body.name === "ai-search-plugin" || body.name === "ai-search") {
16159
+ const defaultSettings = {
16160
+ enabled: true,
16161
+ ai_mode_enabled: true,
16162
+ selected_collections: [],
16163
+ dismissed_collections: [],
16164
+ autocomplete_enabled: true,
16165
+ cache_duration: 1,
16166
+ results_limit: 20,
16167
+ index_media: false
16168
+ };
16169
+ const aiSearchPlugin = await pluginService.installPlugin({
16170
+ id: "ai-search",
16171
+ name: "ai-search-plugin",
16172
+ display_name: "AI Search",
16173
+ description: "Advanced search with Cloudflare AI Search. Full-text search, semantic search, and advanced filtering across all content collections.",
16174
+ version: "1.0.0",
16175
+ author: "SonicJS Team",
16176
+ category: "search",
16177
+ icon: "\u{1F50D}",
16178
+ permissions: [],
16179
+ dependencies: [],
16180
+ is_core: true,
16181
+ settings: defaultSettings
16182
+ });
16183
+ return c.json({ success: true, plugin: aiSearchPlugin });
16184
+ }
14481
16185
  if (body.name === "turnstile-plugin") {
14482
16186
  const turnstilePlugin = await pluginService.installPlugin({
14483
16187
  id: "turnstile",
@@ -14520,7 +16224,7 @@ adminPluginRoutes.post("/:id/uninstall", async (c) => {
14520
16224
  if (user?.role !== "admin") {
14521
16225
  return c.json({ error: "Access denied" }, 403);
14522
16226
  }
14523
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
16227
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
14524
16228
  await pluginService.uninstallPlugin(pluginId);
14525
16229
  return c.json({ success: true });
14526
16230
  } catch (error) {
@@ -14538,7 +16242,7 @@ adminPluginRoutes.post("/:id/settings", async (c) => {
14538
16242
  return c.json({ error: "Access denied" }, 403);
14539
16243
  }
14540
16244
  const settings = await c.req.json();
14541
- const pluginService = new chunkILZ3DP4I_cjs.PluginService(db);
16245
+ const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
14542
16246
  await pluginService.updatePluginSettings(pluginId, settings);
14543
16247
  return c.json({ success: true });
14544
16248
  } catch (error) {
@@ -14559,7 +16263,7 @@ function formatLastUpdated(timestamp) {
14559
16263
  }
14560
16264
 
14561
16265
  // src/templates/pages/admin-logs-list.template.ts
14562
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
16266
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
14563
16267
  function renderLogsListPage(data) {
14564
16268
  const { logs, pagination, filters, user } = data;
14565
16269
  const content = `
@@ -14870,7 +16574,7 @@ function renderLogsListPage(data) {
14870
16574
  user,
14871
16575
  content
14872
16576
  };
14873
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
16577
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
14874
16578
  }
14875
16579
  function renderLogDetailsPage(data) {
14876
16580
  const { log, user } = data;
@@ -15082,7 +16786,7 @@ function renderLogDetailsPage(data) {
15082
16786
  </div>
15083
16787
  </div>
15084
16788
  `;
15085
- return chunkAZLU3ROK_cjs.adminLayoutV2({
16789
+ return chunkBZC4FYW7_cjs.adminLayoutV2({
15086
16790
  title: `Log Details - ${log.id}`,
15087
16791
  user,
15088
16792
  content
@@ -15325,7 +17029,7 @@ function renderLogConfigPage(data) {
15325
17029
 
15326
17030
  <script src="https://unpkg.com/htmx.org@1.9.6"></script>
15327
17031
  `;
15328
- return chunkAZLU3ROK_cjs.adminLayoutV2({
17032
+ return chunkBZC4FYW7_cjs.adminLayoutV2({
15329
17033
  title: "Log Configuration",
15330
17034
  user,
15331
17035
  content
@@ -15334,7 +17038,7 @@ function renderLogConfigPage(data) {
15334
17038
 
15335
17039
  // src/routes/admin-logs.ts
15336
17040
  var adminLogsRoutes = new hono.Hono();
15337
- adminLogsRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
17041
+ adminLogsRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
15338
17042
  adminLogsRoutes.get("/", async (c) => {
15339
17043
  try {
15340
17044
  const user = c.get("user");
@@ -15706,7 +17410,7 @@ adminDesignRoutes.get("/", (c) => {
15706
17410
  role: user.role
15707
17411
  } : void 0
15708
17412
  };
15709
- return c.html(chunkAZLU3ROK_cjs.renderDesignPage(pageData));
17413
+ return c.html(chunkBZC4FYW7_cjs.renderDesignPage(pageData));
15710
17414
  });
15711
17415
  var adminCheckboxRoutes = new hono.Hono();
15712
17416
  adminCheckboxRoutes.get("/", (c) => {
@@ -15718,7 +17422,7 @@ adminCheckboxRoutes.get("/", (c) => {
15718
17422
  role: user.role
15719
17423
  } : void 0
15720
17424
  };
15721
- return c.html(chunkAZLU3ROK_cjs.renderCheckboxPage(pageData));
17425
+ return c.html(chunkBZC4FYW7_cjs.renderCheckboxPage(pageData));
15722
17426
  });
15723
17427
 
15724
17428
  // src/templates/pages/admin-testimonials-form.template.ts
@@ -15746,7 +17450,7 @@ function renderTestimonialsForm(data) {
15746
17450
  </div>
15747
17451
  </div>
15748
17452
 
15749
- ${message ? chunkAZLU3ROK_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
17453
+ ${message ? chunkBZC4FYW7_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
15750
17454
 
15751
17455
  <!-- Form -->
15752
17456
  <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
@@ -15975,7 +17679,7 @@ function renderTestimonialsForm(data) {
15975
17679
  user: data.user,
15976
17680
  content: pageContent
15977
17681
  };
15978
- return chunkAZLU3ROK_cjs.renderAdminLayout(layoutData);
17682
+ return chunkBZC4FYW7_cjs.renderAdminLayout(layoutData);
15979
17683
  }
15980
17684
  function escapeHtml4(unsafe) {
15981
17685
  return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
@@ -16001,7 +17705,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
16001
17705
  const offset = (currentPage - 1) * limit;
16002
17706
  const db = c.env?.DB;
16003
17707
  if (!db) {
16004
- return c.html(chunkAZLU3ROK_cjs.renderTestimonialsList({
17708
+ return c.html(chunkBZC4FYW7_cjs.renderTestimonialsList({
16005
17709
  testimonials: [],
16006
17710
  totalCount: 0,
16007
17711
  currentPage: 1,
@@ -16041,7 +17745,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
16041
17745
  `;
16042
17746
  const { results: testimonials } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
16043
17747
  const totalPages = Math.ceil(totalCount / limit);
16044
- return c.html(chunkAZLU3ROK_cjs.renderTestimonialsList({
17748
+ return c.html(chunkBZC4FYW7_cjs.renderTestimonialsList({
16045
17749
  testimonials: testimonials || [],
16046
17750
  totalCount,
16047
17751
  currentPage,
@@ -16055,7 +17759,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
16055
17759
  } catch (error) {
16056
17760
  console.error("Error fetching testimonials:", error);
16057
17761
  const user = c.get("user");
16058
- return c.html(chunkAZLU3ROK_cjs.renderTestimonialsList({
17762
+ return c.html(chunkBZC4FYW7_cjs.renderTestimonialsList({
16059
17763
  testimonials: [],
16060
17764
  totalCount: 0,
16061
17765
  currentPage: 1,
@@ -16374,7 +18078,7 @@ function renderCodeExamplesForm(data) {
16374
18078
  </div>
16375
18079
  </div>
16376
18080
 
16377
- ${message ? chunkAZLU3ROK_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
18081
+ ${message ? chunkBZC4FYW7_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
16378
18082
 
16379
18083
  <!-- Form -->
16380
18084
  <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
@@ -16644,7 +18348,7 @@ function renderCodeExamplesForm(data) {
16644
18348
  user: data.user,
16645
18349
  content: pageContent
16646
18350
  };
16647
- return chunkAZLU3ROK_cjs.renderAdminLayout(layoutData);
18351
+ return chunkBZC4FYW7_cjs.renderAdminLayout(layoutData);
16648
18352
  }
16649
18353
  function escapeHtml5(unsafe) {
16650
18354
  return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
@@ -16671,7 +18375,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
16671
18375
  const offset = (currentPage - 1) * limit;
16672
18376
  const db = c.env?.DB;
16673
18377
  if (!db) {
16674
- return c.html(chunkAZLU3ROK_cjs.renderCodeExamplesList({
18378
+ return c.html(chunkBZC4FYW7_cjs.renderCodeExamplesList({
16675
18379
  codeExamples: [],
16676
18380
  totalCount: 0,
16677
18381
  currentPage: 1,
@@ -16711,7 +18415,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
16711
18415
  `;
16712
18416
  const { results: codeExamples } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
16713
18417
  const totalPages = Math.ceil(totalCount / limit);
16714
- return c.html(chunkAZLU3ROK_cjs.renderCodeExamplesList({
18418
+ return c.html(chunkBZC4FYW7_cjs.renderCodeExamplesList({
16715
18419
  codeExamples: codeExamples || [],
16716
18420
  totalCount,
16717
18421
  currentPage,
@@ -16725,7 +18429,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
16725
18429
  } catch (error) {
16726
18430
  console.error("Error fetching code examples:", error);
16727
18431
  const user = c.get("user");
16728
- return c.html(chunkAZLU3ROK_cjs.renderCodeExamplesList({
18432
+ return c.html(chunkBZC4FYW7_cjs.renderCodeExamplesList({
16729
18433
  codeExamples: [],
16730
18434
  totalCount: 0,
16731
18435
  currentPage: 1,
@@ -17114,7 +18818,7 @@ function renderDashboardPage(data) {
17114
18818
  version: data.version,
17115
18819
  content: pageContent
17116
18820
  };
17117
- return chunkAZLU3ROK_cjs.renderAdminLayout(layoutData);
18821
+ return chunkBZC4FYW7_cjs.renderAdminLayout(layoutData);
17118
18822
  }
17119
18823
  function renderStatsCards(stats) {
17120
18824
  const cards = [
@@ -17662,9 +19366,9 @@ function renderStorageUsage(databaseSizeBytes, mediaSizeBytes) {
17662
19366
  }
17663
19367
 
17664
19368
  // src/routes/admin-dashboard.ts
17665
- var VERSION = chunkZWV3EBZ7_cjs.getCoreVersion();
19369
+ var VERSION = chunkYMTTGHEK_cjs.getCoreVersion();
17666
19370
  var router = new hono.Hono();
17667
- router.use("*", chunkYYV3XQOQ_cjs.requireAuth());
19371
+ router.use("*", chunkT3YIKW2A_cjs.requireAuth());
17668
19372
  router.get("/", async (c) => {
17669
19373
  const user = c.get("user");
17670
19374
  try {
@@ -17889,7 +19593,7 @@ router.get("/system-status", async (c) => {
17889
19593
  });
17890
19594
 
17891
19595
  // src/templates/pages/admin-collections-list.template.ts
17892
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
19596
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
17893
19597
 
17894
19598
  // src/templates/components/table.template.ts
17895
19599
  function renderTable2(data) {
@@ -18363,11 +20067,11 @@ function renderCollectionsListPage(data) {
18363
20067
  version: data.version,
18364
20068
  content: pageContent
18365
20069
  };
18366
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
20070
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
18367
20071
  }
18368
20072
 
18369
20073
  // src/templates/pages/admin-collections-form.template.ts
18370
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
20074
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
18371
20075
  function getFieldTypeBadge(fieldType) {
18372
20076
  const typeLabels = {
18373
20077
  "text": "Text",
@@ -18628,7 +20332,7 @@ function renderCollectionFormPage(data) {
18628
20332
  }
18629
20333
  </style>
18630
20334
 
18631
- ${chunkAZLU3ROK_cjs.renderForm(formData)}
20335
+ ${chunkBZC4FYW7_cjs.renderForm(formData)}
18632
20336
 
18633
20337
  ${isEdit && data.managed ? `
18634
20338
  <!-- Read-Only Fields Display for Managed Collections -->
@@ -19417,12 +21121,12 @@ function renderCollectionFormPage(data) {
19417
21121
  version: data.version,
19418
21122
  content: pageContent
19419
21123
  };
19420
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
21124
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
19421
21125
  }
19422
21126
 
19423
21127
  // src/routes/admin-collections.ts
19424
21128
  var adminCollectionsRoutes = new hono.Hono();
19425
- adminCollectionsRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
21129
+ adminCollectionsRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
19426
21130
  adminCollectionsRoutes.get("/", async (c) => {
19427
21131
  try {
19428
21132
  const user = c.get("user");
@@ -20138,7 +21842,7 @@ adminCollectionsRoutes.post("/:collectionId/fields/reorder", async (c) => {
20138
21842
  });
20139
21843
 
20140
21844
  // src/templates/pages/admin-settings.template.ts
20141
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
21845
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
20142
21846
  function renderSettingsPage(data) {
20143
21847
  const activeTab = data.activeTab || "general";
20144
21848
  const pageContent = `
@@ -20520,7 +22224,7 @@ function renderSettingsPage(data) {
20520
22224
  version: data.version,
20521
22225
  content: pageContent
20522
22226
  };
20523
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
22227
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
20524
22228
  }
20525
22229
  function renderTabButton(tabId, label, iconPath, activeTab) {
20526
22230
  const isActive = activeTab === tabId;
@@ -21602,7 +23306,7 @@ function renderDatabaseToolsSettings(settings) {
21602
23306
 
21603
23307
  // src/routes/admin-settings.ts
21604
23308
  var adminSettingsRoutes = new hono.Hono();
21605
- adminSettingsRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
23309
+ adminSettingsRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
21606
23310
  function getMockSettings(user) {
21607
23311
  return {
21608
23312
  general: {
@@ -21770,7 +23474,7 @@ adminSettingsRoutes.get("/database-tools", (c) => {
21770
23474
  adminSettingsRoutes.get("/api/migrations/status", async (c) => {
21771
23475
  try {
21772
23476
  const db = c.env.DB;
21773
- const migrationService = new chunkI4V3VZWF_cjs.MigrationService(db);
23477
+ const migrationService = new chunkIIRVZSP2_cjs.MigrationService(db);
21774
23478
  const status = await migrationService.getMigrationStatus();
21775
23479
  return c.json({
21776
23480
  success: true,
@@ -21794,7 +23498,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
21794
23498
  }, 403);
21795
23499
  }
21796
23500
  const db = c.env.DB;
21797
- const migrationService = new chunkI4V3VZWF_cjs.MigrationService(db);
23501
+ const migrationService = new chunkIIRVZSP2_cjs.MigrationService(db);
21798
23502
  const result = await migrationService.runPendingMigrations();
21799
23503
  return c.json({
21800
23504
  success: result.success,
@@ -21812,7 +23516,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
21812
23516
  adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
21813
23517
  try {
21814
23518
  const db = c.env.DB;
21815
- const migrationService = new chunkI4V3VZWF_cjs.MigrationService(db);
23519
+ const migrationService = new chunkIIRVZSP2_cjs.MigrationService(db);
21816
23520
  const validation = await migrationService.validateSchema();
21817
23521
  return c.json({
21818
23522
  success: true,
@@ -22053,9 +23757,8 @@ exports.api_default = api_default;
22053
23757
  exports.api_media_default = api_media_default;
22054
23758
  exports.api_system_default = api_system_default;
22055
23759
  exports.auth_default = auth_default;
22056
- exports.checkAdminUserExists = checkAdminUserExists;
22057
23760
  exports.router = router;
22058
23761
  exports.test_cleanup_default = test_cleanup_default;
22059
23762
  exports.userRoutes = userRoutes;
22060
- //# sourceMappingURL=chunk-UAQL2VWX.cjs.map
22061
- //# sourceMappingURL=chunk-UAQL2VWX.cjs.map
23763
+ //# sourceMappingURL=chunk-N7TDLOUE.cjs.map
23764
+ //# sourceMappingURL=chunk-N7TDLOUE.cjs.map