@sonicjs-cms/core 2.3.17 → 2.5.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 (73) hide show
  1. package/dist/{chunk-CPXAVWCU.js → chunk-3YUHXWSG.js} +278 -3
  2. package/dist/chunk-3YUHXWSG.js.map +1 -0
  3. package/dist/chunk-AI2JJIJX.cjs +211 -0
  4. package/dist/chunk-AI2JJIJX.cjs.map +1 -0
  5. package/dist/{chunk-TMQOLXLY.js → chunk-BHNDALCA.js} +56 -4
  6. package/dist/chunk-BHNDALCA.js.map +1 -0
  7. package/dist/{chunk-IEWLOVP3.cjs → chunk-I4V3VZWF.cjs} +46 -2
  8. package/dist/chunk-I4V3VZWF.cjs.map +1 -0
  9. package/dist/{chunk-DTLB6UIH.cjs → chunk-LWG2MWDA.cjs} +280 -2
  10. package/dist/chunk-LWG2MWDA.cjs.map +1 -0
  11. package/dist/{chunk-QBWD6FKH.js → chunk-OJZ45OJD.js} +559 -281
  12. package/dist/chunk-OJZ45OJD.js.map +1 -0
  13. package/dist/chunk-QDBNW7KQ.js +209 -0
  14. package/dist/chunk-QDBNW7KQ.js.map +1 -0
  15. package/dist/{chunk-VYL6RIV6.js → chunk-TJTWRO4G.js} +5 -5
  16. package/dist/chunk-TJTWRO4G.js.map +1 -0
  17. package/dist/{chunk-74DP754U.cjs → chunk-UAQL2VWX.cjs} +644 -366
  18. package/dist/chunk-UAQL2VWX.cjs.map +1 -0
  19. package/dist/{chunk-B6YJRVFQ.js → chunk-VEL7QRYI.js} +46 -2
  20. package/dist/chunk-VEL7QRYI.js.map +1 -0
  21. package/dist/{chunk-YHJB26RJ.cjs → chunk-YYV3XQOQ.cjs} +6 -6
  22. package/dist/chunk-YYV3XQOQ.cjs.map +1 -0
  23. package/dist/{chunk-2XCJ3HT5.cjs → chunk-ZWV3EBZ7.cjs} +58 -3
  24. package/dist/chunk-ZWV3EBZ7.cjs.map +1 -0
  25. package/dist/{collection-config-FLlGtsh9.d.cts → collection-config-B6gMPunn.d.cts} +9 -1
  26. package/dist/{collection-config-FLlGtsh9.d.ts → collection-config-B6gMPunn.d.ts} +9 -1
  27. package/dist/index.cjs +114 -85
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +4 -4
  30. package/dist/index.d.ts +4 -4
  31. package/dist/index.js +37 -8
  32. package/dist/index.js.map +1 -1
  33. package/dist/middleware.cjs +23 -23
  34. package/dist/middleware.js +2 -2
  35. package/dist/migrations-NIEUFG44.cjs +13 -0
  36. package/dist/{migrations-EOV7NJZ7.cjs.map → migrations-NIEUFG44.cjs.map} +1 -1
  37. package/dist/migrations-TGZKJKV4.js +4 -0
  38. package/dist/{migrations-6HKPNPTK.js.map → migrations-TGZKJKV4.js.map} +1 -1
  39. package/dist/{plugin-bootstrap-C0E3jdz-.d.cts → plugin-bootstrap-SHsdjE6X.d.cts} +1 -1
  40. package/dist/{plugin-bootstrap-CDh0JHtW.d.ts → plugin-bootstrap-dYhD9fQR.d.ts} +1 -1
  41. package/dist/plugin-manager-Baa6xXqB.d.ts +328 -0
  42. package/dist/plugin-manager-vBal9Zip.d.cts +328 -0
  43. package/dist/plugins.cjs +20 -7
  44. package/dist/plugins.d.cts +53 -310
  45. package/dist/plugins.d.ts +53 -310
  46. package/dist/plugins.js +2 -1
  47. package/dist/routes.cjs +25 -24
  48. package/dist/routes.js +5 -4
  49. package/dist/services.cjs +2 -2
  50. package/dist/services.d.cts +2 -2
  51. package/dist/services.d.ts +2 -2
  52. package/dist/services.js +1 -1
  53. package/dist/types.d.cts +1 -1
  54. package/dist/types.d.ts +1 -1
  55. package/dist/utils.cjs +23 -11
  56. package/dist/utils.d.cts +38 -1
  57. package/dist/utils.d.ts +38 -1
  58. package/dist/utils.js +1 -1
  59. package/migrations/027_fix_slug_field_type.sql +18 -0
  60. package/migrations/028_fix_slug_field_type_in_schemas.sql +30 -0
  61. package/package.json +2 -1
  62. package/dist/chunk-2XCJ3HT5.cjs.map +0 -1
  63. package/dist/chunk-74DP754U.cjs.map +0 -1
  64. package/dist/chunk-B6YJRVFQ.js.map +0 -1
  65. package/dist/chunk-CPXAVWCU.js.map +0 -1
  66. package/dist/chunk-DTLB6UIH.cjs.map +0 -1
  67. package/dist/chunk-IEWLOVP3.cjs.map +0 -1
  68. package/dist/chunk-QBWD6FKH.js.map +0 -1
  69. package/dist/chunk-TMQOLXLY.js.map +0 -1
  70. package/dist/chunk-VYL6RIV6.js.map +0 -1
  71. package/dist/chunk-YHJB26RJ.cjs.map +0 -1
  72. package/dist/migrations-6HKPNPTK.js +0 -4
  73. package/dist/migrations-EOV7NJZ7.cjs +0 -13
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var chunk7FOAMNTI_cjs = require('./chunk-7FOAMNTI.cjs');
4
- var chunkYHJB26RJ_cjs = require('./chunk-YHJB26RJ.cjs');
4
+ var chunkYYV3XQOQ_cjs = require('./chunk-YYV3XQOQ.cjs');
5
5
  var chunkILZ3DP4I_cjs = require('./chunk-ILZ3DP4I.cjs');
6
- var chunkIEWLOVP3_cjs = require('./chunk-IEWLOVP3.cjs');
6
+ var chunkI4V3VZWF_cjs = require('./chunk-I4V3VZWF.cjs');
7
7
  var chunkAZLU3ROK_cjs = require('./chunk-AZLU3ROK.cjs');
8
- var chunk2XCJ3HT5_cjs = require('./chunk-2XCJ3HT5.cjs');
8
+ var chunkAI2JJIJX_cjs = require('./chunk-AI2JJIJX.cjs');
9
+ var chunkZWV3EBZ7_cjs = require('./chunk-ZWV3EBZ7.cjs');
9
10
  var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
10
11
  var hono = require('hono');
11
12
  var cors = require('hono/cors');
@@ -16,6 +17,37 @@ var html = require('hono/html');
16
17
  // src/schemas/index.ts
17
18
  var schemaDefinitions = [];
18
19
  var apiContentCrudRoutes = new hono.Hono();
20
+ apiContentCrudRoutes.get("/check-slug", async (c) => {
21
+ try {
22
+ const db = c.env.DB;
23
+ const collectionId = c.req.query("collectionId");
24
+ const slug = c.req.query("slug");
25
+ const excludeId = c.req.query("excludeId");
26
+ if (!collectionId || !slug) {
27
+ return c.json({ error: "collectionId and slug are required" }, 400);
28
+ }
29
+ let query = "SELECT id FROM content WHERE collection_id = ? AND slug = ?";
30
+ const params = [collectionId, slug];
31
+ if (excludeId) {
32
+ query += " AND id != ?";
33
+ params.push(excludeId);
34
+ }
35
+ const existing = await db.prepare(query).bind(...params).first();
36
+ if (existing) {
37
+ return c.json({
38
+ available: false,
39
+ message: "This URL slug is already in use in this collection"
40
+ });
41
+ }
42
+ return c.json({ available: true });
43
+ } catch (error) {
44
+ console.error("Error checking slug:", error);
45
+ return c.json({
46
+ error: "Failed to check slug availability",
47
+ details: error instanceof Error ? error.message : String(error)
48
+ }, 500);
49
+ }
50
+ });
19
51
  apiContentCrudRoutes.get("/:id", async (c) => {
20
52
  try {
21
53
  const id = c.req.param("id");
@@ -44,7 +76,7 @@ apiContentCrudRoutes.get("/:id", async (c) => {
44
76
  }, 500);
45
77
  }
46
78
  });
