@sonicjs-cms/core 2.4.0 → 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-VNCYCH3H.js → chunk-BHNDALCA.js} +56 -4
  6. package/dist/chunk-BHNDALCA.js.map +1 -0
  7. package/dist/{chunk-2MI3LZFH.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-FT6NBHNX.js → chunk-OJZ45OJD.js} +507 -275
  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-DXM575E2.js → chunk-TJTWRO4G.js} +5 -5
  16. package/dist/chunk-TJTWRO4G.js.map +1 -0
  17. package/dist/{chunk-A4SVOGG6.cjs → chunk-UAQL2VWX.cjs} +591 -360
  18. package/dist/chunk-UAQL2VWX.cjs.map +1 -0
  19. package/dist/{chunk-D2NLCPO2.js → chunk-VEL7QRYI.js} +46 -2
  20. package/dist/chunk-VEL7QRYI.js.map +1 -0
  21. package/dist/{chunk-7I5INVNR.cjs → chunk-YYV3XQOQ.cjs} +6 -6
  22. package/dist/chunk-YYV3XQOQ.cjs.map +1 -0
  23. package/dist/{chunk-FYEDK7K7.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 +90 -87
  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 +12 -9
  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-32QAYLWJ.cjs.map → migrations-NIEUFG44.cjs.map} +1 -1
  37. package/dist/migrations-TGZKJKV4.js +4 -0
  38. package/dist/{migrations-57ZHBQ4X.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-2MI3LZFH.cjs.map +0 -1
  63. package/dist/chunk-7I5INVNR.cjs.map +0 -1
  64. package/dist/chunk-A4SVOGG6.cjs.map +0 -1
  65. package/dist/chunk-CPXAVWCU.js.map +0 -1
  66. package/dist/chunk-D2NLCPO2.js.map +0 -1
  67. package/dist/chunk-DTLB6UIH.cjs.map +0 -1
  68. package/dist/chunk-DXM575E2.js.map +0 -1
  69. package/dist/chunk-FT6NBHNX.js.map +0 -1
  70. package/dist/chunk-FYEDK7K7.cjs.map +0 -1
  71. package/dist/chunk-VNCYCH3H.js.map +0 -1
  72. package/dist/migrations-32QAYLWJ.cjs +0 -13
  73. package/dist/migrations-57ZHBQ4X.js +0 -4
@@ -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-DXM575E2.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-D2NLCPO2.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-VNCYCH3H.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-57ZHBQ4X.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-57ZHBQ4X.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-57ZHBQ4X.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();
@@ -3383,7 +3415,7 @@ init_admin_layout_catalyst_template();
3383
3415
 
3384
3416
  // src/templates/components/dynamic-field.template.ts
3385
3417
  function renderDynamicField(field, options = {}) {
3386
- const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {} } = options;
3418
+ const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {}, collectionId = "", contentId = "" } = options;
3387
3419
  const opts = field.field_options || {};
3388
3420
  const required = field.is_required ? "required" : "";
3389
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}`;
@@ -3636,67 +3668,171 @@ function renderDynamicField(field, options = {}) {
3636
3668
  `;
3637
3669
  break;
3638
3670
  case "slug":
