@sonicjs-cms/core 2.8.3 → 2.10.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.
- package/dist/{app-DnQ26Lho.d.cts → app-Ozl9agJG.d.cts} +1 -1
- package/dist/{app-DnQ26Lho.d.ts → app-Ozl9agJG.d.ts} +1 -1
- package/dist/{chunk-YFJJU26H.js → chunk-27AOVQTR.js} +10 -2
- package/dist/chunk-27AOVQTR.js.map +1 -0
- package/dist/{chunk-Y3VMEGY2.js → chunk-4TTMQQC7.js} +4 -4
- package/dist/{chunk-Y3VMEGY2.js.map → chunk-4TTMQQC7.js.map} +1 -1
- package/dist/{chunk-VNLR35GO.cjs → chunk-64APW3DW.cjs} +339 -2
- package/dist/chunk-64APW3DW.cjs.map +1 -0
- package/dist/{chunk-GTFMI24U.js → chunk-6O3RJV3C.js} +2 -2
- package/dist/{chunk-GTFMI24U.js.map → chunk-6O3RJV3C.js.map} +1 -1
- package/dist/{chunk-G44QUVNM.js → chunk-7JMMLHPQ.js} +337 -4
- package/dist/chunk-7JMMLHPQ.js.map +1 -0
- package/dist/chunk-CJYFSKH7.js +54 -54
- package/dist/chunk-CJYFSKH7.js.map +1 -1
- package/dist/{chunk-JDIM5AG7.cjs → chunk-EKPLKUZT.cjs} +11 -5
- package/dist/chunk-EKPLKUZT.cjs.map +1 -0
- package/dist/{chunk-MPT5PA6U.cjs → chunk-IIBRG5S5.cjs} +10 -2
- package/dist/chunk-IIBRG5S5.cjs.map +1 -0
- package/dist/{chunk-K4Q4SFJJ.cjs → chunk-IT2TC4ZD.cjs} +7 -7
- package/dist/{chunk-K4Q4SFJJ.cjs.map → chunk-IT2TC4ZD.cjs.map} +1 -1
- package/dist/{chunk-5XAI2XUF.js → chunk-IZWNIUJI.js} +11 -5
- package/dist/chunk-IZWNIUJI.js.map +1 -0
- package/dist/{chunk-CH5UHZVM.js → chunk-JTNUM7JE.js} +1218 -442
- package/dist/chunk-JTNUM7JE.js.map +1 -0
- package/dist/chunk-MNFY6DWY.cjs +54 -54
- package/dist/chunk-MNFY6DWY.cjs.map +1 -1
- package/dist/{chunk-R4WR3VTN.cjs → chunk-RCA6R6VE.cjs} +1329 -553
- package/dist/chunk-RCA6R6VE.cjs.map +1 -0
- package/dist/{chunk-HXHVU5GM.cjs → chunk-ZMVWMJ3S.cjs} +2 -2
- package/dist/{chunk-HXHVU5GM.cjs.map → chunk-ZMVWMJ3S.cjs.map} +1 -1
- package/dist/{collection-config-i8EaAF7z.d.cts → collection-config-B4PG-AaF.d.cts} +4 -2
- package/dist/{collection-config-i8EaAF7z.d.ts → collection-config-B4PG-AaF.d.ts} +4 -2
- package/dist/{filter-bar.template-Daw8ZDoq.d.cts → filter-bar.template-DlVYMk-T.d.cts} +1 -1
- package/dist/{filter-bar.template-Daw8ZDoq.d.ts → filter-bar.template-DlVYMk-T.d.ts} +1 -1
- package/dist/index.cjs +142 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +11 -10
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +29 -29
- package/dist/middleware.d.cts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +3 -3
- package/dist/migrations-N2C2VPJU.js +4 -0
- package/dist/{migrations-KHWFJ2HN.js.map → migrations-N2C2VPJU.js.map} +1 -1
- package/dist/migrations-ONIAY6GK.cjs +13 -0
- package/dist/{migrations-7X4RPH7O.cjs.map → migrations-ONIAY6GK.cjs.map} +1 -1
- package/dist/{plugin-zvZpaiP5.d.cts → plugin-0Xogrln-.d.cts} +1 -1
- package/dist/{plugin-zvZpaiP5.d.ts → plugin-0Xogrln-.d.ts} +1 -1
- package/dist/{plugin-bootstrap-CJozpgmI.d.cts → plugin-bootstrap-WmpvYM5w.d.ts} +2 -2
- package/dist/{plugin-bootstrap-DU5VmuHZ.d.ts → plugin-bootstrap-fpG98Otb.d.cts} +2 -2
- package/dist/{plugin-manager-Baa6xXqB.d.ts → plugin-manager-Clf2gXwj.d.ts} +2 -2
- package/dist/{plugin-manager-vBal9Zip.d.cts → plugin-manager-GcIeb226.d.cts} +2 -2
- package/dist/plugins.d.cts +2 -2
- package/dist/plugins.d.ts +2 -2
- package/dist/routes.cjs +29 -29
- package/dist/routes.d.cts +1 -1
- package/dist/routes.d.ts +1 -1
- package/dist/routes.js +6 -6
- package/dist/services.cjs +44 -28
- package/dist/services.d.cts +29 -4
- package/dist/services.d.ts +29 -4
- package/dist/services.js +3 -3
- package/dist/{telemetry-UiD1i9GS.d.cts → telemetry-B9vIV4wh.d.cts} +1 -1
- package/dist/{telemetry-UiD1i9GS.d.ts → telemetry-B9vIV4wh.d.ts} +1 -1
- package/dist/templates.d.cts +1 -1
- package/dist/templates.d.ts +1 -1
- package/dist/types.d.cts +3 -3
- package/dist/types.d.ts +3 -3
- package/dist/utils.cjs +11 -11
- package/dist/utils.d.cts +3 -3
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +1 -1
- package/dist/{version-C_CXrN_T.d.cts → version-ChpccWQ1.d.cts} +1 -1
- package/dist/{version-C_CXrN_T.d.ts → version-ChpccWQ1.d.ts} +1 -1
- package/package.json +9 -3
- package/dist/chunk-5XAI2XUF.js.map +0 -1
- package/dist/chunk-CH5UHZVM.js.map +0 -1
- package/dist/chunk-G44QUVNM.js.map +0 -1
- package/dist/chunk-JDIM5AG7.cjs.map +0 -1
- package/dist/chunk-MPT5PA6U.cjs.map +0 -1
- package/dist/chunk-R4WR3VTN.cjs.map +0 -1
- package/dist/chunk-VNLR35GO.cjs.map +0 -1
- package/dist/chunk-YFJJU26H.js.map +0 -1
- package/dist/migrations-7X4RPH7O.cjs +0 -13
- package/dist/migrations-KHWFJ2HN.js +0 -4
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService } from './chunk-
|
|
2
|
-
import { requireAuth, isPluginActive, optionalAuth,
|
|
3
|
-
import { PluginService } from './chunk-
|
|
4
|
-
import { MigrationService } from './chunk-
|
|
1
|
+
import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService, getAppInstance, buildRouteList, CATEGORY_INFO } from './chunk-7JMMLHPQ.js';
|
|
2
|
+
import { requireAuth, requireRole, isPluginActive, optionalAuth, rateLimit, AuthManager, logActivity, generateCsrfToken } from './chunk-4TTMQQC7.js';
|
|
3
|
+
import { PluginService } from './chunk-27AOVQTR.js';
|
|
4
|
+
import { MigrationService } from './chunk-6O3RJV3C.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-
|
|
7
|
+
import { QueryFilterBuilder, getCoreVersion, getBlocksFieldConfig, parseBlocksValue } from './chunk-IZWNIUJI.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';
|
|
@@ -119,7 +119,7 @@ apiContentCrudRoutes.get("/:id", async (c) => {
|
|
|
119
119
|
}, 500);
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
|
-
apiContentCrudRoutes.post("/", requireAuth(), async (c) => {
|
|
122
|
+
apiContentCrudRoutes.post("/", requireAuth(), requireRole(["admin", "editor", "author"]), async (c) => {
|
|
123
123
|
try {
|
|
124
124
|
const db = c.env.DB;
|
|
125
125
|
const user = c.get("user");
|
|
@@ -185,7 +185,7 @@ apiContentCrudRoutes.post("/", requireAuth(), async (c) => {
|
|
|
185
185
|
}, 500);
|
|
186
186
|
}
|
|
187
187
|
});
|
|
188
|
-
apiContentCrudRoutes.put("/:id", requireAuth(), async (c) => {
|
|
188
|
+
apiContentCrudRoutes.put("/:id", requireAuth(), requireRole(["admin", "editor", "author"]), async (c) => {
|
|
189
189
|
try {
|
|
190
190
|
const id = c.req.param("id");
|
|
191
191
|
const db = c.env.DB;
|
|
@@ -249,7 +249,7 @@ apiContentCrudRoutes.put("/:id", requireAuth(), async (c) => {
|
|
|
249
249
|
}, 500);
|
|
250
250
|
}
|
|
251
251
|
});
|
|
252
|
-
apiContentCrudRoutes.delete("/:id", requireAuth(), async (c) => {
|
|
252
|
+
apiContentCrudRoutes.delete("/:id", requireAuth(), requireRole(["admin", "editor", "author"]), async (c) => {
|
|
253
253
|
try {
|
|
254
254
|
const id = c.req.param("id");
|
|
255
255
|
const db = c.env.DB;
|
|
@@ -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-
|
|
2284
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-N2C2VPJU.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-
|
|
2309
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-N2C2VPJU.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-
|
|
2328
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-N2C2VPJU.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
|
|
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
|
|
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;
|
|
@@ -4775,6 +4775,39 @@ function getReadFieldValueScript() {
|
|
|
4775
4775
|
window.__sonicReadFieldValueInit = true;
|
|
4776
4776
|
|
|
4777
4777
|
window.sonicReadFieldValue = function(fieldWrapper) {
|
|
4778
|
+
const getDirectChild = (parent, selector) => {
|
|
4779
|
+
if (!(parent instanceof Element)) return null;
|
|
4780
|
+
return Array.from(parent.children).find(
|
|
4781
|
+
(child) => child instanceof Element && child.matches(selector),
|
|
4782
|
+
) || null;
|
|
4783
|
+
};
|
|
4784
|
+
const getDirectStructuredSubfields = (host) =>
|
|
4785
|
+
Array.from(host.children).filter(
|
|
4786
|
+
(child) => child instanceof Element && child.classList.contains('structured-subfield'),
|
|
4787
|
+
);
|
|
4788
|
+
const getStructuredObjectFieldsHost = (container) => {
|
|
4789
|
+
const directFieldsHost = getDirectChild(container, '[data-structured-object-fields]');
|
|
4790
|
+
if (directFieldsHost) return directFieldsHost;
|
|
4791
|
+
const groupContent = getDirectChild(container, '.field-group-content');
|
|
4792
|
+
const nestedFieldsHost = groupContent
|
|
4793
|
+
? getDirectChild(groupContent, '[data-structured-object-fields]')
|
|
4794
|
+
: null;
|
|
4795
|
+
if (nestedFieldsHost) return nestedFieldsHost;
|
|
4796
|
+
return getDirectChild(container, '[data-array-item-fields]') || container;
|
|
4797
|
+
};
|
|
4798
|
+
const getDirectStructuredObject = (fieldWrapper) => {
|
|
4799
|
+
const directObject = getDirectChild(fieldWrapper, '[data-structured-object]');
|
|
4800
|
+
if (directObject) return directObject;
|
|
4801
|
+
const formGroup = getDirectChild(fieldWrapper, '.form-group');
|
|
4802
|
+
return formGroup ? getDirectChild(formGroup, '[data-structured-object]') : null;
|
|
4803
|
+
};
|
|
4804
|
+
const getDirectStructuredArray = (fieldWrapper) => {
|
|
4805
|
+
const directArray = getDirectChild(fieldWrapper, '[data-structured-array]');
|
|
4806
|
+
if (directArray) return directArray;
|
|
4807
|
+
const formGroup = getDirectChild(fieldWrapper, '.form-group');
|
|
4808
|
+
return formGroup ? getDirectChild(formGroup, '[data-structured-array]') : null;
|
|
4809
|
+
};
|
|
4810
|
+
|
|
4778
4811
|
const fieldType = fieldWrapper.dataset.fieldType;
|
|
4779
4812
|
const select = fieldWrapper.querySelector('select');
|
|
4780
4813
|
const textarea = fieldWrapper.querySelector('textarea');
|
|
@@ -4784,7 +4817,47 @@ function getReadFieldValueScript() {
|
|
|
4784
4817
|
const nonHiddenInput = inputs.find((input) => input.type !== 'hidden' && input.type !== 'checkbox');
|
|
4785
4818
|
const hiddenInput = inputs.find((input) => input.type === 'hidden');
|
|
4786
4819
|
|
|
4820
|
+
const readStructuredFieldsHost = (host) => {
|
|
4821
|
+
const fields = getDirectStructuredSubfields(host);
|
|
4822
|
+
if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
|
|
4823
|
+
return window.sonicReadFieldValue(fields[0]);
|
|
4824
|
+
}
|
|
4825
|
+
return fields.reduce((acc, subfield) => {
|
|
4826
|
+
const fieldName = subfield.dataset.structuredField;
|
|
4827
|
+
if (!fieldName || fieldName === '__value') return acc;
|
|
4828
|
+
acc[fieldName] = window.sonicReadFieldValue(subfield);
|
|
4829
|
+
return acc;
|
|
4830
|
+
}, {});
|
|
4831
|
+
};
|
|
4832
|
+
|
|
4833
|
+
const readStructuredObject = () => {
|
|
4834
|
+
const objectContainer = getDirectStructuredObject(fieldWrapper);
|
|
4835
|
+
if (!objectContainer) return null;
|
|
4836
|
+
const host = getStructuredObjectFieldsHost(objectContainer);
|
|
4837
|
+
return readStructuredFieldsHost(host);
|
|
4838
|
+
};
|
|
4839
|
+
|
|
4840
|
+
const readStructuredArray = () => {
|
|
4841
|
+
const arrayContainer = getDirectStructuredArray(fieldWrapper);
|
|
4842
|
+
if (!arrayContainer) return null;
|
|
4843
|
+
const list = arrayContainer.querySelector('[data-structured-array-list]');
|
|
4844
|
+
if (!list) return [];
|
|
4845
|
+
const items = Array.from(list.querySelectorAll(':scope > .structured-array-item'));
|
|
4846
|
+
return items.map((item) => {
|
|
4847
|
+
const host =
|
|
4848
|
+
item.querySelector(':scope > [data-array-item-fields]') ||
|
|
4849
|
+
item.querySelector('[data-array-item-fields]') ||
|
|
4850
|
+
item;
|
|
4851
|
+
return readStructuredFieldsHost(host);
|
|
4852
|
+
});
|
|
4853
|
+
};
|
|
4854
|
+
|
|
4787
4855
|
if (fieldType === 'object' || fieldType === 'array') {
|
|
4856
|
+
const liveValue = fieldType === 'array' ? readStructuredArray() : readStructuredObject();
|
|
4857
|
+
if (liveValue !== null) {
|
|
4858
|
+
return liveValue;
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4788
4861
|
if (!hiddenInput) {
|
|
4789
4862
|
return fieldType === 'array' ? [] : {};
|
|
4790
4863
|
}
|
|
@@ -4833,6 +4906,33 @@ function getReadFieldValueScript() {
|
|
|
4833
4906
|
</script>
|
|
4834
4907
|
`;
|
|
4835
4908
|
}
|
|
4909
|
+
var STRUCTURED_INDEX_TOKEN = "__INDEX__";
|
|
4910
|
+
var BLOCK_INDEX_TOKEN = "__BLOCK_INDEX__";
|
|
4911
|
+
function sanitizeStructuredGroupId(fieldName) {
|
|
4912
|
+
return `object-${fieldName}`.split(BLOCK_INDEX_TOKEN).map(
|
|
4913
|
+
(blockSegment) => blockSegment.split(STRUCTURED_INDEX_TOKEN).map(
|
|
4914
|
+
(segment) => segment.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
|
|
4915
|
+
).join(STRUCTURED_INDEX_TOKEN)
|
|
4916
|
+
).join(BLOCK_INDEX_TOKEN);
|
|
4917
|
+
}
|
|
4918
|
+
function isMarkdownEditorFieldType(fieldType) {
|
|
4919
|
+
return fieldType === "markdown" || fieldType === "mdxeditor" || fieldType === "easymde";
|
|
4920
|
+
}
|
|
4921
|
+
function getEditorMetadata(fieldType) {
|
|
4922
|
+
if (fieldType === "richtext" || fieldType === "tinymce") {
|
|
4923
|
+
return {
|
|
4924
|
+
family: "richtext",
|
|
4925
|
+
provider: "tinymce"
|
|
4926
|
+
};
|
|
4927
|
+
}
|
|
4928
|
+
if (isMarkdownEditorFieldType(fieldType)) {
|
|
4929
|
+
return {
|
|
4930
|
+
family: "markdown",
|
|
4931
|
+
provider: "easymde"
|
|
4932
|
+
};
|
|
4933
|
+
}
|
|
4934
|
+
return null;
|
|
4935
|
+
}
|
|
4836
4936
|
function renderDynamicField(field, options = {}) {
|
|
4837
4937
|
const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {}, collectionId = "", contentId = "" } = options;
|
|
4838
4938
|
const opts = field.field_options || {};
|
|
@@ -4846,10 +4946,10 @@ function renderDynamicField(field, options = {}) {
|
|
|
4846
4946
|
if (field.field_type === "quill" && !pluginStatuses.quillEnabled) {
|
|
4847
4947
|
fallbackToTextarea = true;
|
|
4848
4948
|
fallbackWarning = "\u26A0\uFE0F Quill Editor plugin is inactive. Using textarea fallback.";
|
|
4849
|
-
} else if ((field.field_type
|
|
4949
|
+
} else if (isMarkdownEditorFieldType(field.field_type) && !pluginStatuses.mdxeditorEnabled) {
|
|
4850
4950
|
fallbackToTextarea = true;
|
|
4851
|
-
fallbackWarning = "\u26A0\uFE0F
|
|
4852
|
-
} else if (field.field_type === "tinymce" && !pluginStatuses.tinymceEnabled) {
|
|
4951
|
+
fallbackWarning = "\u26A0\uFE0F Markdown editor plugin is inactive. Using textarea fallback.";
|
|
4952
|
+
} else if ((field.field_type === "richtext" || field.field_type === "tinymce") && !pluginStatuses.tinymceEnabled) {
|
|
4853
4953
|
fallbackToTextarea = true;
|
|
4854
4954
|
fallbackWarning = "\u26A0\uFE0F TinyMCE plugin is inactive. Using textarea fallback.";
|
|
4855
4955
|
}
|
|
@@ -4971,8 +5071,10 @@ function renderDynamicField(field, options = {}) {
|
|
|
4971
5071
|
`;
|
|
4972
5072
|
break;
|
|
4973
5073
|
case "richtext":
|
|
5074
|
+
case "tinymce": {
|
|
5075
|
+
const editorMetadata = getEditorMetadata(field.field_type);
|
|
4974
5076
|
fieldHTML = `
|
|
4975
|
-
<div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}">
|
|
5077
|
+
<div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}" data-editor-family="${editorMetadata?.family || ""}" data-editor-provider="${editorMetadata?.provider || ""}">
|
|
4976
5078
|
<textarea
|
|
4977
5079
|
id="${fieldId}"
|
|
4978
5080
|
name="${fieldName}"
|
|
@@ -4983,6 +5085,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
4983
5085
|
</div>
|
|
4984
5086
|
`;
|
|
4985
5087
|
break;
|
|
5088
|
+
}
|
|
4986
5089
|
case "quill":
|
|
4987
5090
|
fieldHTML = `
|
|
4988
5091
|
<div class="quill-editor-container" data-field-id="${fieldId}">
|
|
@@ -5006,12 +5109,12 @@ function renderDynamicField(field, options = {}) {
|
|
|
5006
5109
|
</div>
|
|
5007
5110
|
`;
|
|
5008
5111
|
break;
|
|
5009
|
-
case "mdxeditor":
|
|
5010
|
-
case "tinymce":
|
|
5011
|
-
case "easymde":
|
|
5012
5112
|
case "markdown":
|
|
5113
|
+
case "mdxeditor":
|
|
5114
|
+
case "easymde": {
|
|
5115
|
+
const editorMetadata = getEditorMetadata(field.field_type);
|
|
5013
5116
|
fieldHTML = `
|
|
5014
|
-
<div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}">
|
|
5117
|
+
<div class="richtext-container" data-height="${opts.height || 300}" data-toolbar="${opts.toolbar || "full"}" data-editor-family="${editorMetadata?.family || ""}" data-editor-provider="${editorMetadata?.provider || ""}">
|
|
5015
5118
|
<textarea
|
|
5016
5119
|
id="${fieldId}"
|
|
5017
5120
|
name="${fieldName}"
|
|
@@ -5022,6 +5125,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5022
5125
|
</div>
|
|
5023
5126
|
`;
|
|
5024
5127
|
break;
|
|
5128
|
+
}
|
|
5025
5129
|
case "number":
|
|
5026
5130
|
fieldHTML = `
|
|
5027
5131
|
<input
|
|
@@ -5391,12 +5495,14 @@ function renderDynamicField(field, options = {}) {
|
|
|
5391
5495
|
|
|
5392
5496
|
${isMultiple ? `
|
|
5393
5497
|
<div class="media-preview-grid grid grid-cols-4 gap-2 mb-2 ${mediaValues.length === 0 ? "hidden" : ""}" id="${fieldId}-preview">
|
|
5394
|
-
${mediaValues.map(
|
|
5498
|
+
${mediaValues.map(
|
|
5499
|
+
(url, idx) => `
|
|
5395
5500
|
<div class="relative media-preview-item" data-url="${url}">
|
|
5396
5501
|
${renderMediaPreview(url, `Media ${idx + 1}`, "w-full h-24 object-cover rounded-lg border border-white/20")}
|
|
5397
5502
|
<button
|
|
5398
5503
|
type="button"
|
|
5399
5504
|
onclick="removeMediaFromMultiple('${fieldId}', '${url}')"
|
|
5505
|
+
data-media-remove="true"
|
|
5400
5506
|
class="absolute top-1 right-1 bg-red-600 text-white rounded-full p-1 hover:bg-red-700"
|
|
5401
5507
|
${disabled ? "disabled" : ""}
|
|
5402
5508
|
>
|
|
@@ -5405,7 +5511,8 @@ function renderDynamicField(field, options = {}) {
|
|
|
5405
5511
|
</svg>
|
|
5406
5512
|
</button>
|
|
5407
5513
|
</div>
|
|
5408
|
-
`
|
|
5514
|
+
`
|
|
5515
|
+
).join("")}
|
|
5409
5516
|
</div>
|
|
5410
5517
|
` : `
|
|
5411
5518
|
<div class="media-preview ${singleValue ? "" : "hidden"}" id="${fieldId}-preview">
|
|
@@ -5429,6 +5536,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5429
5536
|
<button
|
|
5430
5537
|
type="button"
|
|
5431
5538
|
onclick="clearMediaField('${fieldId}')"
|
|
5539
|
+
data-media-remove="true"
|
|
5432
5540
|
class="inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all"
|
|
5433
5541
|
${disabled ? "disabled" : ""}
|
|
5434
5542
|
>
|
|
@@ -5462,7 +5570,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5462
5570
|
}
|
|
5463
5571
|
const showLabel = field.field_type !== "boolean";
|
|
5464
5572
|
return `
|
|
5465
|
-
<div class="form-group">
|
|
5573
|
+
<div class="form-group" data-has-errors="${errors.length > 0 ? "true" : "false"}">
|
|
5466
5574
|
${showLabel ? `
|
|
5467
5575
|
<label for="${fieldId}" class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2">
|
|
5468
5576
|
${escapeHtml3(field.field_label)}
|
|
@@ -5471,7 +5579,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5471
5579
|
` : ""}
|
|
5472
5580
|
${fieldHTML}
|
|
5473
5581
|
${errors.length > 0 ? `
|
|
5474
|
-
<div class="mt-2 text-sm text-pink-600 dark:text-pink-400">
|
|
5582
|
+
<div class="mt-2 text-sm text-pink-600 dark:text-pink-400" data-validation-error-message>
|
|
5475
5583
|
${errors.map((error) => `<div>${escapeHtml3(error)}</div>`).join("")}
|
|
5476
5584
|
</div>
|
|
5477
5585
|
` : ""}
|
|
@@ -5486,8 +5594,8 @@ function renderDynamicField(field, options = {}) {
|
|
|
5486
5594
|
function renderFieldGroup(title, fields, collapsible = false) {
|
|
5487
5595
|
const groupId = title.toLowerCase().replace(/\s+/g, "-");
|
|
5488
5596
|
return `
|
|
5489
|
-
<div class="field-group rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 mb-6">
|
|
5490
|
-
<div class="field-group-header border-b border-zinc-950/5 dark:border-white/10 px-6 py-4 ${collapsible ? "cursor-pointer" : ""}" ${collapsible ? `onclick="toggleFieldGroup(
|
|
5597
|
+
<div class="field-group rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 mb-6" data-group-id="${escapeHtml3(groupId)}">
|
|
5598
|
+
<div class="field-group-header border-b border-zinc-950/5 dark:border-white/10 px-6 py-4 ${collapsible ? "cursor-pointer" : ""}" ${collapsible ? `onclick="toggleFieldGroup(this)"` : ""}>
|
|
5491
5599
|
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
|
|
5492
5600
|
${escapeHtml3(title)}
|
|
5493
5601
|
${collapsible ? `
|
|
@@ -5531,6 +5639,12 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
|
5531
5639
|
>
|
|
5532
5640
|
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(blockValues))}">
|
|
5533
5641
|
|
|
5642
|
+
<div class="flex items-center justify-between border-b border-zinc-950/5 dark:border-white/10 py-4">
|
|
5643
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">
|
|
5644
|
+
${escapeHtml3(field.field_label || "Content Blocks")}
|
|
5645
|
+
</h3>
|
|
5646
|
+
</div>
|
|
5647
|
+
|
|
5534
5648
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5535
5649
|
<div class="flex-1">
|
|
5536
5650
|
<select
|
|
@@ -5561,12 +5675,14 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
|
5561
5675
|
`;
|
|
5562
5676
|
}
|
|
5563
5677
|
function renderStructuredObjectField(field, options, baseClasses, errorClasses) {
|
|
5564
|
-
const { value = {}, pluginStatuses = {} } = options;
|
|
5678
|
+
const { value = {}, pluginStatuses = {}, errors = [] } = options;
|
|
5565
5679
|
const opts = field.field_options || {};
|
|
5566
5680
|
const properties = opts.properties && typeof opts.properties === "object" ? opts.properties : {};
|
|
5567
5681
|
const fieldId = `field-${field.field_name}`;
|
|
5568
5682
|
const fieldName = field.field_name;
|
|
5569
5683
|
const objectValue = normalizeStructuredObjectValue(value);
|
|
5684
|
+
const objectLayout = opts.objectLayout || "nested";
|
|
5685
|
+
const useNestedLayout = objectLayout !== "flat";
|
|
5570
5686
|
const subfields = Object.entries(properties).map(
|
|
5571
5687
|
([propertyName, propertyConfig]) => renderStructuredSubfield(
|
|
5572
5688
|
field,
|
|
@@ -5577,11 +5693,40 @@ function renderStructuredObjectField(field, options, baseClasses, errorClasses)
|
|
|
5577
5693
|
field.field_name
|
|
5578
5694
|
)
|
|
5579
5695
|
).join("");
|
|
5696
|
+
const groupTitle = field.field_label || field.field_name;
|
|
5697
|
+
if (!useNestedLayout) {
|
|
5698
|
+
return `
|
|
5699
|
+
<div class="space-y-4" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
|
|
5700
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
|
|
5701
|
+
<div class="flex items-center justify-between border-b border-zinc-950/5 dark:border-white/10 py-4 first-of-type:pt-0">
|
|
5702
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">
|
|
5703
|
+
${escapeHtml3(groupTitle)}
|
|
5704
|
+
</h3>
|
|
5705
|
+
</div>
|
|
5706
|
+
<div class="space-y-4" data-structured-object-fields>
|
|
5707
|
+
${subfields}
|
|
5708
|
+
</div>
|
|
5709
|
+
</div>
|
|
5710
|
+
${getStructuredFieldScript()}
|
|
5711
|
+
`;
|
|
5712
|
+
}
|
|
5713
|
+
const groupId = sanitizeStructuredGroupId(field.field_name);
|
|
5714
|
+
const isCollapsed = errors.length > 0 ? false : opts.collapsed !== false;
|
|
5580
5715
|
return `
|
|
5581
|
-
<div class="
|
|
5582
|
-
<
|
|
5583
|
-
|
|
5584
|
-
|
|
5716
|
+
<div class="field-group rounded-lg shadow-sm mb-6" data-group-id="${escapeHtml3(groupId)}" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
|
|
5717
|
+
<div class="field-group-header border-b border-zinc-950/5 dark:border-white/10 pr-6 pb-4 cursor-pointer" onclick="toggleFieldGroup(this)">
|
|
5718
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
|
|
5719
|
+
${escapeHtml3(groupTitle)}
|
|
5720
|
+
<svg id="${groupId}-icon" class="w-5 h-5 ml-2 transform transition-transform ${isCollapsed ? "-rotate-90" : ""} text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5721
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
5722
|
+
</svg>
|
|
5723
|
+
</h3>
|
|
5724
|
+
</div>
|
|
5725
|
+
<div id="${groupId}-content" class="field-group-content px-6 py-6 space-y-4 ${isCollapsed ? "hidden" : ""}">
|
|
5726
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
|
|
5727
|
+
<div class="space-y-4" data-structured-object-fields>
|
|
5728
|
+
${subfields}
|
|
5729
|
+
</div>
|
|
5585
5730
|
</div>
|
|
5586
5731
|
</div>
|
|
5587
5732
|
${getStructuredFieldScript()}
|
|
@@ -5652,7 +5797,7 @@ function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
|
|
|
5652
5797
|
function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginStatuses, arrayItemTitle) {
|
|
5653
5798
|
const itemFields = renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses);
|
|
5654
5799
|
return `
|
|
5655
|
-
<div class="structured-array-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-
|
|
5800
|
+
<div class="structured-array-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-zinc-600/5 p-4 shadow-lg shadow-zinc-950/20" data-array-index="${escapeHtml3(index)}" draggable="true">
|
|
5656
5801
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5657
5802
|
<div class="flex items-center gap-3">
|
|
5658
5803
|
<div class="drag-handle cursor-move text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-400" data-action="drag-handle" title="Drag to reorder">
|
|
@@ -5665,6 +5810,11 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
|
|
|
5665
5810
|
</div>
|
|
5666
5811
|
</div>
|
|
5667
5812
|
<div class="flex flex-wrap gap-2 text-xs">
|
|
5813
|
+
<button type="button" data-action="toggle-item" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10" aria-label="Expand item" title="Expand">
|
|
5814
|
+
<svg class="h-4 w-4 transition-transform -rotate-90 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" data-item-toggle-icon>
|
|
5815
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
|
5816
|
+
</svg>
|
|
5817
|
+
</button>
|
|
5668
5818
|
<button type="button" data-action="move-up" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent dark:disabled:hover:bg-transparent" aria-label="Move item up" title="Move up">
|
|
5669
5819
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
5670
5820
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
@@ -5683,7 +5833,7 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
|
|
|
5683
5833
|
</button>
|
|
5684
5834
|
</div>
|
|
5685
5835
|
</div>
|
|
5686
|
-
<div class="mt-4 space-y-4" data-array-item-fields>
|
|
5836
|
+
<div class="mt-4 space-y-4 hidden" data-array-item-fields>
|
|
5687
5837
|
${itemFields}
|
|
5688
5838
|
</div>
|
|
5689
5839
|
</div>
|
|
@@ -5793,7 +5943,7 @@ function normalizeBlocksValue(value, discriminator) {
|
|
|
5793
5943
|
function renderBlockTemplate(field, block, discriminator, pluginStatuses) {
|
|
5794
5944
|
return `
|
|
5795
5945
|
<template data-block-template="${escapeHtml3(block.name)}">
|
|
5796
|
-
${renderBlockCard(field, block, discriminator,
|
|
5946
|
+
${renderBlockCard(field, block, discriminator, BLOCK_INDEX_TOKEN, {}, pluginStatuses)}
|
|
5797
5947
|
</template>
|
|
5798
5948
|
`;
|
|
5799
5949
|
}
|
|
@@ -5835,7 +5985,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5835
5985
|
`;
|
|
5836
5986
|
}).join("");
|
|
5837
5987
|
return `
|
|
5838
|
-
<div class="blocks-item rounded-lg border border-zinc-200 dark:border-white/10
|
|
5988
|
+
<div class="blocks-item rounded-lg border border-zinc-200 dark:border-white/10 dark:bg-zinc-600/5 p-4 shadow-lg shadow-zinc-950/20" data-block-type="${escapeHtml3(block.name)}" data-block-discriminator="${escapeHtml3(discriminator)}" draggable="true">
|
|
5839
5989
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5840
5990
|
<div class="flex items-start gap-3">
|
|
5841
5991
|
<div class="drag-handle cursor-move text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-400" data-action="drag-handle" title="Drag to reorder">
|
|
@@ -5843,7 +5993,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5843
5993
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
|
|
5844
5994
|
</svg>
|
|
5845
5995
|
</div>
|
|
5846
|
-
<div>
|
|
5996
|
+
<div class="cursor-pointer" data-action="toggle-block">
|
|
5847
5997
|
<div class="text-sm font-semibold text-zinc-900 dark:text-white">
|
|
5848
5998
|
${escapeHtml3(block.label)}
|
|
5849
5999
|
<span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-block-order-label></span>
|
|
@@ -5852,6 +6002,11 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5852
6002
|
</div>
|
|
5853
6003
|
</div>
|
|
5854
6004
|
<div class="flex flex-wrap gap-2 text-xs">
|
|
6005
|
+
<button type="button" data-action="toggle-block" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10" aria-label="Expand block" title="Expand">
|
|
6006
|
+
<svg class="h-4 w-4 transition-transform -rotate-90 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" data-block-toggle-icon>
|
|
6007
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
|
6008
|
+
</svg>
|
|
6009
|
+
</button>
|
|
5855
6010
|
<button type="button" data-action="move-up" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent dark:disabled:hover:bg-transparent" aria-label="Move block up" title="Move up">
|
|
5856
6011
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
5857
6012
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
@@ -5870,7 +6025,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5870
6025
|
</button>
|
|
5871
6026
|
</div>
|
|
5872
6027
|
</div>
|
|
5873
|
-
<div class="mt-4 space-y-4">
|
|
6028
|
+
<div class="mt-4 space-y-4 hidden" data-block-content>
|
|
5874
6029
|
${blockFields}
|
|
5875
6030
|
</div>
|
|
5876
6031
|
</div>
|
|
@@ -5904,9 +6059,101 @@ function getStructuredFieldScript() {
|
|
|
5904
6059
|
|
|
5905
6060
|
function initializeStructuredFields() {
|
|
5906
6061
|
const readFieldValue = window.sonicReadFieldValue;
|
|
6062
|
+
const getDirectChild = (parent, selector) => {
|
|
6063
|
+
if (!(parent instanceof Element)) return null;
|
|
6064
|
+
return Array.from(parent.children).find(
|
|
6065
|
+
(child) => child instanceof Element && child.matches(selector),
|
|
6066
|
+
) || null;
|
|
6067
|
+
};
|
|
6068
|
+
const getDirectStructuredSubfields = (host) =>
|
|
6069
|
+
Array.from(host.children).filter(
|
|
6070
|
+
(child) => child instanceof Element && child.classList.contains('structured-subfield'),
|
|
6071
|
+
);
|
|
6072
|
+
const getStructuredValueHost = (container) => {
|
|
6073
|
+
const directObjectHost = getDirectChild(container, '[data-structured-object-fields]');
|
|
6074
|
+
if (directObjectHost) return directObjectHost;
|
|
6075
|
+
const groupContent = getDirectChild(container, '.field-group-content');
|
|
6076
|
+
const nestedObjectHost = groupContent
|
|
6077
|
+
? getDirectChild(groupContent, '[data-structured-object-fields]')
|
|
6078
|
+
: null;
|
|
6079
|
+
if (nestedObjectHost) return nestedObjectHost;
|
|
6080
|
+
return getDirectChild(container, '[data-array-item-fields]') || container;
|
|
6081
|
+
};
|
|
6082
|
+
const getCollectionScope = () => {
|
|
6083
|
+
const url = new URL(window.location.href);
|
|
6084
|
+
const collectionFromQuery = url.searchParams.get('collection');
|
|
6085
|
+
const form = document.getElementById('content-form');
|
|
6086
|
+
const collectionInput = form?.querySelector('input[name="collection_id"]');
|
|
6087
|
+
const collectionFromForm = collectionInput instanceof HTMLInputElement ? collectionInput.value : '';
|
|
6088
|
+
const collectionId = collectionFromQuery || collectionFromForm || '';
|
|
6089
|
+
return window.location.pathname + ':' + collectionId;
|
|
6090
|
+
};
|
|
6091
|
+
|
|
6092
|
+
const getArrayStateKey = (container) => {
|
|
6093
|
+
const fieldName = container.dataset.fieldName || 'unknown';
|
|
6094
|
+
return 'sonic:ui:repeaters:' + getCollectionScope() + ':' + fieldName;
|
|
6095
|
+
};
|
|
6096
|
+
|
|
6097
|
+
const readArrayState = (container) => {
|
|
6098
|
+
try {
|
|
6099
|
+
const raw = sessionStorage.getItem(getArrayStateKey(container));
|
|
6100
|
+
if (!raw) return null;
|
|
6101
|
+
const parsed = JSON.parse(raw);
|
|
6102
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
6103
|
+
} catch {
|
|
6104
|
+
return null;
|
|
6105
|
+
}
|
|
6106
|
+
};
|
|
6107
|
+
|
|
6108
|
+
const writeArrayState = (container, state) => {
|
|
6109
|
+
try {
|
|
6110
|
+
sessionStorage.setItem(getArrayStateKey(container), JSON.stringify(state));
|
|
6111
|
+
} catch {}
|
|
6112
|
+
};
|
|
6113
|
+
|
|
6114
|
+
const setArrayItemExpanded = (item, isExpanded) => {
|
|
6115
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6116
|
+
const icon = item.querySelector('[data-item-toggle-icon]');
|
|
6117
|
+
if (content instanceof HTMLElement) {
|
|
6118
|
+
content.classList.toggle('hidden', !isExpanded);
|
|
6119
|
+
}
|
|
6120
|
+
if (icon instanceof Element) {
|
|
6121
|
+
icon.classList.toggle('-rotate-90', !isExpanded);
|
|
6122
|
+
}
|
|
6123
|
+
};
|
|
6124
|
+
|
|
6125
|
+
const getArrayItems = (container, list) => {
|
|
6126
|
+
if (list) {
|
|
6127
|
+
return Array.from(list.querySelectorAll(':scope > .structured-array-item'));
|
|
6128
|
+
}
|
|
6129
|
+
return Array.from(
|
|
6130
|
+
container.querySelectorAll(':scope > [data-structured-array-list] > .structured-array-item'),
|
|
6131
|
+
);
|
|
6132
|
+
};
|
|
6133
|
+
|
|
6134
|
+
const captureArrayState = (container) => {
|
|
6135
|
+
return getArrayItems(container).map((item) => {
|
|
6136
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6137
|
+
return content instanceof HTMLElement ? !content.classList.contains('hidden') : false;
|
|
6138
|
+
});
|
|
6139
|
+
};
|
|
6140
|
+
|
|
6141
|
+
const applyArrayState = (container, state) => {
|
|
6142
|
+
const items = getArrayItems(container);
|
|
6143
|
+
items.forEach((item, index) => {
|
|
6144
|
+
if (typeof state[index] === 'boolean') {
|
|
6145
|
+
setArrayItemExpanded(item, state[index]);
|
|
6146
|
+
}
|
|
6147
|
+
});
|
|
6148
|
+
};
|
|
6149
|
+
|
|
6150
|
+
const syncArrayState = (container) => {
|
|
6151
|
+
writeArrayState(container, captureArrayState(container));
|
|
6152
|
+
};
|
|
5907
6153
|
|
|
5908
6154
|
const readStructuredValue = (container) => {
|
|
5909
|
-
const
|
|
6155
|
+
const fieldHost = getStructuredValueHost(container);
|
|
6156
|
+
const fields = getDirectStructuredSubfields(fieldHost);
|
|
5910
6157
|
if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
|
|
5911
6158
|
return readFieldValue(fields[0]);
|
|
5912
6159
|
}
|
|
@@ -5920,111 +6167,229 @@ function getStructuredFieldScript() {
|
|
|
5920
6167
|
};
|
|
5921
6168
|
|
|
5922
6169
|
document.querySelectorAll('[data-structured-object]').forEach((container) => {
|
|
6170
|
+
if (container.closest('template')) {
|
|
6171
|
+
return;
|
|
6172
|
+
}
|
|
5923
6173
|
if (container.dataset.structuredInitialized === 'true') {
|
|
5924
6174
|
return;
|
|
5925
6175
|
}
|
|
5926
|
-
container.dataset.
|
|
5927
|
-
|
|
6176
|
+
if (container.dataset.structuredInitializing === 'true') {
|
|
6177
|
+
return;
|
|
6178
|
+
}
|
|
6179
|
+
container.dataset.structuredInitializing = 'true';
|
|
6180
|
+
try {
|
|
6181
|
+
const hiddenInput = container.querySelector('input[type="hidden"]');
|
|
5928
6182
|
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
6183
|
+
const updateHiddenInput = () => {
|
|
6184
|
+
if (!hiddenInput) return;
|
|
6185
|
+
const value = readStructuredValue(container);
|
|
6186
|
+
hiddenInput.value = JSON.stringify(value);
|
|
6187
|
+
};
|
|
5934
6188
|
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
6189
|
+
container.addEventListener('input', updateHiddenInput);
|
|
6190
|
+
container.addEventListener('change', updateHiddenInput);
|
|
6191
|
+
updateHiddenInput();
|
|
6192
|
+
container.dataset.structuredInitialized = 'true';
|
|
6193
|
+
} catch (error) {
|
|
6194
|
+
delete container.dataset.structuredInitialized;
|
|
6195
|
+
console.error('[structured-object] initialization failed', error);
|
|
6196
|
+
} finally {
|
|
6197
|
+
delete container.dataset.structuredInitializing;
|
|
6198
|
+
}
|
|
5938
6199
|
});
|
|
5939
6200
|
|
|
5940
6201
|
document.querySelectorAll('[data-structured-array]').forEach((container) => {
|
|
6202
|
+
if (container.closest('template')) {
|
|
6203
|
+
return;
|
|
6204
|
+
}
|
|
5941
6205
|
if (container.dataset.structuredInitialized === 'true') {
|
|
5942
6206
|
return;
|
|
5943
6207
|
}
|
|
5944
|
-
container.dataset.
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
6208
|
+
if (container.dataset.structuredInitializing === 'true') {
|
|
6209
|
+
return;
|
|
6210
|
+
}
|
|
6211
|
+
container.dataset.structuredInitializing = 'true';
|
|
6212
|
+
try {
|
|
6213
|
+
const list = container.querySelector(':scope > [data-structured-array-list]');
|
|
6214
|
+
const hiddenInput = container.querySelector(':scope > input[type="hidden"]');
|
|
6215
|
+
const template = container.querySelector(':scope > template[data-structured-array-template]');
|
|
6216
|
+
if (
|
|
6217
|
+
template instanceof HTMLTemplateElement &&
|
|
6218
|
+
typeof template.innerHTML === 'string' &&
|
|
6219
|
+
template.innerHTML.trim()
|
|
6220
|
+
) {
|
|
6221
|
+
container.__sonicStructuredArrayTemplate = template.innerHTML;
|
|
6222
|
+
}
|
|
5948
6223
|
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
6224
|
+
const getLiveList = () =>
|
|
6225
|
+
list || container.querySelector(':scope > [data-structured-array-list]');
|
|
6226
|
+
const getLiveHiddenInput = () =>
|
|
6227
|
+
hiddenInput || container.querySelector(':scope > input[type="hidden"]');
|
|
6228
|
+
const getTemplateHtml = () => {
|
|
6229
|
+
if (typeof container.__sonicStructuredArrayTemplate === 'string' &&
|
|
6230
|
+
container.__sonicStructuredArrayTemplate.trim()) {
|
|
6231
|
+
return container.__sonicStructuredArrayTemplate;
|
|
5955
6232
|
}
|
|
5956
6233
|
|
|
5957
|
-
const
|
|
5958
|
-
|
|
5959
|
-
|
|
6234
|
+
const liveTemplate =
|
|
6235
|
+
template instanceof HTMLTemplateElement
|
|
6236
|
+
? template
|
|
6237
|
+
: container.querySelector(':scope > template[data-structured-array-template]');
|
|
6238
|
+
if (
|
|
6239
|
+
liveTemplate instanceof HTMLTemplateElement &&
|
|
6240
|
+
typeof liveTemplate.innerHTML === 'string' &&
|
|
6241
|
+
liveTemplate.innerHTML.trim()
|
|
6242
|
+
) {
|
|
6243
|
+
container.__sonicStructuredArrayTemplate = liveTemplate.innerHTML;
|
|
6244
|
+
return liveTemplate.innerHTML;
|
|
5960
6245
|
}
|
|
6246
|
+
return typeof container.__sonicStructuredArrayTemplate === 'string'
|
|
6247
|
+
? container.__sonicStructuredArrayTemplate
|
|
6248
|
+
: '';
|
|
6249
|
+
};
|
|
5961
6250
|
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
6251
|
+
const updateOrderLabels = () => {
|
|
6252
|
+
const liveList = getLiveList();
|
|
6253
|
+
if (!liveList) return;
|
|
6254
|
+
const items = getArrayItems(container, liveList);
|
|
6255
|
+
items.forEach((item, index) => {
|
|
6256
|
+
const label = item.querySelector('[data-array-order-label]');
|
|
6257
|
+
if (label) {
|
|
6258
|
+
label.textContent = '#'+ (index + 1);
|
|
6259
|
+
}
|
|
6260
|
+
|
|
6261
|
+
const moveUpButton = item.querySelector('[data-action="move-up"]');
|
|
6262
|
+
if (moveUpButton instanceof HTMLButtonElement) {
|
|
6263
|
+
moveUpButton.disabled = index === 0;
|
|
6264
|
+
}
|
|
6265
|
+
|
|
6266
|
+
const moveDownButton = item.querySelector('[data-action="move-down"]');
|
|
6267
|
+
if (moveDownButton instanceof HTMLButtonElement) {
|
|
6268
|
+
moveDownButton.disabled = index === items.length - 1;
|
|
6269
|
+
}
|
|
6270
|
+
});
|
|
6271
|
+
};
|
|
6272
|
+
|
|
6273
|
+
const updateHiddenInput = () => {
|
|
6274
|
+
const liveHiddenInput = getLiveHiddenInput();
|
|
6275
|
+
const liveList = getLiveList();
|
|
6276
|
+
if (!liveHiddenInput || !liveList) return;
|
|
6277
|
+
const items = getArrayItems(container, liveList);
|
|
6278
|
+
const values = items.map((item) => readStructuredValue(item));
|
|
6279
|
+
liveHiddenInput.value = JSON.stringify(values);
|
|
6280
|
+
// Notify parent structured containers after non-input actions (add/remove/move)
|
|
6281
|
+
// so nested array mutations are persisted correctly.
|
|
6282
|
+
liveHiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
6283
|
+
|
|
6284
|
+
const emptyState = liveList.querySelector(':scope > [data-structured-empty]');
|
|
6285
|
+
if (emptyState) {
|
|
6286
|
+
emptyState.style.display = values.length === 0 ? 'block' : 'none';
|
|
5965
6287
|
}
|
|
5966
|
-
|
|
5967
|
-
|
|
6288
|
+
updateOrderLabels();
|
|
6289
|
+
};
|
|
5968
6290
|
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
6291
|
+
const addArrayItem = () => {
|
|
6292
|
+
const liveList = getLiveList();
|
|
6293
|
+
if (!liveList) return;
|
|
6294
|
+
const templateHtml = getTemplateHtml();
|
|
6295
|
+
if (!templateHtml) return;
|
|
6296
|
+
try {
|
|
6297
|
+
const nextIndex = getArrayItems(container, liveList).length;
|
|
6298
|
+
const html = templateHtml.replace(/__INDEX__/g, String(nextIndex));
|
|
6299
|
+
liveList.insertAdjacentHTML('beforeend', html);
|
|
6300
|
+
const newItem = liveList.lastElementChild;
|
|
6301
|
+
if (newItem instanceof HTMLElement) {
|
|
6302
|
+
// Ensure cloned template content can be initialized even if stale
|
|
6303
|
+
// data-structured-initialized attributes were copied.
|
|
6304
|
+
newItem
|
|
6305
|
+
.querySelectorAll('[data-structured-object], [data-structured-array]')
|
|
6306
|
+
.forEach((nestedContainer) => {
|
|
6307
|
+
if (nestedContainer instanceof HTMLElement) {
|
|
6308
|
+
delete nestedContainer.dataset.structuredInitialized;
|
|
6309
|
+
}
|
|
6310
|
+
});
|
|
6311
|
+
setArrayItemExpanded(newItem, true);
|
|
6312
|
+
}
|
|
6313
|
+
if (typeof initializeTinyMCE === 'function') {
|
|
6314
|
+
initializeTinyMCE();
|
|
6315
|
+
}
|
|
6316
|
+
if (typeof window.initializeQuillEditors === 'function') {
|
|
6317
|
+
window.initializeQuillEditors();
|
|
6318
|
+
}
|
|
6319
|
+
if (typeof initializeMDXEditor === 'function') {
|
|
6320
|
+
initializeMDXEditor();
|
|
6321
|
+
}
|
|
6322
|
+
if (typeof window.initializeStructuredFields === 'function') {
|
|
6323
|
+
window.initializeStructuredFields();
|
|
6324
|
+
}
|
|
6325
|
+
updateHiddenInput();
|
|
6326
|
+
syncArrayState(container);
|
|
6327
|
+
} catch (error) {
|
|
6328
|
+
console.error('[structured-array] add-item failed', error);
|
|
6329
|
+
}
|
|
6330
|
+
};
|
|
5974
6331
|
|
|
5975
|
-
const
|
|
5976
|
-
|
|
5977
|
-
|
|
6332
|
+
const topLevelAddButton = container.querySelector(
|
|
6333
|
+
':scope > .flex.items-center.justify-between.gap-3 [data-action="add-item"]',
|
|
6334
|
+
);
|
|
6335
|
+
if (topLevelAddButton instanceof HTMLElement) {
|
|
6336
|
+
topLevelAddButton.addEventListener('click', (event) => {
|
|
6337
|
+
event.preventDefault();
|
|
6338
|
+
event.stopPropagation();
|
|
6339
|
+
addArrayItem();
|
|
6340
|
+
});
|
|
5978
6341
|
}
|
|
5979
|
-
updateOrderLabels();
|
|
5980
|
-
};
|
|
5981
6342
|
|
|
5982
|
-
|
|
5983
|
-
window.initializeDragSortable
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
6343
|
+
const dragList = getLiveList();
|
|
6344
|
+
if (typeof window.initializeDragSortable === 'function' && dragList) {
|
|
6345
|
+
window.initializeDragSortable(dragList, {
|
|
6346
|
+
itemSelector: '.structured-array-item',
|
|
6347
|
+
handleSelector: '[data-action="drag-handle"]',
|
|
6348
|
+
onUpdate: () => {
|
|
6349
|
+
updateHiddenInput();
|
|
6350
|
+
syncArrayState(container);
|
|
6351
|
+
}
|
|
6352
|
+
});
|
|
6353
|
+
}
|
|
5989
6354
|
|
|
5990
|
-
|
|
6355
|
+
container.addEventListener('click', (event) => {
|
|
5991
6356
|
const target = event.target;
|
|
5992
6357
|
if (!(target instanceof Element)) return;
|
|
5993
6358
|
const actionButton = target.closest('[data-action]');
|
|
5994
6359
|
if (!actionButton || actionButton.hasAttribute('disabled')) return;
|
|
6360
|
+
const actionOwner = actionButton.closest('[data-structured-array]');
|
|
6361
|
+
if (actionOwner !== container) return;
|
|
5995
6362
|
|
|
5996
|
-
|
|
6363
|
+
const action = actionButton.getAttribute('data-action');
|
|
5997
6364
|
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
const html = template.innerHTML.replace(/__INDEX__/g, String(nextIndex));
|
|
6002
|
-
list.insertAdjacentHTML('beforeend', html);
|
|
6003
|
-
if (typeof initializeTinyMCE === 'function') {
|
|
6004
|
-
initializeTinyMCE();
|
|
6005
|
-
}
|
|
6006
|
-
if (typeof window.initializeQuillEditors === 'function') {
|
|
6007
|
-
window.initializeQuillEditors();
|
|
6008
|
-
}
|
|
6009
|
-
if (typeof initializeMDXEditor === 'function') {
|
|
6010
|
-
initializeMDXEditor();
|
|
6365
|
+
if (action === 'add-item') {
|
|
6366
|
+
addArrayItem();
|
|
6367
|
+
return;
|
|
6011
6368
|
}
|
|
6012
|
-
updateHiddenInput();
|
|
6013
|
-
return;
|
|
6014
|
-
}
|
|
6015
6369
|
|
|
6016
6370
|
const item = actionButton.closest('.structured-array-item');
|
|
6017
|
-
|
|
6371
|
+
const liveList = getLiveList();
|
|
6372
|
+
if (!item || !liveList) return;
|
|
6373
|
+
|
|
6374
|
+
if (action === 'toggle-item') {
|
|
6375
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6376
|
+
if (!(content instanceof HTMLElement)) return;
|
|
6377
|
+
setArrayItemExpanded(item, content.classList.contains('hidden'));
|
|
6378
|
+
syncArrayState(container);
|
|
6379
|
+
return;
|
|
6380
|
+
}
|
|
6018
6381
|
|
|
6019
6382
|
if (action === 'remove-item') {
|
|
6020
6383
|
if (typeof requestRepeaterDelete === 'function') {
|
|
6021
6384
|
requestRepeaterDelete(() => {
|
|
6022
6385
|
item.remove();
|
|
6023
6386
|
updateHiddenInput();
|
|
6387
|
+
syncArrayState(container);
|
|
6024
6388
|
});
|
|
6025
6389
|
} else {
|
|
6026
6390
|
item.remove();
|
|
6027
6391
|
updateHiddenInput();
|
|
6392
|
+
syncArrayState(container);
|
|
6028
6393
|
}
|
|
6029
6394
|
return;
|
|
6030
6395
|
}
|
|
@@ -6032,8 +6397,9 @@ function getStructuredFieldScript() {
|
|
|
6032
6397
|
if (action === 'move-up') {
|
|
6033
6398
|
const previous = item.previousElementSibling;
|
|
6034
6399
|
if (previous) {
|
|
6035
|
-
|
|
6400
|
+
liveList.insertBefore(item, previous);
|
|
6036
6401
|
updateHiddenInput();
|
|
6402
|
+
syncArrayState(container);
|
|
6037
6403
|
}
|
|
6038
6404
|
return;
|
|
6039
6405
|
}
|
|
@@ -6041,29 +6407,43 @@ function getStructuredFieldScript() {
|
|
|
6041
6407
|
if (action === 'move-down') {
|
|
6042
6408
|
const next = item.nextElementSibling;
|
|
6043
6409
|
if (next) {
|
|
6044
|
-
|
|
6410
|
+
liveList.insertBefore(next, item);
|
|
6045
6411
|
updateHiddenInput();
|
|
6412
|
+
syncArrayState(container);
|
|
6046
6413
|
}
|
|
6047
6414
|
}
|
|
6048
|
-
|
|
6415
|
+
});
|
|
6049
6416
|
|
|
6050
|
-
|
|
6417
|
+
container.addEventListener('input', (event) => {
|
|
6051
6418
|
const target = event.target;
|
|
6052
6419
|
if (!(target instanceof Element)) return;
|
|
6053
6420
|
if (target.closest('[data-structured-array-list]')) {
|
|
6054
6421
|
updateHiddenInput();
|
|
6055
6422
|
}
|
|
6056
|
-
|
|
6423
|
+
});
|
|
6057
6424
|
|
|
6058
|
-
|
|
6425
|
+
container.addEventListener('change', (event) => {
|
|
6059
6426
|
const target = event.target;
|
|
6060
6427
|
if (!(target instanceof Element)) return;
|
|
6061
6428
|
if (target.closest('[data-structured-array-list]')) {
|
|
6062
6429
|
updateHiddenInput();
|
|
6063
6430
|
}
|
|
6064
|
-
|
|
6431
|
+
});
|
|
6065
6432
|
|
|
6066
|
-
|
|
6433
|
+
updateHiddenInput();
|
|
6434
|
+
const savedArrayState = readArrayState(container);
|
|
6435
|
+
if (savedArrayState) {
|
|
6436
|
+
applyArrayState(container, savedArrayState);
|
|
6437
|
+
} else {
|
|
6438
|
+
syncArrayState(container);
|
|
6439
|
+
}
|
|
6440
|
+
container.dataset.structuredInitialized = 'true';
|
|
6441
|
+
} catch (error) {
|
|
6442
|
+
delete container.dataset.structuredInitialized;
|
|
6443
|
+
console.error('[structured-array] initialization failed', error);
|
|
6444
|
+
} finally {
|
|
6445
|
+
delete container.dataset.structuredInitializing;
|
|
6446
|
+
}
|
|
6067
6447
|
});
|
|
6068
6448
|
}
|
|
6069
6449
|
|
|
@@ -6078,7 +6458,10 @@ function getStructuredFieldScript() {
|
|
|
6078
6458
|
document.addEventListener('htmx:afterSwap', function() {
|
|
6079
6459
|
setTimeout(initializeStructuredFields, 50);
|
|
6080
6460
|
});
|
|
6081
|
-
} else if (
|
|
6461
|
+
} else if (
|
|
6462
|
+
typeof window.initializeStructuredFields === 'function' &&
|
|
6463
|
+
document.readyState !== 'loading'
|
|
6464
|
+
) {
|
|
6082
6465
|
window.initializeStructuredFields();
|
|
6083
6466
|
}
|
|
6084
6467
|
</script>
|
|
@@ -6090,6 +6473,68 @@ function getBlocksFieldScript() {
|
|
|
6090
6473
|
<script>
|
|
6091
6474
|
if (!window.__sonicBlocksFieldInit) {
|
|
6092
6475
|
window.__sonicBlocksFieldInit = true;
|
|
6476
|
+
const getCollectionScope = () => {
|
|
6477
|
+
const url = new URL(window.location.href);
|
|
6478
|
+
const collectionFromQuery = url.searchParams.get('collection');
|
|
6479
|
+
const form = document.getElementById('content-form');
|
|
6480
|
+
const collectionInput = form?.querySelector('input[name="collection_id"]');
|
|
6481
|
+
const collectionFromForm = collectionInput instanceof HTMLInputElement ? collectionInput.value : '';
|
|
6482
|
+
const collectionId = collectionFromQuery || collectionFromForm || '';
|
|
6483
|
+
return window.location.pathname + ':' + collectionId;
|
|
6484
|
+
};
|
|
6485
|
+
|
|
6486
|
+
const getBlocksStateKey = (container) => {
|
|
6487
|
+
const fieldName = container.dataset.fieldName || 'unknown';
|
|
6488
|
+
return 'sonic:ui:blocks:' + getCollectionScope() + ':' + fieldName;
|
|
6489
|
+
};
|
|
6490
|
+
|
|
6491
|
+
const readBlocksState = (container) => {
|
|
6492
|
+
try {
|
|
6493
|
+
const raw = sessionStorage.getItem(getBlocksStateKey(container));
|
|
6494
|
+
if (!raw) return null;
|
|
6495
|
+
const parsed = JSON.parse(raw);
|
|
6496
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
6497
|
+
} catch {
|
|
6498
|
+
return null;
|
|
6499
|
+
}
|
|
6500
|
+
};
|
|
6501
|
+
|
|
6502
|
+
const writeBlocksState = (container, state) => {
|
|
6503
|
+
try {
|
|
6504
|
+
sessionStorage.setItem(getBlocksStateKey(container), JSON.stringify(state));
|
|
6505
|
+
} catch {}
|
|
6506
|
+
};
|
|
6507
|
+
|
|
6508
|
+
const setBlockExpanded = (item, isExpanded) => {
|
|
6509
|
+
const content = item.querySelector('[data-block-content]');
|
|
6510
|
+
const icon = item.querySelector('[data-block-toggle-icon]');
|
|
6511
|
+
if (content instanceof HTMLElement) {
|
|
6512
|
+
content.classList.toggle('hidden', !isExpanded);
|
|
6513
|
+
}
|
|
6514
|
+
if (icon instanceof Element) {
|
|
6515
|
+
icon.classList.toggle('-rotate-90', !isExpanded);
|
|
6516
|
+
}
|
|
6517
|
+
};
|
|
6518
|
+
|
|
6519
|
+
const captureBlocksState = (container) => {
|
|
6520
|
+
return Array.from(container.querySelectorAll('.blocks-item')).map((item) => {
|
|
6521
|
+
const content = item.querySelector('[data-block-content]');
|
|
6522
|
+
return content instanceof HTMLElement ? !content.classList.contains('hidden') : false;
|
|
6523
|
+
});
|
|
6524
|
+
};
|
|
6525
|
+
|
|
6526
|
+
const applyBlocksState = (container, state) => {
|
|
6527
|
+
const items = Array.from(container.querySelectorAll('.blocks-item'));
|
|
6528
|
+
items.forEach((item, index) => {
|
|
6529
|
+
if (typeof state[index] === 'boolean') {
|
|
6530
|
+
setBlockExpanded(item, state[index]);
|
|
6531
|
+
}
|
|
6532
|
+
});
|
|
6533
|
+
};
|
|
6534
|
+
|
|
6535
|
+
const syncBlocksState = (container) => {
|
|
6536
|
+
writeBlocksState(container, captureBlocksState(container));
|
|
6537
|
+
};
|
|
6093
6538
|
|
|
6094
6539
|
function initializeBlocksFields() {
|
|
6095
6540
|
document.querySelectorAll('.blocks-field').forEach((container) => {
|
|
@@ -6177,7 +6622,10 @@ function getBlocksFieldScript() {
|
|
|
6177
6622
|
window.initializeDragSortable(list, {
|
|
6178
6623
|
itemSelector: '.blocks-item',
|
|
6179
6624
|
handleSelector: '[data-action="drag-handle"]',
|
|
6180
|
-
onUpdate:
|
|
6625
|
+
onUpdate: () => {
|
|
6626
|
+
updateHiddenInput();
|
|
6627
|
+
syncBlocksState(container);
|
|
6628
|
+
}
|
|
6181
6629
|
});
|
|
6182
6630
|
}
|
|
6183
6631
|
|
|
@@ -6199,8 +6647,12 @@ function getBlocksFieldScript() {
|
|
|
6199
6647
|
if (!template) return;
|
|
6200
6648
|
|
|
6201
6649
|
const nextIndex = list.querySelectorAll('.blocks-item').length;
|
|
6202
|
-
const html = template.innerHTML.replace(/
|
|
6650
|
+
const html = template.innerHTML.replace(/__BLOCK_INDEX__/g, String(nextIndex));
|
|
6203
6651
|
list.insertAdjacentHTML('beforeend', html);
|
|
6652
|
+
const newItem = list.lastElementChild;
|
|
6653
|
+
if (newItem instanceof HTMLElement) {
|
|
6654
|
+
setBlockExpanded(newItem, true);
|
|
6655
|
+
}
|
|
6204
6656
|
if (typeSelect) {
|
|
6205
6657
|
typeSelect.value = '';
|
|
6206
6658
|
}
|
|
@@ -6209,21 +6661,32 @@ function getBlocksFieldScript() {
|
|
|
6209
6661
|
window.initializeStructuredFields();
|
|
6210
6662
|
}
|
|
6211
6663
|
updateHiddenInput();
|
|
6664
|
+
syncBlocksState(container);
|
|
6212
6665
|
return;
|
|
6213
6666
|
}
|
|
6214
6667
|
|
|
6215
6668
|
const item = actionButton.closest('.blocks-item');
|
|
6216
6669
|
if (!item || !list) return;
|
|
6217
6670
|
|
|
6671
|
+
if (action === 'toggle-block') {
|
|
6672
|
+
const content = item.querySelector('[data-block-content]');
|
|
6673
|
+
if (!(content instanceof HTMLElement)) return;
|
|
6674
|
+
setBlockExpanded(item, content.classList.contains('hidden'));
|
|
6675
|
+
syncBlocksState(container);
|
|
6676
|
+
return;
|
|
6677
|
+
}
|
|
6678
|
+
|
|
6218
6679
|
if (action === 'remove-block') {
|
|
6219
6680
|
if (typeof requestRepeaterDelete === 'function') {
|
|
6220
6681
|
requestRepeaterDelete(() => {
|
|
6221
6682
|
item.remove();
|
|
6222
6683
|
updateHiddenInput();
|
|
6684
|
+
syncBlocksState(container);
|
|
6223
6685
|
}, 'block');
|
|
6224
6686
|
} else {
|
|
6225
6687
|
item.remove();
|
|
6226
6688
|
updateHiddenInput();
|
|
6689
|
+
syncBlocksState(container);
|
|
6227
6690
|
}
|
|
6228
6691
|
return;
|
|
6229
6692
|
}
|
|
@@ -6233,6 +6696,7 @@ function getBlocksFieldScript() {
|
|
|
6233
6696
|
if (previous) {
|
|
6234
6697
|
list.insertBefore(item, previous);
|
|
6235
6698
|
updateHiddenInput();
|
|
6699
|
+
syncBlocksState(container);
|
|
6236
6700
|
}
|
|
6237
6701
|
return;
|
|
6238
6702
|
}
|
|
@@ -6242,6 +6706,7 @@ function getBlocksFieldScript() {
|
|
|
6242
6706
|
if (next) {
|
|
6243
6707
|
list.insertBefore(next, item);
|
|
6244
6708
|
updateHiddenInput();
|
|
6709
|
+
syncBlocksState(container);
|
|
6245
6710
|
}
|
|
6246
6711
|
}
|
|
6247
6712
|
});
|
|
@@ -6263,6 +6728,12 @@ function getBlocksFieldScript() {
|
|
|
6263
6728
|
});
|
|
6264
6729
|
|
|
6265
6730
|
updateHiddenInput();
|
|
6731
|
+
const savedBlocksState = readBlocksState(container);
|
|
6732
|
+
if (savedBlocksState) {
|
|
6733
|
+
applyBlocksState(container, savedBlocksState);
|
|
6734
|
+
} else {
|
|
6735
|
+
syncBlocksState(container);
|
|
6736
|
+
}
|
|
6266
6737
|
});
|
|
6267
6738
|
}
|
|
6268
6739
|
|
|
@@ -6299,6 +6770,7 @@ init_admin_layout_catalyst_template();
|
|
|
6299
6770
|
function renderContentFormPage(data) {
|
|
6300
6771
|
const isEdit = data.isEdit || !!data.id;
|
|
6301
6772
|
const title = isEdit ? `Edit: ${data.title || "Content"}` : `New ${data.collection.display_name}`;
|
|
6773
|
+
const hasValidationErrors = Boolean(data.validationErrors && Object.keys(data.validationErrors).length > 0);
|
|
6302
6774
|
const backUrl = data.referrerParams ? `/admin/content?${data.referrerParams}` : `/admin/content?collection=${data.collection.id}`;
|
|
6303
6775
|
const coreFields = data.fields.filter((f) => ["title", "slug", "content"].includes(f.field_name));
|
|
6304
6776
|
const contentFields = data.fields.filter((f) => !["title", "slug", "content"].includes(f.field_name) && !f.field_name.startsWith("meta_"));
|
|
@@ -6387,6 +6859,7 @@ function renderContentFormPage(data) {
|
|
|
6387
6859
|
${isEdit ? `hx-put="/admin/content/${data.id}"` : `hx-post="/admin/content"`}
|
|
6388
6860
|
hx-target="#form-messages"
|
|
6389
6861
|
hx-encoding="multipart/form-data"
|
|
6862
|
+
data-has-validation-errors="${hasValidationErrors ? "true" : "false"}"
|
|
6390
6863
|
class="space-y-6"
|
|
6391
6864
|
>
|
|
6392
6865
|
<input type="hidden" name="collection_id" value="${data.collection.id}">
|
|
@@ -6667,39 +7140,456 @@ function renderContentFormPage(data) {
|
|
|
6667
7140
|
|
|
6668
7141
|
<!-- Dynamic Field Scripts -->
|
|
6669
7142
|
<script>
|
|
7143
|
+
const contentFormCollectionId = ${JSON.stringify(data.collection.id)};
|
|
7144
|
+
|
|
7145
|
+
function getFieldGroupScope() {
|
|
7146
|
+
const url = new URL(window.location.href);
|
|
7147
|
+
const urlCollectionId = url.searchParams.get('collection');
|
|
7148
|
+
const effectiveCollectionId = urlCollectionId || contentFormCollectionId || '';
|
|
7149
|
+
return window.location.pathname + ':' + effectiveCollectionId;
|
|
7150
|
+
}
|
|
7151
|
+
|
|
7152
|
+
function getItemPosition(itemSelector, item) {
|
|
7153
|
+
if (!(item instanceof Element)) return -1;
|
|
7154
|
+
const parent = item.parentElement;
|
|
7155
|
+
if (!parent) return -1;
|
|
7156
|
+
return Array.from(parent.querySelectorAll(':scope > ' + itemSelector)).indexOf(item);
|
|
7157
|
+
}
|
|
7158
|
+
|
|
7159
|
+
function stripIndexedFieldPrefix(fullFieldName, prefix) {
|
|
7160
|
+
if (!fullFieldName || !prefix || !fullFieldName.startsWith(prefix)) {
|
|
7161
|
+
return fullFieldName;
|
|
7162
|
+
}
|
|
7163
|
+
|
|
7164
|
+
const remainder = fullFieldName.slice(prefix.length);
|
|
7165
|
+
const indexMatch = remainder.match(/^(\\d+)(-|__)(.*)$/);
|
|
7166
|
+
if (!indexMatch) {
|
|
7167
|
+
return fullFieldName;
|
|
7168
|
+
}
|
|
7169
|
+
|
|
7170
|
+
return indexMatch[3];
|
|
7171
|
+
}
|
|
7172
|
+
|
|
7173
|
+
function getFieldGroupStorageKey(groupOrId) {
|
|
7174
|
+
const defaultGroupId = typeof groupOrId === 'string' ? groupOrId : (groupOrId?.getAttribute('data-group-id') || 'unknown');
|
|
7175
|
+
const group = typeof groupOrId === 'string'
|
|
7176
|
+
? document.querySelector('.field-group[data-group-id="' + defaultGroupId + '"]')
|
|
7177
|
+
: groupOrId;
|
|
7178
|
+
|
|
7179
|
+
const scopePrefix = 'sonic:ui:objects:' + getFieldGroupScope() + ':';
|
|
7180
|
+
if (!(group instanceof Element)) {
|
|
7181
|
+
return scopePrefix + defaultGroupId;
|
|
7182
|
+
}
|
|
7183
|
+
|
|
7184
|
+
const fullFieldName = group.getAttribute('data-field-name') || '';
|
|
7185
|
+
|
|
7186
|
+
const blocksField = group.closest('.blocks-field');
|
|
7187
|
+
const blockItem = group.closest('.blocks-item');
|
|
7188
|
+
if (blocksField instanceof Element && blockItem instanceof Element) {
|
|
7189
|
+
const blocksFieldName = blocksField.getAttribute('data-field-name') || 'unknown';
|
|
7190
|
+
const blockPosition = getItemPosition('.blocks-item', blockItem);
|
|
7191
|
+
const relativePath = stripIndexedFieldPrefix(fullFieldName, 'block-' + blocksFieldName + '-') || defaultGroupId;
|
|
7192
|
+
return scopePrefix + 'blocks:' + blocksFieldName + ':' + blockPosition + ':' + relativePath;
|
|
7193
|
+
}
|
|
7194
|
+
|
|
7195
|
+
const arrayField = group.closest('[data-structured-array][data-field-name]');
|
|
7196
|
+
const arrayItem = group.closest('.structured-array-item');
|
|
7197
|
+
if (arrayField instanceof Element && arrayItem instanceof Element) {
|
|
7198
|
+
const arrayFieldName = arrayField.getAttribute('data-field-name') || 'unknown';
|
|
7199
|
+
const itemPosition = getItemPosition('.structured-array-item', arrayItem);
|
|
7200
|
+
const relativePath = stripIndexedFieldPrefix(fullFieldName, 'array-' + arrayFieldName + '-') || defaultGroupId;
|
|
7201
|
+
return scopePrefix + 'repeaters:' + arrayFieldName + ':' + itemPosition + ':' + relativePath;
|
|
7202
|
+
}
|
|
7203
|
+
|
|
7204
|
+
return scopePrefix + defaultGroupId;
|
|
7205
|
+
}
|
|
7206
|
+
|
|
7207
|
+
function loadFieldGroupState(group) {
|
|
7208
|
+
try {
|
|
7209
|
+
const value = sessionStorage.getItem(getFieldGroupStorageKey(group));
|
|
7210
|
+
if (value === '1') return true;
|
|
7211
|
+
if (value === '0') return false;
|
|
7212
|
+
} catch {}
|
|
7213
|
+
return null;
|
|
7214
|
+
}
|
|
7215
|
+
|
|
7216
|
+
function saveFieldGroupState(group, isCollapsed) {
|
|
7217
|
+
try {
|
|
7218
|
+
sessionStorage.setItem(getFieldGroupStorageKey(group), isCollapsed ? '1' : '0');
|
|
7219
|
+
} catch {}
|
|
7220
|
+
}
|
|
7221
|
+
|
|
7222
|
+
function resolveFieldGroupElements(groupOrId) {
|
|
7223
|
+
let group = null;
|
|
7224
|
+
|
|
7225
|
+
if (groupOrId instanceof Element) {
|
|
7226
|
+
group = groupOrId.classList.contains('field-group')
|
|
7227
|
+
? groupOrId
|
|
7228
|
+
: groupOrId.closest('.field-group[data-group-id]');
|
|
7229
|
+
} else if (typeof groupOrId === 'string' && groupOrId) {
|
|
7230
|
+
group = document.querySelector('.field-group[data-group-id="' + groupOrId + '"]');
|
|
7231
|
+
}
|
|
7232
|
+
|
|
7233
|
+
let content = null;
|
|
7234
|
+
let icon = null;
|
|
7235
|
+
|
|
7236
|
+
if (group instanceof Element) {
|
|
7237
|
+
content = group.querySelector(':scope > .field-group-content');
|
|
7238
|
+
icon = group.querySelector(':scope > .field-group-header svg[id$="-icon"]');
|
|
7239
|
+
}
|
|
7240
|
+
|
|
7241
|
+
// Legacy fallback for any existing calls still passing string IDs.
|
|
7242
|
+
if (!(content instanceof HTMLElement) && typeof groupOrId === 'string') {
|
|
7243
|
+
content = document.getElementById(groupOrId + '-content');
|
|
7244
|
+
}
|
|
7245
|
+
if (!(icon instanceof Element) && typeof groupOrId === 'string') {
|
|
7246
|
+
icon = document.getElementById(groupOrId + '-icon');
|
|
7247
|
+
}
|
|
7248
|
+
|
|
7249
|
+
if (!(group instanceof Element) && content instanceof Element) {
|
|
7250
|
+
group = content.closest('.field-group[data-group-id]');
|
|
7251
|
+
}
|
|
7252
|
+
|
|
7253
|
+
return { group, content, icon };
|
|
7254
|
+
}
|
|
7255
|
+
|
|
7256
|
+
function applyFieldGroupState(groupOrId, isCollapsed) {
|
|
7257
|
+
const { content, icon } = resolveFieldGroupElements(groupOrId);
|
|
7258
|
+
if (!(content instanceof HTMLElement) || !(icon instanceof Element)) return;
|
|
7259
|
+
content.classList.toggle('hidden', isCollapsed);
|
|
7260
|
+
icon.classList.toggle('-rotate-90', isCollapsed);
|
|
7261
|
+
}
|
|
7262
|
+
|
|
7263
|
+
function restoreFieldGroupStates() {
|
|
7264
|
+
document.querySelectorAll('.field-group[data-group-id]').forEach((group) => {
|
|
7265
|
+
const savedState = loadFieldGroupState(group);
|
|
7266
|
+
if (savedState === null) return;
|
|
7267
|
+
applyFieldGroupState(group, savedState);
|
|
7268
|
+
});
|
|
7269
|
+
}
|
|
7270
|
+
|
|
7271
|
+
function persistAllFieldGroupStates() {
|
|
7272
|
+
document.querySelectorAll('.field-group[data-group-id]').forEach((group) => {
|
|
7273
|
+
const { content } = resolveFieldGroupElements(group);
|
|
7274
|
+
if (!(content instanceof HTMLElement)) return;
|
|
7275
|
+
saveFieldGroupState(group, content.classList.contains('hidden'));
|
|
7276
|
+
});
|
|
7277
|
+
}
|
|
7278
|
+
|
|
7279
|
+
function setValidationHeaderIndicator(container) {
|
|
7280
|
+
if (!(container instanceof Element)) return;
|
|
7281
|
+
let header = null;
|
|
7282
|
+
let markerTarget = null;
|
|
7283
|
+
|
|
7284
|
+
if (container.classList.contains('field-group')) {
|
|
7285
|
+
header = container.querySelector(':scope > .field-group-header');
|
|
7286
|
+
markerTarget = container.querySelector(':scope > .field-group-header h3');
|
|
7287
|
+
} else if (container.classList.contains('structured-array-item')) {
|
|
7288
|
+
header = container.querySelector('[data-action="toggle-item"]');
|
|
7289
|
+
markerTarget = header;
|
|
7290
|
+
} else if (container.classList.contains('blocks-item')) {
|
|
7291
|
+
header = container.querySelector('[data-action="toggle-block"]');
|
|
7292
|
+
markerTarget = header;
|
|
7293
|
+
}
|
|
7294
|
+
|
|
7295
|
+
if (!(header instanceof HTMLElement)) return;
|
|
7296
|
+
if (!(markerTarget instanceof HTMLElement)) {
|
|
7297
|
+
markerTarget = header;
|
|
7298
|
+
}
|
|
7299
|
+
|
|
7300
|
+
header.dataset.validationHeaderError = 'true';
|
|
7301
|
+
header.classList.add('text-pink-700', 'dark:text-pink-300');
|
|
7302
|
+
|
|
7303
|
+
if (!markerTarget.querySelector('[data-validation-indicator]')) {
|
|
7304
|
+
const marker = document.createElement('span');
|
|
7305
|
+
marker.setAttribute('data-validation-indicator', 'true');
|
|
7306
|
+
marker.className = 'ml-2 inline-block h-2 w-2 rounded-full bg-pink-500 align-middle';
|
|
7307
|
+
marker.setAttribute('aria-hidden', 'true');
|
|
7308
|
+
markerTarget.appendChild(marker);
|
|
7309
|
+
}
|
|
7310
|
+
}
|
|
7311
|
+
|
|
7312
|
+
function clearValidationIndicators() {
|
|
7313
|
+
document.querySelectorAll('[data-validation-header-error="true"]').forEach((el) => {
|
|
7314
|
+
if (!(el instanceof HTMLElement)) return;
|
|
7315
|
+
delete el.dataset.validationHeaderError;
|
|
7316
|
+
el.classList.remove('text-pink-700', 'dark:text-pink-300');
|
|
7317
|
+
});
|
|
7318
|
+
|
|
7319
|
+
document.querySelectorAll('[data-validation-indicator]').forEach((el) => el.remove());
|
|
7320
|
+
}
|
|
7321
|
+
|
|
7322
|
+
function expandContainerForValidation(container) {
|
|
7323
|
+
if (!(container instanceof Element)) return;
|
|
7324
|
+
|
|
7325
|
+
if (container.classList.contains('field-group')) {
|
|
7326
|
+
applyFieldGroupState(container, false);
|
|
7327
|
+
return;
|
|
7328
|
+
}
|
|
7329
|
+
|
|
7330
|
+
if (container.classList.contains('structured-array-item')) {
|
|
7331
|
+
const content = container.querySelector('[data-array-item-fields]');
|
|
7332
|
+
const icon = container.querySelector('[data-item-toggle-icon]');
|
|
7333
|
+
if (content instanceof HTMLElement) {
|
|
7334
|
+
content.classList.remove('hidden');
|
|
7335
|
+
}
|
|
7336
|
+
if (icon instanceof Element) {
|
|
7337
|
+
icon.classList.remove('-rotate-90');
|
|
7338
|
+
}
|
|
7339
|
+
return;
|
|
7340
|
+
}
|
|
7341
|
+
|
|
7342
|
+
if (container.classList.contains('blocks-item')) {
|
|
7343
|
+
const content = container.querySelector('[data-block-content]');
|
|
7344
|
+
const icon = container.querySelector('[data-block-toggle-icon]');
|
|
7345
|
+
if (content instanceof HTMLElement) {
|
|
7346
|
+
content.classList.remove('hidden');
|
|
7347
|
+
}
|
|
7348
|
+
if (icon instanceof Element) {
|
|
7349
|
+
icon.classList.remove('-rotate-90');
|
|
7350
|
+
}
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
|
|
7354
|
+
function walkErrorContainers(node, expand) {
|
|
7355
|
+
if (!(node instanceof Element)) return;
|
|
7356
|
+
const visited = new Set();
|
|
7357
|
+
let cursor = node;
|
|
7358
|
+
while (cursor) {
|
|
7359
|
+
const candidates = [
|
|
7360
|
+
cursor.closest('.structured-array-item'),
|
|
7361
|
+
cursor.closest('.blocks-item'),
|
|
7362
|
+
cursor.closest('.field-group[data-group-id]')
|
|
7363
|
+
].filter((c) => c instanceof Element && !visited.has(c));
|
|
7364
|
+
|
|
7365
|
+
if (candidates.length === 0) break;
|
|
7366
|
+
|
|
7367
|
+
// Pick nearest ancestor container to preserve "first-error path only".
|
|
7368
|
+
let nearest = candidates[0];
|
|
7369
|
+
let bestDistance = Number.MAX_SAFE_INTEGER;
|
|
7370
|
+
for (const candidate of candidates) {
|
|
7371
|
+
let distance = 0;
|
|
7372
|
+
let walker = cursor;
|
|
7373
|
+
while (walker && walker !== candidate) {
|
|
7374
|
+
walker = walker.parentElement;
|
|
7375
|
+
distance += 1;
|
|
7376
|
+
}
|
|
7377
|
+
if (distance < bestDistance) {
|
|
7378
|
+
bestDistance = distance;
|
|
7379
|
+
nearest = candidate;
|
|
7380
|
+
}
|
|
7381
|
+
}
|
|
7382
|
+
|
|
7383
|
+
visited.add(nearest);
|
|
7384
|
+
setValidationHeaderIndicator(nearest);
|
|
7385
|
+
if (expand) {
|
|
7386
|
+
expandContainerForValidation(nearest);
|
|
7387
|
+
}
|
|
7388
|
+
cursor = nearest.parentElement;
|
|
7389
|
+
}
|
|
7390
|
+
}
|
|
7391
|
+
|
|
7392
|
+
function getFocusableTargetFromErrorGroup(group) {
|
|
7393
|
+
if (!(group instanceof Element)) return null;
|
|
7394
|
+
return (
|
|
7395
|
+
group.querySelector('input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"]') ||
|
|
7396
|
+
group.querySelector('button:not([disabled])')
|
|
7397
|
+
);
|
|
7398
|
+
}
|
|
7399
|
+
|
|
7400
|
+
function revealServerValidationErrors() {
|
|
7401
|
+
clearValidationIndicators();
|
|
7402
|
+
|
|
7403
|
+
const errorGroups = Array.from(document.querySelectorAll('.form-group[data-has-errors="true"]'));
|
|
7404
|
+
if (errorGroups.length === 0) return;
|
|
7405
|
+
|
|
7406
|
+
// Add indicators for all errored sections, expand only first-error path.
|
|
7407
|
+
errorGroups.forEach((group, index) => {
|
|
7408
|
+
walkErrorContainers(group, index === 0);
|
|
7409
|
+
});
|
|
7410
|
+
|
|
7411
|
+
const firstTarget = getFocusableTargetFromErrorGroup(errorGroups[0]);
|
|
7412
|
+
if (firstTarget instanceof HTMLElement) {
|
|
7413
|
+
firstTarget.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
7414
|
+
firstTarget.focus({ preventScroll: true });
|
|
7415
|
+
}
|
|
7416
|
+
}
|
|
7417
|
+
|
|
7418
|
+
function revealNativeValidationErrors(form) {
|
|
7419
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
7420
|
+
clearValidationIndicators();
|
|
7421
|
+
|
|
7422
|
+
const invalidControls = Array.from(form.querySelectorAll(':invalid'));
|
|
7423
|
+
if (invalidControls.length === 0) return;
|
|
7424
|
+
|
|
7425
|
+
invalidControls.forEach((control, index) => {
|
|
7426
|
+
walkErrorContainers(control, index === 0);
|
|
7427
|
+
});
|
|
7428
|
+
|
|
7429
|
+
const first = invalidControls[0];
|
|
7430
|
+
if (first instanceof HTMLElement) {
|
|
7431
|
+
first.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
7432
|
+
first.focus({ preventScroll: true });
|
|
7433
|
+
}
|
|
7434
|
+
}
|
|
7435
|
+
|
|
6670
7436
|
// Field group toggle
|
|
6671
|
-
function toggleFieldGroup(
|
|
6672
|
-
const content =
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
7437
|
+
function toggleFieldGroup(groupOrTrigger) {
|
|
7438
|
+
const { group, content } = resolveFieldGroupElements(groupOrTrigger);
|
|
7439
|
+
if (!(group instanceof Element)) return;
|
|
7440
|
+
if (!(content instanceof HTMLElement)) return;
|
|
7441
|
+
|
|
7442
|
+
const isCollapsed = !content.classList.contains('hidden');
|
|
7443
|
+
applyFieldGroupState(group, isCollapsed);
|
|
7444
|
+
saveFieldGroupState(group, isCollapsed);
|
|
7445
|
+
}
|
|
7446
|
+
|
|
7447
|
+
if (document.readyState === 'loading') {
|
|
7448
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
7449
|
+
restoreFieldGroupStates();
|
|
7450
|
+
const form = document.getElementById('content-form');
|
|
7451
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7452
|
+
revealServerValidationErrors();
|
|
7453
|
+
}
|
|
7454
|
+
});
|
|
7455
|
+
} else {
|
|
7456
|
+
restoreFieldGroupStates();
|
|
7457
|
+
const form = document.getElementById('content-form');
|
|
7458
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7459
|
+
revealServerValidationErrors();
|
|
6681
7460
|
}
|
|
6682
7461
|
}
|
|
6683
7462
|
|
|
7463
|
+
document.addEventListener('htmx:afterSwap', function() {
|
|
7464
|
+
setTimeout(() => {
|
|
7465
|
+
restoreFieldGroupStates();
|
|
7466
|
+
const form = document.getElementById('content-form');
|
|
7467
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7468
|
+
revealServerValidationErrors();
|
|
7469
|
+
}
|
|
7470
|
+
}, 50);
|
|
7471
|
+
});
|
|
7472
|
+
|
|
7473
|
+
const contentFormEl = document.getElementById('content-form');
|
|
7474
|
+
if (contentFormEl instanceof HTMLFormElement) {
|
|
7475
|
+
contentFormEl.addEventListener('submit', () => {
|
|
7476
|
+
persistAllFieldGroupStates();
|
|
7477
|
+
}, true);
|
|
7478
|
+
}
|
|
7479
|
+
|
|
7480
|
+
window.addEventListener('beforeunload', () => {
|
|
7481
|
+
persistAllFieldGroupStates();
|
|
7482
|
+
});
|
|
7483
|
+
|
|
7484
|
+
document.addEventListener('visibilitychange', () => {
|
|
7485
|
+
if (document.visibilityState === 'hidden') {
|
|
7486
|
+
persistAllFieldGroupStates();
|
|
7487
|
+
}
|
|
7488
|
+
});
|
|
7489
|
+
|
|
7490
|
+
let pendingNativeValidationReveal = false;
|
|
7491
|
+
document.addEventListener('invalid', function(event) {
|
|
7492
|
+
const target = event.target;
|
|
7493
|
+
if (!(target instanceof Element)) return;
|
|
7494
|
+
const form = target.closest('form');
|
|
7495
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
7496
|
+
|
|
7497
|
+
if (pendingNativeValidationReveal) return;
|
|
7498
|
+
pendingNativeValidationReveal = true;
|
|
7499
|
+
|
|
7500
|
+
// Expand only first invalid path synchronously so the browser can focus it
|
|
7501
|
+
// and avoid "invalid form control is not focusable" errors.
|
|
7502
|
+
walkErrorContainers(target, true);
|
|
7503
|
+
|
|
7504
|
+
setTimeout(() => {
|
|
7505
|
+
pendingNativeValidationReveal = false;
|
|
7506
|
+
revealNativeValidationErrors(form);
|
|
7507
|
+
}, 0);
|
|
7508
|
+
}, true);
|
|
7509
|
+
|
|
6684
7510
|
// Media field functions
|
|
6685
|
-
|
|
7511
|
+
function notifyFieldChange(input) {
|
|
7512
|
+
if (!input) return;
|
|
7513
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
7514
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
7515
|
+
}
|
|
7516
|
+
|
|
7517
|
+
function getActiveMediaModal() {
|
|
7518
|
+
const modal = document.getElementById('media-selector-modal');
|
|
7519
|
+
return modal instanceof HTMLElement ? modal : null;
|
|
7520
|
+
}
|
|
7521
|
+
|
|
7522
|
+
function getMediaFieldElements(fieldId) {
|
|
7523
|
+
if (!fieldId) {
|
|
7524
|
+
return {
|
|
7525
|
+
fieldId: '',
|
|
7526
|
+
hiddenInput: null,
|
|
7527
|
+
preview: null,
|
|
7528
|
+
mediaField: null,
|
|
7529
|
+
actionsDiv: null,
|
|
7530
|
+
};
|
|
7531
|
+
}
|
|
7532
|
+
|
|
7533
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
7534
|
+
const preview = document.getElementById(fieldId + '-preview');
|
|
7535
|
+
const mediaField = hiddenInput?.closest('.media-field-container') || null;
|
|
7536
|
+
const actionsDiv = mediaField?.querySelector('.media-actions') || null;
|
|
7537
|
+
|
|
7538
|
+
return {
|
|
7539
|
+
fieldId,
|
|
7540
|
+
hiddenInput,
|
|
7541
|
+
preview,
|
|
7542
|
+
mediaField,
|
|
7543
|
+
actionsDiv,
|
|
7544
|
+
};
|
|
7545
|
+
}
|
|
7546
|
+
|
|
7547
|
+
function getActiveMediaTarget() {
|
|
7548
|
+
const modal = getActiveMediaModal();
|
|
7549
|
+
const fieldId = modal?.dataset.targetFieldId || '';
|
|
7550
|
+
return {
|
|
7551
|
+
modal,
|
|
7552
|
+
originalValue: modal?.dataset.originalValue || '',
|
|
7553
|
+
...getMediaFieldElements(fieldId),
|
|
7554
|
+
};
|
|
7555
|
+
}
|
|
7556
|
+
|
|
7557
|
+
function ensureSingleMediaRemoveButton(fieldId, actionsDiv) {
|
|
7558
|
+
if (!(actionsDiv instanceof HTMLElement)) return;
|
|
7559
|
+
const existingRemoveButton = actionsDiv.querySelector('[data-media-remove="true"]');
|
|
7560
|
+
if (existingRemoveButton) return;
|
|
7561
|
+
|
|
7562
|
+
const removeBtn = document.createElement('button');
|
|
7563
|
+
removeBtn.type = 'button';
|
|
7564
|
+
removeBtn.setAttribute('data-media-remove', 'true');
|
|
7565
|
+
removeBtn.onclick = () => clearMediaField(fieldId);
|
|
7566
|
+
removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
|
|
7567
|
+
removeBtn.textContent = 'Remove';
|
|
7568
|
+
actionsDiv.appendChild(removeBtn);
|
|
7569
|
+
}
|
|
6686
7570
|
|
|
6687
7571
|
function openMediaSelector(fieldId) {
|
|
6688
|
-
|
|
7572
|
+
const existingModal = getActiveMediaModal();
|
|
7573
|
+
if (existingModal) {
|
|
7574
|
+
existingModal.remove();
|
|
7575
|
+
}
|
|
7576
|
+
|
|
6689
7577
|
// Store the original value in case user cancels
|
|
6690
|
-
const originalValue =
|
|
7578
|
+
const originalValue = getMediaFieldElements(fieldId).hiddenInput?.value || '';
|
|
6691
7579
|
|
|
6692
7580
|
// Open media library modal
|
|
6693
7581
|
const modal = document.createElement('div');
|
|
6694
7582
|
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50';
|
|
6695
7583
|
modal.id = 'media-selector-modal';
|
|
7584
|
+
modal.dataset.targetFieldId = fieldId;
|
|
7585
|
+
modal.dataset.originalValue = originalValue;
|
|
6696
7586
|
modal.innerHTML = \`
|
|
6697
7587
|
<div class="rounded-xl bg-white dark:bg-zinc-900 shadow-xl ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
|
|
6698
7588
|
<h3 class="text-lg font-semibold text-zinc-950 dark:text-white mb-4">Select Media</h3>
|
|
6699
7589
|
<div id="media-grid-container" hx-get="/admin/media/selector" hx-trigger="load"></div>
|
|
6700
7590
|
<div class="mt-4 flex justify-end space-x-2">
|
|
6701
7591
|
<button
|
|
6702
|
-
onclick="cancelMediaSelection(
|
|
7592
|
+
onclick="cancelMediaSelection()"
|
|
6703
7593
|
class="rounded-lg bg-white dark:bg-zinc-800 px-4 py-2 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
|
|
6704
7594
|
Cancel
|
|
6705
7595
|
</button>
|
|
@@ -6719,23 +7609,23 @@ function renderContentFormPage(data) {
|
|
|
6719
7609
|
}
|
|
6720
7610
|
|
|
6721
7611
|
function closeMediaSelector() {
|
|
6722
|
-
const modal =
|
|
7612
|
+
const modal = getActiveMediaModal();
|
|
6723
7613
|
if (modal) {
|
|
6724
7614
|
modal.remove();
|
|
6725
7615
|
}
|
|
6726
|
-
currentMediaFieldId = null;
|
|
6727
7616
|
}
|
|
6728
7617
|
|
|
6729
|
-
function cancelMediaSelection(
|
|
7618
|
+
function cancelMediaSelection() {
|
|
7619
|
+
const { hiddenInput, preview, originalValue } = getActiveMediaTarget();
|
|
7620
|
+
|
|
6730
7621
|
// Restore original value
|
|
6731
|
-
const hiddenInput = document.getElementById(fieldId);
|
|
6732
7622
|
if (hiddenInput) {
|
|
6733
7623
|
hiddenInput.value = originalValue;
|
|
7624
|
+
notifyFieldChange(hiddenInput);
|
|
6734
7625
|
}
|
|
6735
7626
|
|
|
6736
7627
|
// If original value was empty, hide the preview and show select button
|
|
6737
7628
|
if (!originalValue) {
|
|
6738
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6739
7629
|
if (preview) {
|
|
6740
7630
|
preview.classList.add('hidden');
|
|
6741
7631
|
}
|
|
@@ -6746,11 +7636,11 @@ function renderContentFormPage(data) {
|
|
|
6746
7636
|
}
|
|
6747
7637
|
|
|
6748
7638
|
function clearMediaField(fieldId) {
|
|
6749
|
-
const hiddenInput =
|
|
6750
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
7639
|
+
const { hiddenInput, preview, actionsDiv } = getMediaFieldElements(fieldId);
|
|
6751
7640
|
|
|
6752
7641
|
if (hiddenInput) {
|
|
6753
7642
|
hiddenInput.value = '';
|
|
7643
|
+
notifyFieldChange(hiddenInput);
|
|
6754
7644
|
}
|
|
6755
7645
|
|
|
6756
7646
|
if (preview) {
|
|
@@ -6760,25 +7650,34 @@ function renderContentFormPage(data) {
|
|
|
6760
7650
|
}
|
|
6761
7651
|
preview.classList.add('hidden');
|
|
6762
7652
|
}
|
|
7653
|
+
|
|
7654
|
+
const removeButton = actionsDiv?.querySelector('[data-media-remove="true"]');
|
|
7655
|
+
if (removeButton) {
|
|
7656
|
+
removeButton.remove();
|
|
7657
|
+
}
|
|
6763
7658
|
}
|
|
6764
7659
|
|
|
6765
7660
|
// Global function to remove a single media from multiple selection
|
|
6766
7661
|
window.removeMediaFromMultiple = function(fieldId, urlToRemove) {
|
|
6767
|
-
const hiddenInput =
|
|
7662
|
+
const { hiddenInput, preview } = getMediaFieldElements(fieldId);
|
|
6768
7663
|
if (!hiddenInput) return;
|
|
6769
7664
|
|
|
6770
7665
|
const values = hiddenInput.value.split(',').filter(url => url !== urlToRemove);
|
|
6771
7666
|
hiddenInput.value = values.join(',');
|
|
7667
|
+
notifyFieldChange(hiddenInput);
|
|
6772
7668
|
|
|
6773
7669
|
// Remove preview item
|
|
6774
|
-
const previewItem =
|
|
7670
|
+
const previewItem =
|
|
7671
|
+
preview &&
|
|
7672
|
+
Array.from(preview.querySelectorAll('[data-url]')).find(
|
|
7673
|
+
(item) => item.getAttribute('data-url') === urlToRemove,
|
|
7674
|
+
);
|
|
6775
7675
|
if (previewItem) {
|
|
6776
7676
|
previewItem.remove();
|
|
6777
7677
|
}
|
|
6778
7678
|
|
|
6779
7679
|
// Hide preview grid if empty
|
|
6780
7680
|
if (values.length === 0) {
|
|
6781
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6782
7681
|
if (preview) {
|
|
6783
7682
|
preview.classList.add('hidden');
|
|
6784
7683
|
}
|
|
@@ -6787,39 +7686,24 @@ function renderContentFormPage(data) {
|
|
|
6787
7686
|
|
|
6788
7687
|
// Global function called by media selector buttons
|
|
6789
7688
|
window.selectMediaFile = function(mediaId, mediaUrl, filename) {
|
|
6790
|
-
|
|
7689
|
+
const { fieldId, hiddenInput, preview, actionsDiv } = getActiveMediaTarget();
|
|
7690
|
+
if (!fieldId || !hiddenInput) {
|
|
6791
7691
|
console.error('No field ID set for media selection');
|
|
6792
7692
|
return;
|
|
6793
7693
|
}
|
|
6794
7694
|
|
|
6795
|
-
const fieldId = currentMediaFieldId;
|
|
6796
|
-
|
|
6797
7695
|
// Set the hidden input value to the media URL (not ID)
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
hiddenInput.value = mediaUrl;
|
|
6801
|
-
}
|
|
7696
|
+
hiddenInput.value = mediaUrl;
|
|
7697
|
+
notifyFieldChange(hiddenInput);
|
|
6802
7698
|
|
|
6803
7699
|
// Update the preview
|
|
6804
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6805
7700
|
if (preview) {
|
|
6806
7701
|
preview.innerHTML = \`<img src="\${mediaUrl}" alt="\${filename}" class="w-32 h-32 object-cover rounded-lg border border-white/20">\`;
|
|
6807
7702
|
preview.classList.remove('hidden');
|
|
6808
7703
|
}
|
|
6809
7704
|
|
|
6810
7705
|
// Show the remove button by finding the media actions container and updating it
|
|
6811
|
-
|
|
6812
|
-
if (mediaField) {
|
|
6813
|
-
const actionsDiv = mediaField.querySelector('.media-actions');
|
|
6814
|
-
if (actionsDiv && !actionsDiv.querySelector('button:has-text("Remove")')) {
|
|
6815
|
-
const removeBtn = document.createElement('button');
|
|
6816
|
-
removeBtn.type = 'button';
|
|
6817
|
-
removeBtn.onclick = () => clearMediaField(fieldId);
|
|
6818
|
-
removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
|
|
6819
|
-
removeBtn.textContent = 'Remove';
|
|
6820
|
-
actionsDiv.appendChild(removeBtn);
|
|
6821
|
-
}
|
|
6822
|
-
}
|
|
7706
|
+
ensureSingleMediaRemoveButton(fieldId, actionsDiv);
|
|
6823
7707
|
|
|
6824
7708
|
// DON'T close the modal - let user click OK button
|
|
6825
7709
|
// Visual feedback: highlight the selected item
|
|
@@ -6833,7 +7717,9 @@ function renderContentFormPage(data) {
|
|
|
6833
7717
|
};
|
|
6834
7718
|
|
|
6835
7719
|
function setMediaField(fieldId, mediaUrl) {
|
|
6836
|
-
document.getElementById(fieldId)
|
|
7720
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
7721
|
+
hiddenInput.value = mediaUrl;
|
|
7722
|
+
notifyFieldChange(hiddenInput);
|
|
6837
7723
|
const preview = document.getElementById(fieldId + '-preview');
|
|
6838
7724
|
preview.innerHTML = \`<img src="\${mediaUrl}" alt="Selected media" class="w-32 h-32 object-cover rounded-lg ring-1 ring-zinc-950/10 dark:ring-white/10">\`;
|
|
6839
7725
|
preview.classList.remove('hidden');
|
|
@@ -8240,6 +9126,40 @@ function renderContentListPage(data) {
|
|
|
8240
9126
|
return renderAdminLayoutCatalyst(layoutData);
|
|
8241
9127
|
}
|
|
8242
9128
|
|
|
9129
|
+
// src/routes/admin-content-field-types.ts
|
|
9130
|
+
function resolveSchemaFieldType(fieldConfig) {
|
|
9131
|
+
if (fieldConfig.type === "slug" || fieldConfig.format === "slug") {
|
|
9132
|
+
return "slug";
|
|
9133
|
+
}
|
|
9134
|
+
if (fieldConfig.type && fieldConfig.type !== "string") {
|
|
9135
|
+
return fieldConfig.type;
|
|
9136
|
+
}
|
|
9137
|
+
if (fieldConfig.format === "richtext") {
|
|
9138
|
+
return "richtext";
|
|
9139
|
+
}
|
|
9140
|
+
if (fieldConfig.format === "media") {
|
|
9141
|
+
return "media";
|
|
9142
|
+
}
|
|
9143
|
+
if (fieldConfig.format === "date-time") {
|
|
9144
|
+
return "date";
|
|
9145
|
+
}
|
|
9146
|
+
if (Array.isArray(fieldConfig.enum)) {
|
|
9147
|
+
return "select";
|
|
9148
|
+
}
|
|
9149
|
+
return fieldConfig.type || "string";
|
|
9150
|
+
}
|
|
9151
|
+
function buildSchemaFieldOptions(fieldConfig) {
|
|
9152
|
+
const fieldOptions = { ...fieldConfig };
|
|
9153
|
+
const resolvedFieldType = resolveSchemaFieldType(fieldConfig);
|
|
9154
|
+
if (resolvedFieldType === "select" && Array.isArray(fieldConfig.enum)) {
|
|
9155
|
+
fieldOptions.options = fieldConfig.enum.map((value, index) => ({
|
|
9156
|
+
value,
|
|
9157
|
+
label: fieldConfig.enumLabels?.[index] || value
|
|
9158
|
+
}));
|
|
9159
|
+
}
|
|
9160
|
+
return fieldOptions;
|
|
9161
|
+
}
|
|
9162
|
+
|
|
8243
9163
|
// src/routes/admin-content.ts
|
|
8244
9164
|
var adminContentRoutes = new Hono();
|
|
8245
9165
|
function parseFieldValue(field, formData, options = {}) {
|
|
@@ -8372,17 +9292,11 @@ async function getCollectionFields(db, collectionId) {
|
|
|
8372
9292
|
if (schema && schema.properties) {
|
|
8373
9293
|
let fieldOrder = 0;
|
|
8374
9294
|
return Object.entries(schema.properties).map(([fieldName, fieldConfig]) => {
|
|
8375
|
-
|
|
8376
|
-
if (fieldConfig.type === "select" && fieldConfig.enum) {
|
|
8377
|
-
fieldOptions.options = fieldConfig.enum.map((value, index) => ({
|
|
8378
|
-
value,
|
|
8379
|
-
label: fieldConfig.enumLabels?.[index] || value
|
|
8380
|
-
}));
|
|
8381
|
-
}
|
|
9295
|
+
const fieldOptions = buildSchemaFieldOptions(fieldConfig);
|
|
8382
9296
|
return {
|
|
8383
9297
|
id: `schema-${fieldName}`,
|
|
8384
9298
|
field_name: fieldName,
|
|
8385
|
-
field_type: fieldConfig
|
|
9299
|
+
field_type: resolveSchemaFieldType(fieldConfig),
|
|
8386
9300
|
field_label: fieldConfig.title || fieldName,
|
|
8387
9301
|
field_options: fieldOptions,
|
|
8388
9302
|
field_order: fieldOrder++,
|
|
@@ -11400,6 +12314,13 @@ function renderUsersListPage(data) {
|
|
|
11400
12314
|
// src/routes/admin-users.ts
|
|
11401
12315
|
var userRoutes = new Hono();
|
|
11402
12316
|
userRoutes.use("*", requireAuth());
|
|
12317
|
+
userRoutes.use("/users/*", requireRole(["admin"]));
|
|
12318
|
+
userRoutes.use("/users", requireRole(["admin"]));
|
|
12319
|
+
userRoutes.use("/invite-user", requireRole(["admin"]));
|
|
12320
|
+
userRoutes.use("/resend-invitation/*", requireRole(["admin"]));
|
|
12321
|
+
userRoutes.use("/cancel-invitation/*", requireRole(["admin"]));
|
|
12322
|
+
userRoutes.use("/activity-logs", requireRole(["admin"]));
|
|
12323
|
+
userRoutes.use("/activity-logs/*", requireRole(["admin"]));
|
|
11403
12324
|
userRoutes.get("/", (c) => {
|
|
11404
12325
|
return c.redirect("/admin/dashboard");
|
|
11405
12326
|
});
|
|
@@ -11887,7 +12808,9 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
11887
12808
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
11888
12809
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
11889
12810
|
const bio = sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
11890
|
-
const
|
|
12811
|
+
const roleInput = formData.get("role")?.toString() || "viewer";
|
|
12812
|
+
const validRoles = ["admin", "editor", "author", "viewer"];
|
|
12813
|
+
const role = validRoles.includes(roleInput) ? roleInput : "viewer";
|
|
11891
12814
|
const password = formData.get("password")?.toString() || "";
|
|
11892
12815
|
const confirmPassword = formData.get("confirm_password")?.toString() || "";
|
|
11893
12816
|
const isActive = formData.get("is_active") === "1";
|
|
@@ -12107,7 +13030,9 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
12107
13030
|
const username = sanitizeInput(formData.get("username")?.toString());
|
|
12108
13031
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
12109
13032
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
12110
|
-
const
|
|
13033
|
+
const roleInput = formData.get("role")?.toString() || "viewer";
|
|
13034
|
+
const validRoles = ["admin", "editor", "author", "viewer"];
|
|
13035
|
+
const role = validRoles.includes(roleInput) ? roleInput : "viewer";
|
|
12111
13036
|
const isActive = formData.get("is_active") === "1";
|
|
12112
13037
|
const emailVerified = formData.get("email_verified") === "1";
|
|
12113
13038
|
const profileDisplayName = sanitizeInput(formData.get("profile_display_name")?.toString()) || null;
|
|
@@ -17432,6 +18357,18 @@ adminPluginRoutes.post("/:id/settings", async (c) => {
|
|
|
17432
18357
|
const settings = await c.req.json();
|
|
17433
18358
|
const pluginService = new PluginService(db);
|
|
17434
18359
|
await pluginService.updatePluginSettings(pluginId, settings);
|
|
18360
|
+
if (pluginId === "core-auth") {
|
|
18361
|
+
try {
|
|
18362
|
+
const cacheKv = c.env.CACHE_KV;
|
|
18363
|
+
if (cacheKv) {
|
|
18364
|
+
await cacheKv.delete("auth:settings");
|
|
18365
|
+
await cacheKv.delete("auth:registration-enabled");
|
|
18366
|
+
console.log("[AuthSettings] Cache cleared after updating authentication settings");
|
|
18367
|
+
}
|
|
18368
|
+
} catch (cacheError) {
|
|
18369
|
+
console.error("[AuthSettings] Failed to clear cache:", cacheError);
|
|
18370
|
+
}
|
|
18371
|
+
}
|
|
17435
18372
|
return c.json({ success: true });
|
|
17436
18373
|
} catch (error) {
|
|
17437
18374
|
console.error("Error updating plugin settings:", error);
|
|
@@ -20780,6 +21717,20 @@ router.get("/system-status", async (c) => {
|
|
|
20780
21717
|
}
|
|
20781
21718
|
});
|
|
20782
21719
|
|
|
21720
|
+
// src/routes/admin-collections-field-types.ts
|
|
21721
|
+
function isMarkdownEditorType(fieldType) {
|
|
21722
|
+
return fieldType === "markdown" || fieldType === "mdxeditor" || fieldType === "easymde";
|
|
21723
|
+
}
|
|
21724
|
+
function normalizeFieldType(fieldType) {
|
|
21725
|
+
if (isMarkdownEditorType(fieldType)) {
|
|
21726
|
+
return "markdown";
|
|
21727
|
+
}
|
|
21728
|
+
if (fieldType === "tinymce") {
|
|
21729
|
+
return "richtext";
|
|
21730
|
+
}
|
|
21731
|
+
return fieldType;
|
|
21732
|
+
}
|
|
21733
|
+
|
|
20783
21734
|
// src/templates/pages/admin-collections-list.template.ts
|
|
20784
21735
|
init_admin_layout_catalyst_template();
|
|
20785
21736
|
|
|
@@ -21266,7 +22217,9 @@ function getFieldTypeBadge(fieldType) {
|
|
|
21266
22217
|
"slug": "URL Slug",
|
|
21267
22218
|
"richtext": "Rich Text (TinyMCE)",
|
|
21268
22219
|
"quill": "Rich Text (Quill)",
|
|
21269
|
-
"
|
|
22220
|
+
"markdown": "Markdown",
|
|
22221
|
+
"mdxeditor": "Markdown",
|
|
22222
|
+
"easymde": "Markdown",
|
|
21270
22223
|
"number": "Number",
|
|
21271
22224
|
"boolean": "Boolean",
|
|
21272
22225
|
"date": "Date",
|
|
@@ -21279,7 +22232,9 @@ function getFieldTypeBadge(fieldType) {
|
|
|
21279
22232
|
"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",
|
|
21280
22233
|
"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",
|
|
21281
22234
|
"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",
|
|
22235
|
+
"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",
|
|
21282
22236
|
"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",
|
|
22237
|
+
"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",
|
|
21283
22238
|
"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",
|
|
21284
22239
|
"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",
|
|
21285
22240
|
"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",
|
|
@@ -21760,7 +22715,7 @@ function renderCollectionFormPage(data) {
|
|
|
21760
22715
|
<option value="slug">URL Slug</option>
|
|
21761
22716
|
${data.editorPlugins?.tinymce ? '<option value="richtext">Rich Text (TinyMCE)</option>' : ""}
|
|
21762
22717
|
${data.editorPlugins?.quill ? '<option value="quill">Rich Text (Quill)</option>' : ""}
|
|
21763
|
-
${data.editorPlugins?.easyMdx ? '<option value="
|
|
22718
|
+
${data.editorPlugins?.easyMdx ? '<option value="markdown">Markdown</option>' : ""}
|
|
21764
22719
|
<option value="number">Number</option>
|
|
21765
22720
|
<option value="boolean">Boolean</option>
|
|
21766
22721
|
<option value="date">Date</option>
|
|
@@ -21985,7 +22940,7 @@ function renderCollectionFormPage(data) {
|
|
|
21985
22940
|
// Check if it's a schema field with field_options that might indicate the actual type
|
|
21986
22941
|
if (field.field_options && typeof field.field_options === 'object') {
|
|
21987
22942
|
// Only convert to richtext if type is explicitly 'string' and format is richtext
|
|
21988
|
-
// Don't convert if it's already a specific editor type like '
|
|
22943
|
+
// Don't convert if it's already a specific editor type like 'markdown', 'quill', etc.
|
|
21989
22944
|
if (field.field_options.format === 'richtext' && uiFieldType === 'string') {
|
|
21990
22945
|
uiFieldType = 'richtext';
|
|
21991
22946
|
}
|
|
@@ -22006,6 +22961,12 @@ function renderCollectionFormPage(data) {
|
|
|
22006
22961
|
uiFieldType = typeMapping[uiFieldType];
|
|
22007
22962
|
}
|
|
22008
22963
|
|
|
22964
|
+
if (uiFieldType === 'mdxeditor' || uiFieldType === 'easymde') {
|
|
22965
|
+
uiFieldType = 'markdown';
|
|
22966
|
+
} else if (uiFieldType === 'tinymce') {
|
|
22967
|
+
uiFieldType = 'richtext';
|
|
22968
|
+
}
|
|
22969
|
+
|
|
22009
22970
|
// Log all available options
|
|
22010
22971
|
const availableOptions = Array.from(fieldTypeSelect.options).map(opt => ({ value: opt.value, text: opt.text }));
|
|
22011
22972
|
console.log('Available dropdown options:', availableOptions);
|
|
@@ -22092,7 +23053,7 @@ function renderCollectionFormPage(data) {
|
|
|
22092
23053
|
|
|
22093
23054
|
console.log('[Edit Field] Showing options for field type:', fieldType, '(original:', field.field_type, ')');
|
|
22094
23055
|
|
|
22095
|
-
if (['select', 'radio', 'media', 'richtext', 'reference'].includes(fieldType)) {
|
|
23056
|
+
if (['select', 'radio', 'media', 'richtext', 'markdown', 'reference'].includes(fieldType)) {
|
|
22096
23057
|
optionsContainer.classList.remove('hidden');
|
|
22097
23058
|
|
|
22098
23059
|
// Set help text based on type
|
|
@@ -22109,6 +23070,9 @@ function renderCollectionFormPage(data) {
|
|
|
22109
23070
|
case 'richtext':
|
|
22110
23071
|
helpText.textContent = 'Full-featured WYSIWYG text editor with formatting options';
|
|
22111
23072
|
break;
|
|
23073
|
+
case 'markdown':
|
|
23074
|
+
helpText.textContent = 'Markdown editor with live preview powered by the EasyMDE plugin';
|
|
23075
|
+
break;
|
|
22112
23076
|
case 'reference':
|
|
22113
23077
|
helpText.textContent = 'Link to content from other collections';
|
|
22114
23078
|
break;
|
|
@@ -22249,7 +23213,7 @@ function renderCollectionFormPage(data) {
|
|
|
22249
23213
|
const fieldNameInput = document.getElementById('modal-field-name');
|
|
22250
23214
|
|
|
22251
23215
|
// Show/hide options based on field type
|
|
22252
|
-
if (['select', 'radio', 'media', 'richtext', 'guid', 'reference'].includes(this.value)) {
|
|
23216
|
+
if (['select', 'radio', 'media', 'richtext', 'markdown', 'guid', 'reference'].includes(this.value)) {
|
|
22253
23217
|
optionsContainer.classList.remove('hidden');
|
|
22254
23218
|
|
|
22255
23219
|
// Set default options and help text based on type
|
|
@@ -22270,6 +23234,10 @@ function renderCollectionFormPage(data) {
|
|
|
22270
23234
|
fieldOptions.value = '{"toolbar": "full", "height": 400}';
|
|
22271
23235
|
helpText.textContent = 'Full-featured WYSIWYG text editor with formatting options';
|
|
22272
23236
|
break;
|
|
23237
|
+
case 'markdown':
|
|
23238
|
+
fieldOptions.value = '{"toolbar": "full", "height": 400}';
|
|
23239
|
+
helpText.textContent = 'Markdown editor with live preview powered by the EasyMDE plugin';
|
|
23240
|
+
break;
|
|
22273
23241
|
case 'reference':
|
|
22274
23242
|
fieldOptions.value = '{"collection": ["pages", "posts"]}';
|
|
22275
23243
|
helpText.textContent = 'Link to content from other collections';
|
|
@@ -22345,6 +23313,9 @@ function renderCollectionFormPage(data) {
|
|
|
22345
23313
|
// src/routes/admin-collections.ts
|
|
22346
23314
|
var adminCollectionsRoutes = new Hono();
|
|
22347
23315
|
adminCollectionsRoutes.use("*", requireAuth());
|
|
23316
|
+
adminCollectionsRoutes.post("*", requireRole(["admin"]));
|
|
23317
|
+
adminCollectionsRoutes.put("*", requireRole(["admin"]));
|
|
23318
|
+
adminCollectionsRoutes.delete("*", requireRole(["admin"]));
|
|
22348
23319
|
adminCollectionsRoutes.get("/", async (c) => {
|
|
22349
23320
|
try {
|
|
22350
23321
|
const user = c.get("user");
|
|
@@ -22830,11 +23801,12 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
|
|
|
22830
23801
|
searchable: isSearchable,
|
|
22831
23802
|
...parsedOptions
|
|
22832
23803
|
};
|
|
22833
|
-
|
|
23804
|
+
const normalizedFieldType = normalizeFieldType(fieldType);
|
|
23805
|
+
if (normalizedFieldType === "richtext") {
|
|
22834
23806
|
fieldConfig.format = "richtext";
|
|
22835
|
-
} else if (
|
|
23807
|
+
} else if (normalizedFieldType === "date") {
|
|
22836
23808
|
fieldConfig.format = "date-time";
|
|
22837
|
-
} else if (
|
|
23809
|
+
} else if (normalizedFieldType === "select") {
|
|
22838
23810
|
fieldConfig.enum = parsedOptions.options || [];
|
|
22839
23811
|
} else if (fieldType === "radio") {
|
|
22840
23812
|
fieldConfig.type = "radio";
|
|
@@ -22843,20 +23815,14 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
|
|
|
22843
23815
|
}
|
|
22844
23816
|
} else if (fieldType === "media") {
|
|
22845
23817
|
fieldConfig.format = "media";
|
|
22846
|
-
} else if (
|
|
23818
|
+
} else if (normalizedFieldType === "slug") {
|
|
22847
23819
|
fieldConfig.type = "slug";
|
|
22848
23820
|
fieldConfig.format = "slug";
|
|
22849
|
-
} else if (
|
|
23821
|
+
} else if (normalizedFieldType === "quill") {
|
|
22850
23822
|
fieldConfig.type = "quill";
|
|
22851
|
-
} else if (
|
|
22852
|
-
fieldConfig.type = "mdxeditor";
|
|
22853
|
-
} else if (fieldType === "tinymce") {
|
|
22854
|
-
fieldConfig.type = "tinymce";
|
|
22855
|
-
} else if (fieldType === "easymde") {
|
|
22856
|
-
fieldConfig.type = "easymde";
|
|
22857
|
-
} else if (fieldType === "markdown") {
|
|
23823
|
+
} else if (normalizedFieldType === "markdown") {
|
|
22858
23824
|
fieldConfig.type = "markdown";
|
|
22859
|
-
} else if (
|
|
23825
|
+
} else if (normalizedFieldType === "reference") {
|
|
22860
23826
|
fieldConfig.type = "reference";
|
|
22861
23827
|
}
|
|
22862
23828
|
schema.properties[fieldName] = fieldConfig;
|
|
@@ -27461,6 +28427,33 @@ var public_forms_default = publicFormsRoutes;
|
|
|
27461
28427
|
|
|
27462
28428
|
// src/templates/pages/admin-api-reference.template.ts
|
|
27463
28429
|
init_admin_layout_catalyst_template();
|
|
28430
|
+
function renderAuthBadge(auth) {
|
|
28431
|
+
if (auth === true) {
|
|
28432
|
+
return `
|
|
28433
|
+
<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">
|
|
28434
|
+
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
28435
|
+
<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"/>
|
|
28436
|
+
</svg>
|
|
28437
|
+
Auth
|
|
28438
|
+
</span>`;
|
|
28439
|
+
}
|
|
28440
|
+
if (auth === false) {
|
|
28441
|
+
return `
|
|
28442
|
+
<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">
|
|
28443
|
+
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
28444
|
+
<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"/>
|
|
28445
|
+
</svg>
|
|
28446
|
+
Public
|
|
28447
|
+
</span>`;
|
|
28448
|
+
}
|
|
28449
|
+
return `
|
|
28450
|
+
<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">
|
|
28451
|
+
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
28452
|
+
<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"/>
|
|
28453
|
+
</svg>
|
|
28454
|
+
Unknown
|
|
28455
|
+
</span>`;
|
|
28456
|
+
}
|
|
27464
28457
|
function renderAPIReferencePage(data) {
|
|
27465
28458
|
const endpointsByCategory = data.endpoints.reduce((acc, endpoint) => {
|
|
27466
28459
|
if (!acc[endpoint.category]) {
|
|
@@ -27469,40 +28462,18 @@ function renderAPIReferencePage(data) {
|
|
|
27469
28462
|
acc[endpoint.category].push(endpoint);
|
|
27470
28463
|
return acc;
|
|
27471
28464
|
}, {});
|
|
27472
|
-
const
|
|
27473
|
-
|
|
27474
|
-
|
|
27475
|
-
|
|
27476
|
-
|
|
27477
|
-
},
|
|
27478
|
-
"Content": {
|
|
27479
|
-
title: "Content Management",
|
|
27480
|
-
description: "Content creation, retrieval, and management",
|
|
27481
|
-
icon: "\u{1F4DD}"
|
|
27482
|
-
},
|
|
27483
|
-
"Media": {
|
|
27484
|
-
title: "Media Management",
|
|
27485
|
-
description: "File upload, storage, and media operations",
|
|
27486
|
-
icon: "\u{1F5BC}\uFE0F"
|
|
27487
|
-
},
|
|
27488
|
-
"Admin": {
|
|
27489
|
-
title: "Admin Interface",
|
|
27490
|
-
description: "Administrative panel and management features",
|
|
27491
|
-
icon: "\u2699\uFE0F"
|
|
27492
|
-
},
|
|
27493
|
-
"System": {
|
|
27494
|
-
title: "System",
|
|
27495
|
-
description: "Health checks and system information",
|
|
27496
|
-
icon: "\u{1F527}"
|
|
27497
|
-
}
|
|
27498
|
-
};
|
|
28465
|
+
const categories = Object.keys(endpointsByCategory);
|
|
28466
|
+
const totalEndpoints = data.endpoints.length;
|
|
28467
|
+
const publicEndpoints = data.endpoints.filter((e) => e.authentication === false).length;
|
|
28468
|
+
const protectedEndpoints = data.endpoints.filter((e) => e.authentication === true).length;
|
|
28469
|
+
const undocumentedCount = data.endpoints.filter((e) => e.documented === false).length;
|
|
27499
28470
|
const pageContent = `
|
|
27500
28471
|
<div class="space-y-6">
|
|
27501
28472
|
<!-- Header -->
|
|
27502
28473
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
|
27503
28474
|
<div>
|
|
27504
28475
|
<h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">API Reference</h1>
|
|
27505
|
-
<p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">
|
|
28476
|
+
<p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">Auto-discovered documentation of all registered API endpoints</p>
|
|
27506
28477
|
</div>
|
|
27507
28478
|
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
|
27508
28479
|
<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">
|
|
@@ -27515,29 +28486,35 @@ function renderAPIReferencePage(data) {
|
|
|
27515
28486
|
</div>
|
|
27516
28487
|
|
|
27517
28488
|
<!-- Stats -->
|
|
27518
|
-
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-
|
|
28489
|
+
<dl class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5">
|
|
27519
28490
|
<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">
|
|
27520
28491
|
<dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Total Endpoints</dt>
|
|
27521
28492
|
<dd class="mt-2 flex items-baseline gap-x-2">
|
|
27522
|
-
<span class="text-4xl font-semibold tracking-tight text-zinc-950 dark:text-white">${
|
|
28493
|
+
<span class="text-4xl font-semibold tracking-tight text-zinc-950 dark:text-white">${totalEndpoints}</span>
|
|
27523
28494
|
</dd>
|
|
27524
28495
|
</div>
|
|
27525
28496
|
<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">
|
|
27526
28497
|
<dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Public Endpoints</dt>
|
|
27527
28498
|
<dd class="mt-2 flex items-baseline gap-x-2">
|
|
27528
|
-
<span class="text-4xl font-semibold tracking-tight text-lime-600 dark:text-lime-400">${
|
|
28499
|
+
<span class="text-4xl font-semibold tracking-tight text-lime-600 dark:text-lime-400">${publicEndpoints}</span>
|
|
27529
28500
|
</dd>
|
|
27530
28501
|
</div>
|
|
27531
28502
|
<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">
|
|
27532
28503
|
<dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Protected Endpoints</dt>
|
|
27533
28504
|
<dd class="mt-2 flex items-baseline gap-x-2">
|
|
27534
|
-
<span class="text-4xl font-semibold tracking-tight text-amber-600 dark:text-amber-400">${
|
|
28505
|
+
<span class="text-4xl font-semibold tracking-tight text-amber-600 dark:text-amber-400">${protectedEndpoints}</span>
|
|
27535
28506
|
</dd>
|
|
27536
28507
|
</div>
|
|
27537
28508
|
<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">
|
|
27538
28509
|
<dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Categories</dt>
|
|
27539
28510
|
<dd class="mt-2 flex items-baseline gap-x-2">
|
|
27540
|
-
<span class="text-4xl font-semibold tracking-tight text-cyan-600 dark:text-cyan-400">${
|
|
28511
|
+
<span class="text-4xl font-semibold tracking-tight text-cyan-600 dark:text-cyan-400">${categories.length}</span>
|
|
28512
|
+
</dd>
|
|
28513
|
+
</div>
|
|
28514
|
+
<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">
|
|
28515
|
+
<dt class="text-sm/6 font-medium text-zinc-500 dark:text-zinc-400">Undocumented</dt>
|
|
28516
|
+
<dd class="mt-2 flex items-baseline gap-x-2">
|
|
28517
|
+
<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>
|
|
27541
28518
|
</dd>
|
|
27542
28519
|
</div>
|
|
27543
28520
|
</dl>
|
|
@@ -27589,9 +28566,11 @@ function renderAPIReferencePage(data) {
|
|
|
27589
28566
|
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]"
|
|
27590
28567
|
>
|
|
27591
28568
|
<option value="">All Categories</option>
|
|
27592
|
-
${
|
|
27593
|
-
|
|
27594
|
-
|
|
28569
|
+
${categories.map((category) => {
|
|
28570
|
+
const info = CATEGORY_INFO[category];
|
|
28571
|
+
const title = info ? info.title : category;
|
|
28572
|
+
return `<option value="${category}">${title}</option>`;
|
|
28573
|
+
}).join("\n ")}
|
|
27595
28574
|
</select>
|
|
27596
28575
|
<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">
|
|
27597
28576
|
<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" />
|
|
@@ -27605,7 +28584,7 @@ function renderAPIReferencePage(data) {
|
|
|
27605
28584
|
<!-- API Categories -->
|
|
27606
28585
|
<div class="space-y-6">
|
|
27607
28586
|
${Object.entries(endpointsByCategory).map(([category, endpoints]) => {
|
|
27608
|
-
const info =
|
|
28587
|
+
const info = CATEGORY_INFO[category] || { title: category, description: "", icon: "📋" };
|
|
27609
28588
|
return `
|
|
27610
28589
|
<div class="api-category" data-category="${category}">
|
|
27611
28590
|
<div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 overflow-hidden">
|
|
@@ -27639,23 +28618,14 @@ function renderAPIReferencePage(data) {
|
|
|
27639
28618
|
<div class="flex-1 min-w-0">
|
|
27640
28619
|
<div class="flex items-center gap-x-2 mb-2">
|
|
27641
28620
|
<code class="text-zinc-950 dark:text-white text-sm font-mono font-medium break-all">${endpoint.path}</code>
|
|
27642
|
-
${endpoint.authentication
|
|
27643
|
-
|
|
27644
|
-
|
|
27645
|
-
|
|
27646
|
-
</svg>
|
|
27647
|
-
Auth
|
|
27648
|
-
</span>
|
|
27649
|
-
` : `
|
|
27650
|
-
<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">
|
|
27651
|
-
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
27652
|
-
<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"/>
|
|
27653
|
-
</svg>
|
|
27654
|
-
Public
|
|
28621
|
+
${renderAuthBadge(endpoint.authentication)}
|
|
28622
|
+
${endpoint.documented === false ? `
|
|
28623
|
+
<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">
|
|
28624
|
+
Auto-discovered
|
|
27655
28625
|
</span>
|
|
27656
|
-
`}
|
|
28626
|
+
` : ""}
|
|
27657
28627
|
</div>
|
|
27658
|
-
<p class="text-zinc-600 dark:text-zinc-400 text-sm leading-6">${endpoint.description}</p>
|
|
28628
|
+
<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>
|
|
27659
28629
|
</div>
|
|
27660
28630
|
</div>
|
|
27661
28631
|
</div>
|
|
@@ -27733,8 +28703,8 @@ function renderAPIReferencePage(data) {
|
|
|
27733
28703
|
const path = endpoint.dataset.path.toLowerCase();
|
|
27734
28704
|
const description = endpoint.dataset.description.toLowerCase();
|
|
27735
28705
|
|
|
27736
|
-
const matchesSearch = !searchTerm ||
|
|
27737
|
-
path.includes(searchTerm) ||
|
|
28706
|
+
const matchesSearch = !searchTerm ||
|
|
28707
|
+
path.includes(searchTerm) ||
|
|
27738
28708
|
description.includes(searchTerm);
|
|
27739
28709
|
const matchesMethod = !selectedMethod || method === selectedMethod;
|
|
27740
28710
|
|
|
@@ -27794,207 +28764,13 @@ function renderAPIReferencePage(data) {
|
|
|
27794
28764
|
var VERSION2 = getCoreVersion();
|
|
27795
28765
|
var router2 = new Hono();
|
|
27796
28766
|
router2.use("*", requireAuth());
|
|
27797
|
-
var apiEndpoints = [
|
|
27798
|
-
// Auth endpoints
|
|
27799
|
-
{
|
|
27800
|
-
method: "POST",
|
|
27801
|
-
path: "/auth/login",
|
|
27802
|
-
description: "Authenticate user with email and password",
|
|
27803
|
-
authentication: false,
|
|
27804
|
-
category: "Auth"
|
|
27805
|
-
},
|
|
27806
|
-
{
|
|
27807
|
-
method: "POST",
|
|
27808
|
-
path: "/auth/register",
|
|
27809
|
-
description: "Register a new user account",
|
|
27810
|
-
authentication: false,
|
|
27811
|
-
category: "Auth"
|
|
27812
|
-
},
|
|
27813
|
-
{
|
|
27814
|
-
method: "POST",
|
|
27815
|
-
path: "/auth/logout",
|
|
27816
|
-
description: "Log out the current user and invalidate session",
|
|
27817
|
-
authentication: true,
|
|
27818
|
-
category: "Auth"
|
|
27819
|
-
},
|
|
27820
|
-
{
|
|
27821
|
-
method: "GET",
|
|
27822
|
-
path: "/auth/me",
|
|
27823
|
-
description: "Get current authenticated user information",
|
|
27824
|
-
authentication: true,
|
|
27825
|
-
category: "Auth"
|
|
27826
|
-
},
|
|
27827
|
-
{
|
|
27828
|
-
method: "POST",
|
|
27829
|
-
path: "/auth/refresh",
|
|
27830
|
-
description: "Refresh authentication token",
|
|
27831
|
-
authentication: true,
|
|
27832
|
-
category: "Auth"
|
|
27833
|
-
},
|
|
27834
|
-
// Content endpoints
|
|
27835
|
-
{
|
|
27836
|
-
method: "GET",
|
|
27837
|
-
path: "/api/collections",
|
|
27838
|
-
description: "List all available collections",
|
|
27839
|
-
authentication: false,
|
|
27840
|
-
category: "Content"
|
|
27841
|
-
},
|
|
27842
|
-
{
|
|
27843
|
-
method: "GET",
|
|
27844
|
-
path: "/api/collections/:collection/content",
|
|
27845
|
-
description: "Get all content items from a specific collection",
|
|
27846
|
-
authentication: false,
|
|
27847
|
-
category: "Content"
|
|
27848
|
-
},
|
|
27849
|
-
{
|
|
27850
|
-
method: "GET",
|
|
27851
|
-
path: "/api/content/:id",
|
|
27852
|
-
description: "Get a specific content item by ID",
|
|
27853
|
-
authentication: false,
|
|
27854
|
-
category: "Content"
|
|
27855
|
-
},
|
|
27856
|
-
{
|
|
27857
|
-
method: "POST",
|
|
27858
|
-
path: "/api/content",
|
|
27859
|
-
description: "Create a new content item",
|
|
27860
|
-
authentication: true,
|
|
27861
|
-
category: "Content"
|
|
27862
|
-
},
|
|
27863
|
-
{
|
|
27864
|
-
method: "PUT",
|
|
27865
|
-
path: "/api/content/:id",
|
|
27866
|
-
description: "Update an existing content item",
|
|
27867
|
-
authentication: true,
|
|
27868
|
-
category: "Content"
|
|
27869
|
-
},
|
|
27870
|
-
{
|
|
27871
|
-
method: "DELETE",
|
|
27872
|
-
path: "/api/content/:id",
|
|
27873
|
-
description: "Delete a content item",
|
|
27874
|
-
authentication: true,
|
|
27875
|
-
category: "Content"
|
|
27876
|
-
},
|
|
27877
|
-
// Media endpoints
|
|
27878
|
-
{
|
|
27879
|
-
method: "GET",
|
|
27880
|
-
path: "/api/media",
|
|
27881
|
-
description: "List all media files with pagination",
|
|
27882
|
-
authentication: false,
|
|
27883
|
-
category: "Media"
|
|
27884
|
-
},
|
|
27885
|
-
{
|
|
27886
|
-
method: "GET",
|
|
27887
|
-
path: "/api/media/:id",
|
|
27888
|
-
description: "Get a specific media file by ID",
|
|
27889
|
-
authentication: false,
|
|
27890
|
-
category: "Media"
|
|
27891
|
-
},
|
|
27892
|
-
{
|
|
27893
|
-
method: "POST",
|
|
27894
|
-
path: "/api/media/upload",
|
|
27895
|
-
description: "Upload a new media file to R2 storage",
|
|
27896
|
-
authentication: true,
|
|
27897
|
-
category: "Media"
|
|
27898
|
-
},
|
|
27899
|
-
{
|
|
27900
|
-
method: "DELETE",
|
|
27901
|
-
path: "/api/media/:id",
|
|
27902
|
-
description: "Delete a media file from storage",
|
|
27903
|
-
authentication: true,
|
|
27904
|
-
category: "Media"
|
|
27905
|
-
},
|
|
27906
|
-
// Admin endpoints
|
|
27907
|
-
{
|
|
27908
|
-
method: "GET",
|
|
27909
|
-
path: "/admin/api/stats",
|
|
27910
|
-
description: "Get dashboard statistics (collections, content, media, users)",
|
|
27911
|
-
authentication: true,
|
|
27912
|
-
category: "Admin"
|
|
27913
|
-
},
|
|
27914
|
-
{
|
|
27915
|
-
method: "GET",
|
|
27916
|
-
path: "/admin/api/storage",
|
|
27917
|
-
description: "Get storage usage information",
|
|
27918
|
-
authentication: true,
|
|
27919
|
-
category: "Admin"
|
|
27920
|
-
},
|
|
27921
|
-
{
|
|
27922
|
-
method: "GET",
|
|
27923
|
-
path: "/admin/api/activity",
|
|
27924
|
-
description: "Get recent activity logs",
|
|
27925
|
-
authentication: true,
|
|
27926
|
-
category: "Admin"
|
|
27927
|
-
},
|
|
27928
|
-
{
|
|
27929
|
-
method: "GET",
|
|
27930
|
-
path: "/admin/api/collections",
|
|
27931
|
-
description: "List all collections with field counts",
|
|
27932
|
-
authentication: true,
|
|
27933
|
-
category: "Admin"
|
|
27934
|
-
},
|
|
27935
|
-
{
|
|
27936
|
-
method: "POST",
|
|
27937
|
-
path: "/admin/api/collections",
|
|
27938
|
-
description: "Create a new collection",
|
|
27939
|
-
authentication: true,
|
|
27940
|
-
category: "Admin"
|
|
27941
|
-
},
|
|
27942
|
-
{
|
|
27943
|
-
method: "PATCH",
|
|
27944
|
-
path: "/admin/api/collections/:id",
|
|
27945
|
-
description: "Update an existing collection",
|
|
27946
|
-
authentication: true,
|
|
27947
|
-
category: "Admin"
|
|
27948
|
-
},
|
|
27949
|
-
{
|
|
27950
|
-
method: "DELETE",
|
|
27951
|
-
path: "/admin/api/collections/:id",
|
|
27952
|
-
description: "Delete a collection (must be empty)",
|
|
27953
|
-
authentication: true,
|
|
27954
|
-
category: "Admin"
|
|
27955
|
-
},
|
|
27956
|
-
{
|
|
27957
|
-
method: "GET",
|
|
27958
|
-
path: "/admin/api/migrations/status",
|
|
27959
|
-
description: "Get database migration status",
|
|
27960
|
-
authentication: true,
|
|
27961
|
-
category: "Admin"
|
|
27962
|
-
},
|
|
27963
|
-
{
|
|
27964
|
-
method: "POST",
|
|
27965
|
-
path: "/admin/api/migrations/run",
|
|
27966
|
-
description: "Run pending database migrations",
|
|
27967
|
-
authentication: true,
|
|
27968
|
-
category: "Admin"
|
|
27969
|
-
},
|
|
27970
|
-
// System endpoints
|
|
27971
|
-
{
|
|
27972
|
-
method: "GET",
|
|
27973
|
-
path: "/health",
|
|
27974
|
-
description: "Health check endpoint for monitoring",
|
|
27975
|
-
authentication: false,
|
|
27976
|
-
category: "System"
|
|
27977
|
-
},
|
|
27978
|
-
{
|
|
27979
|
-
method: "GET",
|
|
27980
|
-
path: "/api/health",
|
|
27981
|
-
description: "API health check with schema information",
|
|
27982
|
-
authentication: false,
|
|
27983
|
-
category: "System"
|
|
27984
|
-
},
|
|
27985
|
-
{
|
|
27986
|
-
method: "GET",
|
|
27987
|
-
path: "/api",
|
|
27988
|
-
description: "API root - returns API information and OpenAPI spec",
|
|
27989
|
-
authentication: false,
|
|
27990
|
-
category: "System"
|
|
27991
|
-
}
|
|
27992
|
-
];
|
|
27993
28767
|
router2.get("/", async (c) => {
|
|
27994
28768
|
const user = c.get("user");
|
|
27995
28769
|
try {
|
|
28770
|
+
const app2 = getAppInstance();
|
|
28771
|
+
const endpoints = buildRouteList(app2);
|
|
27996
28772
|
const pageData = {
|
|
27997
|
-
endpoints
|
|
28773
|
+
endpoints,
|
|
27998
28774
|
user: user ? {
|
|
27999
28775
|
name: user.email.split("@")[0] || user.email,
|
|
28000
28776
|
email: user.email,
|
|
@@ -28050,5 +28826,5 @@ var ROUTES_INFO = {
|
|
|
28050
28826
|
};
|
|
28051
28827
|
|
|
28052
28828
|
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 };
|
|
28053
|
-
//# sourceMappingURL=chunk-
|
|
28054
|
-
//# sourceMappingURL=chunk-
|
|
28829
|
+
//# sourceMappingURL=chunk-JTNUM7JE.js.map
|
|
28830
|
+
//# sourceMappingURL=chunk-JTNUM7JE.js.map
|