@sonicjs-cms/core 2.8.2 → 2.9.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 (81) hide show
  1. package/dist/{app-DnQ26Lho.d.cts → app-Ozl9agJG.d.cts} +1 -1
  2. package/dist/{app-DnQ26Lho.d.ts → app-Ozl9agJG.d.ts} +1 -1
  3. package/dist/{chunk-FUUVSYVQ.js → chunk-25YNV4RK.js} +3 -3
  4. package/dist/{chunk-FUUVSYVQ.js.map → chunk-25YNV4RK.js.map} +1 -1
  5. package/dist/{chunk-4Z5BQZT6.js → chunk-2JGQKF7B.js} +324 -301
  6. package/dist/chunk-2JGQKF7B.js.map +1 -0
  7. package/dist/{chunk-WI5ESQKT.js → chunk-3FHMXGLF.js} +7 -5
  8. package/dist/chunk-3FHMXGLF.js.map +1 -0
  9. package/dist/{chunk-VNLR35GO.cjs → chunk-64APW3DW.cjs} +339 -2
  10. package/dist/chunk-64APW3DW.cjs.map +1 -0
  11. package/dist/{chunk-G44QUVNM.js → chunk-7JMMLHPQ.js} +337 -4
  12. package/dist/chunk-7JMMLHPQ.js.map +1 -0
  13. package/dist/chunk-CJYFSKH7.js +54 -54
  14. package/dist/chunk-CJYFSKH7.js.map +1 -1
  15. package/dist/{chunk-ZWKCL46S.cjs → chunk-DQZVU3WB.cjs} +4 -4
  16. package/dist/{chunk-ZWKCL46S.cjs.map → chunk-DQZVU3WB.cjs.map} +1 -1
  17. package/dist/{chunk-3U5YHS4G.cjs → chunk-KSB6FXOP.cjs} +425 -402
  18. package/dist/chunk-KSB6FXOP.cjs.map +1 -0
  19. package/dist/{chunk-VGSZWZP3.cjs → chunk-LDFMYRG6.cjs} +2 -2
  20. package/dist/{chunk-VGSZWZP3.cjs.map → chunk-LDFMYRG6.cjs.map} +1 -1
  21. package/dist/chunk-MNFY6DWY.cjs +54 -54
  22. package/dist/chunk-MNFY6DWY.cjs.map +1 -1
  23. package/dist/{chunk-JSHIGVIF.cjs → chunk-SHU7Q66Q.cjs} +7 -5
  24. package/dist/chunk-SHU7Q66Q.cjs.map +1 -0
  25. package/dist/{chunk-I6REMSMF.js → chunk-STTZVLY2.js} +2 -2
  26. package/dist/{chunk-I6REMSMF.js.map → chunk-STTZVLY2.js.map} +1 -1
  27. package/dist/{collection-config-BF95LgQb.d.cts → collection-config-DckWhkdL.d.cts} +3 -2
  28. package/dist/{collection-config-BF95LgQb.d.ts → collection-config-DckWhkdL.d.ts} +3 -2
  29. package/dist/{filter-bar.template-Daw8ZDoq.d.cts → filter-bar.template-DlVYMk-T.d.cts} +1 -1
  30. package/dist/{filter-bar.template-Daw8ZDoq.d.ts → filter-bar.template-DlVYMk-T.d.ts} +1 -1
  31. package/dist/index.cjs +128 -127
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +8 -8
  34. package/dist/index.d.ts +8 -8
  35. package/dist/index.js +10 -9
  36. package/dist/index.js.map +1 -1
  37. package/dist/middleware.cjs +28 -28
  38. package/dist/middleware.d.cts +1 -1
  39. package/dist/middleware.d.ts +1 -1
  40. package/dist/middleware.js +2 -2
  41. package/dist/migrations-QQWGDWGB.cjs +13 -0
  42. package/dist/{migrations-F3G6CTRS.cjs.map → migrations-QQWGDWGB.cjs.map} +1 -1
  43. package/dist/migrations-SZSR3C3G.js +4 -0
  44. package/dist/{migrations-LLNEST75.js.map → migrations-SZSR3C3G.js.map} +1 -1
  45. package/dist/{plugin-zvZpaiP5.d.cts → plugin-0Xogrln-.d.cts} +1 -1
  46. package/dist/{plugin-zvZpaiP5.d.ts → plugin-0Xogrln-.d.ts} +1 -1
  47. package/dist/{plugin-bootstrap-C7Mj00Ud.d.ts → plugin-bootstrap-BAz7NY0H.d.cts} +2 -2
  48. package/dist/{plugin-bootstrap-DKB5f8-E.d.cts → plugin-bootstrap-Cz3-bj8X.d.ts} +2 -2
  49. package/dist/{plugin-manager-Baa6xXqB.d.ts → plugin-manager-Clf2gXwj.d.ts} +2 -2
  50. package/dist/{plugin-manager-vBal9Zip.d.cts → plugin-manager-GcIeb226.d.cts} +2 -2
  51. package/dist/plugins.d.cts +2 -2
  52. package/dist/plugins.d.ts +2 -2
  53. package/dist/routes.cjs +28 -28
  54. package/dist/routes.d.cts +1 -1
  55. package/dist/routes.d.ts +1 -1
  56. package/dist/routes.js +5 -5
  57. package/dist/services.cjs +30 -14
  58. package/dist/services.d.cts +29 -4
  59. package/dist/services.d.ts +29 -4
  60. package/dist/services.js +2 -2
  61. package/dist/{telemetry-UiD1i9GS.d.cts → telemetry-B9vIV4wh.d.cts} +1 -1
  62. package/dist/{telemetry-UiD1i9GS.d.ts → telemetry-B9vIV4wh.d.ts} +1 -1
  63. package/dist/templates.d.cts +1 -1
  64. package/dist/templates.d.ts +1 -1
  65. package/dist/types.d.cts +3 -3
  66. package/dist/types.d.ts +3 -3
  67. package/dist/utils.cjs +11 -11
  68. package/dist/utils.d.cts +3 -3
  69. package/dist/utils.d.ts +3 -3
  70. package/dist/utils.js +1 -1
  71. package/dist/{version-C_CXrN_T.d.cts → version-ChpccWQ1.d.cts} +1 -1
  72. package/dist/{version-C_CXrN_T.d.ts → version-ChpccWQ1.d.ts} +1 -1
  73. package/package.json +5 -3
  74. package/dist/chunk-3U5YHS4G.cjs.map +0 -1
  75. package/dist/chunk-4Z5BQZT6.js.map +0 -1
  76. package/dist/chunk-G44QUVNM.js.map +0 -1
  77. package/dist/chunk-JSHIGVIF.cjs.map +0 -1
  78. package/dist/chunk-VNLR35GO.cjs.map +0 -1
  79. package/dist/chunk-WI5ESQKT.js.map +0 -1
  80. package/dist/migrations-F3G6CTRS.cjs +0 -13
  81. package/dist/migrations-LLNEST75.js +0 -4
@@ -1,10 +1,10 @@
1
- import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService } from './chunk-G44QUVNM.js';
2
- import { requireAuth, isPluginActive, optionalAuth, requireRole, rateLimit, AuthManager, logActivity, generateCsrfToken } from './chunk-FUUVSYVQ.js';
1
+ import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService, getAppInstance, buildRouteList, CATEGORY_INFO } from './chunk-7JMMLHPQ.js';
2
+ import { requireAuth, isPluginActive, optionalAuth, requireRole, rateLimit, AuthManager, logActivity, generateCsrfToken } from './chunk-25YNV4RK.js';
3
3
  import { PluginService } from './chunk-YFJJU26H.js';