3639
- let slugPattern = opts.pattern || "^[a-z0-9-]+$";
3640
- let slugHelp = '<p class="mt-2 text-xs text-zinc-500 dark:text-zinc-400">Use lowercase letters, numbers, and hyphens only</p>';
3641
- 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;
3642
3675
  fieldHTML = `
3643
- <input
3644
- type="text"
3645
- id="${fieldId}"
3646
- name="${fieldName}"
3647
- value="${escapeHtml2(value)}"
3648
- placeholder="${opts.placeholder || "url-friendly-slug"}"
3649
- maxlength="${opts.maxLength || ""}"
3650
- data-pattern="${slugPattern}"
3651
- class="${baseClasses} ${errorClasses}"
3652
- ${required}
3653
- ${disabled ? "disabled" : ""}
3654
- >
3655
- ${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
+
3656
3706
  <script>
3657
3707
  (function() {
3658
- 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';
3659
3711
  const pattern = new RegExp('${slugPattern}');
3660
-
3661
- field.addEventListener('input', function() {
3662
- if (this.value && !pattern.test(this.value)) {
3663
- this.setCustomValidity('Please use only lowercase letters, numbers, and hyphens.');
3664
- } else {
3665
- this.setCustomValidity('');
3666
- }
3667
- });
3668
-
3669
- field.addEventListener('blur', function() {
3670
- this.reportValidity();
3671
- });
3672
- })();
3673
-
3674
- function generateSlugFromTitle(slugFieldId) {
3675
- const titleField = document.querySelector('input[name="title"]');
3676
- const slugField = document.getElementById(slugFieldId);
3677
- if (titleField && slugField) {
3678
- 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
3679
3724
  .toLowerCase()
3725
+ .normalize('NFD')
3726
+ .replace(/[\\u0300-\\u036f]/g, '')
3680
3727
  .replace(/[^a-z0-9\\s_-]/g, '')
3681
3728
  .replace(/\\s+/g, '-')
3682
3729
  .replace(/[-_]+/g, '-')
3683
- .replace(/^[-_]|[-_]$/g, '');
3684
- slugField.value = slug;
3730
+ .replace(/^[-_]+|[-_]+$/g, '')
3731
+ .substring(0, 100);
3685
3732
  }
3686
- }
3687
-
3688
- // Auto-generate slug when title changes
3689
- document.addEventListener('DOMContentLoaded', function() {
3690
- const titleField = document.querySelector('input[name="title"]');
3691
- const slugField = document.getElementById('${fieldId}');
3692
- if (titleField && slugField && !slugField.value) {
3693
- titleField.addEventListener('input', function() {
3694
- if (!slugField.value) {
3695
- 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)}\`;
3696
3750
  }
3697
- });
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
+ }
3698
3766
  }
3699
- });
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
+ })();
3700
3836
  </script>
3701
3837
  `;
3702
3838
  break;
@@ -3829,207 +3965,6 @@ function escapeHtml2(text) {
3829
3965
  "'": "&#39;"
3830
3966
  })[char] || char);
3831
3967
  }