47
- apiContentCrudRoutes.post("/", chunkYHJB26RJ_cjs.requireAuth(), async (c) => {
79
+ apiContentCrudRoutes.post("/", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
48
80
  try {
49
81
  const db = c.env.DB;
50
82
  const user = c.get("user");
@@ -110,7 +142,7 @@ apiContentCrudRoutes.post("/", chunkYHJB26RJ_cjs.requireAuth(), async (c) => {
110
142
  }, 500);
111
143
  }
112
144
  });
113
- apiContentCrudRoutes.put("/:id", chunkYHJB26RJ_cjs.requireAuth(), async (c) => {
145
+ apiContentCrudRoutes.put("/:id", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
114
146
  try {
115
147
  const id = c.req.param("id");
116
148
  const db = c.env.DB;
@@ -174,7 +206,7 @@ apiContentCrudRoutes.put("/:id", chunkYHJB26RJ_cjs.requireAuth(), async (c) => {
174
206
  }, 500);
175
207
  }
176
208
  });
177
- apiContentCrudRoutes.delete("/:id", chunkYHJB26RJ_cjs.requireAuth(), async (c) => {
209
+ apiContentCrudRoutes.delete("/:id", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
178
210
  try {
179
211
  const id = c.req.param("id");
180
212
  const db = c.env.DB;
@@ -210,7 +242,7 @@ apiRoutes.use("*", async (c, next) => {
210
242
  c.header("X-Response-Time", `${totalTime}ms`);
211
243
  });
212
244
  apiRoutes.use("*", async (c, next) => {
213
- const cacheEnabled = await chunkYHJB26RJ_cjs.isPluginActive(c.env.DB, "core-cache");
245
+ const cacheEnabled = await chunkYYV3XQOQ_cjs.isPluginActive(c.env.DB, "core-cache");
214
246
  c.set("cacheEnabled", cacheEnabled);
215
247
  await next();
216
248
  });
@@ -335,12 +367,12 @@ apiRoutes.get("/content", async (c) => {
335
367
  });
336
368
  }
337
369
  }
338
- const filter = chunk2XCJ3HT5_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
370
+ const filter = chunkZWV3EBZ7_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
339
371
  if (!filter.limit) {
340
372
  filter.limit = 50;
341
373
  }
342
374
  filter.limit = Math.min(filter.limit, 1e3);
343
- const builder3 = new chunk2XCJ3HT5_cjs.QueryFilterBuilder();
375
+ const builder3 = new chunkZWV3EBZ7_cjs.QueryFilterBuilder();
344
376
  const queryResult = builder3.build("content", filter);
345
377
  if (queryResult.errors.length > 0) {
346
378
  return c.json({
@@ -427,7 +459,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
427
459
  if (!collectionResult) {
428
460
  return c.json({ error: "Collection not found" }, 404);
429
461
  }
430
- const filter = chunk2XCJ3HT5_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
462
+ const filter = chunkZWV3EBZ7_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
431
463
  if (!filter.where) {
432
464
  filter.where = { and: [] };
433
465
  }
@@ -443,7 +475,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
443
475
  filter.limit = 50;
444
476
  }
445
477
  filter.limit = Math.min(filter.limit, 1e3);
446
- const builder3 = new chunk2XCJ3HT5_cjs.QueryFilterBuilder();
478
+ const builder3 = new chunkZWV3EBZ7_cjs.QueryFilterBuilder();
447
479
  const queryResult = builder3.build("content", filter);
448
480
  if (queryResult.errors.length > 0) {
449
481
  return c.json({
@@ -568,7 +600,7 @@ var fileValidationSchema = zod.z.object({
568
600
  // 50MB max
569
601
  });
570
602
  var apiMediaRoutes = new hono.Hono();
571
- apiMediaRoutes.use("*", chunkYHJB26RJ_cjs.requireAuth());
603
+ apiMediaRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
572
604
  apiMediaRoutes.post("/upload", async (c) => {
573
605
  try {
574
606
  const user = c.get("user");
@@ -1312,8 +1344,8 @@ apiSystemRoutes.get("/env", (c) => {
1312
1344
  });
1313
1345
  var api_system_default = apiSystemRoutes;
1314
1346
  var adminApiRoutes = new hono.Hono();
1315
- adminApiRoutes.use("*", chunkYHJB26RJ_cjs.requireAuth());
1316
- adminApiRoutes.use("*", chunkYHJB26RJ_cjs.requireRole(["admin", "editor"]));
1347
+ adminApiRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
1348
+ adminApiRoutes.use("*", chunkYYV3XQOQ_cjs.requireRole(["admin", "editor"]));
1317
1349
  adminApiRoutes.get("/stats", async (c) => {
1318
1350
  try {
1319
1351
  const db = c.env.DB;
@@ -1722,7 +1754,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
1722
1754
  });
1723
1755
  adminApiRoutes.get("/migrations/status", async (c) => {
1724
1756
  try {
1725
- const { MigrationService: MigrationService2 } = await import('./migrations-EOV7NJZ7.cjs');
1757
+ const { MigrationService: MigrationService2 } = await import('./migrations-NIEUFG44.cjs');
1726
1758
  const db = c.env.DB;
1727
1759
  const migrationService = new MigrationService2(db);
1728
1760
  const status = await migrationService.getMigrationStatus();
@@ -1747,7 +1779,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
1747
1779
  error: "Unauthorized. Admin access required."
1748
1780
  }, 403);
1749
1781
  }
1750
- const { MigrationService: MigrationService2 } = await import('./migrations-EOV7NJZ7.cjs');
1782
+ const { MigrationService: MigrationService2 } = await import('./migrations-NIEUFG44.cjs');
1751
1783
  const db = c.env.DB;
1752
1784
  const migrationService = new MigrationService2(db);
1753
1785
  const result = await migrationService.runPendingMigrations();
@@ -1766,7 +1798,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
1766
1798
  });
1767
1799
  adminApiRoutes.get("/migrations/validate", async (c) => {
1768
1800
  try {
1769
- const { MigrationService: MigrationService2 } = await import('./migrations-EOV7NJZ7.cjs');
1801
+ const { MigrationService: MigrationService2 } = await import('./migrations-NIEUFG44.cjs');
1770
1802
  const db = c.env.DB;
1771
1803
  const migrationService = new MigrationService2(db);
1772
1804
  const validation = await migrationService.validateSchema();
@@ -1993,18 +2025,40 @@ function renderRegisterPage(data) {
1993
2025
  <div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
1994
2026
  <!-- Logo Section -->
1995
2027
  <div class="sm:mx-auto sm:w-full sm:max-w-md text-center">
1996
- <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-lg bg-white">
1997
- <svg class="h-7 w-7 text-zinc-950" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1998
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
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>
1999
2039
  </svg>
2000
2040
  </div>
2001
- <h1 class="mt-6 text-3xl font-semibold tracking-tight text-white">SonicJS AI</h1>
2002
- <p class="mt-2 text-sm text-zinc-400">Create your account and get started</p>
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>`}
2003
2043
  </div>
2004
2044
 
2005
2045
  <!-- Form Container -->
2006
2046
  <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
2007
2047
  <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
+ ` : ""}
2008
2062
  <!-- Alerts -->
2009
2063
  ${data.error ? `<div class="mb-6">${chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
2010
2064
 
@@ -2119,6 +2173,7 @@ function renderRegisterPage(data) {
2119
2173
  </html>
2120
2174
  `;
2121
2175
  }
2176
+ var adminExistsCache = null;
2122
2177
  async function isRegistrationEnabled(db) {
2123
2178
  try {
2124
2179
  const plugin = await db.prepare("SELECT settings FROM plugins WHERE id = ?").bind("core-auth").first();
@@ -2140,6 +2195,21 @@ async function isFirstUserRegistration(db) {
2140
2195
  return false;
2141
2196
  }
2142
2197
  }
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
+ }
2143
2213
  var baseRegistrationSchema = zod.z.object({
2144
2214
  email: zod.z.string().email("Valid email is required"),
2145
2215
  password: zod.z.string().min(8, "Password must be at least 8 characters"),
@@ -2201,8 +2271,11 @@ authRoutes.get("/register", async (c) => {
2201
2271
  }
2202
2272
  }
2203
2273
  const error = c.req.query("error");
2274
+ const isSetup = c.req.query("setup") === "true";
2204
2275
  const pageData = {
2205
- error: error || void 0
2276
+ error: error || void 0,
2277
+ isSetup: isSetup && isFirstUser
2278
+ // Only show setup message if truly first user
2206
2279
  };
2207
2280
  return c.html(renderRegisterPage(pageData));
2208
2281
  });
@@ -2248,7 +2321,7 @@ authRoutes.post(
2248
2321
  if (existingUser) {
2249
2322
  return c.json({ error: "User with this email or username already exists" }, 400);
2250
2323
  }
2251
- const passwordHash = await chunkYHJB26RJ_cjs.AuthManager.hashPassword(password);
2324
+ const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
2252
2325
  const userId = crypto.randomUUID();
2253
2326
  const now = /* @__PURE__ */ new Date();
2254
2327
  await db.prepare(`
@@ -2268,7 +2341,7 @@ authRoutes.post(
2268
2341
  now.getTime(),
2269
2342
  now.getTime()
2270
2343
  ).run();
2271
- const token = await chunkYHJB26RJ_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer");
2344
+ const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer");
2272
2345
  cookie.setCookie(c, "auth_token", token, {
2273
2346
  httpOnly: true,
2274
2347
  secure: true,
@@ -2321,11 +2394,11 @@ authRoutes.post("/login", async (c) => {
2321
2394
  if (!user) {
2322
2395
  return c.json({ error: "Invalid email or password" }, 401);
2323
2396
  }
2324
- const isValidPassword = await chunkYHJB26RJ_cjs.AuthManager.verifyPassword(password, user.password_hash);
2397
+ const isValidPassword = await chunkYYV3XQOQ_cjs.AuthManager.verifyPassword(password, user.password_hash);
2325
2398
  if (!isValidPassword) {
2326
2399
  return c.json({ error: "Invalid email or password" }, 401);
2327
2400
  }
2328
- const token = await chunkYHJB26RJ_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2401
+ const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2329
2402
  cookie.setCookie(c, "auth_token", token, {
2330
2403
  httpOnly: true,
2331
2404
  secure: true,
@@ -2374,7 +2447,7 @@ authRoutes.get("/logout", (c) => {
2374
2447
  });
2375
2448
  return c.redirect("/auth/login?message=You have been logged out successfully");
2376
2449
  });
2377
- authRoutes.get("/me", chunkYHJB26RJ_cjs.requireAuth(), async (c) => {
2450
+ authRoutes.get("/me", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
2378
2451
  try {
2379
2452
  const user = c.get("user");
2380
2453
  if (!user) {
@@ -2391,13 +2464,13 @@ authRoutes.get("/me", chunkYHJB26RJ_cjs.requireAuth(), async (c) => {
2391
2464
  return c.json({ error: "Failed to get user" }, 500);
2392
2465
  }
2393
2466
  });
2394
- authRoutes.post("/refresh", chunkYHJB26RJ_cjs.requireAuth(), async (c) => {
2467
+ authRoutes.post("/refresh", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
2395
2468
  try {
2396
2469
  const user = c.get("user");
2397
2470
  if (!user) {
2398
2471
  return c.json({ error: "Not authenticated" }, 401);
2399
2472
  }
2400
- const token = await chunkYHJB26RJ_cjs.AuthManager.generateToken(user.userId, user.email, user.role);
2473
+ const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(user.userId, user.email, user.role);
2401
2474
  cookie.setCookie(c, "auth_token", token, {
2402
2475
  httpOnly: true,
2403
2476
  secure: true,
@@ -2457,7 +2530,7 @@ authRoutes.post("/register/form", async (c) => {
2457
2530
  </div>
2458
2531
  `);
2459
2532
  }
2460
- const passwordHash = await chunkYHJB26RJ_cjs.AuthManager.hashPassword(password);
2533
+ const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
2461
2534
  const role = isFirstUser ? "admin" : "viewer";
2462
2535
  const userId = crypto.randomUUID();
2463
2536
  const now = /* @__PURE__ */ new Date();
@@ -2477,7 +2550,10 @@ authRoutes.post("/register/form", async (c) => {
2477
2550
  now.getTime(),
2478
2551
  now.getTime()
2479
2552
  ).run();
2480
- const token = await chunkYHJB26RJ_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
2553
+ if (isFirstUser) {
2554
+ setAdminExists();
2555
+ }
2556
+ const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
2481
2557
  cookie.setCookie(c, "auth_token", token, {
2482
2558
  httpOnly: true,
2483
2559
  secure: false,
@@ -2529,7 +2605,7 @@ authRoutes.post("/login/form", async (c) => {
2529
2605
  </div>
2530
2606
  `);
2531
2607
  }
2532
- const isValidPassword = await chunkYHJB26RJ_cjs.AuthManager.verifyPassword(password, user.password_hash);
2608
+ const isValidPassword = await chunkYYV3XQOQ_cjs.AuthManager.verifyPassword(password, user.password_hash);
2533
2609
  if (!isValidPassword) {
2534
2610
  return c.html(html.html`
2535
2611
  <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
@@ -2537,7 +2613,7 @@ authRoutes.post("/login/form", async (c) => {
2537
2613
  </div>
2538
2614
  `);
2539
2615
  }
2540
- const token = await chunkYHJB26RJ_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2616
+ const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2541
2617
  cookie.setCookie(c, "auth_token", token, {
2542
2618
  httpOnly: true,
2543
2619
  secure: false,
@@ -2596,8 +2672,9 @@ authRoutes.post("/seed-admin", async (c) => {
2596
2672
  `).run();
2597
2673
  const existingAdmin = await db.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind("admin@sonicjs.com", "admin").first();
2598
2674
  if (existingAdmin) {
2599
- const passwordHash2 = await chunkYHJB26RJ_cjs.AuthManager.hashPassword("sonicjs!");
2675
+ const passwordHash2 = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword("sonicjs!");
2600
2676
  await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
2677
+ setAdminExists();
2601
2678
  return c.json({
2602
2679
  message: "Admin user already exists (password updated)",
2603
2680
  user: {
@@ -2608,7 +2685,7 @@ authRoutes.post("/seed-admin", async (c) => {
2608
2685
  }
2609
2686
  });
2610
2687
  }
2611
- const passwordHash = await chunkYHJB26RJ_cjs.AuthManager.hashPassword("sonicjs!");
2688
+ const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword("sonicjs!");
2612
2689
  const userId = "admin-user-id";
2613
2690
  const now = Date.now();
2614
2691
  const adminEmail = "admin@sonicjs.com".toLowerCase();
@@ -2628,6 +2705,7 @@ authRoutes.post("/seed-admin", async (c) => {
2628
2705
  now,
2629
2706
  now
2630
2707
  ).run();
2708
+ setAdminExists();
2631
2709
  return c.json({
2632
2710
  message: "Admin user created successfully",
2633
2711
  user: {
@@ -2828,7 +2906,7 @@ authRoutes.post("/accept-invitation", async (c) => {
2828
2906
  if (existingUsername) {
2829
2907
  return c.json({ error: "Username is already taken" }, 400);
2830
2908
  }
2831
- const passwordHash = await chunkYHJB26RJ_cjs.AuthManager.hashPassword(password);
2909
+ const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
2832
2910
  const updateStmt = db.prepare(`
2833
2911
  UPDATE users SET
2834
2912
  username = ?,
@@ -2847,7 +2925,7 @@ authRoutes.post("/accept-invitation", async (c) => {
2847
2925
  Date.now(),
2848
2926
  invitedUser.id
2849
2927
  ).run();
2850
- const authToken = await chunkYHJB26RJ_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role);
2928
+ const authToken = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role);
2851
2929
  cookie.setCookie(c, "auth_token", authToken, {
2852
2930
  httpOnly: true,
2853
2931
  secure: true,
@@ -3077,7 +3155,7 @@ authRoutes.post("/reset-password", async (c) => {
3077
3155
  if (Date.now() > user.password_reset_expires) {
3078
3156
  return c.json({ error: "Reset token has expired" }, 400);
3079
3157
  }
3080
- const newPasswordHash = await chunkYHJB26RJ_cjs.AuthManager.hashPassword(password);
3158
+ const newPasswordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
3081
3159
  try {
3082
3160
  const historyStmt = db.prepare(`
3083
3161
  INSERT INTO password_history (id, user_id, password_hash, created_at)
@@ -3339,7 +3417,7 @@ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
3339
3417
 
3340
3418
  // src/templates/components/dynamic-field.template.ts
3341
3419
  function renderDynamicField(field, options = {}) {
3342
- const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {} } = options;
3420
+ const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {}, collectionId = "", contentId = "" } = options;
3343
3421
  const opts = field.field_options || {};
3344
3422
  const required = field.is_required ? "required" : "";
3345
3423
  const baseClasses = `w-full rounded-lg px-3 py-2 text-sm text-zinc-950 dark:text-white bg-white dark:bg-zinc-800 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 ${className}`;
@@ -3592,67 +3670,171 @@ function renderDynamicField(field, options = {}) {
3592
3670
  `;
3593
3671
  break;
3594
3672
  case "slug":
3595
- let slugPattern = opts.pattern || "^[a-z0-9-]+$";
3596
- let slugHelp = '<p class="mt-2 text-xs text-zinc-500 dark:text-zinc-400">Use lowercase letters, numbers, and hyphens only</p>';
3597
- slugHelp += `<button type="button" class="mt-1 text-xs text-cyan-600 dark:text-cyan-400 hover:text-cyan-700 dark:hover:text-cyan-300" onclick="generateSlugFromTitle('\${fieldId}')">Generate from title</button>`;
3673
+ const slugPattern = opts.pattern || "^[a-z0-9-]+$";
3674
+ const collectionIdValue = collectionId || opts.collectionId || "";
3675
+ const contentIdValue = contentId || opts.contentId || "";
3676
+ const isEditMode = !!value;
3598
3677
  fieldHTML = `
3599
- <input
3600
- type="text"
3601
- id="${fieldId}"
3602
- name="${fieldName}"
3603
- value="${escapeHtml2(value)}"
3604
- placeholder="${opts.placeholder || "url-friendly-slug"}"
3605
- maxlength="${opts.maxLength || ""}"
3606
- data-pattern="${slugPattern}"
3607
- class="${baseClasses} ${errorClasses}"
3608
- ${required}
3609
- ${disabled ? "disabled" : ""}
3610
- >
3611
- ${slugHelp}
3678
+ <div class="slug-field-container">
3679
+ <input
3680
+ type="text"
3681
+ id="${fieldId}"
3682
+ name="${fieldName}"
3683
+ value="${escapeHtml2(value)}"
3684
+ placeholder="${opts.placeholder || "url-friendly-slug"}"
3685
+ maxlength="${opts.maxLength || 100}"
3686
+ data-pattern="${slugPattern}"
3687
+ data-collection-id="${collectionIdValue}"
3688
+ data-content-id="${contentIdValue}"
3689
+ data-is-edit-mode="${isEditMode}"
3690
+ class="${baseClasses} ${errorClasses}"
3691
+ ${required}
3692
+ ${disabled ? "disabled" : ""}
3693
+ >
3694
+ <div id="${fieldId}-status" class="slug-status mt-1 text-sm min-h-[20px]"></div>
3695
+ <button
3696
+ type="button"
3697
+ class="regenerate-slug-btn mt-2 text-sm text-cyan-600 dark:text-cyan-400 hover:text-cyan-700 dark:hover:text-cyan-300 flex items-center gap-1 transition-colors"
3698
+ onclick="window.regenerateSlugFromTitle_${fieldId.replace(/-/g, "_")}()"
3699
+ >
3700
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3701
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
3702
+ </svg>
3703
+ Regenerate from title
3704
+ </button>
3705
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">Use lowercase letters, numbers, and hyphens only</p>
3706
+ </div>
3707
+
3612
3708
  <script>
3613
3709
  (function() {
3614
- const field = document.getElementById('${fieldId}');
3710
+ const slugField = document.getElementById('${fieldId}');
3711
+ const statusDiv = document.getElementById('${fieldId}-status');
3712
+ const isEditMode = slugField.dataset.isEditMode === 'true';
3615
3713
  const pattern = new RegExp('${slugPattern}');
3616
-
3617
- field.addEventListener('input', function() {
3618
- if (this.value && !pattern.test(this.value)) {
3619
- this.setCustomValidity('Please use only lowercase letters, numbers, and hyphens.');
3620
- } else {
3621
- this.setCustomValidity('');
3622
- }
3623
- });
3624
-
3625
- field.addEventListener('blur', function() {
3626
- this.reportValidity();
3627
- });
3628
- })();
3629
-
3630
- function generateSlugFromTitle(slugFieldId) {
3631
- const titleField = document.querySelector('input[name="title"]');
3632
- const slugField = document.getElementById(slugFieldId);
3633
- if (titleField && slugField) {
3634
- const slug = titleField.value
3714
+ const collectionId = slugField.dataset.collectionId;
3715
+ const contentId = slugField.dataset.contentId;
3716
+
3717
+ let checkTimeout;
3718
+ let lastCheckedSlug = '';
3719
+ let manuallyEdited = false;
3720
+
3721
+ // Shared slug generation function
3722
+ function generateSlug(text) {
3723
+ if (!text) return '';
3724
+
3725
+ return text
3635
3726
  .toLowerCase()
3727
+ .normalize('NFD')
3728
+ .replace(/[\\u0300-\\u036f]/g, '')
3636
3729
  .replace(/[^a-z0-9\\s_-]/g, '')
3637
3730
  .replace(/\\s+/g, '-')
3638
3731
  .replace(/[-_]+/g, '-')
3639
- .replace(/^[-_]|[-_]$/g, '');
3640
- slugField.value = slug;
3732
+ .replace(/^[-_]+|[-_]+$/g, '')
3733
+ .substring(0, 100);
3641
3734
  }
3642
- }
3643
-
3644
- // Auto-generate slug when title changes
3645
- document.addEventListener('DOMContentLoaded', function() {
3646
- const titleField = document.querySelector('input[name="title"]');
3647
- const slugField = document.getElementById('${fieldId}');
3648
- if (titleField && slugField && !slugField.value) {
3649
- titleField.addEventListener('input', function() {
3650
- if (!slugField.value) {
3651
- generateSlugFromTitle('${fieldId}');
3735
+
3736
+ // Check if slug is available
3737
+ async function checkSlugAvailability(slug) {
3738
+ if (!slug || !collectionId) return;
3739
+
3740
+ // Don't check if it's the same as last time
3741
+ if (slug === lastCheckedSlug) return;
3742
+ lastCheckedSlug = slug;
3743
+
3744
+ try {
3745
+ // Show checking status
3746
+ statusDiv.innerHTML = '<span class="text-gray-400">\u23F3 Checking availability...</span>';
3747
+
3748
+ // Build URL
3749
+ let url = \`/api/content/check-slug?collectionId=\${encodeURIComponent(collectionId)}&slug=\${encodeURIComponent(slug)}\`;
3750
+ if (contentId) {
3751
+ url += \`&excludeId=\${encodeURIComponent(contentId)}\`;
3652
3752
  }
3653
- });
3753
+
3754
+ const response = await fetch(url);
3755
+ const data = await response.json();
3756
+
3757
+ if (data.available) {
3758
+ statusDiv.innerHTML = '<span class="text-green-500 dark:text-green-400">\u2713 Available</span>';
3759
+ slugField.setCustomValidity('');
3760
+ } else {
3761
+ statusDiv.innerHTML = \`<span class="text-red-500 dark:text-red-400">\u2717 \${data.message || 'Already in use'}</span>\`;
3762
+ slugField.setCustomValidity(data.message || 'This slug is already in use');
3763
+ }
3764
+ } catch (error) {
3765
+ console.error('Error checking slug:', error);
3766
+ statusDiv.innerHTML = '<span class="text-yellow-500 dark:text-yellow-400">\u26A0 Could not verify</span>';
3767
+ }
3654
3768
  }
3655
- });
3769
+
3770
+ // Format validation and duplicate checking
3771
+ slugField.addEventListener('input', function() {
3772
+ const value = this.value;
3773
+
3774
+ // Mark as manually edited if user types directly
3775
+ if (document.activeElement === this) {
3776
+ manuallyEdited = true;
3777
+ }
3778
+
3779
+ // Clear status if empty
3780
+ if (!value) {
3781
+ statusDiv.innerHTML = '';
3782
+ this.setCustomValidity('');
3783
+ return;
3784
+ }
3785
+
3786
+ // Pattern validation
3787
+ if (!pattern.test(value)) {
3788
+ this.setCustomValidity('Please use only lowercase letters, numbers, and hyphens.');
3789
+ statusDiv.innerHTML = '<span class="text-red-500 dark:text-red-400">\u2717 Invalid format</span>';
3790
+ return;
3791
+ }
3792
+
3793
+ // Debounce the availability check
3794
+ clearTimeout(checkTimeout);
3795
+ checkTimeout = setTimeout(() => {
3796
+ checkSlugAvailability(value);
3797
+ }, 500); // Wait 500ms after user stops typing
3798
+ });
3799
+
3800
+ // Initial check if field has value
3801
+ if (slugField.value) {
3802
+ checkSlugAvailability(slugField.value);
3803
+ }
3804
+
3805
+ // Auto-generate only in create mode
3806
+ // Wait for all fields to be rendered before attaching listeners
3807
+ if (!isEditMode) {
3808
+ // Use setTimeout to ensure all fields in the form are rendered
3809
+ setTimeout(() => {
3810
+ const titleField = document.querySelector('input[name="title"]');
3811
+ if (titleField) {
3812
+ titleField.addEventListener('input', function() {
3813
+ if (!manuallyEdited) {
3814
+ const slug = generateSlug(this.value);
3815
+ slugField.value = slug;
3816
+
3817
+ // Trigger validation and duplicate check
3818
+ slugField.dispatchEvent(new Event('input', { bubbles: true }));
3819
+ }
3820
+ });
3821
+ }
3822
+ }, 0);
3823
+ }
3824
+
3825
+ // Global function for regenerate button
3826
+ window.regenerateSlugFromTitle_${fieldId.replace(/-/g, "_")} = function() {
3827
+ const titleField = document.querySelector('input[name="title"]');
3828
+ if (titleField && slugField) {
3829
+ const slug = generateSlug(titleField.value);
3830
+ slugField.value = slug;
3831
+ manuallyEdited = false;
3832
+
3833
+ // Trigger validation and duplicate check
3834
+ slugField.dispatchEvent(new Event('input', { bubbles: true }));
3835
+ }
3836
+ };
3837
+ })();
3656
3838
  </script>
3657
3839
  `;
3658
3840
  break;
@@ -3785,210 +3967,9 @@ function escapeHtml2(text) {
3785
3967
  "'": "&#39;"
3786
3968
  })[char] || char);
3787
3969
  }
3788
- var PluginBuilder = class _PluginBuilder {
3789
- plugin;
3790
- constructor(options) {
3791
- this.plugin = {
3792
- name: options.name,
3793
- version: options.version,
3794
- description: options.description,
3795
- author: options.author,
3796
- dependencies: options.dependencies,
3797
- routes: [],
3798
- middleware: [],
3799
- models: [],
3800
- services: [],
3801
- adminPages: [],
3802
- adminComponents: [],
3803
- menuItems: [],
3804
- hooks: []
3805
- };
3806
- }
3807
- /**
3808
- * Create a new plugin builder
3809
- */
3810
- static create(options) {
3811
- return new _PluginBuilder(options);
3812
- }
3813
- /**
3814
- * Add metadata to the plugin
3815
- */
3816
- metadata(metadata) {
3817
- Object.assign(this.plugin, metadata);
3818
- return this;
3819
- }
3820
- /**
3821
- * Add routes to plugin
3822
- */
3823
- addRoutes(routes) {
3824
- this.plugin.routes = [...this.plugin.routes || [], ...routes];
3825
- return this;
3826
- }
3827
- /**
3828
- * Add a single route to plugin
3829
- */
3830
- addRoute(path, handler, options) {
3831
- const route = {
3832
- path,
3833
- handler,
3834
- ...options
3835
- };
3836
- this.plugin.routes = [...this.plugin.routes || [], route];
3837
- return this;
3838
- }
3839
- /**
3840
- * Add middleware to plugin
3841
- */
3842
- addMiddleware(middleware) {
3843
- this.plugin.middleware = [...this.plugin.middleware || [], ...middleware];
3844
- return this;
3845
- }
3846
- /**
3847
- * Add a single middleware to plugin
3848
- */
3849
- addSingleMiddleware(name, handler, options) {
3850
- const middleware = {
3851
- name,
3852
- handler,
3853
- ...options
3854
- };
3855
- this.plugin.middleware = [...this.plugin.middleware || [], middleware];
3856
- return this;
3857
- }
3858
- /**
3859
- * Add models to plugin
3860
- */
3861
- addModels(models) {
3862
- this.plugin.models = [...this.plugin.models || [], ...models];
3863
- return this;
3864
- }
3865
- /**
3866
- * Add a single model to plugin
3867
- */
3868
- addModel(name, options) {
3869
- const model = {
3870
- name,
3871
- ...options
3872
- };
3873
- this.plugin.models = [...this.plugin.models || [], model];
3874
- return this;
3875
- }
3876
- /**
3877
- * Add services to plugin
3878
- */
3879
- addServices(services) {
3880
- this.plugin.services = [...this.plugin.services || [], ...services];
3881
- return this;
3882
- }
3883
- /**
3884
- * Add a single service to plugin
3885
- */
3886
- addService(name, implementation, options) {
3887
- const service = {
3888
- name,
3889
- implementation,
3890
- ...options
3891
- };
3892
- this.plugin.services = [...this.plugin.services || [], service];
3893
- return this;
3894
- }
3895
- /**
3896
- * Add admin pages to plugin
3897
- */
3898
- addAdminPages(pages) {
3899
- this.plugin.adminPages = [...this.plugin.adminPages || [], ...pages];
3900
- return this;
3901
- }
3902
- /**
3903
- * Add a single admin page to plugin
3904
- */
3905
- addAdminPage(path, title, component, options) {
3906
- const page = {
3907
- path,
3908
- title,
3909
- component,
3910
- ...options
3911
- };
3912
- this.plugin.adminPages = [...this.plugin.adminPages || [], page];
3913
- return this;
3914
- }
3915
- /**
3916
- * Add admin components to plugin
3917
- */
3918
- addComponents(components) {
3919
- this.plugin.adminComponents = [...this.plugin.adminComponents || [], ...components];
3920
- return this;
3921
- }
3922
- /**
3923
- * Add a single admin component to plugin
3924
- */
3925
- addComponent(name, template, options) {
3926
- const component = {
3927
- name,
3928
- template,
3929
- ...options
3930
- };
3931
- this.plugin.adminComponents = [...this.plugin.adminComponents || [], component];
3932
- return this;
3933
- }
3934
- /**
3935
- * Add menu items to plugin
3936
- */
3937
- addMenuItems(items) {
3938
- this.plugin.menuItems = [...this.plugin.menuItems || [], ...items];
3939
- return this;
3940
- }
3941
- /**
3942
- * Add a single menu item to plugin
3943
- */
3944
- addMenuItem(label, path, options) {
3945
- const menuItem = {
3946
- label,
3947
- path,
3948
- ...options
3949
- };
3950
- this.plugin.menuItems = [...this.plugin.menuItems || [], menuItem];
3951
- return this;
3952
- }
3953
- /**
3954
- * Add hooks to plugin
3955
- */
3956
- addHooks(hooks) {
3957
- this.plugin.hooks = [...this.plugin.hooks || [], ...hooks];
3958
- return this;
3959
- }
3960
- /**
3961
- * Add a single hook to plugin
3962
- */
3963
- addHook(name, handler, options) {
3964
- const hook = {
3965
- name,
3966
- handler,
3967
- ...options
3968
- };
3969
- this.plugin.hooks = [...this.plugin.hooks || [], hook];
3970
- return this;
3971
- }
3972
- /**
3973
- * Add lifecycle hooks
3974
- */
3975
- lifecycle(hooks) {
3976
- Object.assign(this.plugin, hooks);
3977
- return this;
3978
- }
3979
- /**
3980
- * Build the plugin
3981
- */
3982
- build() {
3983
- if (!this.plugin.name || !this.plugin.version) {
3984
- throw new Error("Plugin name and version are required");
3985
- }
3986
- return this.plugin;
3987
- }
3988
- };
3989
3970
 
3990
3971
  // src/plugins/available/tinymce-plugin/index.ts
3991
- var builder = PluginBuilder.create({
3972
+ var builder = chunkAI2JJIJX_cjs.PluginBuilder.create({
3992
3973
  name: "tinymce-plugin",
3993
3974
  version: "1.0.0",
3994
3975
  description: "Powerful WYSIWYG rich text editor for content creation"
@@ -4271,7 +4252,7 @@ function getQuillCDN(version = "2.0.2") {
4271
4252
  `;
4272
4253
  }
4273
4254
  function createQuillEditorPlugin() {
4274
- const builder3 = PluginBuilder.create({
4255
+ const builder3 = chunkAI2JJIJX_cjs.PluginBuilder.create({
4275
4256
  name: "quill-editor",
4276
4257
  version: "1.0.0",
4277
4258
  description: "Quill rich text editor integration for SonicJS"
@@ -4297,7 +4278,7 @@ function createQuillEditorPlugin() {
4297
4278
  createQuillEditorPlugin();
4298
4279
 
4299
4280
  // src/plugins/available/easy-mdx/index.ts
4300
- var builder2 = PluginBuilder.create({
4281
+ var builder2 = chunkAI2JJIJX_cjs.PluginBuilder.create({
4301
4282
  name: "easy-mdx",
4302
4283
  version: "1.0.0",
4303
4284
  description: "Lightweight markdown editor with live preview"
@@ -4529,17 +4510,24 @@ function renderContentFormPage(data) {
4529
4510
  const coreFieldsHTML = coreFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4530
4511
  value: getFieldValue(field.field_name),
4531
4512
  errors: data.validationErrors?.[field.field_name] || [],
4532
- pluginStatuses
4513
+ pluginStatuses,
4514
+ collectionId: data.collection.id,
4515
+ contentId: data.id
4516
+ // Pass content ID when editing
4533
4517
  }));
4534
4518
  const contentFieldsHTML = contentFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4535
4519
  value: getFieldValue(field.field_name),
4536
4520
  errors: data.validationErrors?.[field.field_name] || [],
4537
- pluginStatuses
4521
+ pluginStatuses,
4522
+ collectionId: data.collection.id,
4523
+ contentId: data.id
4538
4524
  }));
4539
4525
  const metaFieldsHTML = metaFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4540
4526
  value: getFieldValue(field.field_name),
4541
4527
  errors: data.validationErrors?.[field.field_name] || [],
4542
- pluginStatuses
4528
+ pluginStatuses,
4529
+ collectionId: data.collection.id,
4530
+ contentId: data.id
4543
4531
  }));
4544
4532
  const pageContent = `
4545
4533
  <div class="space-y-6">
@@ -5979,7 +5967,7 @@ async function isPluginActive2(db, pluginId) {
5979
5967
 
5980
5968
  // src/routes/admin-content.ts
5981
5969
  var adminContentRoutes = new hono.Hono();
5982
- adminContentRoutes.use("*", chunkYHJB26RJ_cjs.requireAuth());
5970
+ adminContentRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
5983
5971
  async function getCollectionFields(db, collectionId) {
5984
5972
  const cache = chunk7FOAMNTI_cjs.getCacheService(chunk7FOAMNTI_cjs.CACHE_CONFIGS.collection);
5985
5973
  return cache.getOrSet(
@@ -6456,6 +6444,18 @@ adminContentRoutes.post("/", async (c) => {
6456
6444
  const errors = {};
6457
6445
  for (const field of fields) {
6458
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
6459
  if (field.is_required && (!value || value.toString().trim() === "")) {
6460
6460
  errors[field.field_name] = [`${field.field_label} is required`];
6461
6461
  continue;
@@ -6478,6 +6478,67 @@ adminContentRoutes.post("/", async (c) => {
6478
6478
  data[field.field_name] = value;
6479
6479
  }
6480
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
+ }
6481
6542
  default:
6482
6543
  data[field.field_name] = value;
6483
6544
  }
@@ -6602,6 +6663,18 @@ adminContentRoutes.put("/:id", async (c) => {
6602
6663
  const errors = {};
6603
6664
  for (const field of fields) {
6604
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
+ }
6605
6678
  if (field.is_required && (!value || value.toString().trim() === "")) {
6606
6679
  errors[field.field_name] = [`${field.field_label} is required`];
6607
6680
  continue;
@@ -6624,6 +6697,67 @@ adminContentRoutes.put("/:id", async (c) => {
6624
6697
  data[field.field_name] = value;
6625
6698
  }
6626
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
+ }
6627
6761
  default:
6628
6762
  data[field.field_name] = value;
6629
6763
  }
@@ -6743,6 +6877,12 @@ adminContentRoutes.post("/preview", async (c) => {
6743
6877
  const data = {};
6744
6878
  for (const field of fields) {
6745
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
+ }
6746
6886
  switch (field.field_type) {
6747
6887
  case "number":
6748
6888
  data[field.field_name] = value ? Number(value) : null;
@@ -8040,7 +8180,7 @@ function renderUserEditPage(data) {
8040
8180
  <input
8041
8181
  type="text"
8042
8182
  name="first_name"
8043
- value="${chunk2XCJ3HT5_cjs.escapeHtml(data.userToEdit.firstName || "")}"
8183
+ value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.firstName || "")}"
8044
8184
  required
8045
8185
  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"
8046
8186
  />
@@ -8051,7 +8191,7 @@ function renderUserEditPage(data) {
8051
8191
  <input
8052
8192
  type="text"
8053
8193
  name="last_name"
8054
- value="${chunk2XCJ3HT5_cjs.escapeHtml(data.userToEdit.lastName || "")}"
8194
+ value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.lastName || "")}"
8055
8195
  required
8056
8196
  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"
8057
8197
  />
@@ -8062,7 +8202,7 @@ function renderUserEditPage(data) {
8062
8202
  <input
8063
8203
  type="text"
8064
8204
  name="username"
8065
- value="${chunk2XCJ3HT5_cjs.escapeHtml(data.userToEdit.username || "")}"
8205
+ value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.username || "")}"
8066
8206
  required
8067
8207
  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"
8068
8208
  />
@@ -8073,7 +8213,7 @@ function renderUserEditPage(data) {
8073
8213
  <input
8074
8214
  type="email"
8075
8215
  name="email"
8076
- value="${chunk2XCJ3HT5_cjs.escapeHtml(data.userToEdit.email || "")}"
8216
+ value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.email || "")}"
8077
8217
  required
8078
8218
  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"
8079
8219
  />
@@ -8084,7 +8224,7 @@ function renderUserEditPage(data) {
8084
8224
  <input
8085
8225
  type="tel"
8086
8226
  name="phone"
8087
- value="${chunk2XCJ3HT5_cjs.escapeHtml(data.userToEdit.phone || "")}"
8227
+ value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.phone || "")}"
8088
8228
  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"
8089
8229
  />
8090
8230
  </div>
@@ -8098,7 +8238,7 @@ function renderUserEditPage(data) {
8098
8238
  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"
8099
8239
  >
8100
8240
  ${data.roles.map((role) => `
8101
- <option value="${chunk2XCJ3HT5_cjs.escapeHtml(role.value)}" ${data.userToEdit.role === role.value ? "selected" : ""}>${chunk2XCJ3HT5_cjs.escapeHtml(role.label)}</option>
8241
+ <option value="${chunkZWV3EBZ7_cjs.escapeHtml(role.value)}" ${data.userToEdit.role === role.value ? "selected" : ""}>${chunkZWV3EBZ7_cjs.escapeHtml(role.label)}</option>
8102
8242
  `).join("")}
8103
8243
  </select>
8104
8244
  <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">
@@ -8114,7 +8254,7 @@ function renderUserEditPage(data) {
8114
8254
  name="bio"
8115
8255
  rows="3"
8116
8256
  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"
8117
- >${chunk2XCJ3HT5_cjs.escapeHtml(data.userToEdit.bio || "")}</textarea>
8257
+ >${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.bio || "")}</textarea>
8118
8258
  </div>
8119
8259
  </div>
8120
8260
 
@@ -9014,7 +9154,7 @@ function renderUsersListPage(data) {
9014
9154
 
9015
9155
  // src/routes/admin-users.ts
9016
9156
  var userRoutes = new hono.Hono();
9017
- userRoutes.use("*", chunkYHJB26RJ_cjs.requireAuth());
9157
+ userRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
9018
9158
  userRoutes.get("/", (c) => {
9019
9159
  return c.redirect("/admin/dashboard");
9020
9160
  });
@@ -9113,12 +9253,12 @@ userRoutes.put("/profile", async (c) => {
9113
9253
  const db = c.env.DB;
9114
9254
  try {
9115
9255
  const formData = await c.req.formData();
9116
- const firstName = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("first_name")?.toString());
9117
- const lastName = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("last_name")?.toString());
9118
- const username = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("username")?.toString());
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());
9119
9259
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9120
- const phone = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9121
- const bio = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9260
+ const phone = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9261
+ const bio = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9122
9262
  const timezone = formData.get("timezone")?.toString() || "UTC";
9123
9263
  const language = formData.get("language")?.toString() || "en";
9124
9264
  const emailNotifications = formData.get("email_notifications") === "1";
@@ -9169,7 +9309,7 @@ userRoutes.put("/profile", async (c) => {
9169
9309
  Date.now(),
9170
9310
  user.userId
9171
9311
  ).run();
9172
- await chunkYHJB26RJ_cjs.logActivity(
9312
+ await chunkYYV3XQOQ_cjs.logActivity(
9173
9313
  db,
9174
9314
  user.userId,
9175
9315
  "profile.update",
@@ -9232,7 +9372,7 @@ userRoutes.post("/profile/avatar", async (c) => {
9232
9372
  SELECT first_name, last_name FROM users WHERE id = ?
9233
9373
  `);
9234
9374
  const userData = await userStmt.bind(user.userId).first();
9235
- await chunkYHJB26RJ_cjs.logActivity(
9375
+ await chunkYYV3XQOQ_cjs.logActivity(
9236
9376
  db,
9237
9377
  user.userId,
9238
9378
  "profile.avatar_update",
@@ -9303,7 +9443,7 @@ userRoutes.post("/profile/password", async (c) => {
9303
9443
  dismissible: true
9304
9444
  }));
9305
9445
  }
9306
- const validPassword = await chunkYHJB26RJ_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
9446
+ const validPassword = await chunkYYV3XQOQ_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
9307
9447
  if (!validPassword) {
9308
9448
  return c.html(renderAlert2({
9309
9449
  type: "error",
@@ -9311,7 +9451,7 @@ userRoutes.post("/profile/password", async (c) => {
9311
9451
  dismissible: true
9312
9452
  }));
9313
9453
  }
9314
- const newPasswordHash = await chunkYHJB26RJ_cjs.AuthManager.hashPassword(newPassword);
9454
+ const newPasswordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(newPassword);
9315
9455
  const historyStmt = db.prepare(`
9316
9456
  INSERT INTO password_history (id, user_id, password_hash, created_at)
9317
9457
  VALUES (?, ?, ?, ?)
@@ -9327,7 +9467,7 @@ userRoutes.post("/profile/password", async (c) => {
9327
9467
  WHERE id = ?
9328
9468
  `);
9329
9469
  await updateStmt.bind(newPasswordHash, Date.now(), user.userId).run();
9330
- await chunkYHJB26RJ_cjs.logActivity(
9470
+ await chunkYYV3XQOQ_cjs.logActivity(
9331
9471
  db,
9332
9472
  user.userId,
9333
9473
  "profile.password_change",
@@ -9394,7 +9534,7 @@ userRoutes.get("/users", async (c) => {
9394
9534
  `);
9395
9535
  const countResult = await countStmt.bind(...params).first();
9396
9536
  const totalUsers = countResult?.total || 0;
9397
- await chunkYHJB26RJ_cjs.logActivity(
9537
+ await chunkYYV3XQOQ_cjs.logActivity(
9398
9538
  db,
9399
9539
  user.userId,
9400
9540
  "users.list_view",
@@ -9496,12 +9636,12 @@ userRoutes.post("/users/new", async (c) => {
9496
9636
  const user = c.get("user");
9497
9637
  try {
9498
9638
  const formData = await c.req.formData();
9499
- const firstName = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("first_name")?.toString());
9500
- const lastName = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("last_name")?.toString());
9501
- const username = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("username")?.toString());
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());
9502
9642
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9503
- const phone = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9504
- const bio = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9643
+ const phone = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9644
+ const bio = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9505
9645
  const role = formData.get("role")?.toString() || "viewer";
9506
9646
  const password = formData.get("password")?.toString() || "";
9507
9647
  const confirmPassword = formData.get("confirm_password")?.toString() || "";
@@ -9548,7 +9688,7 @@ userRoutes.post("/users/new", async (c) => {
9548
9688
  dismissible: true
9549
9689
  }));
9550
9690
  }
9551
- const passwordHash = await chunkYHJB26RJ_cjs.AuthManager.hashPassword(password);
9691
+ const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
9552
9692
  const userId = crypto.randomUUID();
9553
9693
  const createStmt = db.prepare(`
9554
9694
  INSERT INTO users (
@@ -9571,7 +9711,7 @@ userRoutes.post("/users/new", async (c) => {
9571
9711
  Date.now(),
9572
9712
  Date.now()
9573
9713
  ).run();
9574
- await chunkYHJB26RJ_cjs.logActivity(
9714
+ await chunkYYV3XQOQ_cjs.logActivity(
9575
9715
  db,
9576
9716
  user.userId,
9577
9717
  "user!.create",
@@ -9609,7 +9749,7 @@ userRoutes.get("/users/:id", async (c) => {
9609
9749
  if (!userRecord) {
9610
9750
  return c.json({ error: "User not found" }, 404);
9611
9751
  }
9612
- await chunkYHJB26RJ_cjs.logActivity(
9752
+ await chunkYYV3XQOQ_cjs.logActivity(
9613
9753
  db,
9614
9754
  user.userId,
9615
9755
  "user!.view",
@@ -9702,12 +9842,12 @@ userRoutes.put("/users/:id", async (c) => {
9702
9842
  const userId = c.req.param("id");
9703
9843
  try {
9704
9844
  const formData = await c.req.formData();
9705
- const firstName = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("first_name")?.toString());
9706
- const lastName = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("last_name")?.toString());
9707
- const username = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("username")?.toString());
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());
9708
9848
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9709
- const phone = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9710
- const bio = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9849
+ const phone = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9850
+ const bio = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9711
9851
  const role = formData.get("role")?.toString() || "viewer";
9712
9852
  const isActive = formData.get("is_active") === "1";
9713
9853
  const emailVerified = formData.get("email_verified") === "1";
@@ -9758,7 +9898,7 @@ userRoutes.put("/users/:id", async (c) => {
9758
9898
  Date.now(),
9759
9899
  userId
9760
9900
  ).run();
9761
- await chunkYHJB26RJ_cjs.logActivity(
9901
+ await chunkYYV3XQOQ_cjs.logActivity(
9762
9902
  db,
9763
9903
  user.userId,
9764
9904
  "user!.update",
@@ -9803,7 +9943,7 @@ userRoutes.post("/users/:id/toggle", async (c) => {
9803
9943
  UPDATE users SET is_active = ?, updated_at = ? WHERE id = ?
9804
9944
  `);
9805
9945
  await toggleStmt.bind(active ? 1 : 0, Date.now(), userId).run();
9806
- await chunkYHJB26RJ_cjs.logActivity(
9946
+ await chunkYYV3XQOQ_cjs.logActivity(
9807
9947
  db,
9808
9948
  user.userId,
9809
9949
  active ? "user.activate" : "user.deactivate",
@@ -9844,7 +9984,7 @@ userRoutes.delete("/users/:id", async (c) => {
9844
9984
  DELETE FROM users WHERE id = ?
9845
9985
  `);
9846
9986
  await deleteStmt.bind(userId).run();
9847
- await chunkYHJB26RJ_cjs.logActivity(
9987
+ await chunkYYV3XQOQ_cjs.logActivity(
9848
9988
  db,
9849
9989
  user.userId,
9850
9990
  "user!.hard_delete",
@@ -9863,7 +10003,7 @@ userRoutes.delete("/users/:id", async (c) => {
9863
10003
  UPDATE users SET is_active = 0, updated_at = ? WHERE id = ?
9864
10004
  `);
9865
10005
  await deleteStmt.bind(Date.now(), userId).run();
9866
- await chunkYHJB26RJ_cjs.logActivity(
10006
+ await chunkYYV3XQOQ_cjs.logActivity(
9867
10007
  db,
9868
10008
  user.userId,
9869
10009
  "user!.soft_delete",
@@ -9890,8 +10030,8 @@ userRoutes.post("/invite-user", async (c) => {
9890
10030
  const formData = await c.req.formData();
9891
10031
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9892
10032
  const role = formData.get("role")?.toString()?.trim() || "viewer";
9893
- const firstName = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("first_name")?.toString());
9894
- const lastName = chunk2XCJ3HT5_cjs.sanitizeInput(formData.get("last_name")?.toString());
10033
+ const firstName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("first_name")?.toString());
10034
+ const lastName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9895
10035
  if (!email || !firstName || !lastName) {
9896
10036
  return c.json({ error: "Email, first name, and last name are required" }, 400);
9897
10037
  }
@@ -9929,7 +10069,7 @@ userRoutes.post("/invite-user", async (c) => {
9929
10069
  Date.now(),
9930
10070
  Date.now()
9931
10071
  ).run();
9932
- await chunkYHJB26RJ_cjs.logActivity(
10072
+ await chunkYYV3XQOQ_cjs.logActivity(
9933
10073
  db,
9934
10074
  user.userId,
9935
10075
  "user!.invite_sent",
@@ -9986,7 +10126,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
9986
10126
  Date.now(),
9987
10127
  userId
9988
10128
  ).run();
9989
- await chunkYHJB26RJ_cjs.logActivity(
10129
+ await chunkYYV3XQOQ_cjs.logActivity(
9990
10130
  db,
9991
10131
  user.userId,
9992
10132
  "user!.invitation_resent",
@@ -10022,7 +10162,7 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
10022
10162
  }
10023
10163
  const deleteStmt = db.prepare(`DELETE FROM users WHERE id = ?`);
10024
10164
  await deleteStmt.bind(userId).run();
10025
- await chunkYHJB26RJ_cjs.logActivity(
10165
+ await chunkYYV3XQOQ_cjs.logActivity(
10026
10166
  db,
10027
10167
  user.userId,
10028
10168
  "user!.invitation_cancelled",
@@ -10105,7 +10245,7 @@ userRoutes.get("/activity-logs", async (c) => {
10105
10245
  ...log,
10106
10246
  details: log.details ? JSON.parse(log.details) : null
10107
10247
  }));
10108
- await chunkYHJB26RJ_cjs.logActivity(
10248
+ await chunkYYV3XQOQ_cjs.logActivity(
10109
10249
  db,
10110
10250
  user.userId,
10111
10251
  "activity.logs_viewed",
@@ -10212,7 +10352,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
10212
10352
  csvRows.push(row.join(","));
10213
10353
  }
10214
10354
  const csvContent = csvRows.join("\n");
10215
- await chunkYHJB26RJ_cjs.logActivity(
10355
+ await chunkYYV3XQOQ_cjs.logActivity(
10216
10356
  db,
10217
10357
  user.userId,
10218
10358
  "activity.logs_exported",
@@ -11551,7 +11691,7 @@ var fileValidationSchema2 = zod.z.object({
11551
11691
  // 50MB max
11552
11692
  });
11553
11693
  var adminMediaRoutes = new hono.Hono();
11554
- adminMediaRoutes.use("*", chunkYHJB26RJ_cjs.requireAuth());
11694
+ adminMediaRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
11555
11695
  adminMediaRoutes.get("/", async (c) => {
11556
11696
  try {
11557
11697
  const user = c.get("user");
@@ -12137,7 +12277,7 @@ adminMediaRoutes.put("/:id", async (c) => {
12137
12277
  `);
12138
12278
  }
12139
12279
  });
12140
- adminMediaRoutes.delete("/cleanup", chunkYHJB26RJ_cjs.requireRole("admin"), async (c) => {
12280
+ adminMediaRoutes.delete("/cleanup", chunkYYV3XQOQ_cjs.requireRole("admin"), async (c) => {
12141
12281
  try {
12142
12282
  const db = c.env.DB;
12143
12283
  const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
@@ -13271,6 +13411,9 @@ function renderAuthSettingsForm(settings) {
13271
13411
  }
13272
13412
 
13273
13413
  // src/templates/pages/admin-plugin-settings.template.ts
13414
+ function escapeHtmlAttr(value) {
13415
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13416
+ }
13274
13417
  function renderPluginSettingsPage(data) {
13275
13418
  const { plugin, activity = [], user } = data;
13276
13419
  const pageContent = `
@@ -13548,6 +13691,7 @@ function renderSettingsTab(plugin) {
13548
13691
  const settings = plugin.settings || {};
13549
13692
  const isSeedDataPlugin = plugin.id === "seed-data" || plugin.name === "seed-data";
13550
13693
  const isAuthPlugin = plugin.id === "core-auth" || plugin.name === "core-auth";
13694
+ const isTurnstilePlugin = plugin.id === "turnstile" || plugin.name === "turnstile";
13551
13695
  return `
13552
13696
  ${isSeedDataPlugin ? `
13553
13697
  <div class="backdrop-blur-md bg-black/20 rounded-xl border border-white/10 shadow-xl p-6 mb-6">
@@ -13574,12 +13718,15 @@ function renderSettingsTab(plugin) {
13574
13718
  ${isAuthPlugin ? `
13575
13719
  <h2 class="text-xl font-semibold text-white mb-4">Authentication Settings</h2>
13576
13720
  <p class="text-gray-400 mb-6">Configure user registration fields and validation rules.</p>
13721
+ ` : isTurnstilePlugin ? `
13722
+ <h2 class="text-xl font-semibold text-white mb-4">Cloudflare Turnstile Settings</h2>
13723
+ <p class="text-gray-400 mb-6">Configure CAPTCHA-free bot protection for your forms.</p>
13577
13724
  ` : `
13578
13725
  <h2 class="text-xl font-semibold text-white mb-4">Plugin Settings</h2>
13579
13726
  `}
13580
13727
 
13581
13728
  <form id="settings-form" class="space-y-6">
13582
- ${isAuthPlugin && Object.keys(settings).length > 0 ? renderAuthSettingsForm(settings) : Object.keys(settings).length > 0 ? renderSettingsFields(settings) : renderNoSettings(plugin)}
13729
+ ${isAuthPlugin && Object.keys(settings).length > 0 ? renderAuthSettingsForm(settings) : isTurnstilePlugin && Object.keys(settings).length > 0 ? renderTurnstileSettingsForm(settings) : Object.keys(settings).length > 0 ? renderSettingsFields(settings) : renderNoSettings(plugin)}
13583
13730
 
13584
13731
  ${Object.keys(settings).length > 0 ? `
13585
13732
  <div class="flex items-center justify-end pt-6 border-t border-white/10">
@@ -13643,6 +13790,80 @@ function renderSettingsFields(settings) {
13643
13790
  }
13644
13791
  }).join("");
13645
13792
  }
13793
+ function renderTurnstileSettingsForm(settings) {
13794
+ const inputClass = "backdrop-blur-sm bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white placeholder-gray-300 focus:border-blue-400 focus:outline-none transition-colors w-full";
13795
+ const selectClass = "backdrop-blur-sm bg-zinc-800 border border-white/20 rounded-lg px-3 py-2 text-white focus:border-blue-400 focus:outline-none transition-colors w-full [&>option]:bg-zinc-800 [&>option]:text-white";
13796
+ return `
13797
+ <!-- Enable Toggle -->
13798
+ <div class="flex items-center justify-between">
13799
+ <div>
13800
+ <label for="setting_enabled" class="text-sm font-medium text-gray-300">Enable Turnstile</label>
13801
+ <p class="text-xs text-gray-400">Enable or disable Turnstile verification globally</p>
13802
+ </div>
13803
+ <label class="relative inline-flex items-center cursor-pointer">
13804
+ <input type="checkbox" name="setting_enabled" id="setting_enabled" ${settings.enabled ? "checked" : ""} class="sr-only peer">
13805
+ <div class="w-11 h-6 bg-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
13806
+ </label>
13807
+ </div>
13808
+
13809
+ <!-- Site Key -->
13810
+ <div>
13811
+ <label for="setting_siteKey" class="block text-sm font-medium text-gray-300 mb-2">Site Key</label>
13812
+ <input type="text" name="setting_siteKey" id="setting_siteKey" value="${escapeHtmlAttr(settings.siteKey || "")}" placeholder="0x4AAAAAAAA..." class="${inputClass}">
13813
+ <p class="text-xs text-gray-400 mt-1">Your Cloudflare Turnstile site key (public)</p>
13814
+ </div>
13815
+
13816
+ <!-- Secret Key -->
13817
+ <div>
13818
+ <label for="setting_secretKey" class="block text-sm font-medium text-gray-300 mb-2">Secret Key</label>
13819
+ <input type="password" name="setting_secretKey" id="setting_secretKey" value="${escapeHtmlAttr(settings.secretKey || "")}" placeholder="0x4AAAAAAAA..." class="${inputClass}">
13820
+ <p class="text-xs text-gray-400 mt-1">Your Cloudflare Turnstile secret key (private)</p>
13821
+ </div>
13822
+
13823
+ <!-- Theme -->
13824
+ <div>
13825
+ <label for="setting_theme" class="block text-sm font-medium text-gray-300 mb-2">Widget Theme</label>
13826
+ <select name="setting_theme" id="setting_theme" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13827
+ <option value="auto" ${settings.theme === "auto" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Auto (matches page theme)</option>
13828
+ <option value="light" ${settings.theme === "light" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Light</option>
13829
+ <option value="dark" ${settings.theme === "dark" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Dark</option>
13830
+ </select>
13831
+ <p class="text-xs text-gray-400 mt-1">Visual appearance of the Turnstile widget</p>
13832
+ </div>
13833
+
13834
+ <!-- Size -->
13835
+ <div>
13836
+ <label for="setting_size" class="block text-sm font-medium text-gray-300 mb-2">Widget Size</label>
13837
+ <select name="setting_size" id="setting_size" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13838
+ <option value="normal" ${settings.size === "normal" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Normal (300x65px)</option>
13839
+ <option value="compact" ${settings.size === "compact" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Compact (130x120px)</option>
13840
+ </select>
13841
+ <p class="text-xs text-gray-400 mt-1">Size of the Turnstile challenge widget</p>
13842
+ </div>
13843
+
13844
+ <!-- Widget Mode -->
13845
+ <div>
13846
+ <label for="setting_mode" class="block text-sm font-medium text-gray-300 mb-2">Widget Mode</label>
13847
+ <select name="setting_mode" id="setting_mode" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13848
+ <option value="managed" ${!settings.mode || settings.mode === "managed" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Managed (Recommended) - Adaptive challenge</option>
13849
+ <option value="non-interactive" ${settings.mode === "non-interactive" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Non-Interactive - Always visible, minimal friction</option>
13850
+ <option value="invisible" ${settings.mode === "invisible" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Invisible - No visible widget</option>
13851
+ </select>
13852
+ <p class="text-xs text-gray-400 mt-1"><strong>Managed:</strong> Shows challenge only when needed. <strong>Non-Interactive:</strong> Always shows but doesn't require interaction. <strong>Invisible:</strong> Runs in background without UI.</p>
13853
+ </div>
13854
+
13855
+ <!-- Appearance (Pre-clearance) -->
13856
+ <div>
13857
+ <label for="setting_appearance" class="block text-sm font-medium text-gray-300 mb-2">Pre-clearance / Appearance</label>
13858
+ <select name="setting_appearance" id="setting_appearance" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13859
+ <option value="always" ${!settings.appearance || settings.appearance === "always" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Always - Pre-clearance enabled (verifies immediately)</option>
13860
+ <option value="execute" ${settings.appearance === "execute" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Execute - Challenge on form submit</option>
13861
+ <option value="interaction-only" ${settings.appearance === "interaction-only" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Interaction Only - Only after user interaction</option>
13862
+ </select>
13863
+ <p class="text-xs text-gray-400 mt-1">Controls when Turnstile verification occurs. <strong>Always:</strong> Verifies immediately (pre-clearance). <strong>Execute:</strong> Verifies on form submit. <strong>Interaction Only:</strong> Only after user interaction.</p>
13864
+ </div>
13865
+ `;
13866
+ }
13646
13867
  function renderNoSettings(plugin) {
13647
13868
  if (plugin.id === "seed-data" || plugin.name === "seed-data") {
13648
13869
  return `
@@ -13782,7 +14003,7 @@ function formatTimestamp(timestamp) {
13782
14003
 
13783
14004
  // src/routes/admin-plugins.ts
13784
14005
  var adminPluginRoutes = new hono.Hono();
13785
- adminPluginRoutes.use("*", chunkYHJB26RJ_cjs.requireAuth());
14006
+ adminPluginRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
13786
14007
  var AVAILABLE_PLUGINS = [
13787
14008
  {
13788
14009
  id: "third-party-faq",
@@ -13874,6 +14095,19 @@ var AVAILABLE_PLUGINS = [
13874
14095
  permissions: [],
13875
14096
  dependencies: [],
13876
14097
  is_core: false
14098
+ },
14099
+ {
14100
+ id: "turnstile",
14101
+ name: "turnstile-plugin",
14102
+ display_name: "Cloudflare Turnstile",
14103
+ description: "CAPTCHA-free bot protection for forms using Cloudflare Turnstile. Provides seamless spam prevention with configurable modes, themes, and pre-clearance options.",
14104
+ version: "1.0.0",
14105
+ author: "SonicJS Team",
14106
+ category: "security",
14107
+ icon: "\u{1F6E1}\uFE0F",
14108
+ permissions: [],
14109
+ dependencies: [],
14110
+ is_core: true
13877
14111
  }
13878
14112
  ];
13879
14113
  adminPluginRoutes.get("/", async (c) => {
@@ -14244,6 +14478,33 @@ adminPluginRoutes.post("/install", async (c) => {
14244
14478
  });
14245
14479
  return c.json({ success: true, plugin: easyMdxPlugin2 });
14246
14480
  }
14481
+ if (body.name === "turnstile-plugin") {
14482
+ const turnstilePlugin = await pluginService.installPlugin({
14483
+ id: "turnstile",
14484
+ name: "turnstile-plugin",
14485
+ display_name: "Cloudflare Turnstile",
14486
+ description: "CAPTCHA-free bot protection for forms using Cloudflare Turnstile. Provides seamless spam prevention with configurable modes, themes, and pre-clearance options.",
14487
+ version: "1.0.0",
14488
+ author: "SonicJS Team",
14489
+ category: "security",
14490
+ icon: "\u{1F6E1}\uFE0F",
14491
+ permissions: [],
14492
+ dependencies: [],
14493
+ is_core: true,
14494
+ settings: {
14495
+ siteKey: "",
14496
+ secretKey: "",
14497
+ theme: "auto",
14498
+ size: "normal",
14499
+ mode: "managed",
14500
+ appearance: "always",
14501
+ preClearanceEnabled: false,
14502
+ preClearanceLevel: "managed",
14503
+ enabled: false
14504
+ }
14505
+ });
14506
+ return c.json({ success: true, plugin: turnstilePlugin });
14507
+ }
14247
14508
  return c.json({ error: "Plugin not found in registry" }, 404);
14248
14509
  } catch (error) {
14249
14510
  console.error("Error installing plugin:", error);
@@ -15073,7 +15334,7 @@ function renderLogConfigPage(data) {
15073
15334
 
15074
15335
  // src/routes/admin-logs.ts
15075
15336
  var adminLogsRoutes = new hono.Hono();
15076
- adminLogsRoutes.use("*", chunkYHJB26RJ_cjs.requireAuth());
15337
+ adminLogsRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
15077
15338
  adminLogsRoutes.get("/", async (c) => {
15078
15339
  try {
15079
15340
  const user = c.get("user");
@@ -17401,9 +17662,9 @@ function renderStorageUsage(databaseSizeBytes, mediaSizeBytes) {
17401
17662
  }
17402
17663
 
17403
17664
  // src/routes/admin-dashboard.ts
17404
- var VERSION = chunk2XCJ3HT5_cjs.getCoreVersion();
17665
+ var VERSION = chunkZWV3EBZ7_cjs.getCoreVersion();
17405
17666
  var router = new hono.Hono();
17406
- router.use("*", chunkYHJB26RJ_cjs.requireAuth());
17667
+ router.use("*", chunkYYV3XQOQ_cjs.requireAuth());
17407
17668
  router.get("/", async (c) => {
17408
17669
  const user = c.get("user");
17409
17670
  try {
@@ -19161,7 +19422,7 @@ function renderCollectionFormPage(data) {
19161
19422
 
19162
19423
  // src/routes/admin-collections.ts
19163
19424
  var adminCollectionsRoutes = new hono.Hono();
19164
- adminCollectionsRoutes.use("*", chunkYHJB26RJ_cjs.requireAuth());
19425
+ adminCollectionsRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
19165
19426
  adminCollectionsRoutes.get("/", async (c) => {
19166
19427
  try {
19167
19428
  const user = c.get("user");
@@ -19417,16 +19678,30 @@ adminCollectionsRoutes.get("/:id", async (c) => {
19417
19678
  const schema = typeof collection.schema === "string" ? JSON.parse(collection.schema) : collection.schema;
19418
19679
  if (schema && schema.properties) {
19419
19680
  let fieldOrder = 0;
19420
- fields = Object.entries(schema.properties).map(([fieldName, fieldConfig]) => ({
19421
- id: `schema-${fieldName}`,
19422
- field_name: fieldName,
19423
- field_type: fieldConfig.type || "string",
19424
- field_label: fieldConfig.title || fieldName,
19425
- field_options: fieldConfig,
19426
- field_order: fieldOrder++,
19427
- is_required: fieldConfig.required === true || schema.required && schema.required.includes(fieldName),
19428
- is_searchable: fieldConfig.searchable === true || false
19429
- }));
19681
+ fields = Object.entries(schema.properties).map(([fieldName, fieldConfig]) => {
19682
+ let fieldType = fieldConfig.type || "string";
19683
+ if (fieldConfig.enum) {
19684
+ fieldType = "select";
19685
+ } else if (fieldConfig.format === "richtext") {
19686
+ fieldType = "richtext";
19687
+ } else if (fieldConfig.format === "media") {
19688
+ fieldType = "media";
19689
+ } else if (fieldConfig.format === "date-time") {
19690
+ fieldType = "date";
19691
+ } else if (fieldConfig.type === "slug" || fieldConfig.format === "slug") {
19692
+ fieldType = "slug";
19693
+ }
19694
+ return {
19695
+ id: `schema-${fieldName}`,
19696
+ field_name: fieldName,
19697
+ field_type: fieldType,
19698
+ field_label: fieldConfig.title || fieldName,
19699
+ field_options: fieldConfig,
19700
+ field_order: fieldOrder++,
19701
+ is_required: fieldConfig.required === true || schema.required && schema.required.includes(fieldName),
19702
+ is_searchable: fieldConfig.searchable === true || false
19703
+ };
19704
+ });
19430
19705
  }
19431
19706
  } catch (e) {
19432
19707
  console.error("Error parsing collection schema:", e);
@@ -19641,6 +19916,9 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
19641
19916
  fieldConfig.enum = parsedOptions.options || [];
19642
19917
  } else if (fieldType === "media") {
19643
19918
  fieldConfig.format = "media";
19919
+ } else if (fieldType === "slug") {
19920
+ fieldConfig.type = "slug";
19921
+ fieldConfig.format = "slug";
19644
19922
  } else if (fieldType === "quill") {
19645
19923
  fieldConfig.type = "quill";
19646
19924
  } else if (fieldType === "mdxeditor") {
@@ -21324,7 +21602,7 @@ function renderDatabaseToolsSettings(settings) {
21324
21602
 
21325
21603
  // src/routes/admin-settings.ts
21326
21604
  var adminSettingsRoutes = new hono.Hono();
21327
- adminSettingsRoutes.use("*", chunkYHJB26RJ_cjs.requireAuth());
21605
+ adminSettingsRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
21328
21606
  function getMockSettings(user) {
21329
21607
  return {
21330
21608
  general: {
@@ -21492,7 +21770,7 @@ adminSettingsRoutes.get("/database-tools", (c) => {
21492
21770
  adminSettingsRoutes.get("/api/migrations/status", async (c) => {
21493
21771
  try {
21494
21772
  const db = c.env.DB;
21495
- const migrationService = new chunkIEWLOVP3_cjs.MigrationService(db);
21773
+ const migrationService = new chunkI4V3VZWF_cjs.MigrationService(db);
21496
21774
  const status = await migrationService.getMigrationStatus();
21497
21775
  return c.json({
21498
21776
  success: true,
@@ -21516,7 +21794,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
21516
21794
  }, 403);
21517
21795
  }
21518
21796
  const db = c.env.DB;
21519
- const migrationService = new chunkIEWLOVP3_cjs.MigrationService(db);
21797
+ const migrationService = new chunkI4V3VZWF_cjs.MigrationService(db);
21520
21798
  const result = await migrationService.runPendingMigrations();
21521
21799
  return c.json({
21522
21800
  success: result.success,
@@ -21534,7 +21812,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
21534
21812
  adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
21535
21813
  try {
21536
21814
  const db = c.env.DB;
21537
- const migrationService = new chunkIEWLOVP3_cjs.MigrationService(db);
21815
+ const migrationService = new chunkI4V3VZWF_cjs.MigrationService(db);
21538
21816
  const validation = await migrationService.validateSchema();
21539
21817
  return c.json({
21540
21818
  success: true,
@@ -21758,7 +22036,6 @@ var ROUTES_INFO = {
21758
22036
  reference: "https://github.com/sonicjs/sonicjs"
21759
22037
  };
21760
22038
 
21761
- exports.PluginBuilder = PluginBuilder;
21762
22039
  exports.ROUTES_INFO = ROUTES_INFO;
21763
22040
  exports.adminCheckboxRoutes = adminCheckboxRoutes;
21764
22041
  exports.adminCollectionsRoutes = adminCollectionsRoutes;
@@ -21776,8 +22053,9 @@ exports.api_default = api_default;
21776
22053
  exports.api_media_default = api_media_default;
21777
22054
  exports.api_system_default = api_system_default;
21778
22055
  exports.auth_default = auth_default;
22056
+ exports.checkAdminUserExists = checkAdminUserExists;
21779
22057
  exports.router = router;
21780
22058
  exports.test_cleanup_default = test_cleanup_default;
21781
22059
  exports.userRoutes = userRoutes;
21782
- //# sourceMappingURL=chunk-74DP754U.cjs.map
21783
- //# sourceMappingURL=chunk-74DP754U.cjs.map
22060
+ //# sourceMappingURL=chunk-UAQL2VWX.cjs.map
22061
+ //# sourceMappingURL=chunk-UAQL2VWX.cjs.map