4
- import { MigrationService } from './chunk-I6REMSMF.js';
4
+ import { MigrationService } from './chunk-STTZVLY2.js';
5
5
  import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-JJS7JZCH.js';
6
6
  import { PluginBuilder, TurnstileService } from './chunk-J5WGMRSU.js';
7
- import { QueryFilterBuilder, getCoreVersion, getBlocksFieldConfig, parseBlocksValue } from './chunk-WI5ESQKT.js';
7
+ import { QueryFilterBuilder, getCoreVersion, getBlocksFieldConfig, parseBlocksValue } from './chunk-3FHMXGLF.js';
8
8
  import { metricsTracker } from './chunk-FICTAGD4.js';
9
9
  import { escapeHtml, sanitizeRichText, sanitizeInput } from './chunk-TQABQWOP.js';
10
10
  import { Hono } from 'hono';
@@ -2281,7 +2281,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
2281
2281
  });
2282
2282
  adminApiRoutes.get("/migrations/status", async (c) => {
2283
2283
  try {
2284
- const { MigrationService: MigrationService2 } = await import('./migrations-LLNEST75.js');
2284
+ const { MigrationService: MigrationService2 } = await import('./migrations-SZSR3C3G.js');
2285
2285
  const db = c.env.DB;
2286
2286
  const migrationService = new MigrationService2(db);
2287
2287
  const status = await migrationService.getMigrationStatus();
@@ -2306,7 +2306,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
2306
2306
  error: "Unauthorized. Admin access required."
2307
2307
  }, 403);
2308
2308
  }
2309
- const { MigrationService: MigrationService2 } = await import('./migrations-LLNEST75.js');
2309
+ const { MigrationService: MigrationService2 } = await import('./migrations-SZSR3C3G.js');
2310
2310
  const db = c.env.DB;
2311
2311
  const migrationService = new MigrationService2(db);
2312
2312
  const result = await migrationService.runPendingMigrations();
@@ -2325,7 +2325,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
2325
2325
  });