3832
- var PluginBuilder = class _PluginBuilder {
3833
- plugin;
3834
- constructor(options) {
3835
- this.plugin = {
3836
- name: options.name,
3837
- version: options.version,
3838
- description: options.description,
3839
- author: options.author,
3840
- dependencies: options.dependencies,
3841
- routes: [],
3842
- middleware: [],
3843
- models: [],
3844
- services: [],
3845
- adminPages: [],
3846
- adminComponents: [],
3847
- menuItems: [],
3848
- hooks: []
3849
- };
3850
- }
3851
- /**
3852
- * Create a new plugin builder
3853
- */
3854
- static create(options) {
3855
- return new _PluginBuilder(options);
3856
- }
3857
- /**
3858
- * Add metadata to the plugin
3859
- */
3860
- metadata(metadata) {
3861
- Object.assign(this.plugin, metadata);
3862
- return this;
3863
- }
3864
- /**
3865
- * Add routes to plugin
3866
- */
3867
- addRoutes(routes) {
3868
- this.plugin.routes = [...this.plugin.routes || [], ...routes];
3869
- return this;
3870
- }
3871
- /**
3872
- * Add a single route to plugin
3873
- */
3874
- addRoute(path, handler, options) {
3875
- const route = {
3876
- path,
3877
- handler,
3878
- ...options
3879
- };
3880
- this.plugin.routes = [...this.plugin.routes || [], route];
3881
- return this;
3882
- }
3883
- /**
3884
- * Add middleware to plugin
3885
- */
3886
- addMiddleware(middleware) {
3887
- this.plugin.middleware = [...this.plugin.middleware || [], ...middleware];
3888
- return this;
3889
- }
3890
- /**
3891
- * Add a single middleware to plugin
3892
- */
3893
- addSingleMiddleware(name, handler, options) {
3894
- const middleware = {
3895
- name,
3896
- handler,
3897
- ...options
3898
- };
3899
- this.plugin.middleware = [...this.plugin.middleware || [], middleware];
3900
- return this;
3901
- }
3902
- /**
3903
- * Add models to plugin
3904
- */
3905
- addModels(models) {
3906
- this.plugin.models = [...this.plugin.models || [], ...models];
3907
- return this;
3908
- }
3909
- /**
3910
- * Add a single model to plugin
3911
- */
3912
- addModel(name, options) {
3913
- const model = {
3914
- name,
3915
- ...options
3916
- };
3917
- this.plugin.models = [...this.plugin.models || [], model];
3918
- return this;
3919
- }
3920
- /**
3921
- * Add services to plugin
3922
- */
3923
- addServices(services) {
3924
- this.plugin.services = [...this.plugin.services || [], ...services];
3925
- return this;
3926
- }
3927
- /**
3928
- * Add a single service to plugin
3929
- */
3930
- addService(name, implementation, options) {
3931
- const service = {
3932
- name,
3933
- implementation,
3934
- ...options
3935
- };
3936
- this.plugin.services = [...this.plugin.services || [], service];
3937
- return this;
3938
- }
3939
- /**
3940
- * Add admin pages to plugin
3941
- */
3942
- addAdminPages(pages) {
3943
- this.plugin.adminPages = [...this.plugin.adminPages || [], ...pages];
3944
- return this;
3945
- }
3946
- /**
3947
- * Add a single admin page to plugin
3948
- */
3949
- addAdminPage(path, title, component, options) {
3950
- const page = {
3951
- path,
3952
- title,
3953
- component,
3954
- ...options
3955
- };
3956
- this.plugin.adminPages = [...this.plugin.adminPages || [], page];
3957
- return this;
3958
- }
3959
- /**
3960
- * Add admin components to plugin
3961
- */
3962
- addComponents(components) {
3963
- this.plugin.adminComponents = [...this.plugin.adminComponents || [], ...components];
3964
- return this;
3965
- }
3966
- /**
3967
- * Add a single admin component to plugin
3968
- */
3969
- addComponent(name, template, options) {
3970
- const component = {
3971
- name,
3972
- template,
3973
- ...options
3974
- };
3975
- this.plugin.adminComponents = [...this.plugin.adminComponents || [], component];
3976
- return this;
3977
- }
3978
- /**
3979
- * Add menu items to plugin
3980
- */
3981
- addMenuItems(items) {
3982
- this.plugin.menuItems = [...this.plugin.menuItems || [], ...items];
3983
- return this;
3984
- }
3985
- /**
3986
- * Add a single menu item to plugin
3987
- */
3988
- addMenuItem(label, path, options) {
3989
- const menuItem = {
3990
- label,
3991
- path,
3992
- ...options
3993
- };
3994
- this.plugin.menuItems = [...this.plugin.menuItems || [], menuItem];
3995
- return this;
3996
- }
3997
- /**
3998
- * Add hooks to plugin
3999
- */
4000
- addHooks(hooks) {
4001
- this.plugin.hooks = [...this.plugin.hooks || [], ...hooks];
4002
- return this;
4003
- }
4004
- /**
4005
- * Add a single hook to plugin
4006
- */
4007
- addHook(name, handler, options) {
4008
- const hook = {
4009
- name,
4010
- handler,
4011
- ...options
4012
- };
4013
- this.plugin.hooks = [...this.plugin.hooks || [], hook];
4014
- return this;
4015
- }
4016
- /**
4017
- * Add lifecycle hooks
4018
- */
4019
- lifecycle(hooks) {
4020
- Object.assign(this.plugin, hooks);
4021
- return this;
4022
- }
4023
- /**
4024
- * Build the plugin
4025
- */
4026
- build() {
4027
- if (!this.plugin.name || !this.plugin.version) {
4028
- throw new Error("Plugin name and version are required");
4029
- }
4030
- return this.plugin;
4031
- }
4032
- };
4033
3968
 
