@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,9 +1,10 @@
1
1
  import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService } from './chunk-3YNNVSMC.js';
2
- import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-VYL6RIV6.js';
2
+ import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-TJTWRO4G.js';
3
3
  import { PluginService } from './chunk-SGAG6FD3.js';
4
- import { MigrationService } from './chunk-B6YJRVFQ.js';
4
+ import { MigrationService } from './chunk-VEL7QRYI.js';
5
5
  import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-V5LBQN3I.js';
6
- import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-TMQOLXLY.js';
6
+ import { PluginBuilder } from './chunk-QDBNW7KQ.js';
7
+ import { QueryFilterBuilder, getBlocksFieldConfig, parseBlocksValue, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-BHNDALCA.js';
7
8
  import { metricsTracker } from './chunk-FICTAGD4.js';
8
9
  import { Hono } from 'hono';
9
10
  import { cors } from 'hono/cors';
@@ -14,6 +15,37 @@ import { html, raw } from 'hono/html';
14
15
  // src/schemas/index.ts
15
16
  var schemaDefinitions = [];
16
17
  var apiContentCrudRoutes = new Hono();
18
+ apiContentCrudRoutes.get("/check-slug", async (c) => {
19
+ try {
20
+ const db = c.env.DB;
21
+ const collectionId = c.req.query("collectionId");
22
+ const slug = c.req.query("slug");
23
+ const excludeId = c.req.query("excludeId");
24
+ if (!collectionId || !slug) {
25
+ return c.json({ error: "collectionId and slug are required" }, 400);
26
+ }
27
+ let query = "SELECT id FROM content WHERE collection_id = ? AND slug = ?";
28
+ const params = [collectionId, slug];
29
+ if (excludeId) {
30
+ query += " AND id != ?";
31
+ params.push(excludeId);
32
+ }
33
+ const existing = await db.prepare(query).bind(...params).first();
34
+ if (existing) {
35
+ return c.json({
36
+ available: false,
37
+ message: "This URL slug is already in use in this collection"
38
+ });
39
+ }
40
+ return c.json({ available: true });
41
+ } catch (error) {
42
+ console.error("Error checking slug:", error);
43
+ return c.json({
44
+ error: "Failed to check slug availability",
45
+ details: error instanceof Error ? error.message : String(error)
46
+ }, 500);
47
+ }
48
+ });
17
49
  apiContentCrudRoutes.get("/:id", async (c) => {
18
50
  try {
19
51
  const id = c.req.param("id");
@@ -1720,7 +1752,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
1720
1752
  });
1721
1753
  adminApiRoutes.get("/migrations/status", async (c) => {
1722
1754
  try {
1723
- const { MigrationService: MigrationService2 } = await import('./migrations-6HKPNPTK.js');
1755
+ const { MigrationService: MigrationService2 } = await import('./migrations-TGZKJKV4.js');
1724
1756
  const db = c.env.DB;
1725
1757
  const migrationService = new MigrationService2(db);
1726
1758
  const status = await migrationService.getMigrationStatus();
@@ -1745,7 +1777,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
1745
1777
  error: "Unauthorized. Admin access required."
1746
1778
  }, 403);
1747
1779
  }
1748
- const { MigrationService: MigrationService2 } = await import('./migrations-6HKPNPTK.js');
1780
+ const { MigrationService: MigrationService2 } = await import('./migrations-TGZKJKV4.js');
1749
1781
  const db = c.env.DB;
1750
1782
  const migrationService = new MigrationService2(db);
1751
1783
  const result = await migrationService.runPendingMigrations();
@@ -1764,7 +1796,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
1764
1796
  });
