@sonicjs-cms/core 2.0.8 → 2.0.10

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 (63) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-CHMO2DOC.cjs → chunk-3PHG75W4.cjs} +3 -3
  3. package/dist/chunk-3PHG75W4.cjs.map +1 -0
  4. package/dist/chunk-6FR25MPC.js.map +1 -1
  5. package/dist/{chunk-KHNSPJ6X.cjs → chunk-CAP6QQR2.cjs} +5 -5
  6. package/dist/{chunk-KHNSPJ6X.cjs.map → chunk-CAP6QQR2.cjs.map} +1 -1
  7. package/dist/{chunk-4MBTSUI6.js → chunk-COBUPOMD.js} +22 -6
  8. package/dist/chunk-COBUPOMD.js.map +1 -0
  9. package/dist/chunk-DOR2IU73.cjs.map +1 -1
  10. package/dist/{chunk-HJZOA2O5.cjs → chunk-F5ESJXI2.cjs} +25 -3
  11. package/dist/chunk-F5ESJXI2.cjs.map +1 -0
  12. package/dist/chunk-FICTAGD4.js.map +1 -1
  13. package/dist/{chunk-LS5CMDNL.js → chunk-FTMKKKNH.js} +3 -3
  14. package/dist/{chunk-LS5CMDNL.js.map → chunk-FTMKKKNH.js.map} +1 -1
  15. package/dist/{chunk-EAELJXRV.js → chunk-HKEK7UNV.js} +25 -3
  16. package/dist/chunk-HKEK7UNV.js.map +1 -0
  17. package/dist/{chunk-3R7EQNGO.cjs → chunk-HXA5QSI3.cjs} +15 -11
  18. package/dist/chunk-HXA5QSI3.cjs.map +1 -0
  19. package/dist/{chunk-YHLLVUJC.js → chunk-LW33AOBF.js} +6 -8
  20. package/dist/chunk-LW33AOBF.js.map +1 -0
  21. package/dist/{chunk-7XEESVSX.cjs → chunk-MU3MR2QR.cjs} +6 -8
  22. package/dist/chunk-MU3MR2QR.cjs.map +1 -0
  23. package/dist/{chunk-Z2CZC6TC.js → chunk-MXJJN4IA.js} +3 -3
  24. package/dist/chunk-MXJJN4IA.js.map +1 -0
  25. package/dist/{chunk-YGVWY6KO.cjs → chunk-NBDPIRQS.cjs} +22 -5
  26. package/dist/chunk-NBDPIRQS.cjs.map +1 -0
  27. package/dist/{chunk-GN7Q6V5C.cjs → chunk-Q7SL7U43.cjs} +397 -273
  28. package/dist/chunk-Q7SL7U43.cjs.map +1 -0
  29. package/dist/chunk-RCQ2HIQD.cjs.map +1 -1
  30. package/dist/{chunk-O7LMFJMZ.js → chunk-YHG45LMU.js} +13 -9
  31. package/dist/chunk-YHG45LMU.js.map +1 -0
  32. package/dist/{chunk-CUEIM4FE.js → chunk-Z4H6DBVF.js} +241 -117
  33. package/dist/chunk-Z4H6DBVF.js.map +1 -0
  34. package/dist/index.cjs +108 -104
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.js +12 -12
  37. package/dist/index.js.map +1 -1
  38. package/dist/middleware.cjs +23 -23
  39. package/dist/middleware.js +2 -2
  40. package/dist/plugins.cjs +7 -7
  41. package/dist/plugins.js +1 -1
  42. package/dist/routes.cjs +25 -25
  43. package/dist/routes.js +5 -5
  44. package/dist/services.cjs +18 -14
  45. package/dist/services.js +1 -1
  46. package/dist/templates.cjs +18 -18
  47. package/dist/templates.js +2 -2
  48. package/dist/utils.cjs +11 -11
  49. package/dist/utils.js +1 -1
  50. package/migrations/019_remove_blog_posts_collection.sql +15 -0
  51. package/package.json +1 -1
  52. package/dist/chunk-3R7EQNGO.cjs.map +0 -1
  53. package/dist/chunk-4MBTSUI6.js.map +0 -1
  54. package/dist/chunk-7XEESVSX.cjs.map +0 -1
  55. package/dist/chunk-CHMO2DOC.cjs.map +0 -1
  56. package/dist/chunk-CUEIM4FE.js.map +0 -1
  57. package/dist/chunk-EAELJXRV.js.map +0 -1
  58. package/dist/chunk-GN7Q6V5C.cjs.map +0 -1
  59. package/dist/chunk-HJZOA2O5.cjs.map +0 -1
  60. package/dist/chunk-O7LMFJMZ.js.map +0 -1
  61. package/dist/chunk-YGVWY6KO.cjs.map +0 -1
  62. package/dist/chunk-YHLLVUJC.js.map +0 -1
  63. package/dist/chunk-Z2CZC6TC.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService } from './chunk-6FR25MPC.js';
2
- import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-O7LMFJMZ.js';
3
- import { PluginService, MigrationService } from './chunk-4MBTSUI6.js';
4
- import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderFAQList, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-YHLLVUJC.js';
5
- import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-Z2CZC6TC.js';
2
+ import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-YHG45LMU.js';
3
+ import { PluginService, MigrationService } from './chunk-COBUPOMD.js';
4
+ import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderFAQList, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-LW33AOBF.js';
5
+ import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-MXJJN4IA.js';
6
6
  import { metricsTracker } from './chunk-FICTAGD4.js';
7
7
  import { Hono } from 'hono';
8
8
  import { cors } from 'hono/cors';