4034
3969
  // src/plugins/available/tinymce-plugin/index.ts
4035
3970
  var builder = PluginBuilder.create({
@@ -4573,17 +4508,24 @@ function renderContentFormPage(data) {
4573
4508
  const coreFieldsHTML = coreFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4574
4509
  value: getFieldValue(field.field_name),
4575
4510
  errors: data.validationErrors?.[field.field_name] || [],
4576
- pluginStatuses
4511
+ pluginStatuses,
4512
+ collectionId: data.collection.id,
4513
+ contentId: data.id
4514
+ // Pass content ID when editing
4577
4515
  }));
4578
4516
  const contentFieldsHTML = contentFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4579
4517
  value: getFieldValue(field.field_name),
4580
4518
  errors: data.validationErrors?.[field.field_name] || [],
4581
- pluginStatuses
4519
+ pluginStatuses,
4520
+ collectionId: data.collection.id,
4521
+ contentId: data.id
4582
4522
  }));
4583
4523
  const metaFieldsHTML = metaFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4584
4524
  value: getFieldValue(field.field_name),
4585
4525
  errors: data.validationErrors?.[field.field_name] || [],
4586
- pluginStatuses
4526
+ pluginStatuses,
4527
+ collectionId: data.collection.id,
4528
+ contentId: data.id
4587
4529
  }));
4588
4530
  const pageContent = `
4589
4531
  <div class="space-y-6">
@@ -6500,6 +6442,18 @@ adminContentRoutes.post("/", async (c) => {
6500
6442
  const errors = {};
6501
6443
  for (const field of fields) {
6502
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
+ }
6503
6457
  if (field.is_required && (!value || value.toString().trim() === "")) {
6504
6458
  errors[field.field_name] = [`${field.field_label} is required`];
6505
6459
  continue;
@@ -6522,6 +6476,67 @@ adminContentRoutes.post("/", async (c) => {
6522
6476
  data[field.field_name] = value;
6523
6477
  }
6524
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
+ }
6525
6540
  default:
6526
6541
  data[field.field_name] = value;
6527
6542
  }
@@ -6646,6 +6661,18 @@ adminContentRoutes.put("/:id", async (c) => {
6646
6661
  const errors = {};
6647
6662
  for (const field of fields) {
6648
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
+ }
6649
6676
  if (field.is_required && (!value || value.toString().trim() === "")) {
6650
6677
  errors[field.field_name] = [`${field.field_label} is required`];
6651
6678
  continue;
@@ -6668,6 +6695,67 @@ adminContentRoutes.put("/:id", async (c) => {
6668
6695
  data[field.field_name] = value;
6669
6696
  }
6670
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
+ }
6671
6759
  default:
6672
6760
  data[field.field_name] = value;
6673
6761
  }
@@ -6787,6 +6875,12 @@ adminContentRoutes.post("/preview", async (c) => {
6787
6875
  const data = {};
6788
6876
  for (const field of fields) {
6789
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
+ }
6790
6884
  switch (field.field_type) {
6791
6885
  case "number":
6792
6886
  data[field.field_name] = value ? Number(value) : null;
@@ -13315,6 +13409,9 @@ function renderAuthSettingsForm(settings) {
13315
13409
  }
13316
13410
 
13317
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
+ }
13318
13415
  function renderPluginSettingsPage(data) {
13319
13416
  const { plugin, activity = [], user } = data;
13320
13417
  const pageContent = `