1765
1797
  adminApiRoutes.get("/migrations/validate", async (c) => {
1766
1798
  try {
1767
- const { MigrationService: MigrationService2 } = await import('./migrations-6HKPNPTK.js');
1799
+ const { MigrationService: MigrationService2 } = await import('./migrations-TGZKJKV4.js');
1768
1800
  const db = c.env.DB;
1769
1801
  const migrationService = new MigrationService2(db);
1770
1802
  const validation = await migrationService.validateSchema();
@@ -1991,18 +2023,40 @@ function renderRegisterPage(data) {
1991
2023
  <div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
1992
2024
  <!-- Logo Section -->
1993
2025
  <div class="sm:mx-auto sm:w-full sm:max-w-md text-center">
1994
- <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-lg bg-white">
1995
- <svg class="h-7 w-7 text-zinc-950" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1996
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
2026
+ <div class="mx-auto w-64 mb-8">
2027
+ <svg class="w-full h-auto" viewBox="380 1300 2250 400" aria-hidden="true">
2028
+ <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>
2029
+ <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>
2030
+ <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>
2031
+ <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>
2032
+ <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>
2033
+ <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>
2034
+ <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>
2035
+ <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>
2036
+ <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>
1997
2037
  </svg>
1998
2038
  </div>
1999
- <h1 class="mt-6 text-3xl font-semibold tracking-tight text-white">SonicJS AI</h1>
2000
- <p class="mt-2 text-sm text-zinc-400">Create your account and get started</p>
2039
+ <h2 class="mt-6 text-xl font-medium text-white">${data.isSetup ? "Welcome to SonicJS" : "Create Account"}</h2>
2040
+ ${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>`}
2001
2041
  </div>
2002
2042
 
2003
2043
  <!-- Form Container -->
2004
2044
  <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
2005
2045
  <div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
2046
+ <!-- Setup Banner -->
2047
+ ${data.isSetup ? `
2048
+ <div class="mb-6 rounded-lg bg-blue-500/10 p-4 ring-1 ring-blue-500/20">
2049
+ <div class="flex items-start gap-x-3">
2050
+ <svg class="h-5 w-5 text-blue-400 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2051
+ <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"/>
2052
+ </svg>
2053
+ <div class="flex-1">
2054
+ <p class="text-sm font-medium text-blue-300">First-time Setup</p>
2055
+ <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>
2056
+ </div>
2057
+ </div>
2058
+ </div>
2059
+ ` : ""}
2006
2060
  <!-- Alerts -->
2007
2061
  ${data.error ? `<div class="mb-6">${renderAlert({ type: "error", message: data.error })}</div>` : ""}
2008
2062
 
@@ -2117,6 +2171,7 @@ function renderRegisterPage(data) {
2117
2171
  </html>
2118
2172
  `;
2119
2173
  }
2174
+ var adminExistsCache = null;
2120
2175
  async function isRegistrationEnabled(db) {
2121
2176
  try {
2122
2177
  const plugin = await db.prepare("SELECT settings FROM plugins WHERE id = ?").bind("core-auth").first();
@@ -2138,6 +2193,21 @@ async function isFirstUserRegistration(db) {
2138
2193
  return false;
2139
2194
  }
2140
2195
  }
2196
+ async function checkAdminUserExists(db) {
2197
+ if (adminExistsCache !== null) {
2198
+ return adminExistsCache;
2199
+ }
2200
+ try {
2201
+ const result = await db.prepare("SELECT id FROM users WHERE role = ?").bind("admin").first();
2202
+ adminExistsCache = !!result;
2203
+ return adminExistsCache;
2204
+ } catch {
2205
+ return false;
2206
+ }
2207
+ }
2208
+ function setAdminExists() {
2209
+ adminExistsCache = true;
2210
+ }
2141
2211
  var baseRegistrationSchema = z.object({
2142
2212
  email: z.string().email("Valid email is required"),
2143
2213
  password: z.string().min(8, "Password must be at least 8 characters"),
@@ -2199,8 +2269,11 @@ authRoutes.get("/register", async (c) => {
2199
2269
  }
2200
2270
  }
2201
2271
  const error = c.req.query("error");
2272
+ const isSetup = c.req.query("setup") === "true";
2202
2273
  const pageData = {
2203
- error: error || void 0
2274
+ error: error || void 0,
2275
+ isSetup: isSetup && isFirstUser
2276
+ // Only show setup message if truly first user
2204
2277
  };
2205
2278
  return c.html(renderRegisterPage(pageData));
2206
2279
  });
@@ -2475,6 +2548,9 @@ authRoutes.post("/register/form", async (c) => {
2475
2548
  now.getTime(),
2476
2549
  now.getTime()
2477
2550
  ).run();
2551
+ if (isFirstUser) {
2552
+ setAdminExists();
2553
+ }
2478
2554
  const token = await AuthManager.generateToken(userId, normalizedEmail, role);
2479
2555
  setCookie(c, "auth_token", token, {
2480
2556
  httpOnly: true,
@@ -2596,6 +2672,7 @@ authRoutes.post("/seed-admin", async (c) => {
2596
2672
  if (existingAdmin) {
2597
2673
  const passwordHash2 = await AuthManager.hashPassword("sonicjs!");
2598
2674
  await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
2675
+ setAdminExists();
2599
2676
  return c.json({
2600
2677
  message: "Admin user already exists (password updated)",
2601
2678
  user: {
@@ -2626,6 +2703,7 @@ authRoutes.post("/seed-admin", async (c) => {
2626
2703
  now,
2627
2704
  now
2628
2705
  ).run();
2706
+ setAdminExists();
2629
2707
  return c.json({
2630
2708
  message: "Admin user created successfully",
2631
2709
  user: {
@@ -3337,7 +3415,7 @@ init_admin_layout_catalyst_template();
3337
3415
 
3338
3416
  // src/templates/components/dynamic-field.template.ts
3339
3417
  function renderDynamicField(field, options = {}) {
3340
- const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {} } = options;
3418
+ const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {}, collectionId = "", contentId = "" } = options;
3341
3419
  const opts = field.field_options || {};
3342
3420
  const required = field.is_required ? "required" : "";
3343
3421
  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}`;
@@ -3590,67 +3668,171 @@ function renderDynamicField(field, options = {}) {
3590
3668
  `;
3591
3669
  break;
3592
3670
  case "slug":
3593
- let slugPattern = opts.pattern || "^[a-z0-9-]+$";
3594
- let slugHelp = '<p class="mt-2 text-xs text-zinc-500 dark:text-zinc-400">Use lowercase letters, numbers, and hyphens only</p>';
3595
- 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>`;
3671
+ const slugPattern = opts.pattern || "^[a-z0-9-]+$";
3672
+ const collectionIdValue = collectionId || opts.collectionId || "";
3673
+ const contentIdValue = contentId || opts.contentId || "";
3674
+ const isEditMode = !!value;
3596
3675
  fieldHTML = `
3597
- <input
3598
- type="text"
3599
- id="${fieldId}"
3600
- name="${fieldName}"
3601
- value="${escapeHtml2(value)}"
3602
- placeholder="${opts.placeholder || "url-friendly-slug"}"
3603
- maxlength="${opts.maxLength || ""}"
3604
- data-pattern="${slugPattern}"
3605
- class="${baseClasses} ${errorClasses}"
3606
- ${required}
3607
- ${disabled ? "disabled" : ""}
3608
- >
3609
- ${slugHelp}
3676
+ <div class="slug-field-container">
3677
+ <input
3678
+ type="text"
3679
+ id="${fieldId}"
3680
+ name="${fieldName}"
3681
+ value="${escapeHtml2(value)}"
3682
+ placeholder="${opts.placeholder || "url-friendly-slug"}"
3683
+ maxlength="${opts.maxLength || 100}"
3684
+ data-pattern="${slugPattern}"
3685
+ data-collection-id="${collectionIdValue}"
3686
+ data-content-id="${contentIdValue}"
3687
+ data-is-edit-mode="${isEditMode}"
3688
+ class="${baseClasses} ${errorClasses}"
3689
+ ${required}
3690
+ ${disabled ? "disabled" : ""}
3691
+ >
3692
+ <div id="${fieldId}-status" class="slug-status mt-1 text-sm min-h-[20px]"></div>
3693
+ <button
3694
+ type="button"
3695
+ 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"
3696
+ onclick="window.regenerateSlugFromTitle_${fieldId.replace(/-/g, "_")}()"
3697
+ >
3698
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3699
+ <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>
3700
+ </svg>
3701
+ Regenerate from title
3702
+ </button>
3703
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">Use lowercase letters, numbers, and hyphens only</p>
3704
+ </div>
3705
+
3610
3706
  <script>
3611
3707
  (function() {
3612
- const field = document.getElementById('${fieldId}');
3708
+ const slugField = document.getElementById('${fieldId}');
3709
+ const statusDiv = document.getElementById('${fieldId}-status');
3710
+ const isEditMode = slugField.dataset.isEditMode === 'true';
3613
3711
  const pattern = new RegExp('${slugPattern}');
3614
-
3615
- field.addEventListener('input', function() {
3616
- if (this.value && !pattern.test(this.value)) {
3617
- this.setCustomValidity('Please use only lowercase letters, numbers, and hyphens.');
3618
- } else {
3619
- this.setCustomValidity('');
3620
- }
3621
- });
3622
-
3623
- field.addEventListener('blur', function() {
3624
- this.reportValidity();
3625
- });
3626
- })();
3627
-
3628
- function generateSlugFromTitle(slugFieldId) {
3629
- const titleField = document.querySelector('input[name="title"]');
3630
- const slugField = document.getElementById(slugFieldId);
3631
- if (titleField && slugField) {
3632
- const slug = titleField.value
3712
+ const collectionId = slugField.dataset.collectionId;
3713
+ const contentId = slugField.dataset.contentId;
3714
+
3715
+ let checkTimeout;
3716
+ let lastCheckedSlug = '';
3717
+ let manuallyEdited = false;
3718
+
3719
+ // Shared slug generation function
3720
+ function generateSlug(text) {
3721
+ if (!text) return '';
3722
+
3723
+ return text
3633
3724
  .toLowerCase()
3725
+ .normalize('NFD')
3726
+ .replace(/[\\u0300-\\u036f]/g, '')
3634
3727
  .replace(/[^a-z0-9\\s_-]/g, '')
3635
3728
  .replace(/\\s+/g, '-')
3636
3729
  .replace(/[-_]+/g, '-')
3637
- .replace(/^[-_]|[-_]$/g, '');
3638
- slugField.value = slug;
3730
+ .replace(/^[-_]+|[-_]+$/g, '')
3731
+ .substring(0, 100);
3639
3732
  }
3640
- }
3641
-
3642
- // Auto-generate slug when title changes
3643
- document.addEventListener('DOMContentLoaded', function() {
3644
- const titleField = document.querySelector('input[name="title"]');
3645
- const slugField = document.getElementById('${fieldId}');
3646
- if (titleField && slugField && !slugField.value) {
3647
- titleField.addEventListener('input', function() {
3648
- if (!slugField.value) {
3649
- generateSlugFromTitle('${fieldId}');
3733
+
3734
+ // Check if slug is available
3735
+ async function checkSlugAvailability(slug) {
3736
+ if (!slug || !collectionId) return;
3737
+
3738
+ // Don't check if it's the same as last time
3739
+ if (slug === lastCheckedSlug) return;
3740
+ lastCheckedSlug = slug;
3741
+
3742
+ try {
3743
+ // Show checking status
3744
+ statusDiv.innerHTML = '<span class="text-gray-400">\u23F3 Checking availability...</span>';
3745
+
3746
+ // Build URL
3747
+ let url = \`/api/content/check-slug?collectionId=\${encodeURIComponent(collectionId)}&slug=\${encodeURIComponent(slug)}\`;
3748
+ if (contentId) {
3749
+ url += \`&excludeId=\${encodeURIComponent(contentId)}\`;
3650
3750
  }
3651
- });
3751
+
3752
+ const response = await fetch(url);
3753
+ const data = await response.json();
3754
+
3755
+ if (data.available) {
3756
+ statusDiv.innerHTML = '<span class="text-green-500 dark:text-green-400">\u2713 Available</span>';
3757
+ slugField.setCustomValidity('');
3758
+ } else {
3759
+ statusDiv.innerHTML = \`<span class="text-red-500 dark:text-red-400">\u2717 \${data.message || 'Already in use'}</span>\`;
3760
+ slugField.setCustomValidity(data.message || 'This slug is already in use');
3761
+ }
3762
+ } catch (error) {
3763
+ console.error('Error checking slug:', error);
3764
+ statusDiv.innerHTML = '<span class="text-yellow-500 dark:text-yellow-400">\u26A0 Could not verify</span>';
3765
+ }
3652
3766
  }
3653
- });
3767
+
3768
+ // Format validation and duplicate checking
3769
+ slugField.addEventListener('input', function() {
3770
+ const value = this.value;
3771
+
3772
+ // Mark as manually edited if user types directly
3773
+ if (document.activeElement === this) {
3774
+ manuallyEdited = true;
3775
+ }
3776
+
3777
+ // Clear status if empty
3778
+ if (!value) {
3779
+ statusDiv.innerHTML = '';
3780
+ this.setCustomValidity('');
3781
+ return;
3782
+ }
3783
+
3784
+ // Pattern validation
3785
+ if (!pattern.test(value)) {
3786
+ this.setCustomValidity('Please use only lowercase letters, numbers, and hyphens.');
3787
+ statusDiv.innerHTML = '<span class="text-red-500 dark:text-red-400">\u2717 Invalid format</span>';
3788
+ return;
3789
+ }
3790
+
3791
+ // Debounce the availability check
3792
+ clearTimeout(checkTimeout);
3793
+ checkTimeout = setTimeout(() => {
3794
+ checkSlugAvailability(value);
3795
+ }, 500); // Wait 500ms after user stops typing
3796
+ });
3797
+
3798
+ // Initial check if field has value
3799
+ if (slugField.value) {
3800
+ checkSlugAvailability(slugField.value);
3801
+ }
3802
+
3803
+ // Auto-generate only in create mode
3804
+ // Wait for all fields to be rendered before attaching listeners
3805
+ if (!isEditMode) {
3806
+ // Use setTimeout to ensure all fields in the form are rendered
3807
+ setTimeout(() => {
3808
+ const titleField = document.querySelector('input[name="title"]');
3809
+ if (titleField) {
3810
+ titleField.addEventListener('input', function() {
3811
+ if (!manuallyEdited) {
3812
+ const slug = generateSlug(this.value);
3813
+ slugField.value = slug;
3814
+
3815
+ // Trigger validation and duplicate check
3816
+ slugField.dispatchEvent(new Event('input', { bubbles: true }));
3817
+ }
3818
+ });
3819
+ }
3820
+ }, 0);
3821
+ }
3822
+
3823
+ // Global function for regenerate button
3824
+ window.regenerateSlugFromTitle_${fieldId.replace(/-/g, "_")} = function() {
3825
+ const titleField = document.querySelector('input[name="title"]');
3826
+ if (titleField && slugField) {
3827
+ const slug = generateSlug(titleField.value);
3828
+ slugField.value = slug;
3829
+ manuallyEdited = false;
3830
+
3831
+ // Trigger validation and duplicate check
3832
+ slugField.dispatchEvent(new Event('input', { bubbles: true }));
3833
+ }
3834
+ };
3835
+ })();
3654
3836
  </script>
3655
3837
  `;
3656
3838
  break;
@@ -3783,207 +3965,6 @@ function escapeHtml2(text) {
3783
3965
  "'": "&#39;"
3784
3966
  })[char] || char);
3785
3967
  }
3786
- var PluginBuilder = class _PluginBuilder {
3787
- plugin;
3788
- constructor(options) {
3789
- this.plugin = {
3790
- name: options.name,
3791
- version: options.version,
3792
- description: options.description,
3793
- author: options.author,
3794
- dependencies: options.dependencies,
3795
- routes: [],
3796
- middleware: [],
3797
- models: [],
3798
- services: [],
3799
- adminPages: [],
3800
- adminComponents: [],
3801
- menuItems: [],
3802
- hooks: []
3803
- };
3804
- }
3805
- /**
3806
- * Create a new plugin builder
3807
- */
3808
- static create(options) {
3809
- return new _PluginBuilder(options);
3810
- }
3811
- /**
3812
- * Add metadata to the plugin
3813
- */
3814
- metadata(metadata) {
3815
- Object.assign(this.plugin, metadata);
3816
- return this;
3817
- }
3818
- /**
3819
- * Add routes to plugin
3820
- */
3821
- addRoutes(routes) {
3822
- this.plugin.routes = [...this.plugin.routes || [], ...routes];
3823
- return this;
3824
- }
3825
- /**
3826
- * Add a single route to plugin
3827
- */
3828
- addRoute(path, handler, options) {
3829
- const route = {
3830
- path,
3831
- handler,
3832
- ...options
3833
- };
3834
- this.plugin.routes = [...this.plugin.routes || [], route];
3835
- return this;
3836
- }
3837
- /**
3838
- * Add middleware to plugin
3839
- */
3840
- addMiddleware(middleware) {
3841
- this.plugin.middleware = [...this.plugin.middleware || [], ...middleware];
3842
- return this;
3843
- }
3844
- /**
3845
- * Add a single middleware to plugin
3846
- */
3847
- addSingleMiddleware(name, handler, options) {
3848
- const middleware = {
3849
- name,
3850
- handler,
3851
- ...options
3852
- };
3853
- this.plugin.middleware = [...this.plugin.middleware || [], middleware];
3854
- return this;
3855
- }
3856
- /**
3857
- * Add models to plugin
3858
- */
3859
- addModels(models) {
3860
- this.plugin.models = [...this.plugin.models || [], ...models];
3861
- return this;
3862
- }
3863
- /**
3864
- * Add a single model to plugin
3865
- */
3866
- addModel(name, options) {
3867
- const model = {
3868
- name,
3869
- ...options
3870
- };
3871
- this.plugin.models = [...this.plugin.models || [], model];
3872
- return this;
3873
- }
3874
- /**
3875
- * Add services to plugin
3876
- */
3877
- addServices(services) {
3878
- this.plugin.services = [...this.plugin.services || [], ...services];
3879
- return this;
3880
- }
3881
- /**
3882
- * Add a single service to plugin
3883
- */
3884
- addService(name, implementation, options) {
3885
- const service = {
3886
- name,
3887
- implementation,
3888
- ...options
3889
- };
3890
- this.plugin.services = [...this.plugin.services || [], service];
3891
- return this;
3892
- }
3893
- /**
3894
- * Add admin pages to plugin
3895
- */
3896
- addAdminPages(pages) {
3897
- this.plugin.adminPages = [...this.plugin.adminPages || [], ...pages];
3898
- return this;
3899
- }
3900
- /**
3901
- * Add a single admin page to plugin
3902
- */
3903
- addAdminPage(path, title, component, options) {
3904
- const page = {
3905
- path,
3906
- title,
3907
- component,
3908
- ...options
3909
- };
3910
- this.plugin.adminPages = [...this.plugin.adminPages || [], page];
3911
- return this;
3912
- }
3913
- /**
3914
- * Add admin components to plugin
3915
- */
3916
- addComponents(components) {
3917
- this.plugin.adminComponents = [...this.plugin.adminComponents || [], ...components];
3918
- return this;
3919
- }
3920
- /**
3921
- * Add a single admin component to plugin
3922
- */
3923
- addComponent(name, template, options) {
3924
- const component = {
3925
- name,
3926
- template,
3927
- ...options
3928
- };
3929
- this.plugin.adminComponents = [...this.plugin.adminComponents || [], component];
3930
- return this;
3931
- }
3932
- /**
3933
- * Add menu items to plugin
3934
- */
3935
- addMenuItems(items) {
3936
- this.plugin.menuItems = [...this.plugin.menuItems || [], ...items];
3937
- return this;
3938
- }
3939
- /**
3940
- * Add a single menu item to plugin
3941
- */
3942
- addMenuItem(label, path, options) {
3943
- const menuItem = {
3944
- label,
3945
- path,
3946
- ...options
3947
- };
3948
- this.plugin.menuItems = [...this.plugin.menuItems || [], menuItem];
3949
- return this;
3950
- }
3951
- /**
3952
- * Add hooks to plugin
3953
- */
3954
- addHooks(hooks) {
3955
- this.plugin.hooks = [...this.plugin.hooks || [], ...hooks];
3956
- return this;
3957
- }
3958
- /**
3959
- * Add a single hook to plugin
3960
- */
3961
- addHook(name, handler, options) {
3962
- const hook = {
3963
- name,
3964
- handler,
3965
- ...options
3966
- };
3967
- this.plugin.hooks = [...this.plugin.hooks || [], hook];
3968
- return this;
3969
- }
3970
- /**
3971
- * Add lifecycle hooks
3972
- */
3973
- lifecycle(hooks) {
3974
- Object.assign(this.plugin, hooks);
3975
- return this;
3976
- }
3977
- /**
3978
- * Build the plugin
3979
- */
3980
- build() {
3981
- if (!this.plugin.name || !this.plugin.version) {
3982
- throw new Error("Plugin name and version are required");
3983
- }
3984
- return this.plugin;
3985
- }
3986
- };
3987
3968
 
3988
3969
  // src/plugins/available/tinymce-plugin/index.ts
3989
3970
  var builder = PluginBuilder.create({
@@ -4527,17 +4508,24 @@ function renderContentFormPage(data) {
4527
4508
  const coreFieldsHTML = coreFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4528
4509
  value: getFieldValue(field.field_name),
4529
4510
  errors: data.validationErrors?.[field.field_name] || [],
4530
- pluginStatuses
4511
+ pluginStatuses,
4512
+ collectionId: data.collection.id,
4513
+ contentId: data.id
4514
+ // Pass content ID when editing
4531
4515
  }));
4532
4516
  const contentFieldsHTML = contentFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4533
4517
  value: getFieldValue(field.field_name),
4534
4518
  errors: data.validationErrors?.[field.field_name] || [],
4535
- pluginStatuses
4519
+ pluginStatuses,
4520
+ collectionId: data.collection.id,
4521
+ contentId: data.id
4536
4522
  }));
4537
4523
  const metaFieldsHTML = metaFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4538
4524
  value: getFieldValue(field.field_name),
4539
4525
  errors: data.validationErrors?.[field.field_name] || [],
4540
- pluginStatuses
4526
+ pluginStatuses,
4527
+ collectionId: data.collection.id,
4528
+ contentId: data.id
4541
4529
  }));
4542
4530
  const pageContent = `
4543
4531
  <div class="space-y-6">
@@ -6454,6 +6442,18 @@ adminContentRoutes.post("/", async (c) => {
6454
6442
  const errors = {};
6455
6443
  for (const field of fields) {
6456
6444
  const value = formData.get(field.field_name);
6445
+ const blocksConfig = getBlocksFieldConfig(field.field_options);
6446
+ if (blocksConfig) {
6447
+ const parsed = parseBlocksValue(value, blocksConfig);
6448
+ if (field.is_required && parsed.value.length === 0) {
6449
+ parsed.errors.push(`${field.field_label} is required`);
6450
+ }
6451
+ if (parsed.errors.length > 0) {
6452
+ errors[field.field_name] = parsed.errors;
6453
+ }
6454
+ data[field.field_name] = parsed.value;
6455
+ continue;
6456
+ }
6457
6457
  if (field.is_required && (!value || value.toString().trim() === "")) {
6458
6458
  errors[field.field_name] = [`${field.field_label} is required`];
6459
6459
  continue;
@@ -6476,6 +6476,67 @@ adminContentRoutes.post("/", async (c) => {
6476
6476
  data[field.field_name] = value;
6477
6477
  }
6478
6478
  break;
6479
+ case "array": {
6480
+ if (!value || value.toString().trim() === "") {
6481
+ data[field.field_name] = [];
6482
+ if (field.is_required) {
6483
+ errors[field.field_name] = [`${field.field_label} is required`];
6484
+ }
6485
+ break;
6486
+ }
6487
+ try {
6488
+ const parsed = JSON.parse(value.toString());
6489
+ if (!Array.isArray(parsed)) {
6490
+ errors[field.field_name] = [`${field.field_label} must be a JSON array`];
6491
+ } else {
6492
+ if (field.is_required && parsed.length === 0) {
6493
+ errors[field.field_name] = [`${field.field_label} is required`];
6494
+ }
6495
+ data[field.field_name] = parsed;
6496
+ }
6497
+ } catch {
6498
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6499
+ }
6500
+ break;
6501
+ }
6502
+ case "object": {
6503
+ if (!value || value.toString().trim() === "") {
6504
+ data[field.field_name] = {};
6505
+ if (field.is_required) {
6506
+ errors[field.field_name] = [`${field.field_label} is required`];
6507
+ }
6508
+ break;
6509
+ }
6510
+ try {
6511
+ const parsed = JSON.parse(value.toString());
6512
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
6513
+ errors[field.field_name] = [`${field.field_label} must be a JSON object`];
6514
+ } else {
6515
+ if (field.is_required && Object.keys(parsed).length === 0) {
6516
+ errors[field.field_name] = [`${field.field_label} is required`];
6517
+ }
6518
+ data[field.field_name] = parsed;
6519
+ }
6520
+ } catch {
6521
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6522
+ }
6523
+ break;
6524
+ }
6525
+ case "json": {
6526
+ if (!value || value.toString().trim() === "") {
6527
+ data[field.field_name] = null;
6528
+ if (field.is_required) {
6529
+ errors[field.field_name] = [`${field.field_label} is required`];
6530
+ }
6531
+ break;
6532
+ }
6533
+ try {
6534
+ data[field.field_name] = JSON.parse(value.toString());
6535
+ } catch {
6536
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6537
+ }
6538
+ break;
6539
+ }
6479
6540
  default:
6480
6541
  data[field.field_name] = value;
6481
6542
  }
@@ -6600,6 +6661,18 @@ adminContentRoutes.put("/:id", async (c) => {
6600
6661
  const errors = {};
6601
6662
  for (const field of fields) {
6602
6663
  const value = formData.get(field.field_name);
6664
+ const blocksConfig = getBlocksFieldConfig(field.field_options);
6665
+ if (blocksConfig) {
6666
+ const parsed = parseBlocksValue(value, blocksConfig);
6667
+ if (field.is_required && parsed.value.length === 0) {
6668
+ parsed.errors.push(`${field.field_label} is required`);
6669
+ }
6670
+ if (parsed.errors.length > 0) {
6671
+ errors[field.field_name] = parsed.errors;
6672
+ }
6673
+ data[field.field_name] = parsed.value;
6674
+ continue;
6675
+ }
6603
6676
  if (field.is_required && (!value || value.toString().trim() === "")) {
6604
6677
  errors[field.field_name] = [`${field.field_label} is required`];
6605
6678
  continue;
@@ -6622,6 +6695,67 @@ adminContentRoutes.put("/:id", async (c) => {
6622
6695
  data[field.field_name] = value;
6623
6696
  }
6624
6697
  break;
6698
+ case "array": {
6699
+ if (!value || value.toString().trim() === "") {
6700
+ data[field.field_name] = [];
6701
+ if (field.is_required) {
6702
+ errors[field.field_name] = [`${field.field_label} is required`];
6703
+ }
6704
+ break;
6705
+ }
6706
+ try {
6707
+ const parsed = JSON.parse(value.toString());
6708
+ if (!Array.isArray(parsed)) {
6709
+ errors[field.field_name] = [`${field.field_label} must be a JSON array`];
6710
+ } else {
6711
+ if (field.is_required && parsed.length === 0) {
6712
+ errors[field.field_name] = [`${field.field_label} is required`];
6713
+ }
6714
+ data[field.field_name] = parsed;
6715
+ }
6716
+ } catch {
6717
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6718
+ }
6719
+ break;
6720
+ }
6721
+ case "object": {
6722
+ if (!value || value.toString().trim() === "") {
6723
+ data[field.field_name] = {};
6724
+ if (field.is_required) {
6725
+ errors[field.field_name] = [`${field.field_label} is required`];
6726
+ }
6727
+ break;
6728
+ }
6729
+ try {
6730
+ const parsed = JSON.parse(value.toString());
6731
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
6732
+ errors[field.field_name] = [`${field.field_label} must be a JSON object`];
6733
+ } else {
6734
+ if (field.is_required && Object.keys(parsed).length === 0) {
6735
+ errors[field.field_name] = [`${field.field_label} is required`];
6736
+ }
6737
+ data[field.field_name] = parsed;
6738
+ }
6739
+ } catch {
6740
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6741
+ }
6742
+ break;
6743
+ }
6744
+ case "json": {
6745
+ if (!value || value.toString().trim() === "") {
6746
+ data[field.field_name] = null;
6747
+ if (field.is_required) {
6748
+ errors[field.field_name] = [`${field.field_label} is required`];
6749
+ }
6750
+ break;
6751
+ }
6752
+ try {
6753
+ data[field.field_name] = JSON.parse(value.toString());
6754
+ } catch {
6755
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6756
+ }
6757
+ break;
6758
+ }
6625
6759
  default:
6626
6760
  data[field.field_name] = value;
6627
6761
  }
@@ -6741,6 +6875,12 @@ adminContentRoutes.post("/preview", async (c) => {
6741
6875
  const data = {};
6742
6876
  for (const field of fields) {
6743
6877
  const value = formData.get(field.field_name);
6878
+ const blocksConfig = getBlocksFieldConfig(field.field_options);
6879
+ if (blocksConfig) {
6880
+ const parsed = parseBlocksValue(value, blocksConfig);
6881
+ data[field.field_name] = parsed.value;
6882
+ continue;
6883
+ }
6744
6884
  switch (field.field_type) {
6745
6885
  case "number":
6746
6886
  data[field.field_name] = value ? Number(value) : null;
@@ -13269,6 +13409,9 @@ function renderAuthSettingsForm(settings) {
13269
13409
  }
13270
13410
 
13271
13411
  // src/templates/pages/admin-plugin-settings.template.ts
13412
+ function escapeHtmlAttr(value) {
13413
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13414
+ }
13272
13415
  function renderPluginSettingsPage(data) {
13273
13416
  const { plugin, activity = [], user } = data;
13274
13417
  const pageContent = `
@@ -13546,6 +13689,7 @@ function renderSettingsTab(plugin) {
13546
13689
  const settings = plugin.settings || {};
13547
13690
  const isSeedDataPlugin = plugin.id === "seed-data" || plugin.name === "seed-data";
13548
13691
  const isAuthPlugin = plugin.id === "core-auth" || plugin.name === "core-auth";
13692
+ const isTurnstilePlugin = plugin.id === "turnstile" || plugin.name === "turnstile";
13549
13693
  return `
13550
13694
  ${isSeedDataPlugin ? `
13551
13695
  <div class="backdrop-blur-md bg-black/20 rounded-xl border border-white/10 shadow-xl p-6 mb-6">
@@ -13572,12 +13716,15 @@ function renderSettingsTab(plugin) {
13572
13716
  ${isAuthPlugin ? `
13573
13717
  <h2 class="text-xl font-semibold text-white mb-4">Authentication Settings</h2>
13574
13718
  <p class="text-gray-400 mb-6">Configure user registration fields and validation rules.</p>
13719
+ ` : isTurnstilePlugin ? `
13720
+ <h2 class="text-xl font-semibold text-white mb-4">Cloudflare Turnstile Settings</h2>
13721
+ <p class="text-gray-400 mb-6">Configure CAPTCHA-free bot protection for your forms.</p>
13575
13722
  ` : `
13576
13723
  <h2 class="text-xl font-semibold text-white mb-4">Plugin Settings</h2>
13577
13724
  `}
13578
13725
 
13579
13726
  <form id="settings-form" class="space-y-6">
13580
- ${isAuthPlugin && Object.keys(settings).length > 0 ? renderAuthSettingsForm(settings) : Object.keys(settings).length > 0 ? renderSettingsFields(settings) : renderNoSettings(plugin)}
13727
+ ${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)}
13581
13728
 
13582
13729
  ${Object.keys(settings).length > 0 ? `
13583
13730
  <div class="flex items-center justify-end pt-6 border-t border-white/10">
@@ -13641,6 +13788,80 @@ function renderSettingsFields(settings) {
13641
13788
  }
13642
13789
  }).join("");
13643
13790
  }
13791
+ function renderTurnstileSettingsForm(settings) {
13792
+ 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";
13793
+ 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";
13794
+ return `
13795
+ <!-- Enable Toggle -->
13796
+ <div class="flex items-center justify-between">
13797
+ <div>
13798
+ <label for="setting_enabled" class="text-sm font-medium text-gray-300">Enable Turnstile</label>
13799
+ <p class="text-xs text-gray-400">Enable or disable Turnstile verification globally</p>
13800
+ </div>
13801
+ <label class="relative inline-flex items-center cursor-pointer">
13802
+ <input type="checkbox" name="setting_enabled" id="setting_enabled" ${settings.enabled ? "checked" : ""} class="sr-only peer">
13803
+ <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>
13804
+ </label>
13805
+ </div>
13806
+
13807
+ <!-- Site Key -->
13808
+ <div>
13809
+ <label for="setting_siteKey" class="block text-sm font-medium text-gray-300 mb-2">Site Key</label>
13810
+ <input type="text" name="setting_siteKey" id="setting_siteKey" value="${escapeHtmlAttr(settings.siteKey || "")}" placeholder="0x4AAAAAAAA..." class="${inputClass}">
13811
+ <p class="text-xs text-gray-400 mt-1">Your Cloudflare Turnstile site key (public)</p>
13812
+ </div>
13813
+
13814
+ <!-- Secret Key -->
13815
+ <div>
13816
+ <label for="setting_secretKey" class="block text-sm font-medium text-gray-300 mb-2">Secret Key</label>
13817
+ <input type="password" name="setting_secretKey" id="setting_secretKey" value="${escapeHtmlAttr(settings.secretKey || "")}" placeholder="0x4AAAAAAAA..." class="${inputClass}">
13818
+ <p class="text-xs text-gray-400 mt-1">Your Cloudflare Turnstile secret key (private)</p>
13819
+ </div>
13820
+
13821
+ <!-- Theme -->
13822
+ <div>
13823
+ <label for="setting_theme" class="block text-sm font-medium text-gray-300 mb-2">Widget Theme</label>
13824
+ <select name="setting_theme" id="setting_theme" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13825
+ <option value="auto" ${settings.theme === "auto" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Auto (matches page theme)</option>
13826
+ <option value="light" ${settings.theme === "light" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Light</option>
13827
+ <option value="dark" ${settings.theme === "dark" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Dark</option>
13828
+ </select>
13829
+ <p class="text-xs text-gray-400 mt-1">Visual appearance of the Turnstile widget</p>
13830
+ </div>
13831
+
13832
+ <!-- Size -->
13833
+ <div>
13834
+ <label for="setting_size" class="block text-sm font-medium text-gray-300 mb-2">Widget Size</label>
13835
+ <select name="setting_size" id="setting_size" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13836
+ <option value="normal" ${settings.size === "normal" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Normal (300x65px)</option>
13837
+ <option value="compact" ${settings.size === "compact" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Compact (130x120px)</option>
13838
+ </select>
13839
+ <p class="text-xs text-gray-400 mt-1">Size of the Turnstile challenge widget</p>
13840
+ </div>
13841
+
13842
+ <!-- Widget Mode -->
13843
+ <div>
13844
+ <label for="setting_mode" class="block text-sm font-medium text-gray-300 mb-2">Widget Mode</label>
13845
+ <select name="setting_mode" id="setting_mode" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13846
+ <option value="managed" ${!settings.mode || settings.mode === "managed" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Managed (Recommended) - Adaptive challenge</option>
13847
+ <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>
13848
+ <option value="invisible" ${settings.mode === "invisible" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Invisible - No visible widget</option>
13849
+ </select>
13850
+ <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>
13851
+ </div>
13852
+
13853
+ <!-- Appearance (Pre-clearance) -->
13854
+ <div>
13855
+ <label for="setting_appearance" class="block text-sm font-medium text-gray-300 mb-2">Pre-clearance / Appearance</label>
13856
+ <select name="setting_appearance" id="setting_appearance" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13857
+ <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>
13858
+ <option value="execute" ${settings.appearance === "execute" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Execute - Challenge on form submit</option>
13859
+ <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>
13860
+ </select>
13861
+ <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>
13862
+ </div>
13863
+ `;
13864
+ }
13644
13865
  function renderNoSettings(plugin) {
13645
13866
  if (plugin.id === "seed-data" || plugin.name === "seed-data") {
13646
13867
  return `
@@ -13872,6 +14093,19 @@ var AVAILABLE_PLUGINS = [
13872
14093
  permissions: [],
13873
14094
  dependencies: [],
13874
14095
  is_core: false
14096
+ },
14097
+ {
14098
+ id: "turnstile",
14099
+ name: "turnstile-plugin",
14100
+ display_name: "Cloudflare Turnstile",
14101
+ description: "CAPTCHA-free bot protection for forms using Cloudflare Turnstile. Provides seamless spam prevention with configurable modes, themes, and pre-clearance options.",
14102
+ version: "1.0.0",
14103
+ author: "SonicJS Team",
14104
+ category: "security",
14105
+ icon: "\u{1F6E1}\uFE0F",
14106
+ permissions: [],
14107
+ dependencies: [],
14108
+ is_core: true
13875
14109
  }
13876
14110
  ];
13877
14111
  adminPluginRoutes.get("/", async (c) => {
@@ -14242,6 +14476,33 @@ adminPluginRoutes.post("/install", async (c) => {
14242
14476
  });
14243
14477
  return c.json({ success: true, plugin: easyMdxPlugin2 });
14244
14478
  }
14479
+ if (body.name === "turnstile-plugin") {
14480
+ const turnstilePlugin = await pluginService.installPlugin({
14481
+ id: "turnstile",
14482
+ name: "turnstile-plugin",
14483
+ display_name: "Cloudflare Turnstile",
14484
+ description: "CAPTCHA-free bot protection for forms using Cloudflare Turnstile. Provides seamless spam prevention with configurable modes, themes, and pre-clearance options.",
14485
+ version: "1.0.0",
14486
+ author: "SonicJS Team",
14487
+ category: "security",
14488
+ icon: "\u{1F6E1}\uFE0F",
14489
+ permissions: [],
14490
+ dependencies: [],
14491
+ is_core: true,
14492
+ settings: {
14493
+ siteKey: "",
14494
+ secretKey: "",
14495
+ theme: "auto",
14496
+ size: "normal",
14497
+ mode: "managed",
14498
+ appearance: "always",
14499
+ preClearanceEnabled: false,
14500
+ preClearanceLevel: "managed",
14501
+ enabled: false
14502
+ }
14503
+ });
14504
+ return c.json({ success: true, plugin: turnstilePlugin });
14505
+ }
14245
14506
  return c.json({ error: "Plugin not found in registry" }, 404);
14246
14507
  } catch (error) {
14247
14508
  console.error("Error installing plugin:", error);
@@ -19415,16 +19676,30 @@ adminCollectionsRoutes.get("/:id", async (c) => {
19415
19676
  const schema = typeof collection.schema === "string" ? JSON.parse(collection.schema) : collection.schema;
19416
19677
  if (schema && schema.properties) {
19417
19678
  let fieldOrder = 0;
19418
- fields = Object.entries(schema.properties).map(([fieldName, fieldConfig]) => ({
19419
- id: `schema-${fieldName}`,
19420
- field_name: fieldName,
19421
- field_type: fieldConfig.type || "string",
19422
- field_label: fieldConfig.title || fieldName,
19423
- field_options: fieldConfig,
19424
- field_order: fieldOrder++,
19425
- is_required: fieldConfig.required === true || schema.required && schema.required.includes(fieldName),
19426
- is_searchable: fieldConfig.searchable === true || false
19427
- }));
19679
+ fields = Object.entries(schema.properties).map(([fieldName, fieldConfig]) => {
19680
+ let fieldType = fieldConfig.type || "string";
19681
+ if (fieldConfig.enum) {
19682
+ fieldType = "select";
19683
+ } else if (fieldConfig.format === "richtext") {
19684
+ fieldType = "richtext";
19685
+ } else if (fieldConfig.format === "media") {
19686
+ fieldType = "media";
19687
+ } else if (fieldConfig.format === "date-time") {
19688
+ fieldType = "date";
19689
+ } else if (fieldConfig.type === "slug" || fieldConfig.format === "slug") {
19690
+ fieldType = "slug";
19691
+ }
19692
+ return {
19693
+ id: `schema-${fieldName}`,
19694
+ field_name: fieldName,
19695
+ field_type: fieldType,
19696
+ field_label: fieldConfig.title || fieldName,
19697
+ field_options: fieldConfig,
19698
+ field_order: fieldOrder++,
19699
+ is_required: fieldConfig.required === true || schema.required && schema.required.includes(fieldName),
19700
+ is_searchable: fieldConfig.searchable === true || false
19701
+ };
19702
+ });
19428
19703
  }
19429
19704
  } catch (e) {
19430
19705
  console.error("Error parsing collection schema:", e);
@@ -19639,6 +19914,9 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
19639
19914
  fieldConfig.enum = parsedOptions.options || [];
19640
19915
  } else if (fieldType === "media") {
19641
19916
  fieldConfig.format = "media";
19917
+ } else if (fieldType === "slug") {
19918
+ fieldConfig.type = "slug";
19919
+ fieldConfig.format = "slug";
19642
19920
  } else if (fieldType === "quill") {
19643
19921
  fieldConfig.type = "quill";
19644
19922
  } else if (fieldType === "mdxeditor") {
@@ -21756,6 +22034,6 @@ var ROUTES_INFO = {
21756
22034
  reference: "https://github.com/sonicjs/sonicjs"
21757
22035
  };
21758
22036
 
21759
- export { PluginBuilder, ROUTES_INFO, adminCheckboxRoutes, adminCollectionsRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_code_examples_default, admin_content_default, admin_testimonials_default, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, router, test_cleanup_default, userRoutes };
21760
- //# sourceMappingURL=chunk-QBWD6FKH.js.map
21761
- //# sourceMappingURL=chunk-QBWD6FKH.js.map
22037
+ export { ROUTES_INFO, adminCheckboxRoutes, adminCollectionsRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_code_examples_default, admin_content_default, admin_testimonials_default, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, checkAdminUserExists, router, test_cleanup_default, userRoutes };
22038
+ //# sourceMappingURL=chunk-OJZ45OJD.js.map
22039
+ //# sourceMappingURL=chunk-OJZ45OJD.js.map