@@ -1084,10 +1084,10 @@ apiMediaRoutes.patch("/:id", async (c) => {
1084
1084
  const allowedFields = ["alt", "caption", "tags", "folder"];
1085
1085
  const updates = [];
1086
1086
  const values = [];
1087
- for (const [key, value] of Object.entries(body)) {
1087
+ for (const [key, value2] of Object.entries(body)) {
1088
1088
  if (allowedFields.includes(key)) {
1089
1089
  updates.push(`${key} = ?`);
1090
- values.push(key === "tags" ? JSON.stringify(value) : value);
1090
+ values.push(key === "tags" ? JSON.stringify(value2) : value2);
1091
1091
  }
1092
1092
  }
1093
1093
  if (updates.length === 0) {
@@ -1568,7 +1568,7 @@ adminApiRoutes.post("/collections", async (c) => {
1568
1568
  }
1569
1569
  const validatedData = validation.data;
1570
1570
  const db = c.env.DB;
1571
- const user = c.get("user");
1571
+ const ____user = c.get("user");
1572
1572
  const displayName = validatedData.displayName || validatedData.display_name || "";
1573
1573
  const existingStmt = db.prepare("SELECT id FROM collections WHERE name = ?");
1574
1574
  const existing = await existingStmt.bind(validatedData.name).first();
@@ -2320,7 +2320,6 @@ authRoutes.post("/register/form", async (c) => {
2320
2320
  `);
2321
2321
  }
2322
2322
  const validatedData = validation.data;
2323
- const email = validatedData.email;
2324
2323
  const password = validatedData.password;
2325
2324
  const username = validatedData.username || authValidationService.generateDefaultValue("username", validatedData);
2326
2325
  const firstName = validatedData.firstName || authValidationService.generateDefaultValue("firstName", validatedData);
@@ -2991,7 +2990,7 @@ init_admin_layout_catalyst_template();
2991
2990
 
2992
2991
  // src/templates/components/dynamic-field.template.ts
2993
2992
  function renderDynamicField(field, options = {}) {
2994
- const { value = "", errors = [], disabled = false, className = "" } = options;
2993
+ const { value: value2 = "", errors = [], disabled = false, className = "" } = options;
2995
2994
  const opts = field.field_options || {};
2996
2995
  const required = field.is_required ? "required" : "";
2997
2996
  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}`;
@@ -3048,7 +3047,7 @@ function renderDynamicField(field, options = {}) {
3048
3047
  type="text"
3049
3048
  id="${fieldId}"
3050
3049
  name="${fieldName}"
3051
- value="${escapeHtml2(value)}"
3050
+ value="${escapeHtml2(value2)}"
3052
3051
  placeholder="${opts.placeholder || ""}"
3053
3052
  maxlength="${opts.maxLength || ""}"
3054
3053
  ${opts.pattern ? `data-pattern="${opts.pattern}"` : ""}
@@ -3093,7 +3092,7 @@ function renderDynamicField(field, options = {}) {
3093
3092
  class="${baseClasses} ${errorClasses} min-h-[${opts.height || 300}px]"
3094
3093
  ${required}
3095
3094
  ${disabled ? "disabled" : ""}
3096
- >${escapeHtml2(value)}</textarea>
3095
+ >${escapeHtml2(value2)}</textarea>
3097
3096
  <script>
3098
3097
  // Initialize TinyMCE for this field
3099
3098
  if (typeof tinymce !== 'undefined') {
@@ -3127,7 +3126,7 @@ function renderDynamicField(field, options = {}) {
3127
3126
  type="number"
3128
3127
  id="${fieldId}"
3129
3128
  name="${fieldName}"
3130
- value="${value}"
3129
+ value="${value2}"
3131
3130
  min="${opts.min || ""}"
3132
3131
  max="${opts.max || ""}"
3133
3132
  step="${opts.step || ""}"
@@ -3139,7 +3138,7 @@ function renderDynamicField(field, options = {}) {
3139
3138
  `;
3140
3139
  break;
3141
3140
  case "boolean":
3142
- const checked = value === true || value === "true" || value === "1" ? "checked" : "";
3141
+ const checked = value2 === true || value2 === "true" || value2 === "1" ? "checked" : "";
3143
3142
  fieldHTML = `
3144
3143
  <div class="flex items-center space-x-3">
3145
3144
  <input
@@ -3164,7 +3163,7 @@ function renderDynamicField(field, options = {}) {
3164
3163
  type="date"
3165
3164
  id="${fieldId}"
3166
3165
  name="${fieldName}"
3167
- value="${value}"
3166
+ value="${value2}"
3168
3167
  min="${opts.min || ""}"
3169
3168
  max="${opts.max || ""}"
3170
3169
  class="${baseClasses} ${errorClasses}"
@@ -3176,7 +3175,7 @@ function renderDynamicField(field, options = {}) {
3176
3175
  case "select":
3177
3176
  const options2 = opts.options || [];
3178
3177
  const multiple = opts.multiple ? "multiple" : "";
3179
- const selectedValues = Array.isArray(value) ? value : [value];
3178
+ const selectedValues = Array.isArray(value2) ? value2 : [value2];
3180
3179
  fieldHTML = `
3181
3180
  <select
3182
3181
  id="${fieldId}"
@@ -3209,9 +3208,9 @@ function renderDynamicField(field, options = {}) {
3209
3208
  case "media":
3210
3209
  fieldHTML = `
3211
3210
  <div class="media-field-container">
3212
- <input type="hidden" id="${fieldId}" name="${fieldName}" value="${value}">
3213
- <div class="media-preview ${value ? "" : "hidden"}" id="${fieldId}-preview">
3214
- ${value ? `<img src="${value}" alt="Selected media" class="w-32 h-32 object-cover rounded-lg border border-white/20">` : ""}
3211
+ <input type="hidden" id="${fieldId}" name="${fieldName}" value="${value2}">
3212
+ <div class="media-preview ${value2 ? "" : "hidden"}" id="${fieldId}-preview">
3213
+ ${value2 ? `<img src="${value2}" alt="Selected media" class="w-32 h-32 object-cover rounded-lg border border-white/20">` : ""}
3215
3214
  </div>
3216
3215
  <div class="media-actions mt-2 space-x-2">
3217
3216
  <button
@@ -3225,7 +3224,7 @@ function renderDynamicField(field, options = {}) {
3225
3224
  </svg>
3226
3225
  Select Media
3227
3226
  </button>
3228
- ${value ? `
3227
+ ${value2 ? `
3229
3228
  <button
3230
3229
  type="button"
3231
3230
  onclick="clearMediaField('${fieldId}')"
@@ -3246,7 +3245,7 @@ function renderDynamicField(field, options = {}) {
3246
3245
  type="text"
3247
3246
  id="${fieldId}"
3248
3247
  name="${fieldName}"
3249
- value="${escapeHtml2(value)}"
3248
+ value="${escapeHtml2(value2)}"
3250
3249
  class="${baseClasses} bg-zinc-100 dark:bg-zinc-800/50 cursor-not-allowed"
3251
3250
  readonly
3252
3251
  disabled
@@ -3256,7 +3255,7 @@ function renderDynamicField(field, options = {}) {
3256
3255
  <path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"/>
3257
3256
  </svg>
3258
3257
  <div class="text-xs text-zinc-600 dark:text-zinc-400">
3259
- ${value ? "This unique identifier was automatically generated and cannot be changed." : "A unique identifier (UUID) will be automatically generated when you save this content."}
3258
+ ${value2 ? "This unique identifier was automatically generated and cannot be changed." : "A unique identifier (UUID) will be automatically generated when you save this content."}
3260
3259
  </div>
3261
3260
  </div>
3262
3261
  </div>
@@ -3268,7 +3267,7 @@ function renderDynamicField(field, options = {}) {
3268
3267
  type="text"
3269
3268
  id="${fieldId}"
3270
3269
  name="${fieldName}"
3271
- value="${escapeHtml2(value)}"
3270
+ value="${escapeHtml2(value2)}"
3272
3271
  class="${baseClasses} ${errorClasses}"
3273
3272
  ${required}
3274
3273
  ${disabled ? "disabled" : ""}
@@ -3956,9 +3955,9 @@ function renderContentListPage(data) {
3956
3955
  name: "model",
3957
3956
  label: "Model",
3958
3957
  options: [
3959
- { value: "all", label: "All Models", selected: data.modelName === "all" },
3958
+ { __value: "all", label: "All Models", selected: data.modelName === "all" },
3960
3959
  ...data.models.map((model) => ({
3961
- value: model.name,
3960
+ __value: model.name,
3962
3961
  label: model.displayName,
3963
3962
  selected: data.modelName === model.name
3964
3963
  }))
@@ -3968,13 +3967,13 @@ function renderContentListPage(data) {
3968
3967
  name: "status",
3969
3968
  label: "Status",
3970
3969
  options: [
3971
- { value: "all", label: "All Status", selected: data.status === "all" },
3972
- { value: "draft", label: "Draft", selected: data.status === "draft" },
3973
- { value: "review", label: "Under Review", selected: data.status === "review" },
3974
- { value: "scheduled", label: "Scheduled", selected: data.status === "scheduled" },
3975
- { value: "published", label: "Published", selected: data.status === "published" },
3976
- { value: "archived", label: "Archived", selected: data.status === "archived" },
3977
- { value: "deleted", label: "Deleted", selected: data.status === "deleted" }
3970
+ { __value: "all", label: "All Status", selected: data.status === "all" },
3971
+ { __value: "draft", label: "Draft", selected: data.status === "draft" },
3972
+ { __value: "review", label: "Under Review", selected: data.status === "review" },
3973
+ { __value: "scheduled", label: "Scheduled", selected: data.status === "scheduled" },
3974
+ { __value: "published", label: "Published", selected: data.status === "published" },
3975
+ { __value: "archived", label: "Archived", selected: data.status === "archived" },
3976
+ { __value: "deleted", label: "Deleted", selected: data.status === "deleted" }
3978
3977
  ]
3979
3978
  }
3980
3979
  ],
@@ -3986,9 +3985,9 @@ function renderContentListPage(data) {
3986
3985
  }
3987
3986
  ],
3988
3987
  bulkActions: [
3989
- { label: "Publish", value: "publish", icon: "check-circle" },
3990
- { label: "Unpublish", value: "unpublish", icon: "x-circle" },
3991
- { label: "Delete", value: "delete", icon: "trash", className: "text-pink-600" }
3988
+ { label: "Publish", ___value: "publish", icon: "check-circle" },
3989
+ { label: "Unpublish", ___value: "unpublish", icon: "x-circle" },
3990
+ { label: "Delete", ___value: "delete", icon: "trash", className: "text-pink-600" }
3992
3991
  ]
3993
3992
  };
3994
3993
  const tableColumns = [
@@ -3997,7 +3996,7 @@ function renderContentListPage(data) {
3997
3996
  label: "Title",
3998
3997
  sortable: true,
3999
3998
  sortType: "string",
4000
- render: (value, row) => `
3999
+ render: (value2, row) => `
4001
4000
  <div class="flex items-center">
4002
4001
  <div>
4003
4002
  <div class="text-sm font-medium text-zinc-950 dark:text-white">
@@ -4020,7 +4019,7 @@ function renderContentListPage(data) {
4020
4019
  label: "Status",
4021
4020
  sortable: true,
4022
4021
  sortType: "string",
4023
- render: (value) => value
4022
+ render: (value2) => value2
4024
4023
  },
4025
4024
  {
4026
4025
  key: "authorName",
@@ -4041,7 +4040,7 @@ function renderContentListPage(data) {
4041
4040
  label: "Actions",
4042
4041
  sortable: false,
4043
4042
  className: "text-sm font-medium",
4044
- render: (value, row) => `
4043
+ render: (value2, row) => `
4045
4044
  <div class="flex space-x-2">
4046
4045
  <button
4047
4046
  class="inline-flex items-center justify-center p-1.5 rounded-lg bg-cyan-50 dark:bg-cyan-500/10 text-cyan-700 dark:text-cyan-400 ring-1 ring-inset ring-cyan-600/20 dark:ring-cyan-500/20 hover:bg-cyan-100 dark:hover:bg-cyan-500/20 transition-colors"
@@ -4761,6 +4760,28 @@ async function getCollectionFields(db, collectionId) {
4761
4760
  return cache.getOrSet(
4762
4761
  cache.generateKey("fields", collectionId),
4763
4762
  async () => {
4763
+ const collectionStmt = db.prepare("SELECT schema FROM collections WHERE id = ?");
4764
+ const collectionRow = await collectionStmt.bind(collectionId).first();
4765
+ if (collectionRow && collectionRow.schema) {
4766
+ try {
4767
+ const schema = typeof collectionRow.schema === "string" ? JSON.parse(collectionRow.schema) : collectionRow.schema;
4768
+ if (schema && schema.properties) {
4769
+ let fieldOrder = 0;
4770
+ return Object.entries(schema.properties).map(([fieldName, fieldConfig]) => ({
4771
+ id: `schema-${fieldName}`,
4772
+ field_name: fieldName,
4773
+ field_type: fieldConfig.type || "string",
4774
+ field_label: fieldConfig.title || fieldName,
4775
+ field_options: fieldConfig,
4776
+ field_order: fieldOrder++,
4777
+ is_required: fieldConfig.required === true || schema.required && schema.required.includes(fieldName),
4778
+ is_searchable: false
4779
+ }));
4780
+ }
4781
+ } catch (e) {
4782
+ console.error("Error parsing collection schema:", e);
4783
+ }
4784
+ }
4764
4785
  const stmt = db.prepare(`
4765
4786
  SELECT * FROM content_fields
4766
4787
  WHERE collection_id = ?
@@ -5140,7 +5161,7 @@ adminContentRoutes.post("/", async (c) => {
5140
5161
  const data = {};
5141
5162
  const errors = {};
5142
5163
  for (const field of fields) {
5143
- const value = formData.get(field.field_name);
5164
+ const value2 = formData.get(field.field_name);
5144
5165
  if (field.field_type === "guid") {
5145
5166
  const options = field.field_options || {};
5146
5167
  if (options.autoGenerate) {
@@ -5148,33 +5169,33 @@ adminContentRoutes.post("/", async (c) => {
5148
5169
  continue;
5149
5170
  }
5150
5171
  }
5151
- if (field.is_required && (!value || value.toString().trim() === "")) {
5172
+ if (field.is_required && (!value2 || value2.toString().trim() === "")) {
5152
5173
  errors[field.field_name] = [`${field.field_label} is required`];
5153
5174
  continue;
5154
5175
  }
5155
5176
  switch (field.field_type) {
5156
5177
  case "number":
5157
- if (value && isNaN(Number(value))) {
5178
+ if (value2 && isNaN(Number(value2))) {
5158
5179
  errors[field.field_name] = [`${field.field_label} must be a valid number`];
5159
5180
  } else {
5160
- data[field.field_name] = value ? Number(value) : null;
5181
+ data[field.field_name] = value2 ? Number(value2) : null;
5161
5182
  }
5162
5183
  break;
5163
5184
  case "boolean":
5164
- data[field.field_name] = formData.get(`${field.field_name}_submitted`) ? value === "true" : false;
5185
+ data[field.field_name] = formData.get(`${field.field_name}_submitted`) ? value2 === "true" : false;
5165
5186
  break;
5166
5187
  case "select":
5167
5188
  if (field.field_options?.multiple) {
5168
5189
  data[field.field_name] = formData.getAll(`${field.field_name}[]`);
5169
5190
  } else {
5170
- data[field.field_name] = value;
5191
+ data[field.field_name] = value2;
5171
5192
  }
5172
5193
  break;
5173
5194
  case "guid":
5174
- data[field.field_name] = value || null;
5195
+ data[field.field_name] = value2 || null;
5175
5196
  break;
5176
5197
  default:
5177
- data[field.field_name] = value;
5198
+ data[field.field_name] = value2;
5178
5199
  }
5179
5200
  }
5180
5201
  if (Object.keys(errors).length > 0) {
@@ -5208,9 +5229,9 @@ adminContentRoutes.post("/", async (c) => {
5208
5229
  INSERT INTO content (
5209
5230
  id, collection_id, slug, title, data, status,
5210
5231
  scheduled_publish_at, scheduled_unpublish_at,
5211
- meta_title, meta_description, author_id, created_by, created_at, updated_at
5232
+ meta_title, meta_description, author_id, created_at, updated_at
5212
5233
  )
5213
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5234
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5214
5235
  `);
5215
5236
  await insertStmt.bind(
5216
5237
  contentId,
@@ -5224,7 +5245,6 @@ adminContentRoutes.post("/", async (c) => {
5224
5245
  data.meta_title || null,
5225
5246
  data.meta_description || null,
5226
5247
  user?.userId || "unknown",
5227
- user?.userId || "unknown",
5228
5248
  now,
5229
5249
  now
5230
5250
  ).run();
@@ -5302,31 +5322,31 @@ adminContentRoutes.put("/:id", async (c) => {
5302
5322
  const data = {};
5303
5323
  const errors = {};
5304
5324
  for (const field of fields) {
5305
- const value = formData.get(field.field_name);
5306
- if (field.is_required && (!value || value.toString().trim() === "")) {
5325
+ const value2 = formData.get(field.field_name);
5326
+ if (field.is_required && (!value2 || value2.toString().trim() === "")) {
5307
5327
  errors[field.field_name] = [`${field.field_label} is required`];
5308
5328
  continue;
5309
5329
  }
5310
5330
  switch (field.field_type) {
5311
5331
  case "number":
5312
- if (value && isNaN(Number(value))) {
5332
+ if (value2 && isNaN(Number(value2))) {
5313
5333
  errors[field.field_name] = [`${field.field_label} must be a valid number`];
5314
5334
  } else {
5315
- data[field.field_name] = value ? Number(value) : null;
5335
+ data[field.field_name] = value2 ? Number(value2) : null;
5316
5336
  }
5317
5337
  break;
5318
5338
  case "boolean":
5319
- data[field.field_name] = formData.get(`${field.field_name}_submitted`) ? value === "true" : false;
5339
+ data[field.field_name] = formData.get(`${field.field_name}_submitted`) ? value2 === "true" : false;
5320
5340
  break;
5321
5341
  case "select":
5322
5342
  if (field.field_options?.multiple) {
5323
5343
  data[field.field_name] = formData.getAll(`${field.field_name}[]`);
5324
5344
  } else {
5325
- data[field.field_name] = value;
5345
+ data[field.field_name] = value2;
5326
5346
  }
5327
5347
  break;
5328
5348
  default:
5329
- data[field.field_name] = value;
5349
+ data[field.field_name] = value2;
5330
5350
  }
5331
5351
  }
5332
5352
  if (Object.keys(errors).length > 0) {
@@ -5443,23 +5463,23 @@ adminContentRoutes.post("/preview", async (c) => {
5443
5463
  const fields = await getCollectionFields(db, collectionId);
5444
5464
  const data = {};
5445
5465
  for (const field of fields) {
5446
- const value = formData.get(field.field_name);
5466
+ const value2 = formData.get(field.field_name);
5447
5467
  switch (field.field_type) {
5448
5468
  case "number":
5449
- data[field.field_name] = value ? Number(value) : null;
5469
+ data[field.field_name] = value2 ? Number(value2) : null;
5450
5470
  break;
5451
5471
  case "boolean":
5452
- data[field.field_name] = value === "true";
5472
+ data[field.field_name] = value2 === "true";
5453
5473
  break;
5454
5474
  case "select":
5455
5475
  if (field.field_options?.multiple) {
5456
5476
  data[field.field_name] = formData.getAll(`${field.field_name}[]`);
5457
5477
  } else {
5458
- data[field.field_name] = value;
5478
+ data[field.field_name] = value2;
5459
5479
  }
5460
5480
  break;
5461
5481
  default:
5462
- data[field.field_name] = value;
5482
+ data[field.field_name] = value2;
5463
5483
  }
5464
5484
  }
5465
5485
  const previewHTML = `
@@ -5527,9 +5547,9 @@ adminContentRoutes.post("/duplicate", async (c) => {
5527
5547
  const insertStmt = db.prepare(`
5528
5548
  INSERT INTO content (
5529
5549
  id, collection_id, slug, title, data, status,
5530
- author_id, created_by, created_at, updated_at
5550
+ author_id, created_at, updated_at
5531
5551
  )
5532
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5552
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
5533
5553
  `);
5534
5554
  await insertStmt.bind(
5535
5555
  newId,
@@ -5540,7 +5560,6 @@ adminContentRoutes.post("/duplicate", async (c) => {
5540
5560
  "draft",
5541
5561
  // Always start as draft
5542
5562
  user?.userId || "unknown",
5543
- user?.userId || "unknown",
5544
5563
  now,
5545
5564
  now
5546
5565
  ).run();
@@ -7316,10 +7335,10 @@ function renderUsersListPage(data) {
7316
7335
  label: "",
7317
7336
  className: "w-12",
7318
7337
  sortable: false,
7319
- render: (value, row) => {
7338
+ render: (value2, row) => {
7320
7339
  const initials = `${row.firstName.charAt(0)}${row.lastName.charAt(0)}`.toUpperCase();
7321
- if (value) {
7322
- return `<img src="${value}" alt="${row.firstName} ${row.lastName}" class="w-8 h-8 rounded-full">`;
7340
+ if (value2) {
7341
+ return `<img src="${value2}" alt="${row.firstName} ${row.lastName}" class="w-8 h-8 rounded-full">`;
7323
7342
  }
7324
7343
  return `
7325
7344
  <div class="w-8 h-8 bg-gradient-to-br from-cyan-400 to-blue-500 dark:from-cyan-300 dark:to-blue-400 rounded-full flex items-center justify-center">
@@ -7333,7 +7352,7 @@ function renderUsersListPage(data) {
7333
7352
  label: "Name",
7334
7353
  sortable: true,
7335
7354
  sortType: "string",
7336
- render: (value, row) => {
7355
+ render: (_value, row) => {
7337
7356
  const escapeHtml7 = (text) => text.replace(/[&<>"']/g, (char) => ({
7338
7357
  "&": "&amp;",
7339
7358
  "<": "&lt;",
@@ -7360,7 +7379,7 @@ function renderUsersListPage(data) {
7360
7379
  label: "Email",
7361
7380
  sortable: true,
7362
7381
  sortType: "string",
7363
- render: (value) => {
7382
+ render: (value2) => {
7364
7383
  const escapeHtml7 = (text) => text.replace(/[&<>"']/g, (char) => ({
7365
7384
  "&": "&amp;",
7366
7385
  "<": "&lt;",
@@ -7368,7 +7387,7 @@ function renderUsersListPage(data) {
7368
7387
  '"': "&quot;",
7369
7388
  "'": "&#39;"
7370
7389
  })[char] || char);
7371
- const escapedEmail = escapeHtml7(value);
7390
+ const escapedEmail = escapeHtml7(value2);
7372
7391
  return `<a href="mailto:${escapedEmail}" class="text-cyan-600 dark:text-cyan-400 hover:text-cyan-700 dark:hover:text-cyan-300 transition-colors">${escapedEmail}</a>`;
7373
7392
  }
7374
7393
  },
@@ -7377,7 +7396,7 @@ function renderUsersListPage(data) {
7377
7396
  label: "Role",
7378
7397
  sortable: true,
7379
7398
  sortType: "string",
7380
- render: (value) => {
7399
+ render: (_value) => {
7381
7400
  const roleColors = {
7382
7401
  admin: "bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-400 ring-1 ring-inset ring-red-700/10 dark:ring-red-500/20",
7383
7402
  editor: "bg-blue-50 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400 ring-1 ring-inset ring-blue-700/10 dark:ring-blue-500/20",
@@ -7393,7 +7412,7 @@ function renderUsersListPage(data) {
7393
7412
  label: "Last Login",
7394
7413
  sortable: true,
7395
7414
  sortType: "date",
7396
- render: (value) => {
7415
+ render: (_value) => {
7397
7416
  if (!value) return '<span class="text-zinc-500 dark:text-zinc-400">Never</span>';
7398
7417
  return `<span class="text-sm text-zinc-500 dark:text-zinc-400">${new Date(value).toLocaleDateString()}</span>`;
7399
7418
  }
@@ -7403,14 +7422,14 @@ function renderUsersListPage(data) {
7403
7422
  label: "Created",
7404
7423
  sortable: true,
7405
7424
  sortType: "date",
7406
- render: (value) => `<span class="text-sm text-zinc-500 dark:text-zinc-400">${new Date(value).toLocaleDateString()}</span>`
7425
+ render: (_value) => `<span class="text-sm text-zinc-500 dark:text-zinc-400">${new Date(value).toLocaleDateString()}</span>`
7407
7426
  },
7408
7427
  {
7409
7428
  key: "actions",
7410
7429
  label: "Actions",
7411
7430
  className: "text-right",
7412
7431
  sortable: false,
7413
- render: (value, row) => `
7432
+ render: (_value, row) => `
7414
7433
  <div class="flex justify-end space-x-2">
7415
7434
  ${row.isActive ? `<button onclick="toggleUserStatus('${row.id}', false)" title="Deactivate user" class="inline-flex items-center justify-center p-2 text-sm font-medium rounded-lg bg-gradient-to-r from-red-500 to-pink-500 dark:from-red-400 dark:to-pink-400 text-white hover:from-red-600 hover:to-pink-600 dark:hover:from-red-500 dark:hover:to-pink-500 shadow-sm transition-all duration-200">
7416
7435
  <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -7901,7 +7920,7 @@ userRoutes.post("/profile/avatar", async (c) => {
7901
7920
  try {
7902
7921
  const formData = await c.req.formData();
7903
7922
  const avatarFile = formData.get("avatar");
7904
- if (!avatarFile || !avatarFile.name) {
7923
+ if (!avatarFile || !(avatarFile instanceof File) || !avatarFile.name) {
7905
7924
  return c.html(renderAlert2({
7906
7925
  type: "error",
7907
7926
  message: "Please select an image file.",
@@ -8019,7 +8038,7 @@ userRoutes.post("/profile/password", async (c) => {
8019
8038
  VALUES (?, ?, ?, ?)
8020
8039
  `);
8021
8040
  await historyStmt.bind(
8022
- globalThis.crypto.randomUUID(),
8041
+ crypto.randomUUID(),
8023
8042
  user.userId,
8024
8043
  userData.password_hash,
8025
8044
  Date.now()
@@ -8251,7 +8270,7 @@ userRoutes.post("/users/new", async (c) => {
8251
8270
  }));
8252
8271
  }
8253
8272
  const passwordHash = await AuthManager.hashPassword(password);
8254
- const userId = globalThis.crypto.randomUUID();
8273
+ const userId = crypto.randomUUID();
8255
8274
  const createStmt = db.prepare(`
8256
8275
  INSERT INTO users (
8257
8276
  id, email, username, first_name, last_name, phone, bio,
@@ -8568,9 +8587,8 @@ userRoutes.post("/invite-user", async (c) => {
8568
8587
  if (existingUser) {
8569
8588
  return c.json({ error: "A user with this email already exists" }, 400);
8570
8589
  }
8571
- const invitationToken = globalThis.crypto.randomUUID();
8572
- const invitationExpires = Date.now() + 7 * 24 * 60 * 60 * 1e3;
8573
- const userId = globalThis.crypto.randomUUID();
8590
+ const invitationToken = crypto.randomUUID();
8591
+ const userId = crypto.randomUUID();
8574
8592
  const createUserStmt = db.prepare(`
8575
8593
  INSERT INTO users (
8576
8594
  id, email, first_name, last_name, role,
@@ -8635,7 +8653,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
8635
8653
  if (!invitedUser) {
8636
8654
  return c.json({ error: "User not found or invitation not valid" }, 404);
8637
8655
  }
8638
- const newInvitationToken = globalThis.crypto.randomUUID();
8656
+ const newInvitationToken = crypto.randomUUID();
8639
8657
  const updateStmt = db.prepare(`
8640
8658
  UPDATE users SET
8641
8659
  invitation_token = ?,
@@ -10223,7 +10241,7 @@ adminMediaRoutes.get("/", async (c) => {
10223
10241
  const type = searchParams.get("type") || "all";
10224
10242
  const view = searchParams.get("view") || "grid";
10225
10243
  const page = parseInt(searchParams.get("page") || "1");
10226
- const cacheBust = searchParams.get("t");
10244
+ const ____cacheBust = searchParams.get("t");
10227
10245
  const limit = 24;
10228
10246
  const offset = (page - 1) * limit;
10229
10247
  const db = c.env.DB;
@@ -10649,7 +10667,7 @@ adminMediaRoutes.post("/upload", async (c) => {
10649
10667
  });
10650
10668
  }
10651
10669
  }
10652
- let mediaGridHTML = "";
10670
+ let __mediaGridHTML = "";
10653
10671
  if (uploadResults.length > 0) {
10654
10672
  try {
10655
10673
  const folder = formData.get("folder") || "uploads";
@@ -11092,6 +11110,28 @@ function renderPluginsListPage(data) {
11092
11110
  </div>
11093
11111
  </div>
11094
11112
 
11113
+ <!-- Experimental Notice -->
11114
+ <div class="mb-6 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800/50 p-4">
11115
+ <div class="flex items-start">
11116
+ <div class="flex-shrink-0">
11117
+ <svg class="h-5 w-5 text-amber-600 dark:text-amber-400" viewBox="0 0 20 20" fill="currentColor">
11118
+ <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
11119
+ </svg>
11120
+ </div>
11121
+ <div class="ml-3 flex-1">
11122
+ <h3 class="text-sm font-semibold text-amber-800 dark:text-amber-200">
11123
+ Experimental Feature
11124
+ </h3>
11125
+ <div class="mt-2 text-sm text-amber-700 dark:text-amber-300">
11126
+ <p>
11127
+ Plugin management is currently under active development. While functional, some features may change or have limitations.
11128
+ Please report any issues you encounter on our <a href="https://discord.gg/8bMy6bv3sZ" target="_blank" class="font-medium underline hover:text-amber-900 dark:hover:text-amber-100">Discord community</a>.
11129
+ </p>
11130
+ </div>
11131
+ </div>
11132
+ </div>
11133
+ </div>
11134
+
11095
11135
  <!-- Stats -->
11096
11136
  <div class="mb-6">
11097
11137
  <h3 class="text-base font-semibold text-zinc-950 dark:text-white">Plugin Statistics</h3>
@@ -12145,10 +12185,10 @@ function renderSettingsTab(plugin) {
12145
12185
  `;
12146
12186
  }
12147
12187
  function renderSettingsFields(settings) {
12148
- return Object.entries(settings).map(([key, value]) => {
12188
+ return Object.entries(settings).map(([key, value2]) => {
12149
12189
  const fieldId = `setting_${key}`;
12150
12190
  const displayName = key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
12151
- if (typeof value === "boolean") {
12191
+ if (typeof value2 === "boolean") {
12152
12192
  return `
12153
12193
  <div class="flex items-center justify-between">
12154
12194
  <div>
@@ -12156,12 +12196,12 @@ function renderSettingsFields(settings) {
12156
12196
  <p class="text-xs text-gray-400">Enable or disable this feature</p>
12157
12197
  </div>
12158
12198
  <label class="relative inline-flex items-center cursor-pointer">
12159
- <input type="checkbox" name="${fieldId}" id="${fieldId}" ${value ? "checked" : ""} class="sr-only peer">
12199
+ <input type="checkbox" name="${fieldId}" id="${fieldId}" ${value2 ? "checked" : ""} class="sr-only peer">
12160
12200
  <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>
12161
12201
  </label>
12162
12202
  </div>
12163
12203
  `;
12164
- } else if (typeof value === "number") {
12204
+ } else if (typeof value2 === "number") {
12165
12205
  return `
12166
12206
  <div>
12167
12207
  <label for="${fieldId}" class="block text-sm font-medium text-gray-300 mb-2">${displayName}</label>
@@ -12169,7 +12209,7 @@ function renderSettingsFields(settings) {
12169
12209
  type="number"
12170
12210
  name="${fieldId}"
12171
12211
  id="${fieldId}"
12172
- value="${value}"
12212
+ value="${value2}"
12173
12213
  class="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"
12174
12214
  >
12175
12215
  </div>
@@ -12182,7 +12222,7 @@ function renderSettingsFields(settings) {
12182
12222
  type="text"
12183
12223
  name="${fieldId}"
12184
12224
  id="${fieldId}"
12185
- value="${value}"
12225
+ value="${value2}"
12186
12226
  class="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"
12187
12227
  >
12188
12228
  </div>
@@ -16690,8 +16730,8 @@ function renderTable2(data) {
16690
16730
  </td>
16691
16731
  ` : ""}
16692
16732
  ${data.columns.map((column, colIndex) => {
16693
- const value = row[column.key];
16694
- const displayValue = column.render ? column.render(value, row) : value;
16733
+ const value2 = row[column.key];
16734
+ const displayValue = column.render ? column.render(value2, row) : value2;
16695
16735
  const stopPropagation = column.key === "actions" ? 'onclick="event.stopPropagation()"' : "";
16696
16736
  const isFirst = colIndex === 0 && !data.selectable;
16697
16737
  const isLast = colIndex === data.columns.length - 1;
@@ -16828,7 +16868,7 @@ function renderCollectionsListPage(data) {
16828
16868
  label: "Name",
16829
16869
  sortable: true,
16830
16870
  sortType: "string",
16831
- render: (value, collection) => `
16871
+ render: (_value, collection) => `
16832
16872
  <div class="flex items-center gap-2 ml-2">
16833
16873
  <span class="inline-flex items-center rounded-md bg-cyan-50 dark:bg-cyan-500/10 px-2.5 py-1 text-sm font-medium text-cyan-700 dark:text-cyan-300 ring-1 ring-inset ring-cyan-700/10 dark:ring-cyan-400/20">
16834
16874
  ${collection.name}
@@ -16855,14 +16895,14 @@ function renderCollectionsListPage(data) {
16855
16895
  label: "Description",
16856
16896
  sortable: true,
16857
16897
  sortType: "string",
16858
- render: (value, collection) => collection.description || '<span class="text-zinc-500 dark:text-zinc-400">-</span>'
16898
+ render: (_value, collection) => collection.description || '<span class="text-zinc-500 dark:text-zinc-400">-</span>'
16859
16899
  },
16860
16900
  {
16861
16901
  key: "field_count",
16862
16902
  label: "Fields",
16863
16903
  sortable: true,
16864
16904
  sortType: "number",
16865
- render: (value, collection) => {
16905
+ render: (_value, collection) => {
16866
16906
  const count = collection.field_count || 0;
16867
16907
  return `
16868
16908
  <div class="flex items-center">
@@ -16878,7 +16918,7 @@ function renderCollectionsListPage(data) {
16878
16918
  label: "Source",
16879
16919
  sortable: true,
16880
16920
  sortType: "string",
16881
- render: (value, collection) => {
16921
+ render: (_value, collection) => {
16882
16922
  if (collection.managed) {
16883
16923
  return `
16884
16924
  <div class="flex items-center gap-1.5">
@@ -16912,7 +16952,7 @@ function renderCollectionsListPage(data) {
16912
16952
  key: "actions",
16913
16953
  label: "Content",
16914
16954
  sortable: false,
16915
- render: (value, collection) => {
16955
+ render: (_value, collection) => {
16916
16956
  if (!collection || !collection.id) return '<span class="text-zinc-500 dark:text-zinc-400">-</span>';
16917
16957
  return `
16918
16958
  <div class="flex items-center space-x-2">
@@ -17307,7 +17347,55 @@ function renderCollectionFormPage(data) {
17307
17347
  </style>
17308
17348
 
17309
17349
  ${renderForm(formData)}
17310
-
17350
+
17351
+ ${isEdit && data.managed ? `
17352
+ <!-- Read-Only Fields Display for Managed Collections -->
17353
+ <div class="mt-8 pt-8 border-t border-zinc-950/5 dark:border-white/10">
17354
+ <div class="mb-6">
17355
+ <h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">Collection Fields</h3>
17356
+ <p class="text-sm/6 text-zinc-500 dark:text-zinc-400 mt-1">Fields defined in the configuration file (read-only)</p>
17357
+ </div>
17358
+
17359
+ <!-- Fields List (Read-Only) -->
17360
+ <div class="space-y-3">
17361
+ ${(data.fields || []).map((field) => `
17362
+ <div class="bg-zinc-50 dark:bg-zinc-800/50 rounded-lg border border-zinc-950/5 dark:border-white/10 p-4">
17363
+ <div class="flex items-center justify-between">
17364
+ <div class="flex items-center gap-x-4">
17365
+ <div>
17366
+ <div class="flex items-center gap-x-2">
17367
+ <span class="text-sm/6 font-medium text-zinc-950 dark:text-white">${field.field_label}</span>
17368
+ <span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium bg-cyan-500/10 dark:bg-cyan-400/10 text-cyan-700 dark:text-cyan-300 ring-1 ring-inset ring-cyan-500/20 dark:ring-cyan-400/20">
17369
+ ${field.field_type}
17370
+ </span>
17371
+ ${field.is_required ? `
17372
+ <span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium bg-rose-500/10 dark:bg-rose-400/10 text-rose-700 dark:text-rose-300 ring-1 ring-inset ring-rose-500/20 dark:ring-rose-400/20">
17373
+ Required
17374
+ </span>
17375
+ ` : ""}
17376
+ </div>
17377
+ <div class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
17378
+ <code class="px-1.5 py-0.5 rounded bg-zinc-100 dark:bg-zinc-800 font-mono">${field.field_name}</code>
17379
+ </div>
17380
+ </div>
17381
+ </div>
17382
+ </div>
17383
+ </div>
17384
+ `).join("")}
17385
+
17386
+ ${(data.fields || []).length === 0 ? `
17387
+ <div class="text-center py-12 text-zinc-500 dark:text-zinc-400">
17388
+ <svg class="mx-auto h-12 w-12 text-zinc-400 dark:text-zinc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
17389
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"/>
17390
+ </svg>
17391
+ <p class="mt-4 text-base/7 font-semibold text-zinc-950 dark:text-white">No fields defined</p>
17392
+ <p class="mt-2 text-sm/6">Add fields to your collection configuration file to see them here.</p>
17393
+ </div>
17394
+ ` : ""}
17395
+ </div>
17396
+ </div>
17397
+ ` : ""}
17398
+
17311
17399
  ${isEdit && !data.managed ? `
17312
17400
  <!-- Fields Management Section -->
17313
17401
  <div class="mt-8 pt-8 border-t border-zinc-950/5 dark:border-white/10">
@@ -17866,7 +17954,7 @@ adminCollectionsRoutes.get("/", async (c) => {
17866
17954
  let results;
17867
17955
  if (search) {
17868
17956
  stmt = db.prepare(`
17869
- SELECT id, name, display_name, description, created_at, managed
17957
+ SELECT id, name, display_name, description, created_at, managed, schema
17870
17958
  FROM collections
17871
17959
  WHERE is_active = 1
17872
17960
  AND (name LIKE ? OR display_name LIKE ? OR description LIKE ?)
@@ -17876,7 +17964,7 @@ adminCollectionsRoutes.get("/", async (c) => {
17876
17964
  const queryResults = await stmt.bind(searchParam, searchParam, searchParam).all();
17877
17965
  results = queryResults.results;
17878
17966
  } else {
17879
- stmt = db.prepare("SELECT id, name, display_name, description, created_at, managed FROM collections WHERE is_active = 1 ORDER BY created_at DESC");
17967
+ stmt = db.prepare("SELECT id, name, display_name, description, created_at, managed, schema FROM collections WHERE is_active = 1 ORDER BY created_at DESC");
17880
17968
  const queryResults = await stmt.all();
17881
17969
  results = queryResults.results;
17882
17970
  }
@@ -17884,6 +17972,19 @@ adminCollectionsRoutes.get("/", async (c) => {
17884
17972
  const { results: fieldCountResults } = await fieldCountStmt.all();
17885
17973
  const fieldCounts = new Map((fieldCountResults || []).map((row) => [String(row.collection_id), Number(row.count)]));
17886
17974
  const collections = (results || []).filter((row) => row && row.id).map((row) => {
17975
+ let fieldCount = 0;
17976
+ if (row.schema) {
17977
+ try {
17978
+ const schema = typeof row.schema === "string" ? JSON.parse(row.schema) : row.schema;
17979
+ if (schema && schema.properties) {
17980
+ fieldCount = Object.keys(schema.properties).length;
17981
+ }
17982
+ } catch (e) {
17983
+ fieldCount = fieldCounts.get(String(row.id)) || 0;
17984
+ }
17985
+ } else {
17986
+ fieldCount = fieldCounts.get(String(row.id)) || 0;
17987
+ }
17887
17988
  return {
17888
17989
  id: String(row.id || ""),
17889
17990
  name: String(row.name || ""),
@@ -17891,7 +17992,7 @@ adminCollectionsRoutes.get("/", async (c) => {
17891
17992
  description: row.description ? String(row.description) : void 0,
17892
17993
  created_at: Number(row.created_at || 0),
17893
17994
  formattedDate: row.created_at ? new Date(Number(row.created_at)).toLocaleDateString() : "Unknown",
17894
- field_count: fieldCounts.get(String(row.id)) || 0,
17995
+ field_count: fieldCount,
17895
17996
  managed: row.managed === 1
17896
17997
  };
17897
17998
  });
@@ -18065,22 +18166,45 @@ adminCollectionsRoutes.get("/:id", async (c) => {
18065
18166
  };
18066
18167
  return c.html(renderCollectionFormPage(formData2));
18067
18168
  }
18068
- const fieldsStmt = db.prepare(`
18069
- SELECT * FROM content_fields
18070
- WHERE collection_id = ?
18071
- ORDER BY field_order ASC
18072
- `);
18073
- const { results: fieldsResults } = await fieldsStmt.bind(id).all();
18074
- const fields = (fieldsResults || []).map((row) => ({
18075
- id: row.id,
18076
- field_name: row.field_name,
18077
- field_type: row.field_type,
18078
- field_label: row.field_label,
18079
- field_options: row.field_options ? JSON.parse(row.field_options) : {},
18080
- field_order: row.field_order,
18081
- is_required: row.is_required === 1,
18082
- is_searchable: row.is_searchable === 1
18083
- }));
18169
+ let fields = [];
18170
+ if (collection.schema) {
18171
+ try {
18172
+ const schema = typeof collection.schema === "string" ? JSON.parse(collection.schema) : collection.schema;
18173
+ if (schema && schema.properties) {
18174
+ let fieldOrder = 0;
18175
+ fields = Object.entries(schema.properties).map(([fieldName, fieldConfig]) => ({
18176
+ id: `schema-${fieldName}`,
18177
+ field_name: fieldName,
18178
+ field_type: fieldConfig.type || "string",
18179
+ field_label: fieldConfig.title || fieldName,
18180
+ field_options: fieldConfig,
18181
+ field_order: fieldOrder++,
18182
+ is_required: fieldConfig.required === true || schema.required && schema.required.includes(fieldName),
18183
+ is_searchable: false
18184
+ }));
18185
+ }
18186
+ } catch (e) {
18187
+ console.error("Error parsing collection schema:", e);
18188
+ }
18189
+ }
18190
+ if (fields.length === 0) {
18191
+ const fieldsStmt = db.prepare(`
18192
+ SELECT * FROM content_fields
18193
+ WHERE collection_id = ?
18194
+ ORDER BY field_order ASC
18195
+ `);
18196
+ const { results: fieldsResults } = await fieldsStmt.bind(id).all();
18197
+ fields = (fieldsResults || []).map((row) => ({
18198
+ id: row.id,
18199
+ field_name: row.field_name,
18200
+ field_type: row.field_type,
18201
+ field_label: row.field_label,
18202
+ field_options: row.field_options ? JSON.parse(row.field_options) : {},
18203
+ field_order: row.field_order,
18204
+ is_required: row.is_required === 1,
18205
+ is_searchable: row.is_searchable === 1
18206
+ }));
18207
+ }
18084
18208
  const formData = {
18085
18209
  id: collection.id,
18086
18210
  name: collection.name,
@@ -20170,5 +20294,5 @@ var ROUTES_INFO = {
20170
20294
  };
20171
20295
 
20172
20296
  export { ROUTES_INFO, adminCheckboxRoutes, adminCollectionsRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_code_examples_default, admin_content_default, admin_faq_default, admin_testimonials_default, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, router, userRoutes };
20173
- //# sourceMappingURL=chunk-CUEIM4FE.js.map
20174
- //# sourceMappingURL=chunk-CUEIM4FE.js.map
20297
+ //# sourceMappingURL=chunk-Z4H6DBVF.js.map
20298
+ //# sourceMappingURL=chunk-Z4H6DBVF.js.map