@@ -13592,6 +13689,7 @@ function renderSettingsTab(plugin) {
13592
13689
  const settings = plugin.settings || {};
13593
13690
  const isSeedDataPlugin = plugin.id === "seed-data" || plugin.name === "seed-data";
13594
13691
  const isAuthPlugin = plugin.id === "core-auth" || plugin.name === "core-auth";
13692
+ const isTurnstilePlugin = plugin.id === "turnstile" || plugin.name === "turnstile";
13595
13693
  return `
13596
13694
  ${isSeedDataPlugin ? `
13597
13695
  <div class="backdrop-blur-md bg-black/20 rounded-xl border border-white/10 shadow-xl p-6 mb-6">
@@ -13618,12 +13716,15 @@ function renderSettingsTab(plugin) {
13618
13716
  ${isAuthPlugin ? `
13619
13717
  <h2 class="text-xl font-semibold text-white mb-4">Authentication Settings</h2>
13620
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>
13621
13722
  ` : `
13622
13723
  <h2 class="text-xl font-semibold text-white mb-4">Plugin Settings</h2>
13623
13724
  `}
13624
13725
 
13625
13726
  <form id="settings-form" class="space-y-6">
13626
- ${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)}
13627
13728
 
13628
13729
  ${Object.keys(settings).length > 0 ? `
13629
13730
  <div class="flex items-center justify-end pt-6 border-t border-white/10">
@@ -13687,6 +13788,80 @@ function renderSettingsFields(settings) {
13687
13788
  }
13688
13789
  }).join("");
13689
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
+ }
13690
13865
  function renderNoSettings(plugin) {
13691
13866
  if (plugin.id === "seed-data" || plugin.name === "seed-data") {
13692
13867
  return `
@@ -13918,6 +14093,19 @@ var AVAILABLE_PLUGINS = [
13918
14093
  permissions: [],
13919
14094
  dependencies: [],
13920
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
13921
14109
  }
13922
14110
  ];
13923
14111
  adminPluginRoutes.get("/", async (c) => {
@@ -14288,6 +14476,33 @@ adminPluginRoutes.post("/install", async (c) => {
14288
14476
  });
14289
14477
  return c.json({ success: true, plugin: easyMdxPlugin2 });
14290
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
+ }
14291
14506
  return c.json({ error: "Plugin not found in registry" }, 404);
14292
14507
  } catch (error) {
14293
14508
  console.error("Error installing plugin:", error);
@@ -19461,16 +19676,30 @@ adminCollectionsRoutes.get("/:id", async (c) => {
19461
19676
  const schema = typeof collection.schema === "string" ? JSON.parse(collection.schema) : collection.schema;
19462
19677
  if (schema && schema.properties) {
19463
19678
  let fieldOrder = 0;
19464
- fields = Object.entries(schema.properties).map(([fieldName, fieldConfig]) => ({
19465
- id: `schema-${fieldName}`,
19466
- field_name: fieldName,
19467
- field_type: fieldConfig.type || "string",
19468
- field_label: fieldConfig.title || fieldName,
19469
- field_options: fieldConfig,
19470
- field_order: fieldOrder++,
19471
- is_required: fieldConfig.required === true || schema.required && schema.required.includes(fieldName),
19472
- is_searchable: fieldConfig.searchable === true || false
19473
- }));
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
+ });
19474
19703
  }
19475
19704
  } catch (e) {
19476
19705
  console.error("Error parsing collection schema:", e);
@@ -19685,6 +19914,9 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
19685
19914
  fieldConfig.enum = parsedOptions.options || [];
19686
19915
  } else if (fieldType === "media") {
19687
19916
  fieldConfig.format = "media";
19917
+ } else if (fieldType === "slug") {
19918
+ fieldConfig.type = "slug";
19919
+ fieldConfig.format = "slug";
19688
19920
  } else if (fieldType === "quill") {
19689
19921
  fieldConfig.type = "quill";
19690
19922
  } else if (fieldType === "mdxeditor") {
@@ -21802,6 +22034,6 @@ var ROUTES_INFO = {
21802
22034
  reference: "https://github.com/sonicjs/sonicjs"
21803
22035
  };
21804
22036
 
21805
- 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, checkAdminUserExists, router, test_cleanup_default, userRoutes };
21806
- //# sourceMappingURL=chunk-FT6NBHNX.js.map
21807
- //# sourceMappingURL=chunk-FT6NBHNX.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