2326
2326
  adminApiRoutes.get("/migrations/validate", async (c) => {
2327
2327
  try {
2328
- const { MigrationService: MigrationService2 } = await import('./migrations-LLNEST75.js');
2328
+ const { MigrationService: MigrationService2 } = await import('./migrations-SZSR3C3G.js');
2329
2329
  const db = c.env.DB;
2330
2330
  const migrationService = new MigrationService2(db);
2331
2331
  const validation = await migrationService.validateSchema();
@@ -4284,7 +4284,7 @@ function getMDXEditorInitScript(config) {
4284
4284
  const toolbar = config?.toolbar || "full";
4285
4285
  const placeholder = config?.placeholder || "Start writing your content...";
4286
4286
  return `
4287
- // Initialize EasyMDE (Markdown Editor) for all richtext fields
4287
+ // Initialize EasyMDE (Markdown Editor) only for markdown-marked fields
4288
4288
  function initializeMDXEditor() {
4289
4289
  if (typeof EasyMDE === 'undefined') {
4290
4290
  console.warn('EasyMDE not loaded yet, retrying...');
@@ -4293,7 +4293,7 @@ function getMDXEditorInitScript(config) {
4293
4293
  }
4294
4294
 
4295
4295
  // Find all textareas that need EasyMDE
4296
- document.querySelectorAll('.richtext-container textarea').forEach((textarea) => {
4296
+ document.querySelectorAll('.richtext-container[data-editor-provider="easymde"] textarea').forEach((textarea) => {
4297
4297
  // Skip if already initialized
4298
4298
  if (textarea.dataset.mdxeditorInitialized === 'true') {
4299
4299
  return;
@@ -4392,11 +4392,11 @@ function getTinyMCEInitScript(config) {
4392
4392
  const contentCss = skin.includes("dark") ? "dark" : "default";
4393
4393
  const defaultHeight = config?.defaultHeight || 300;
4394
4394
  return `
4395
- // Initialize TinyMCE for all richtext fields
4395
+ // Initialize TinyMCE only for TinyMCE-backed richtext fields
4396
4396
  function initializeTinyMCE() {
4397
4397
  if (typeof tinymce !== 'undefined') {
4398
4398
  // Find all textareas that need TinyMCE
4399
- document.querySelectorAll('.richtext-container textarea').forEach((textarea) => {
4399
+ document.querySelectorAll('.richtext-container[data-editor-provider="tinymce"] textarea').forEach((textarea) => {
4400
4400
  // Skip if already initialized
4401
4401
  if (tinymce.get(textarea.id)) {
4402
4402
  return;
@@ -4780,6 +4780,7 @@ function getReadFieldValueScript() {
4780
4780
  const textarea = fieldWrapper.querySelector('textarea');
4781
4781
  const inputs = Array.from(fieldWrapper.querySelectorAll('input'));
4782
4782
  const checkbox = inputs.find((input) => input.type === 'checkbox');
4783
+ const checkedRadio = inputs.find((input) => input.type === 'radio' && input.checked);
4783
4784
  const nonHiddenInput = inputs.find((input) => input.type !== 'hidden' && input.type !== 'checkbox');
4784
4785
  const hiddenInput = inputs.find((input) => input.type === 'hidden');
4785
4786
 
@@ -4802,6 +4803,10 @@ function getReadFieldValueScript() {
4802
4803
  return checkbox.checked;
4803
4804
  }
4804
4805
 
4806
+ if (fieldType === 'radio') {
4807
+ return checkedRadio ? checkedRadio.value : '';
4808
+ }
4809
+
4805
4810
  if (select) {
4806
4811
  if (select.multiple) {
4807
4812
  return Array.from(select.selectedOptions).map((option) => option.value);
@@ -4828,6 +4833,24 @@ function getReadFieldValueScript() {
4828
4833
  </script>
4829
4834
  `;
4830
4835
  }
4836
+ function isMarkdownEditorFieldType(fieldType) {
4837
+ return fieldType === "markdown" || fieldType === "mdxeditor" || fieldType === "easymde";
4838
+ }
4839
+ function getEditorMetadata(fieldType) {
4840
+ if (fieldType === "richtext" || fieldType === "tinymce") {
4841
+ return {
4842
+ family: "richtext",
4843
+ provider: "tinymce"
4844
+ };
4845
+ }
4846
+ if (isMarkdownEditorFieldType(fieldType)) {
4847
+ return {
4848
+ family: "markdown",
4849
+ provider: "easymde"
4850
+ };
4851
+ }
4852
+ return null;
4853
+ }
4831
4854
  function renderDynamicField(field, options = {}) {
4832
4855
  const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {}, collectionId = "", contentId = "" } = options;
4833
4856
  const opts = field.field_options || {};
@@ -4841,10 +4864,10 @@ function renderDynamicField(field, options = {}) {
4841
4864
  if (field.field_type === "quill" && !pluginStatuses.quillEnabled) {
4842
4865
  fallbackToTextarea = true;
4843
4866
  fallbackWarning = "\u26A0\uFE0F Quill Editor plugin is inactive. Using textarea fallback.";
4844
- } else if (field.field_type === "mdxeditor" && !pluginStatuses.mdxeditorEnabled) {
4867
+ } else if (isMarkdownEditorFieldType(field.field_type) && !pluginStatuses.mdxeditorEnabled) {
4845
4868
  fallbackToTextarea = true;
4846
- fallbackWarning = "\u26A0\uFE0F MDXEditor plugin is inactive. Using textarea fallback.";
4847
- } else if (field.field_type === "tinymce" && !pluginStatuses.tinymceEnabled) {
4869
+ fallbackWarning = "\u26A0\uFE0F Markdown editor plugin is inactive. Using textarea fallback.";
4870
+ } else if ((field.field_type === "richtext" || field.field_type === "tinymce") && !pluginStatuses.tinymceEnabled) {
4848
4871
  fallbackToTextarea = true;
4849
4872
  fallbackWarning = "\u26A0\uFE0F TinyMCE plugin is inactive. Using textarea fallback.";
4850
4873
  }
@@ -4966,8 +4989,10 @@ function renderDynamicField(field, options = {}) {
4966
4989
  `;
4967
4990
  break;
4968
4991
  case "richtext":
4992
+ case "tinymce": {
4993
+ const editorMetadata = getEditorMetadata(field.field_type);
4969
4994
  fieldHTML = `
4970
- <div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}">
4995
+ <div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}" data-editor-family="${editorMetadata?.family || ""}" data-editor-provider="${editorMetadata?.provider || ""}">
4971
4996
  <textarea
4972
4997
  id="${fieldId}"
4973
4998
  name="${fieldName}"
@@ -4978,6 +5003,7 @@ function renderDynamicField(field, options = {}) {
4978
5003
  </div>
4979
5004
  `;
4980
5005
  break;
5006
+ }
4981
5007
  case "quill":
4982
5008
  fieldHTML = `
4983
5009
  <div class="quill-editor-container" data-field-id="${fieldId}">
@@ -5001,9 +5027,12 @@ function renderDynamicField(field, options = {}) {
5001
5027
  </div>
5002
5028
  `;
5003
5029
  break;
5030
+ case "markdown":
5004
5031
  case "mdxeditor":
5032
+ case "easymde": {
5033
+ const editorMetadata = getEditorMetadata(field.field_type);
5005
5034
  fieldHTML = `
5006
- <div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}">
5035
+ <div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}" data-editor-family="${editorMetadata?.family || ""}" data-editor-provider="${editorMetadata?.provider || ""}">
5007
5036
  <textarea
5008
5037
  id="${fieldId}"
5009
5038
  name="${fieldName}"
@@ -5014,6 +5043,7 @@ function renderDynamicField(field, options = {}) {
5014
5043
  </div>
5015
5044
  `;
5016
5045
  break;
5046
+ }
5017
5047
  case "number":
5018
5048
  fieldHTML = `
5019
5049
  <input
@@ -5283,6 +5313,39 @@ function renderDynamicField(field, options = {}) {
5283
5313
  ` : ""}
5284
5314
  `;
5285
5315
  break;
5316
+ case "radio":
5317
+ const radioOptions = opts.options || (Array.isArray(opts.enum) ? opts.enum.map((optionValue, index) => ({
5318
+ value: optionValue,
5319
+ label: opts.enumLabels?.[index] || optionValue
5320
+ })) : []);
5321
+ const selectedRadioValue = value !== void 0 && value !== null ? String(value) : opts.default ? String(opts.default) : "";
5322
+ const isInline = opts.inline === true;
5323
+ fieldHTML = `
5324
+ <div class="${isInline ? "flex flex-wrap gap-4" : "space-y-3"}">
5325
+ ${radioOptions.map((option, index) => {
5326
+ const optionValue = typeof option === "string" ? option : option.value;
5327
+ const optionLabel = typeof option === "string" ? option : option.label;
5328
+ const inputId = `${fieldId}-option-${index}`;
5329
+ const checked2 = selectedRadioValue === String(optionValue) ? "checked" : "";
5330
+ return `
5331
+ <label for="${inputId}" class="flex items-center gap-3 text-sm text-zinc-700 dark:text-zinc-300">
5332
+ <input
5333
+ type="radio"
5334
+ id="${inputId}"
5335
+ name="${fieldName}"
5336
+ value="${escapeHtml3(optionValue)}"
5337
+ class="h-4 w-4 text-zinc-900 focus:ring-zinc-400 dark:text-white dark:focus:ring-white"
5338
+ ${checked2}
5339
+ ${required}
5340
+ ${disabled ? "disabled" : ""}
5341
+ >
5342
+ <span>${escapeHtml3(optionLabel)}</span>
5343
+ </label>
5344
+ `;
5345
+ }).join("")}
5346
+ </div>
5347
+ `;
5348
+ break;
5286
5349
  case "reference":
5287
5350
  let referenceCollections = [];
5288
5351
  if (Array.isArray(opts.collection)) {
@@ -5553,8 +5616,19 @@ function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
5553
5616
  const fieldId = `field-${field.field_name}`;
5554
5617
  const fieldName = field.field_name;
5555
5618
  const arrayValue = normalizeStructuredArrayValue(value);
5619
+ const arrayTitle = opts.itemLabel || field.field_label || "Items";
5620
+ const hasItemTitle = typeof opts.itemTitle === "string" && opts.itemTitle.trim() !== "";
5621
+ const arrayItemTitle = hasItemTitle ? opts.itemTitle.trim() : "Item";
5622
+ const addItemLabel = hasItemTitle ? `Add ${arrayItemTitle}` : "Add item";
5556
5623
  const items = arrayValue.map(
5557
- (itemValue, index) => renderStructuredArrayItem(field, itemsConfig, String(index), itemValue, pluginStatuses)
5624
+ (itemValue, index) => renderStructuredArrayItem(
5625
+ field,
5626
+ itemsConfig,
5627
+ String(index),
5628
+ itemValue,
5629
+ pluginStatuses,
5630
+ arrayItemTitle
5631
+ )
5558
5632
  ).join("");
5559
5633
  const emptyState = arrayValue.length === 0 ? `
5560
5634
  <div class="rounded-lg border border-dashed border-zinc-200 dark:border-white/10 px-4 py-6 text-center text-sm text-zinc-500 dark:text-zinc-400" data-structured-empty>
@@ -5567,14 +5641,14 @@ function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
5567
5641
 
5568
5642
  <div class="flex items-center justify-between gap-3">
5569
5643
  <div class="text-sm text-zinc-500 dark:text-zinc-400">
5570
- ${escapeHtml3(opts.itemLabel || "Items")}
5644
+ ${escapeHtml3(arrayTitle)}
5571
5645
  </div>
5572
5646
  <button
5573
5647
  type="button"
5574
5648
  data-action="add-item"
5575
5649
  class="inline-flex items-center justify-center rounded-lg bg-zinc-900 px-3 py-2 text-sm font-semibold text-white hover:bg-zinc-800 dark:bg-white/10 dark:hover:bg-white/20"
5576
5650
  >
5577
- Add item
5651
+ ${escapeHtml3(addItemLabel)}
5578
5652
  </button>
5579
5653
  </div>
5580
5654
 
@@ -5583,14 +5657,21 @@ function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
5583
5657
  </div>
5584
5658
 
5585
5659
  <template data-structured-array-template>
5586
- ${renderStructuredArrayItem(field, itemsConfig, "__INDEX__", {}, pluginStatuses)}
5660
+ ${renderStructuredArrayItem(
5661
+ field,
5662
+ itemsConfig,
5663
+ "__INDEX__",
5664
+ {},
5665
+ pluginStatuses,
5666
+ arrayItemTitle
5667
+ )}
5587
5668
  </template>
5588
5669
  </div>
5589
5670
  ${getDragSortableScript()}
5590
5671
  ${getStructuredFieldScript()}
5591
5672
  `;
5592
5673
  }
5593
- function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginStatuses) {
5674
+ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginStatuses, arrayItemTitle) {
5594
5675
  const itemFields = renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses);
5595
5676
  return `
5596
5677
  <div class="structured-array-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-white/5 p-4 shadow-sm" data-array-index="${escapeHtml3(index)}" draggable="true">
@@ -5601,8 +5682,8 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
5601
5682
  <path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
5602
5683
  </svg>
5603
5684
  </div>
5604
- <div class="text-sm font-semibold text-zinc-900 dark:text-white">
5605
- Item <span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-array-order-label></span>
5685
+ <div class="text-sm font-semibold text-zinc-900 dark:text-white cursor-pointer" data-action="toggle-item">
5686
+ ${escapeHtml3(arrayItemTitle)} <span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-array-order-label></span>
5606
5687
  </div>
5607
5688
  </div>
5608
5689
  <div class="flex flex-wrap gap-2 text-xs">
@@ -5958,8 +6039,15 @@ function getStructuredFieldScript() {
5958
6039
  if (!item || !list) return;
5959
6040
 
5960
6041
  if (action === 'remove-item') {
5961
- item.remove();
5962
- updateHiddenInput();
6042
+ if (typeof requestRepeaterDelete === 'function') {
6043
+ requestRepeaterDelete(() => {
6044
+ item.remove();
6045
+ updateHiddenInput();
6046
+ });
6047
+ } else {
6048
+ item.remove();
6049
+ updateHiddenInput();
6050
+ }
5963
6051
  return;
5964
6052
  }
5965
6053
 
@@ -6150,8 +6238,15 @@ function getBlocksFieldScript() {
6150
6238
  if (!item || !list) return;
6151
6239
 
6152
6240
  if (action === 'remove-block') {
6153
- item.remove();
6154
- updateHiddenInput();
6241
+ if (typeof requestRepeaterDelete === 'function') {
6242
+ requestRepeaterDelete(() => {
6243
+ item.remove();
6244
+ updateHiddenInput();
6245
+ }, 'block');
6246
+ } else {
6247
+ item.remove();
6248
+ updateHiddenInput();
6249
+ }
6155
6250
  return;
6156
6251
  }
6157
6252
 
@@ -6560,6 +6655,28 @@ function renderContentFormPage(data) {
6560
6655
  onConfirm: `performDeleteContent('${data.id}')`
6561
6656
  })}
6562
6657
 
6658
+ ${renderConfirmationDialog({
6659
+ id: "delete-repeater-item-confirm",
6660
+ title: "Delete Item",
6661
+ message: "Are you sure you want to delete this item? This action cannot be undone.",
6662
+ confirmText: "Delete",
6663
+ cancelText: "Cancel",
6664
+ iconColor: "red",
6665
+ confirmClass: "bg-red-500 hover:bg-red-400",
6666
+ onConfirm: "performRepeaterDelete()"
6667
+ })}
6668
+
6669
+ ${renderConfirmationDialog({
6670
+ id: "delete-block-confirm",
6671
+ title: "Delete Block",
6672
+ message: "Are you sure you want to delete this block? This action cannot be undone.",
6673
+ confirmText: "Delete",
6674
+ cancelText: "Cancel",
6675
+ iconColor: "red",
6676
+ confirmClass: "bg-red-500 hover:bg-red-400",
6677
+ onConfirm: "performRepeaterDelete()"
6678
+ })}
6679
+
6563
6680
  ${getConfirmationDialogScript()}
6564
6681
 
6565
6682
  ${data.tinymceEnabled ? getTinyMCEScript(data.tinymceSettings?.apiKey) : "<!-- TinyMCE plugin not active -->"}
@@ -7116,6 +7233,29 @@ function renderContentFormPage(data) {
7116
7233
  });
7117
7234
  }
7118
7235
 
7236
+ // Repeater/blocks delete confirmation
7237
+ let pendingRepeaterDelete = null;
7238
+ function requestRepeaterDelete(callback, type = 'item') {
7239
+ pendingRepeaterDelete = callback;
7240
+ if (typeof showConfirmDialog === 'function') {
7241
+ showConfirmDialog(type === 'block' ? 'delete-block-confirm' : 'delete-repeater-item-confirm');
7242
+ return;
7243
+ }
7244
+ if (confirm('Remove this item? This action cannot be undone.')) {
7245
+ if (typeof pendingRepeaterDelete === 'function') {
7246
+ pendingRepeaterDelete();
7247
+ }
7248
+ }
7249
+ pendingRepeaterDelete = null;
7250
+ }
7251
+
7252
+ function performRepeaterDelete() {
7253
+ if (typeof pendingRepeaterDelete === 'function') {
7254
+ pendingRepeaterDelete();
7255
+ }
7256
+ pendingRepeaterDelete = null;
7257
+ }
7258
+
7119
7259
  function showVersionHistory(contentId) {
7120
7260
  // Create and show version history modal
7121
7261
  const modal = document.createElement('div');
@@ -8122,6 +8262,40 @@ function renderContentListPage(data) {
8122
8262
  return renderAdminLayoutCatalyst(layoutData);
8123
8263
  }
8124
8264
 
8265
+ // src/routes/admin-content-field-types.ts
8266
+ function resolveSchemaFieldType(fieldConfig) {
8267
+ if (fieldConfig.type === "slug" || fieldConfig.format === "slug") {
8268
+ return "slug";
8269
+ }
8270
+ if (fieldConfig.type && fieldConfig.type !== "string") {
8271
+ return fieldConfig.type;
8272
+ }
8273
+ if (fieldConfig.format === "richtext") {
8274
+ return "richtext";
8275
+ }
8276
+ if (fieldConfig.format === "media") {
8277
+ return "media";
8278
+ }
8279
+ if (fieldConfig.format === "date-time") {
8280
+ return "date";
8281
+ }
8282
+ if (Array.isArray(fieldConfig.enum)) {
8283
+ return "select";
8284
+ }
8285
+ return fieldConfig.type || "string";
8286
+ }
8287
+ function buildSchemaFieldOptions(fieldConfig) {
8288
+ const fieldOptions = { ...fieldConfig };
8289
+ const resolvedFieldType = resolveSchemaFieldType(fieldConfig);
8290
+ if (resolvedFieldType === "select" && Array.isArray(fieldConfig.enum)) {
8291
+ fieldOptions.options = fieldConfig.enum.map((value, index) => ({
8292
+ value,
8293
+ label: fieldConfig.enumLabels?.[index] || value
8294
+ }));
8295
+ }
8296
+ return fieldOptions;
8297
+ }
8298
+
8125
8299
  // src/routes/admin-content.ts
8126
8300
  var adminContentRoutes = new Hono();
8127
8301
  function parseFieldValue(field, formData, options = {}) {
@@ -8254,17 +8428,11 @@ async function getCollectionFields(db, collectionId) {
8254
8428
  if (schema && schema.properties) {
8255
8429
  let fieldOrder = 0;
8256
8430
  return Object.entries(schema.properties).map(([fieldName, fieldConfig]) => {
8257
- let fieldOptions = { ...fieldConfig };
8258
- if (fieldConfig.type === "select" && fieldConfig.enum) {
8259
- fieldOptions.options = fieldConfig.enum.map((value, index) => ({
8260
- value,
8261
- label: fieldConfig.enumLabels?.[index] || value
8262
- }));
8263
- }
8431
+ const fieldOptions = buildSchemaFieldOptions(fieldConfig);
8264
8432
  return {
8265
8433
  id: `schema-${fieldName}`,
8266
8434
  field_name: fieldName,
8267
- field_type: fieldConfig.type || "string",
8435
+ field_type: resolveSchemaFieldType(fieldConfig),
8268
8436
  field_label: fieldConfig.title || fieldName,
8269
8437
  field_options: fieldOptions,
8270
8438
  field_order: fieldOrder++,
@@ -20662,6 +20830,20 @@ router.get("/system-status", async (c) => {
20662
20830
  }
20663
20831
  });
20664
20832
 
20833
+ // src/routes/admin-collections-field-types.ts
20834
+ function isMarkdownEditorType(fieldType) {
20835
+ return fieldType === "markdown" || fieldType === "mdxeditor" || fieldType === "easymde";
20836
+ }
20837
+ function normalizeFieldType(fieldType) {
20838
+ if (isMarkdownEditorType(fieldType)) {
20839
+ return "markdown";
20840
+ }
20841
+ if (fieldType === "tinymce") {
20842
+ return "richtext";
20843
+ }
20844
+ return fieldType;
20845
+ }
20846
+
20665
20847
  // src/templates/pages/admin-collections-list.template.ts
20666
20848
  init_admin_layout_catalyst_template();
20667
20849
 
@@ -21148,7 +21330,9 @@ function getFieldTypeBadge(fieldType) {
21148
21330
  "slug": "URL Slug",
21149
21331
  "richtext": "Rich Text (TinyMCE)",
21150
21332
  "quill": "Rich Text (Quill)",
21151
- "mdxeditor": "EasyMDX",
21333
+ "markdown": "Markdown",
21334
+ "mdxeditor": "Markdown",
21335
+ "easymde": "Markdown",
21152
21336
  "number": "Number",
21153
21337
  "boolean": "Boolean",
21154
21338
  "date": "Date",
@@ -21161,7 +21345,9 @@ function getFieldTypeBadge(fieldType) {
21161
21345
  "slug": "bg-sky-500/10 dark:bg-sky-400/10 text-sky-700 dark:text-sky-300 ring-sky-500/20 dark:ring-sky-400/20",
21162
21346
  "richtext": "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 ring-purple-500/20 dark:ring-purple-400/20",
21163
21347
  "quill": "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 ring-purple-500/20 dark:ring-purple-400/20",
21348
+ "markdown": "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 ring-purple-500/20 dark:ring-purple-400/20",
21164
21349
  "mdxeditor": "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 ring-purple-500/20 dark:ring-purple-400/20",
21350
+ "easymde": "bg-purple-500/10 dark:bg-purple-400/10 text-purple-700 dark:text-purple-300 ring-purple-500/20 dark:ring-purple-400/20",
21165
21351
  "number": "bg-green-500/10 dark:bg-green-400/10 text-green-700 dark:text-green-300 ring-green-500/20 dark:ring-green-400/20",
21166
21352
  "boolean": "bg-amber-500/10 dark:bg-amber-400/10 text-amber-700 dark:text-amber-300 ring-amber-500/20 dark:ring-amber-400/20",
21167
21353
  "date": "bg-cyan-500/10 dark:bg-cyan-400/10 text-cyan-700 dark:text-cyan-300 ring-cyan-500/20 dark:ring-cyan-400/20",
@@ -21642,10 +21828,11 @@ function renderCollectionFormPage(data) {
21642
21828
  <option value="slug">URL Slug</option>
21643
21829
  ${data.editorPlugins?.tinymce ? '<option value="richtext">Rich Text (TinyMCE)</option>' : ""}
21644
21830
  ${data.editorPlugins?.quill ? '<option value="quill">Rich Text (Quill)</option>' : ""}
21645
- ${data.editorPlugins?.easyMdx ? '<option value="mdxeditor">EasyMDX</option>' : ""}
21831
+ ${data.editorPlugins?.easyMdx ? '<option value="markdown">Markdown</option>' : ""}
21646
21832
  <option value="number">Number</option>
21647
21833
  <option value="boolean">Boolean</option>
21648
21834
  <option value="date">Date</option>
21835
+ <option value="radio">Radio</option>
21649
21836
  <option value="select">Select</option>
21650
21837
  <option value="media">Media</option>
21651
21838
  <option value="reference">Reference</option>
@@ -21866,7 +22053,7 @@ function renderCollectionFormPage(data) {
21866
22053
  // Check if it's a schema field with field_options that might indicate the actual type
21867
22054
  if (field.field_options && typeof field.field_options === 'object') {
21868
22055
  // Only convert to richtext if type is explicitly 'string' and format is richtext
21869
- // Don't convert if it's already a specific editor type like 'mdxeditor', 'quill', etc.
22056
+ // Don't convert if it's already a specific editor type like 'markdown', 'quill', etc.
21870
22057
  if (field.field_options.format === 'richtext' && uiFieldType === 'string') {
21871
22058
  uiFieldType = 'richtext';
21872
22059
  }
@@ -21887,6 +22074,12 @@ function renderCollectionFormPage(data) {
21887
22074
  uiFieldType = typeMapping[uiFieldType];
21888
22075
  }
21889
22076
 
22077
+ if (uiFieldType === 'mdxeditor' || uiFieldType === 'easymde') {
22078
+ uiFieldType = 'markdown';
22079
+ } else if (uiFieldType === 'tinymce') {
22080
+ uiFieldType = 'richtext';
22081
+ }
22082
+
21890
22083
  // Log all available options
21891
22084
  const availableOptions = Array.from(fieldTypeSelect.options).map(opt => ({ value: opt.value, text: opt.text }));
21892
22085
  console.log('Available dropdown options:', availableOptions);
@@ -21973,7 +22166,7 @@ function renderCollectionFormPage(data) {
21973
22166
 
21974
22167
  console.log('[Edit Field] Showing options for field type:', fieldType, '(original:', field.field_type, ')');
21975
22168
 
21976
- if (['select', 'media', 'richtext', 'reference'].includes(fieldType)) {
22169
+ if (['select', 'radio', 'media', 'richtext', 'markdown', 'reference'].includes(fieldType)) {
21977
22170
  optionsContainer.classList.remove('hidden');
21978
22171
 
21979
22172
  // Set help text based on type
@@ -21981,12 +22174,18 @@ function renderCollectionFormPage(data) {
21981
22174
  case 'select':
21982
22175
  helpText.textContent = 'Create a dropdown select field with custom options';
21983
22176
  break;
22177
+ case 'radio':
22178
+ helpText.textContent = 'Single selection from a list of radio options';
22179
+ break;
21984
22180
  case 'media':
21985
22181
  helpText.textContent = 'Upload and manage media files (images, videos, documents)';
21986
22182
  break;
21987
22183
  case 'richtext':
21988
22184
  helpText.textContent = 'Full-featured WYSIWYG text editor with formatting options';
21989
22185
  break;
22186
+ case 'markdown':
22187
+ helpText.textContent = 'Markdown editor with live preview powered by the EasyMDE plugin';
22188
+ break;
21990
22189
  case 'reference':
21991
22190
  helpText.textContent = 'Link to content from other collections';
21992
22191
  break;
@@ -22127,7 +22326,7 @@ function renderCollectionFormPage(data) {
22127
22326
  const fieldNameInput = document.getElementById('modal-field-name');
22128
22327
 
22129
22328
  // Show/hide options based on field type
22130
- if (['select', 'media', 'richtext', 'guid', 'reference'].includes(this.value)) {
22329
+ if (['select', 'radio', 'media', 'richtext', 'markdown', 'guid', 'reference'].includes(this.value)) {
22131
22330
  optionsContainer.classList.remove('hidden');
22132
22331
 
22133
22332
  // Set default options and help text based on type
@@ -22136,6 +22335,10 @@ function renderCollectionFormPage(data) {
22136
22335
  fieldOptions.value = '{"options": ["Option 1", "Option 2"], "multiple": false}';
22137
22336
  helpText.textContent = 'Create a dropdown select field with custom options';
22138
22337
  break;
22338
+ case 'radio':
22339
+ fieldOptions.value = '{"enum": ["Option 1", "Option 2"], "enumLabels": ["Option 1", "Option 2"], "default": "Option 1", "inline": false}';
22340
+ helpText.textContent = 'Single selection from a list of radio options';
22341
+ break;
22139
22342
  case 'media':
22140
22343
  fieldOptions.value = '{"accept": "image/*", "maxSize": "10MB"}';
22141
22344
  helpText.textContent = 'Upload and manage media files (images, videos, documents)';
@@ -22144,6 +22347,10 @@ function renderCollectionFormPage(data) {
22144
22347
  fieldOptions.value = '{"toolbar": "full", "height": 400}';
22145
22348
  helpText.textContent = 'Full-featured WYSIWYG text editor with formatting options';
22146
22349
  break;
22350
+ case 'markdown':
22351
+ fieldOptions.value = '{"toolbar": "full", "height": 400}';
22352
+ helpText.textContent = 'Markdown editor with live preview powered by the EasyMDE plugin';
22353
+ break;
22147
22354
  case 'reference':
22148
22355
  fieldOptions.value = '{"collection": ["pages", "posts"]}';
22149
22356
  helpText.textContent = 'Link to content from other collections';
@@ -22704,22 +22911,28 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
22704
22911
  searchable: isSearchable,
22705
22912
  ...parsedOptions
22706
22913
  };
22707
- if (fieldType === "richtext") {
22914
+ const normalizedFieldType = normalizeFieldType(fieldType);
22915
+ if (normalizedFieldType === "richtext") {
22708
22916
  fieldConfig.format = "richtext";
22709
- } else if (fieldType === "date") {
22917
+ } else if (normalizedFieldType === "date") {
22710
22918
  fieldConfig.format = "date-time";
22711
- } else if (fieldType === "select") {
22919
+ } else if (normalizedFieldType === "select") {
22712
22920
  fieldConfig.enum = parsedOptions.options || [];
22921
+ } else if (fieldType === "radio") {
22922
+ fieldConfig.type = "radio";
22923
+ if (!parsedOptions.enum && parsedOptions.options) {
22924
+ fieldConfig.enum = parsedOptions.options;
22925
+ }
22713
22926
  } else if (fieldType === "media") {
22714
22927
  fieldConfig.format = "media";
22715
- } else if (fieldType === "slug") {
22928
+ } else if (normalizedFieldType === "slug") {
22716
22929
  fieldConfig.type = "slug";
22717
22930
  fieldConfig.format = "slug";
22718
- } else if (fieldType === "quill") {
22931
+ } else if (normalizedFieldType === "quill") {
22719
22932
  fieldConfig.type = "quill";
22720
- } else if (fieldType === "mdxeditor") {
22721
- fieldConfig.type = "mdxeditor";
22722
- } else if (fieldType === "reference") {
22933
+ } else if (normalizedFieldType === "markdown") {
22934
+ fieldConfig.type = "markdown";
22935
+ } else if (normalizedFieldType === "reference") {
22723
22936
  fieldConfig.type = "reference";
22724
22937
  }
22725
22938
  schema.properties[fieldName] = fieldConfig;
@@ -27324,6 +27537,33 @@ var public_forms_default = publicFormsRoutes;
27324
27537
 
27325
27538
  // src/templates/pages/admin-api-reference.template.ts
27326
27539
  init_admin_layout_catalyst_template();
27540
+ function renderAuthBadge(auth) {
27541
+ if (auth === true) {
27542
+ return `
27543
+ <span class="shrink-0 inline-flex items-center gap-x-1 rounded-md bg-amber-50 dark:bg-amber-500/10 px-2 py-1 text-xs font-medium text-amber-700 dark:text-amber-300 ring-1 ring-inset ring-amber-700/10 dark:ring-amber-400/20">
27544
+ <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
27545
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
27546
+ </svg>
27547
+ Auth
27548
+ </span>`;
27549
+ }
27550
+ if (auth === false) {
27551
+ return `
27552
+ <span class="shrink-0 inline-flex items-center gap-x-1 rounded-md bg-lime-50 dark:bg-lime-500/10 px-2 py-1 text-xs font-medium text-lime-700 dark:text-lime-300 ring-1 ring-inset ring-lime-700/10 dark:ring-lime-400/20">
27553
+ <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
27554
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
27555
+ </svg>
27556
+ Public
27557
+ </span>`;
27558
+ }
27559
+ return `
27560
+ <span class="shrink-0 inline-flex items-center gap-x-1 rounded-md bg-zinc-50 dark:bg-zinc-500/10 px-2 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400 ring-1 ring-inset ring-zinc-500/10 dark:ring-zinc-400/20">
27561
+ <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
27562
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
27563
+ </svg>
27564
+ Unknown
27565
+ </span>`;
27566
+ }
27327
27567
  function renderAPIReferencePage(data) {
27328
27568
  const endpointsByCategory = data.endpoints.reduce((acc, endpoint) => {
27329
27569
  if (!acc[endpoint.category]) {
@@ -27332,40 +27572,18 @@ function renderAPIReferencePage(data) {
27332
27572
  acc[endpoint.category].push(endpoint);
27333
27573
  return acc;
27334
27574
  }, {});
27335
- const categoryInfo = {
27336
- "Auth": {
27337
- title: "Authentication",
27338
- description: "User authentication and authorization endpoints",
27339
- icon: "\u{1F510}"
27340
- },
27341
- "Content": {
27342
- title: "Content Management",
27343
- description: "Content creation, retrieval, and management",
27344
- icon: "\u{1F4DD}"
27345
- },
27346
- "Media": {
27347
- title: "Media Management",
27348
- description: "File upload, storage, and media operations",
27349
- icon: "\u{1F5BC}\uFE0F"
27350
- },
27351
- "Admin": {
27352
- title: "Admin Interface",
27353
- description: "Administrative panel and management features",
27354
- icon: "\u2699\uFE0F"
27355
- },
27356
- "System": {
27357
- title: "System",
27358
- description: "Health checks and system information",
27359
- icon: "\u{1F527}"
27360
- }
27361
- };
27575
+ const categories = Object.keys(endpointsByCategory);
27576
+ const totalEndpoints = data.endpoints.length;
27577
+ const publicEndpoints = data.endpoints.filter((e) => e.authentication === false).length;
27578
+ const protectedEndpoints = data.endpoints.filter((e) => e.authentication === true).length;
27579
+ const undocumentedCount = data.endpoints.filter((e) => e.documented === false).length;
27362
27580
  const pageContent = `
27363
27581
  <div class="space-y-6">
27364
27582
  <!-- Header -->
27365
27583
  <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between">
27366
27584
  <div>
27367
27585
  <h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">API Reference</h1>
27368
- <p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">Complete documentation of all available API endpoints</p>
27586
+ <p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">Auto-discovered documentation of all registered API endpoints</p>
27369
27587
  </div>
27370
27588
  <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
27371
27589
  <a href="/api" target="_blank" class="inline-flex items-center justify-center gap-x-1.5 rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm">
@@ -27378,29 +27596,35 @@ function renderAPIReferencePage(data) {
27378
27596
  </div>
27379
27597
 
27380
27598
  <!-- Stats -->
27381
- <dl class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
27599
+ <dl class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5">
27382
27600
  <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-6 py-5">
27383
27601
  <dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Total Endpoints</dt>
27384
27602
  <dd class="mt-2 flex items-baseline gap-x-2">
27385
- <span class="text-4xl font-semibold tracking-tight text-zinc-950 dark:text-white">${data.endpoints.length}</span>
27603
+ <span class="text-4xl font-semibold tracking-tight text-zinc-950 dark:text-white">${totalEndpoints}</span>
27386
27604
  </dd>
27387
27605
  </div>
27388
27606
  <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-6 py-5">
27389
27607
  <dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Public Endpoints</dt>
27390
27608
  <dd class="mt-2 flex items-baseline gap-x-2">
27391
- <span class="text-4xl font-semibold tracking-tight text-lime-600 dark:text-lime-400">${data.endpoints.filter((e) => !e.authentication).length}</span>
27609
+ <span class="text-4xl font-semibold tracking-tight text-lime-600 dark:text-lime-400">${publicEndpoints}</span>
27392
27610
  </dd>
27393
27611
  </div>
27394
27612
  <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-6 py-5">
27395
27613
  <dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Protected Endpoints</dt>
27396
27614
  <dd class="mt-2 flex items-baseline gap-x-2">
27397
- <span class="text-4xl font-semibold tracking-tight text-amber-600 dark:text-amber-400">${data.endpoints.filter((e) => e.authentication).length}</span>
27615
+ <span class="text-4xl font-semibold tracking-tight text-amber-600 dark:text-amber-400">${protectedEndpoints}</span>
27398
27616
  </dd>
27399
27617
  </div>
27400
27618
  <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-6 py-5">
27401
27619
  <dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Categories</dt>
27402
27620
  <dd class="mt-2 flex items-baseline gap-x-2">
27403
- <span class="text-4xl font-semibold tracking-tight text-cyan-600 dark:text-cyan-400">${Object.keys(endpointsByCategory).length}</span>
27621
+ <span class="text-4xl font-semibold tracking-tight text-cyan-600 dark:text-cyan-400">${categories.length}</span>
27622
+ </dd>
27623
+ </div>
27624
+ <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-6 py-5">
27625
+ <dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Undocumented</dt>
27626
+ <dd class="mt-2 flex items-baseline gap-x-2">
27627
+ <span class="text-4xl font-semibold tracking-tight ${undocumentedCount > 0 ? "text-zinc-400 dark:text-zinc-500" : "text-lime-600 dark:text-lime-400"}">${undocumentedCount}</span>
27404
27628
  </dd>
27405
27629
  </div>
27406
27630
  </dl>
@@ -27452,9 +27676,11 @@ function renderAPIReferencePage(data) {
27452
27676
  class="col-start-1 row-start-1 w-full appearance-none rounded-lg bg-white dark:bg-zinc-800 py-2 pl-3 pr-8 text-sm text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-zinc-950/10 dark:outline-white/10 *:bg-white dark:*:bg-zinc-800 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-zinc-950 dark:focus:outline-white min-w-[200px]"
27453
27677
  >
27454
27678
  <option value="">All Categories</option>
27455
- ${Object.keys(categoryInfo).map((category) => `
27456
- <option value="${category}">${categoryInfo[category].title}</option>
27457
- `).join("")}
27679
+ ${categories.map((category) => {
27680
+ const info = CATEGORY_INFO[category];
27681
+ const title = info ? info.title : category;
27682
+ return `<option value="${category}">${title}</option>`;
27683
+ }).join("\n ")}
27458
27684
  </select>
27459
27685
  <svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-zinc-500 dark:text-zinc-400 sm:size-4">
27460
27686
  <path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
@@ -27468,7 +27694,7 @@ function renderAPIReferencePage(data) {
27468
27694
  <!-- API Categories -->
27469
27695
  <div class="space-y-6">
27470
27696
  ${Object.entries(endpointsByCategory).map(([category, endpoints]) => {
27471
- const info = categoryInfo[category] || { title: category, description: "", icon: "\u{1F4CB}" };
27697
+ const info = CATEGORY_INFO[category] || { title: category, description: "", icon: "&#x1f4cb;" };
27472
27698
  return `
27473
27699
  <div class="api-category" data-category="${category}">
27474
27700
  <div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 overflow-hidden">
@@ -27502,23 +27728,14 @@ function renderAPIReferencePage(data) {
27502
27728
  <div class="flex-1 min-w-0">
27503
27729
  <div class="flex items-center gap-x-2 mb-2">
27504
27730
  <code class="text-zinc-950 dark:text-white text-sm font-mono font-medium break-all">${endpoint.path}</code>
27505
- ${endpoint.authentication ? `
27506
- <span class="shrink-0 inline-flex items-center gap-x-1 rounded-md bg-amber-50 dark:bg-amber-500/10 px-2 py-1 text-xs font-medium text-amber-700 dark:text-amber-300 ring-1 ring-inset ring-amber-700/10 dark:ring-amber-400/20">
27507
- <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
27508
- <path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
27509
- </svg>
27510
- Auth
27511
- </span>
27512
- ` : `
27513
- <span class="shrink-0 inline-flex items-center gap-x-1 rounded-md bg-lime-50 dark:bg-lime-500/10 px-2 py-1 text-xs font-medium text-lime-700 dark:text-lime-300 ring-1 ring-inset ring-lime-700/10 dark:ring-lime-400/20">
27514
- <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
27515
- <path stroke-linecap="round" stroke-linejoin="round" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
27516
- </svg>
27517
- Public
27731
+ ${renderAuthBadge(endpoint.authentication)}
27732
+ ${endpoint.documented === false ? `
27733
+ <span class="shrink-0 inline-flex items-center rounded-md bg-zinc-50 dark:bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-400 dark:text-zinc-500 ring-1 ring-inset ring-zinc-200 dark:ring-zinc-700">
27734
+ Auto-discovered
27518
27735
  </span>
27519
- `}
27736
+ ` : ""}
27520
27737
  </div>
27521
- <p class="text-zinc-600 dark:text-zinc-400 text-sm leading-6">${endpoint.description}</p>
27738
+ <p class="text-zinc-600 dark:text-zinc-400 text-sm leading-6">${endpoint.description || '<em class="text-zinc-400 dark:text-zinc-500">No description available</em>'}</p>
27522
27739
  </div>
27523
27740
  </div>
27524
27741
  </div>
@@ -27596,8 +27813,8 @@ function renderAPIReferencePage(data) {
27596
27813
  const path = endpoint.dataset.path.toLowerCase();
27597
27814
  const description = endpoint.dataset.description.toLowerCase();
27598
27815
 
27599
- const matchesSearch = !searchTerm ||
27600
- path.includes(searchTerm) ||
27816
+ const matchesSearch = !searchTerm ||
27817
+ path.includes(searchTerm) ||
27601
27818
  description.includes(searchTerm);
27602
27819
  const matchesMethod = !selectedMethod || method === selectedMethod;
27603
27820
 
@@ -27657,207 +27874,13 @@ function renderAPIReferencePage(data) {
27657
27874
  var VERSION2 = getCoreVersion();
27658
27875
  var router2 = new Hono();
27659
27876
  router2.use("*", requireAuth());
27660
- var apiEndpoints = [
27661
- // Auth endpoints
27662
- {
27663
- method: "POST",
27664
- path: "/auth/login",
27665
- description: "Authenticate user with email and password",
27666
- authentication: false,
27667
- category: "Auth"
27668
- },
27669
- {
27670
- method: "POST",
27671
- path: "/auth/register",
27672
- description: "Register a new user account",
27673
- authentication: false,
27674
- category: "Auth"
27675
- },
27676
- {
27677
- method: "POST",
27678
- path: "/auth/logout",
27679
- description: "Log out the current user and invalidate session",
27680
- authentication: true,
27681
- category: "Auth"
27682
- },
27683
- {
27684
- method: "GET",
27685
- path: "/auth/me",
27686
- description: "Get current authenticated user information",
27687
- authentication: true,
27688
- category: "Auth"
27689
- },
27690
- {
27691
- method: "POST",
27692
- path: "/auth/refresh",
27693
- description: "Refresh authentication token",
27694
- authentication: true,
27695
- category: "Auth"
27696
- },
27697
- // Content endpoints
27698
- {
27699
- method: "GET",
27700
- path: "/api/collections",
27701
- description: "List all available collections",
27702
- authentication: false,
27703
- category: "Content"
27704
- },
27705
- {
27706
- method: "GET",
27707
- path: "/api/collections/:collection/content",
27708
- description: "Get all content items from a specific collection",
27709
- authentication: false,
27710
- category: "Content"
27711
- },
27712
- {
27713
- method: "GET",
27714
- path: "/api/content/:id",
27715
- description: "Get a specific content item by ID",
27716
- authentication: false,
27717
- category: "Content"
27718
- },
27719
- {
27720
- method: "POST",
27721
- path: "/api/content",
27722
- description: "Create a new content item",
27723
- authentication: true,
27724
- category: "Content"
27725
- },
27726
- {
27727
- method: "PUT",
27728
- path: "/api/content/:id",
27729
- description: "Update an existing content item",
27730
- authentication: true,
27731
- category: "Content"
27732
- },
27733
- {
27734
- method: "DELETE",
27735
- path: "/api/content/:id",
27736
- description: "Delete a content item",
27737
- authentication: true,
27738
- category: "Content"
27739
- },
27740
- // Media endpoints
27741
- {
27742
- method: "GET",
27743
- path: "/api/media",
27744
- description: "List all media files with pagination",
27745
- authentication: false,
27746
- category: "Media"
27747
- },
27748
- {
27749
- method: "GET",
27750
- path: "/api/media/:id",
27751
- description: "Get a specific media file by ID",
27752
- authentication: false,
27753
- category: "Media"
27754
- },
27755
- {
27756
- method: "POST",
27757
- path: "/api/media/upload",
27758
- description: "Upload a new media file to R2 storage",
27759
- authentication: true,
27760
- category: "Media"
27761
- },
27762
- {
27763
- method: "DELETE",
27764
- path: "/api/media/:id",
27765
- description: "Delete a media file from storage",
27766
- authentication: true,
27767
- category: "Media"
27768
- },
27769
- // Admin endpoints
27770
- {
27771
- method: "GET",
27772
- path: "/admin/api/stats",
27773
- description: "Get dashboard statistics (collections, content, media, users)",
27774
- authentication: true,
27775
- category: "Admin"
27776
- },
27777
- {
27778
- method: "GET",
27779
- path: "/admin/api/storage",
27780
- description: "Get storage usage information",
27781
- authentication: true,
27782
- category: "Admin"
27783
- },
27784
- {
27785
- method: "GET",
27786
- path: "/admin/api/activity",
27787
- description: "Get recent activity logs",
27788
- authentication: true,
27789
- category: "Admin"
27790
- },
27791
- {
27792
- method: "GET",
27793
- path: "/admin/api/collections",
27794
- description: "List all collections with field counts",
27795
- authentication: true,
27796
- category: "Admin"
27797
- },
27798
- {
27799
- method: "POST",
27800
- path: "/admin/api/collections",
27801
- description: "Create a new collection",
27802
- authentication: true,
27803
- category: "Admin"
27804
- },
27805
- {
27806
- method: "PATCH",
27807
- path: "/admin/api/collections/:id",
27808
- description: "Update an existing collection",
27809
- authentication: true,
27810
- category: "Admin"
27811
- },
27812
- {
27813
- method: "DELETE",
27814
- path: "/admin/api/collections/:id",
27815
- description: "Delete a collection (must be empty)",
27816
- authentication: true,
27817
- category: "Admin"
27818
- },
27819
- {
27820
- method: "GET",
27821
- path: "/admin/api/migrations/status",
27822
- description: "Get database migration status",
27823
- authentication: true,
27824
- category: "Admin"
27825
- },
27826
- {
27827
- method: "POST",
27828
- path: "/admin/api/migrations/run",
27829
- description: "Run pending database migrations",
27830
- authentication: true,
27831
- category: "Admin"
27832
- },
27833
- // System endpoints
27834
- {
27835
- method: "GET",
27836
- path: "/health",
27837
- description: "Health check endpoint for monitoring",
27838
- authentication: false,
27839
- category: "System"
27840
- },
27841
- {
27842
- method: "GET",
27843
- path: "/api/health",
27844
- description: "API health check with schema information",
27845
- authentication: false,
27846
- category: "System"
27847
- },
27848
- {
27849
- method: "GET",
27850
- path: "/api",
27851
- description: "API root - returns API information and OpenAPI spec",
27852
- authentication: false,
27853
- category: "System"
27854
- }
27855
- ];
27856
27877
  router2.get("/", async (c) => {
27857
27878
  const user = c.get("user");
27858
27879
  try {
27880
+ const app2 = getAppInstance();
27881
+ const endpoints = buildRouteList(app2);
27859
27882
  const pageData = {
27860
- endpoints: apiEndpoints,
27883
+ endpoints,
27861
27884
  user: user ? {
27862
27885
  name: user.email.split("@")[0] || user.email,
27863
27886
  email: user.email,
@@ -27913,5 +27936,5 @@ var ROUTES_INFO = {
27913
27936
  };
27914
27937
 
27915
27938
  export { ROUTES_INFO, adminCheckboxRoutes, adminCollectionsRoutes, adminDesignRoutes, adminFormsRoutes, 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, getConfirmationDialogScript2 as getConfirmationDialogScript, public_forms_default, renderConfirmationDialog2 as renderConfirmationDialog, router, router2, test_cleanup_default, userRoutes };
27916
- //# sourceMappingURL=chunk-4Z5BQZT6.js.map
27917
- //# sourceMappingURL=chunk-4Z5BQZT6.js.map
27939
+ //# sourceMappingURL=chunk-2JGQKF7B.js.map
27940
+ //# sourceMappingURL=chunk-2JGQKF7B.js.map