@sonicjs-cms/core 2.9.0 → 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/{chunk-YFJJU26H.js → chunk-27AOVQTR.js} +10 -2
- package/dist/chunk-27AOVQTR.js.map +1 -0
- package/dist/{chunk-25YNV4RK.js → chunk-4TTMQQC7.js} +4 -4
- package/dist/{chunk-25YNV4RK.js.map → chunk-4TTMQQC7.js.map} +1 -1
- package/dist/{chunk-STTZVLY2.js → chunk-6O3RJV3C.js} +2 -2
- package/dist/{chunk-STTZVLY2.js.map → chunk-6O3RJV3C.js.map} +1 -1
- package/dist/{chunk-SHU7Q66Q.cjs → chunk-EKPLKUZT.cjs} +7 -3
- 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-DQZVU3WB.cjs → chunk-IT2TC4ZD.cjs} +7 -7
- package/dist/{chunk-DQZVU3WB.cjs.map → chunk-IT2TC4ZD.cjs.map} +1 -1
- package/dist/{chunk-3FHMXGLF.js → chunk-IZWNIUJI.js} +7 -3
- package/dist/chunk-IZWNIUJI.js.map +1 -0
- package/dist/{chunk-2JGQKF7B.js → chunk-JTNUM7JE.js} +1042 -152
- package/dist/chunk-JTNUM7JE.js.map +1 -0
- package/dist/{chunk-KSB6FXOP.cjs → chunk-RCA6R6VE.cjs} +1130 -240
- package/dist/chunk-RCA6R6VE.cjs.map +1 -0
- package/dist/{chunk-LDFMYRG6.cjs → chunk-ZMVWMJ3S.cjs} +2 -2
- package/dist/{chunk-LDFMYRG6.cjs.map → chunk-ZMVWMJ3S.cjs.map} +1 -1
- package/dist/{collection-config-DckWhkdL.d.cts → collection-config-B4PG-AaF.d.cts} +2 -0
- package/dist/{collection-config-DckWhkdL.d.ts → collection-config-B4PG-AaF.d.ts} +2 -0
- package/dist/index.cjs +97 -97
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -8
- package/dist/middleware.cjs +29 -29
- package/dist/middleware.js +3 -3
- package/dist/migrations-N2C2VPJU.js +4 -0
- package/dist/{migrations-SZSR3C3G.js.map → migrations-N2C2VPJU.js.map} +1 -1
- package/dist/migrations-ONIAY6GK.cjs +13 -0
- package/dist/{migrations-QQWGDWGB.cjs.map → migrations-ONIAY6GK.cjs.map} +1 -1
- package/dist/{plugin-bootstrap-BAz7NY0H.d.cts → plugin-bootstrap-WmpvYM5w.d.ts} +1 -1
- package/dist/{plugin-bootstrap-Cz3-bj8X.d.ts → plugin-bootstrap-fpG98Otb.d.cts} +1 -1
- package/dist/routes.cjs +28 -28
- package/dist/routes.js +5 -5
- package/dist/services.cjs +16 -16
- package/dist/services.d.cts +2 -2
- package/dist/services.d.ts +2 -2
- package/dist/services.js +2 -2
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils.cjs +11 -11
- package/dist/utils.d.cts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/package.json +5 -1
- package/dist/chunk-2JGQKF7B.js.map +0 -1
- package/dist/chunk-3FHMXGLF.js.map +0 -1
- package/dist/chunk-KSB6FXOP.cjs.map +0 -1
- package/dist/chunk-MPT5PA6U.cjs.map +0 -1
- package/dist/chunk-SHU7Q66Q.cjs.map +0 -1
- package/dist/chunk-YFJJU26H.js.map +0 -1
- package/dist/migrations-QQWGDWGB.cjs +0 -13
- package/dist/migrations-SZSR3C3G.js +0 -4
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService, getAppInstance, buildRouteList, CATEGORY_INFO } from './chunk-7JMMLHPQ.js';
|
|
2
|
-
import { requireAuth, isPluginActive, optionalAuth,
|
|
3
|
-
import { PluginService } from './chunk-
|
|
4
|
-
import { MigrationService } from './chunk-
|
|
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();
|
|
@@ -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,15 @@ 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
|
+
}
|
|
4836
4918
|
function isMarkdownEditorFieldType(fieldType) {
|
|
4837
4919
|
return fieldType === "markdown" || fieldType === "mdxeditor" || fieldType === "easymde";
|
|
4838
4920
|
}
|
|
@@ -5413,12 +5495,14 @@ function renderDynamicField(field, options = {}) {
|
|
|
5413
5495
|
|
|
5414
5496
|
${isMultiple ? `
|
|
5415
5497
|
<div class="media-preview-grid grid grid-cols-4 gap-2 mb-2 ${mediaValues.length === 0 ? "hidden" : ""}" id="${fieldId}-preview">
|
|
5416
|
-
${mediaValues.map(
|
|
5498
|
+
${mediaValues.map(
|
|
5499
|
+
(url, idx) => `
|
|
5417
5500
|
<div class="relative media-preview-item" data-url="${url}">
|
|
5418
5501
|
${renderMediaPreview(url, `Media ${idx + 1}`, "w-full h-24 object-cover rounded-lg border border-white/20")}
|
|
5419
5502
|
<button
|
|
5420
5503
|
type="button"
|
|
5421
5504
|
onclick="removeMediaFromMultiple('${fieldId}', '${url}')"
|
|
5505
|
+
data-media-remove="true"
|
|
5422
5506
|
class="absolute top-1 right-1 bg-red-600 text-white rounded-full p-1 hover:bg-red-700"
|
|
5423
5507
|
${disabled ? "disabled" : ""}
|
|
5424
5508
|
>
|
|
@@ -5427,7 +5511,8 @@ function renderDynamicField(field, options = {}) {
|
|
|
5427
5511
|
</svg>
|
|
5428
5512
|
</button>
|
|
5429
5513
|
</div>
|
|
5430
|
-
`
|
|
5514
|
+
`
|
|
5515
|
+
).join("")}
|
|
5431
5516
|
</div>
|
|
5432
5517
|
` : `
|
|
5433
5518
|
<div class="media-preview ${singleValue ? "" : "hidden"}" id="${fieldId}-preview">
|
|
@@ -5451,6 +5536,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5451
5536
|
<button
|
|
5452
5537
|
type="button"
|
|
5453
5538
|
onclick="clearMediaField('${fieldId}')"
|
|
5539
|
+
data-media-remove="true"
|
|
5454
5540
|
class="inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all"
|
|
5455
5541
|
${disabled ? "disabled" : ""}
|
|
5456
5542
|
>
|
|
@@ -5484,7 +5570,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5484
5570
|
}
|
|
5485
5571
|
const showLabel = field.field_type !== "boolean";
|
|
5486
5572
|
return `
|
|
5487
|
-
<div class="form-group">
|
|
5573
|
+
<div class="form-group" data-has-errors="${errors.length > 0 ? "true" : "false"}">
|
|
5488
5574
|
${showLabel ? `
|
|
5489
5575
|
<label for="${fieldId}" class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2">
|
|
5490
5576
|
${escapeHtml3(field.field_label)}
|
|
@@ -5493,7 +5579,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5493
5579
|
` : ""}
|
|
5494
5580
|
${fieldHTML}
|
|
5495
5581
|
${errors.length > 0 ? `
|
|
5496
|
-
<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>
|
|
5497
5583
|
${errors.map((error) => `<div>${escapeHtml3(error)}</div>`).join("")}
|
|
5498
5584
|
</div>
|
|
5499
5585
|
` : ""}
|
|
@@ -5508,8 +5594,8 @@ function renderDynamicField(field, options = {}) {
|
|
|
5508
5594
|
function renderFieldGroup(title, fields, collapsible = false) {
|
|
5509
5595
|
const groupId = title.toLowerCase().replace(/\s+/g, "-");
|
|
5510
5596
|
return `
|
|
5511
|
-
<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">
|
|
5512
|
-
<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)"` : ""}>
|
|
5513
5599
|
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
|
|
5514
5600
|
${escapeHtml3(title)}
|
|
5515
5601
|
${collapsible ? `
|
|
@@ -5553,6 +5639,12 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
|
5553
5639
|
>
|
|
5554
5640
|
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(blockValues))}">
|
|
5555
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
|
+
|
|
5556
5648
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5557
5649
|
<div class="flex-1">
|
|
5558
5650
|
<select
|
|
@@ -5583,12 +5675,14 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
|
5583
5675
|
`;
|
|
5584
5676
|
}
|
|
5585
5677
|
function renderStructuredObjectField(field, options, baseClasses, errorClasses) {
|
|
5586
|
-
const { value = {}, pluginStatuses = {} } = options;
|
|
5678
|
+
const { value = {}, pluginStatuses = {}, errors = [] } = options;
|
|
5587
5679
|
const opts = field.field_options || {};
|
|
5588
5680
|
const properties = opts.properties && typeof opts.properties === "object" ? opts.properties : {};
|
|
5589
5681
|
const fieldId = `field-${field.field_name}`;
|
|
5590
5682
|
const fieldName = field.field_name;
|
|
5591
5683
|
const objectValue = normalizeStructuredObjectValue(value);
|
|
5684
|
+
const objectLayout = opts.objectLayout || "nested";
|
|
5685
|
+
const useNestedLayout = objectLayout !== "flat";
|
|
5592
5686
|
const subfields = Object.entries(properties).map(
|
|
5593
5687
|
([propertyName, propertyConfig]) => renderStructuredSubfield(
|
|
5594
5688
|
field,
|
|
@@ -5599,11 +5693,40 @@ function renderStructuredObjectField(field, options, baseClasses, errorClasses)
|
|
|
5599
5693
|
field.field_name
|
|
5600
5694
|
)
|
|
5601
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;
|
|
5602
5715
|
return `
|
|
5603
|
-
<div class="
|
|
5604
|
-
<
|
|
5605
|
-
|
|
5606
|
-
|
|
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>
|
|
5607
5730
|
</div>
|
|
5608
5731
|
</div>
|
|
5609
5732
|
${getStructuredFieldScript()}
|
|
@@ -5674,7 +5797,7 @@ function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
|
|
|
5674
5797
|
function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginStatuses, arrayItemTitle) {
|
|
5675
5798
|
const itemFields = renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses);
|
|
5676
5799
|
return `
|
|
5677
|
-
<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">
|
|
5678
5801
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5679
5802
|
<div class="flex items-center gap-3">
|
|
5680
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">
|
|
@@ -5687,6 +5810,11 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
|
|
|
5687
5810
|
</div>
|
|
5688
5811
|
</div>
|
|
5689
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>
|
|
5690
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">
|
|
5691
5819
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
5692
5820
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
@@ -5705,7 +5833,7 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
|
|
|
5705
5833
|
</button>
|
|
5706
5834
|
</div>
|
|
5707
5835
|
</div>
|
|
5708
|
-
<div class="mt-4 space-y-4" data-array-item-fields>
|
|
5836
|
+
<div class="mt-4 space-y-4 hidden" data-array-item-fields>
|
|
5709
5837
|
${itemFields}
|
|
5710
5838
|
</div>
|
|
5711
5839
|
</div>
|
|
@@ -5815,7 +5943,7 @@ function normalizeBlocksValue(value, discriminator) {
|
|
|
5815
5943
|
function renderBlockTemplate(field, block, discriminator, pluginStatuses) {
|
|
5816
5944
|
return `
|
|
5817
5945
|
<template data-block-template="${escapeHtml3(block.name)}">
|
|
5818
|
-
${renderBlockCard(field, block, discriminator,
|
|
5946
|
+
${renderBlockCard(field, block, discriminator, BLOCK_INDEX_TOKEN, {}, pluginStatuses)}
|
|
5819
5947
|
</template>
|
|
5820
5948
|
`;
|
|
5821
5949
|
}
|
|
@@ -5857,7 +5985,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5857
5985
|
`;
|
|
5858
5986
|
}).join("");
|
|
5859
5987
|
return `
|
|
5860
|
-
<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">
|
|
5861
5989
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5862
5990
|
<div class="flex items-start gap-3">
|
|
5863
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">
|
|
@@ -5865,7 +5993,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5865
5993
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
|
|
5866
5994
|
</svg>
|
|
5867
5995
|
</div>
|
|
5868
|
-
<div>
|
|
5996
|
+
<div class="cursor-pointer" data-action="toggle-block">
|
|
5869
5997
|
<div class="text-sm font-semibold text-zinc-900 dark:text-white">
|
|
5870
5998
|
${escapeHtml3(block.label)}
|
|
5871
5999
|
<span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-block-order-label></span>
|
|
@@ -5874,6 +6002,11 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5874
6002
|
</div>
|
|
5875
6003
|
</div>
|
|
5876
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>
|
|
5877
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">
|
|
5878
6011
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
5879
6012
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
@@ -5892,7 +6025,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5892
6025
|
</button>
|
|
5893
6026
|
</div>
|
|
5894
6027
|
</div>
|
|
5895
|
-
<div class="mt-4 space-y-4">
|
|
6028
|
+
<div class="mt-4 space-y-4 hidden" data-block-content>
|
|
5896
6029
|
${blockFields}
|
|
5897
6030
|
</div>
|
|
5898
6031
|
</div>
|
|
@@ -5926,9 +6059,101 @@ function getStructuredFieldScript() {
|
|
|
5926
6059
|
|
|
5927
6060
|
function initializeStructuredFields() {
|
|
5928
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
|
+
};
|
|
5929
6153
|
|
|
5930
6154
|
const readStructuredValue = (container) => {
|
|
5931
|
-
const
|
|
6155
|
+
const fieldHost = getStructuredValueHost(container);
|
|
6156
|
+
const fields = getDirectStructuredSubfields(fieldHost);
|
|
5932
6157
|
if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
|
|
5933
6158
|
return readFieldValue(fields[0]);
|
|
5934
6159
|
}
|
|
@@ -5942,111 +6167,229 @@ function getStructuredFieldScript() {
|
|
|
5942
6167
|
};
|
|
5943
6168
|
|
|
5944
6169
|
document.querySelectorAll('[data-structured-object]').forEach((container) => {
|
|
6170
|
+
if (container.closest('template')) {
|
|
6171
|
+
return;
|
|
6172
|
+
}
|
|
5945
6173
|
if (container.dataset.structuredInitialized === 'true') {
|
|
5946
6174
|
return;
|
|
5947
6175
|
}
|
|
5948
|
-
container.dataset.
|
|
5949
|
-
|
|
6176
|
+
if (container.dataset.structuredInitializing === 'true') {
|
|
6177
|
+
return;
|
|
6178
|
+
}
|
|
6179
|
+
container.dataset.structuredInitializing = 'true';
|
|
6180
|
+
try {
|
|
6181
|
+
const hiddenInput = container.querySelector('input[type="hidden"]');
|
|
5950
6182
|
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6183
|
+
const updateHiddenInput = () => {
|
|
6184
|
+
if (!hiddenInput) return;
|
|
6185
|
+
const value = readStructuredValue(container);
|
|
6186
|
+
hiddenInput.value = JSON.stringify(value);
|
|
6187
|
+
};
|
|
5956
6188
|
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
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
|
+
}
|
|
5960
6199
|
});
|
|
5961
6200
|
|
|
5962
6201
|
document.querySelectorAll('[data-structured-array]').forEach((container) => {
|
|
6202
|
+
if (container.closest('template')) {
|
|
6203
|
+
return;
|
|
6204
|
+
}
|
|
5963
6205
|
if (container.dataset.structuredInitialized === 'true') {
|
|
5964
6206
|
return;
|
|
5965
6207
|
}
|
|
5966
|
-
container.dataset.
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
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
|
+
}
|
|
5970
6223
|
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
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;
|
|
5977
6232
|
}
|
|
5978
6233
|
|
|
5979
|
-
const
|
|
5980
|
-
|
|
5981
|
-
|
|
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;
|
|
5982
6245
|
}
|
|
6246
|
+
return typeof container.__sonicStructuredArrayTemplate === 'string'
|
|
6247
|
+
? container.__sonicStructuredArrayTemplate
|
|
6248
|
+
: '';
|
|
6249
|
+
};
|
|
5983
6250
|
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
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';
|
|
5987
6287
|
}
|
|
5988
|
-
|
|
5989
|
-
|
|
6288
|
+
updateOrderLabels();
|
|
6289
|
+
};
|
|
5990
6290
|
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
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
|
+
};
|
|
5996
6331
|
|
|
5997
|
-
const
|
|
5998
|
-
|
|
5999
|
-
|
|
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
|
+
});
|
|
6000
6341
|
}
|
|
6001
|
-
updateOrderLabels();
|
|
6002
|
-
};
|
|
6003
6342
|
|
|
6004
|
-
|
|
6005
|
-
window.initializeDragSortable
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
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
|
+
}
|
|
6011
6354
|
|
|
6012
|
-
|
|
6355
|
+
container.addEventListener('click', (event) => {
|
|
6013
6356
|
const target = event.target;
|
|
6014
6357
|
if (!(target instanceof Element)) return;
|
|
6015
6358
|
const actionButton = target.closest('[data-action]');
|
|
6016
6359
|
if (!actionButton || actionButton.hasAttribute('disabled')) return;
|
|
6360
|
+
const actionOwner = actionButton.closest('[data-structured-array]');
|
|
6361
|
+
if (actionOwner !== container) return;
|
|
6017
6362
|
|
|
6018
|
-
|
|
6363
|
+
const action = actionButton.getAttribute('data-action');
|
|
6019
6364
|
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
const html = template.innerHTML.replace(/__INDEX__/g, String(nextIndex));
|
|
6024
|
-
list.insertAdjacentHTML('beforeend', html);
|
|
6025
|
-
if (typeof initializeTinyMCE === 'function') {
|
|
6026
|
-
initializeTinyMCE();
|
|
6027
|
-
}
|
|
6028
|
-
if (typeof window.initializeQuillEditors === 'function') {
|
|
6029
|
-
window.initializeQuillEditors();
|
|
6030
|
-
}
|
|
6031
|
-
if (typeof initializeMDXEditor === 'function') {
|
|
6032
|
-
initializeMDXEditor();
|
|
6365
|
+
if (action === 'add-item') {
|
|
6366
|
+
addArrayItem();
|
|
6367
|
+
return;
|
|
6033
6368
|
}
|
|
6034
|
-
updateHiddenInput();
|
|
6035
|
-
return;
|
|
6036
|
-
}
|
|
6037
6369
|
|
|
6038
6370
|
const item = actionButton.closest('.structured-array-item');
|
|
6039
|
-
|
|
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
|
+
}
|
|
6040
6381
|
|
|
6041
6382
|
if (action === 'remove-item') {
|
|
6042
6383
|
if (typeof requestRepeaterDelete === 'function') {
|
|
6043
6384
|
requestRepeaterDelete(() => {
|
|
6044
6385
|
item.remove();
|
|
6045
6386
|
updateHiddenInput();
|
|
6387
|
+
syncArrayState(container);
|
|
6046
6388
|
});
|
|
6047
6389
|
} else {
|
|
6048
6390
|
item.remove();
|
|
6049
6391
|
updateHiddenInput();
|
|
6392
|
+
syncArrayState(container);
|
|
6050
6393
|
}
|
|
6051
6394
|
return;
|
|
6052
6395
|
}
|
|
@@ -6054,8 +6397,9 @@ function getStructuredFieldScript() {
|
|
|
6054
6397
|
if (action === 'move-up') {
|
|
6055
6398
|
const previous = item.previousElementSibling;
|
|
6056
6399
|
if (previous) {
|
|
6057
|
-
|
|
6400
|
+
liveList.insertBefore(item, previous);
|
|
6058
6401
|
updateHiddenInput();
|
|
6402
|
+
syncArrayState(container);
|
|
6059
6403
|
}
|
|
6060
6404
|
return;
|
|
6061
6405
|
}
|
|
@@ -6063,29 +6407,43 @@ function getStructuredFieldScript() {
|
|
|
6063
6407
|
if (action === 'move-down') {
|
|
6064
6408
|
const next = item.nextElementSibling;
|
|
6065
6409
|
if (next) {
|
|
6066
|
-
|
|
6410
|
+
liveList.insertBefore(next, item);
|
|
6067
6411
|
updateHiddenInput();
|
|
6412
|
+
syncArrayState(container);
|
|
6068
6413
|
}
|
|
6069
6414
|
}
|
|
6070
|
-
|
|
6415
|
+
});
|
|
6071
6416
|
|
|
6072
|
-
|
|
6417
|
+
container.addEventListener('input', (event) => {
|
|
6073
6418
|
const target = event.target;
|
|
6074
6419
|
if (!(target instanceof Element)) return;
|
|
6075
6420
|
if (target.closest('[data-structured-array-list]')) {
|
|
6076
6421
|
updateHiddenInput();
|
|
6077
6422
|
}
|
|
6078
|
-
|
|
6423
|
+
});
|
|
6079
6424
|
|
|
6080
|
-
|
|
6425
|
+
container.addEventListener('change', (event) => {
|
|
6081
6426
|
const target = event.target;
|
|
6082
6427
|
if (!(target instanceof Element)) return;
|
|
6083
6428
|
if (target.closest('[data-structured-array-list]')) {
|
|
6084
6429
|
updateHiddenInput();
|
|
6085
6430
|
}
|
|
6086
|
-
|
|
6431
|
+
});
|
|
6087
6432
|
|
|
6088
|
-
|
|
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
|
+
}
|
|
6089
6447
|
});
|
|
6090
6448
|
}
|
|
6091
6449
|
|
|
@@ -6100,7 +6458,10 @@ function getStructuredFieldScript() {
|
|
|
6100
6458
|
document.addEventListener('htmx:afterSwap', function() {
|
|
6101
6459
|
setTimeout(initializeStructuredFields, 50);
|
|
6102
6460
|
});
|
|
6103
|
-
} else if (
|
|
6461
|
+
} else if (
|
|
6462
|
+
typeof window.initializeStructuredFields === 'function' &&
|
|
6463
|
+
document.readyState !== 'loading'
|
|
6464
|
+
) {
|
|
6104
6465
|
window.initializeStructuredFields();
|
|
6105
6466
|
}
|
|
6106
6467
|
</script>
|
|
@@ -6112,6 +6473,68 @@ function getBlocksFieldScript() {
|
|
|
6112
6473
|
<script>
|
|
6113
6474
|
if (!window.__sonicBlocksFieldInit) {
|
|
6114
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
|
+
};
|
|
6115
6538
|
|
|
6116
6539
|
function initializeBlocksFields() {
|
|
6117
6540
|
document.querySelectorAll('.blocks-field').forEach((container) => {
|
|
@@ -6199,7 +6622,10 @@ function getBlocksFieldScript() {
|
|
|
6199
6622
|
window.initializeDragSortable(list, {
|
|
6200
6623
|
itemSelector: '.blocks-item',
|
|
6201
6624
|
handleSelector: '[data-action="drag-handle"]',
|
|
6202
|
-
onUpdate:
|
|
6625
|
+
onUpdate: () => {
|
|
6626
|
+
updateHiddenInput();
|
|
6627
|
+
syncBlocksState(container);
|
|
6628
|
+
}
|
|
6203
6629
|
});
|
|
6204
6630
|
}
|
|
6205
6631
|
|
|
@@ -6221,8 +6647,12 @@ function getBlocksFieldScript() {
|
|
|
6221
6647
|
if (!template) return;
|
|
6222
6648
|
|
|
6223
6649
|
const nextIndex = list.querySelectorAll('.blocks-item').length;
|
|
6224
|
-
const html = template.innerHTML.replace(/
|
|
6650
|
+
const html = template.innerHTML.replace(/__BLOCK_INDEX__/g, String(nextIndex));
|
|
6225
6651
|
list.insertAdjacentHTML('beforeend', html);
|
|
6652
|
+
const newItem = list.lastElementChild;
|
|
6653
|
+
if (newItem instanceof HTMLElement) {
|
|
6654
|
+
setBlockExpanded(newItem, true);
|
|
6655
|
+
}
|
|
6226
6656
|
if (typeSelect) {
|
|
6227
6657
|
typeSelect.value = '';
|
|
6228
6658
|
}
|
|
@@ -6231,21 +6661,32 @@ function getBlocksFieldScript() {
|
|
|
6231
6661
|
window.initializeStructuredFields();
|
|
6232
6662
|
}
|
|
6233
6663
|
updateHiddenInput();
|
|
6664
|
+
syncBlocksState(container);
|
|
6234
6665
|
return;
|
|
6235
6666
|
}
|
|
6236
6667
|
|
|
6237
6668
|
const item = actionButton.closest('.blocks-item');
|
|
6238
6669
|
if (!item || !list) return;
|
|
6239
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
|
+
|
|
6240
6679
|
if (action === 'remove-block') {
|
|
6241
6680
|
if (typeof requestRepeaterDelete === 'function') {
|
|
6242
6681
|
requestRepeaterDelete(() => {
|
|
6243
6682
|
item.remove();
|
|
6244
6683
|
updateHiddenInput();
|
|
6684
|
+
syncBlocksState(container);
|
|
6245
6685
|
}, 'block');
|
|
6246
6686
|
} else {
|
|
6247
6687
|
item.remove();
|
|
6248
6688
|
updateHiddenInput();
|
|
6689
|
+
syncBlocksState(container);
|
|
6249
6690
|
}
|
|
6250
6691
|
return;
|
|
6251
6692
|
}
|
|
@@ -6255,6 +6696,7 @@ function getBlocksFieldScript() {
|
|
|
6255
6696
|
if (previous) {
|
|
6256
6697
|
list.insertBefore(item, previous);
|
|
6257
6698
|
updateHiddenInput();
|
|
6699
|
+
syncBlocksState(container);
|
|
6258
6700
|
}
|
|
6259
6701
|
return;
|
|
6260
6702
|
}
|
|
@@ -6264,6 +6706,7 @@ function getBlocksFieldScript() {
|
|
|
6264
6706
|
if (next) {
|
|
6265
6707
|
list.insertBefore(next, item);
|
|
6266
6708
|
updateHiddenInput();
|
|
6709
|
+
syncBlocksState(container);
|
|
6267
6710
|
}
|
|
6268
6711
|
}
|
|
6269
6712
|
});
|
|
@@ -6285,6 +6728,12 @@ function getBlocksFieldScript() {
|
|
|
6285
6728
|
});
|
|
6286
6729
|
|
|
6287
6730
|
updateHiddenInput();
|
|
6731
|
+
const savedBlocksState = readBlocksState(container);
|
|
6732
|
+
if (savedBlocksState) {
|
|
6733
|
+
applyBlocksState(container, savedBlocksState);
|
|
6734
|
+
} else {
|
|
6735
|
+
syncBlocksState(container);
|
|
6736
|
+
}
|
|
6288
6737
|
});
|
|
6289
6738
|
}
|
|
6290
6739
|
|
|
@@ -6321,6 +6770,7 @@ init_admin_layout_catalyst_template();
|
|
|
6321
6770
|
function renderContentFormPage(data) {
|
|
6322
6771
|
const isEdit = data.isEdit || !!data.id;
|
|
6323
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);
|
|
6324
6774
|
const backUrl = data.referrerParams ? `/admin/content?${data.referrerParams}` : `/admin/content?collection=${data.collection.id}`;
|
|
6325
6775
|
const coreFields = data.fields.filter((f) => ["title", "slug", "content"].includes(f.field_name));
|
|
6326
6776
|
const contentFields = data.fields.filter((f) => !["title", "slug", "content"].includes(f.field_name) && !f.field_name.startsWith("meta_"));
|
|
@@ -6409,6 +6859,7 @@ function renderContentFormPage(data) {
|
|
|
6409
6859
|
${isEdit ? `hx-put="/admin/content/${data.id}"` : `hx-post="/admin/content"`}
|
|
6410
6860
|
hx-target="#form-messages"
|
|
6411
6861
|
hx-encoding="multipart/form-data"
|
|
6862
|
+
data-has-validation-errors="${hasValidationErrors ? "true" : "false"}"
|
|
6412
6863
|
class="space-y-6"
|
|
6413
6864
|
>
|
|
6414
6865
|
<input type="hidden" name="collection_id" value="${data.collection.id}">
|
|
@@ -6689,39 +7140,456 @@ function renderContentFormPage(data) {
|
|
|
6689
7140
|
|
|
6690
7141
|
<!-- Dynamic Field Scripts -->
|
|
6691
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
|
+
|
|
6692
7436
|
// Field group toggle
|
|
6693
|
-
function toggleFieldGroup(
|
|
6694
|
-
const content =
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
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();
|
|
6703
7460
|
}
|
|
6704
7461
|
}
|
|
6705
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
|
+
|
|
6706
7510
|
// Media field functions
|
|
6707
|
-
|
|
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
|
+
}
|
|
6708
7570
|
|
|
6709
7571
|
function openMediaSelector(fieldId) {
|
|
6710
|
-
|
|
7572
|
+
const existingModal = getActiveMediaModal();
|
|
7573
|
+
if (existingModal) {
|
|
7574
|
+
existingModal.remove();
|
|
7575
|
+
}
|
|
7576
|
+
|
|
6711
7577
|
// Store the original value in case user cancels
|
|
6712
|
-
const originalValue =
|
|
7578
|
+
const originalValue = getMediaFieldElements(fieldId).hiddenInput?.value || '';
|
|
6713
7579
|
|
|
6714
7580
|
// Open media library modal
|
|
6715
7581
|
const modal = document.createElement('div');
|
|
6716
7582
|
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50';
|
|
6717
7583
|
modal.id = 'media-selector-modal';
|
|
7584
|
+
modal.dataset.targetFieldId = fieldId;
|
|
7585
|
+
modal.dataset.originalValue = originalValue;
|
|
6718
7586
|
modal.innerHTML = \`
|
|
6719
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">
|
|
6720
7588
|
<h3 class="text-lg font-semibold text-zinc-950 dark:text-white mb-4">Select Media</h3>
|
|
6721
7589
|
<div id="media-grid-container" hx-get="/admin/media/selector" hx-trigger="load"></div>
|
|
6722
7590
|
<div class="mt-4 flex justify-end space-x-2">
|
|
6723
7591
|
<button
|
|
6724
|
-
onclick="cancelMediaSelection(
|
|
7592
|
+
onclick="cancelMediaSelection()"
|
|
6725
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">
|
|
6726
7594
|
Cancel
|
|
6727
7595
|
</button>
|
|
@@ -6741,23 +7609,23 @@ function renderContentFormPage(data) {
|
|
|
6741
7609
|
}
|
|
6742
7610
|
|
|
6743
7611
|
function closeMediaSelector() {
|
|
6744
|
-
const modal =
|
|
7612
|
+
const modal = getActiveMediaModal();
|
|
6745
7613
|
if (modal) {
|
|
6746
7614
|
modal.remove();
|
|
6747
7615
|
}
|
|
6748
|
-
currentMediaFieldId = null;
|
|
6749
7616
|
}
|
|
6750
7617
|
|
|
6751
|
-
function cancelMediaSelection(
|
|
7618
|
+
function cancelMediaSelection() {
|
|
7619
|
+
const { hiddenInput, preview, originalValue } = getActiveMediaTarget();
|
|
7620
|
+
|
|
6752
7621
|
// Restore original value
|
|
6753
|
-
const hiddenInput = document.getElementById(fieldId);
|
|
6754
7622
|
if (hiddenInput) {
|
|
6755
7623
|
hiddenInput.value = originalValue;
|
|
7624
|
+
notifyFieldChange(hiddenInput);
|
|
6756
7625
|
}
|
|
6757
7626
|
|
|
6758
7627
|
// If original value was empty, hide the preview and show select button
|
|
6759
7628
|
if (!originalValue) {
|
|
6760
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6761
7629
|
if (preview) {
|
|
6762
7630
|
preview.classList.add('hidden');
|
|
6763
7631
|
}
|
|
@@ -6768,11 +7636,11 @@ function renderContentFormPage(data) {
|
|
|
6768
7636
|
}
|
|
6769
7637
|
|
|
6770
7638
|
function clearMediaField(fieldId) {
|
|
6771
|
-
const hiddenInput =
|
|
6772
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
7639
|
+
const { hiddenInput, preview, actionsDiv } = getMediaFieldElements(fieldId);
|
|
6773
7640
|
|
|
6774
7641
|
if (hiddenInput) {
|
|
6775
7642
|
hiddenInput.value = '';
|
|
7643
|
+
notifyFieldChange(hiddenInput);
|
|
6776
7644
|
}
|
|
6777
7645
|
|
|
6778
7646
|
if (preview) {
|
|
@@ -6782,25 +7650,34 @@ function renderContentFormPage(data) {
|
|
|
6782
7650
|
}
|
|
6783
7651
|
preview.classList.add('hidden');
|
|
6784
7652
|
}
|
|
7653
|
+
|
|
7654
|
+
const removeButton = actionsDiv?.querySelector('[data-media-remove="true"]');
|
|
7655
|
+
if (removeButton) {
|
|
7656
|
+
removeButton.remove();
|
|
7657
|
+
}
|
|
6785
7658
|
}
|
|
6786
7659
|
|
|
6787
7660
|
// Global function to remove a single media from multiple selection
|
|
6788
7661
|
window.removeMediaFromMultiple = function(fieldId, urlToRemove) {
|
|
6789
|
-
const hiddenInput =
|
|
7662
|
+
const { hiddenInput, preview } = getMediaFieldElements(fieldId);
|
|
6790
7663
|
if (!hiddenInput) return;
|
|
6791
7664
|
|
|
6792
7665
|
const values = hiddenInput.value.split(',').filter(url => url !== urlToRemove);
|
|
6793
7666
|
hiddenInput.value = values.join(',');
|
|
7667
|
+
notifyFieldChange(hiddenInput);
|
|
6794
7668
|
|
|
6795
7669
|
// Remove preview item
|
|
6796
|
-
const previewItem =
|
|
7670
|
+
const previewItem =
|
|
7671
|
+
preview &&
|
|
7672
|
+
Array.from(preview.querySelectorAll('[data-url]')).find(
|
|
7673
|
+
(item) => item.getAttribute('data-url') === urlToRemove,
|
|
7674
|
+
);
|
|
6797
7675
|
if (previewItem) {
|
|
6798
7676
|
previewItem.remove();
|
|
6799
7677
|
}
|
|
6800
7678
|
|
|
6801
7679
|
// Hide preview grid if empty
|
|
6802
7680
|
if (values.length === 0) {
|
|
6803
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6804
7681
|
if (preview) {
|
|
6805
7682
|
preview.classList.add('hidden');
|
|
6806
7683
|
}
|
|
@@ -6809,39 +7686,24 @@ function renderContentFormPage(data) {
|
|
|
6809
7686
|
|
|
6810
7687
|
// Global function called by media selector buttons
|
|
6811
7688
|
window.selectMediaFile = function(mediaId, mediaUrl, filename) {
|
|
6812
|
-
|
|
7689
|
+
const { fieldId, hiddenInput, preview, actionsDiv } = getActiveMediaTarget();
|
|
7690
|
+
if (!fieldId || !hiddenInput) {
|
|
6813
7691
|
console.error('No field ID set for media selection');
|
|
6814
7692
|
return;
|
|
6815
7693
|
}
|
|
6816
7694
|
|
|
6817
|
-
const fieldId = currentMediaFieldId;
|
|
6818
|
-
|
|
6819
7695
|
// Set the hidden input value to the media URL (not ID)
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
hiddenInput.value = mediaUrl;
|
|
6823
|
-
}
|
|
7696
|
+
hiddenInput.value = mediaUrl;
|
|
7697
|
+
notifyFieldChange(hiddenInput);
|
|
6824
7698
|
|
|
6825
7699
|
// Update the preview
|
|
6826
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6827
7700
|
if (preview) {
|
|
6828
7701
|
preview.innerHTML = \`<img src="\${mediaUrl}" alt="\${filename}" class="w-32 h-32 object-cover rounded-lg border border-white/20">\`;
|
|
6829
7702
|
preview.classList.remove('hidden');
|
|
6830
7703
|
}
|
|
6831
7704
|
|
|
6832
7705
|
// Show the remove button by finding the media actions container and updating it
|
|
6833
|
-
|
|
6834
|
-
if (mediaField) {
|
|
6835
|
-
const actionsDiv = mediaField.querySelector('.media-actions');
|
|
6836
|
-
if (actionsDiv && !actionsDiv.querySelector('button:has-text("Remove")')) {
|
|
6837
|
-
const removeBtn = document.createElement('button');
|
|
6838
|
-
removeBtn.type = 'button';
|
|
6839
|
-
removeBtn.onclick = () => clearMediaField(fieldId);
|
|
6840
|
-
removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
|
|
6841
|
-
removeBtn.textContent = 'Remove';
|
|
6842
|
-
actionsDiv.appendChild(removeBtn);
|
|
6843
|
-
}
|
|
6844
|
-
}
|
|
7706
|
+
ensureSingleMediaRemoveButton(fieldId, actionsDiv);
|
|
6845
7707
|
|
|
6846
7708
|
// DON'T close the modal - let user click OK button
|
|
6847
7709
|
// Visual feedback: highlight the selected item
|
|
@@ -6855,7 +7717,9 @@ function renderContentFormPage(data) {
|
|
|
6855
7717
|
};
|
|
6856
7718
|
|
|
6857
7719
|
function setMediaField(fieldId, mediaUrl) {
|
|
6858
|
-
document.getElementById(fieldId)
|
|
7720
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
7721
|
+
hiddenInput.value = mediaUrl;
|
|
7722
|
+
notifyFieldChange(hiddenInput);
|
|
6859
7723
|
const preview = document.getElementById(fieldId + '-preview');
|
|
6860
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">\`;
|
|
6861
7725
|
preview.classList.remove('hidden');
|
|
@@ -11450,6 +12314,13 @@ function renderUsersListPage(data) {
|
|
|
11450
12314
|
// src/routes/admin-users.ts
|
|
11451
12315
|
var userRoutes = new Hono();
|
|
11452
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"]));
|
|
11453
12324
|
userRoutes.get("/", (c) => {
|
|
11454
12325
|
return c.redirect("/admin/dashboard");
|
|
11455
12326
|
});
|
|
@@ -11937,7 +12808,9 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
11937
12808
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
11938
12809
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
11939
12810
|
const bio = sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
11940
|
-
const
|
|
12811
|
+
const roleInput = formData.get("role")?.toString() || "viewer";
|
|
12812
|
+
const validRoles = ["admin", "editor", "author", "viewer"];
|
|
12813
|
+
const role = validRoles.includes(roleInput) ? roleInput : "viewer";
|
|
11941
12814
|
const password = formData.get("password")?.toString() || "";
|
|
11942
12815
|
const confirmPassword = formData.get("confirm_password")?.toString() || "";
|
|
11943
12816
|
const isActive = formData.get("is_active") === "1";
|
|
@@ -12157,7 +13030,9 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
12157
13030
|
const username = sanitizeInput(formData.get("username")?.toString());
|
|
12158
13031
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
12159
13032
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
12160
|
-
const
|
|
13033
|
+
const roleInput = formData.get("role")?.toString() || "viewer";
|
|
13034
|
+
const validRoles = ["admin", "editor", "author", "viewer"];
|
|
13035
|
+
const role = validRoles.includes(roleInput) ? roleInput : "viewer";
|
|
12161
13036
|
const isActive = formData.get("is_active") === "1";
|
|
12162
13037
|
const emailVerified = formData.get("email_verified") === "1";
|
|
12163
13038
|
const profileDisplayName = sanitizeInput(formData.get("profile_display_name")?.toString()) || null;
|
|
@@ -17482,6 +18357,18 @@ adminPluginRoutes.post("/:id/settings", async (c) => {
|
|
|
17482
18357
|
const settings = await c.req.json();
|
|
17483
18358
|
const pluginService = new PluginService(db);
|
|
17484
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
|
+
}
|
|
17485
18372
|
return c.json({ success: true });
|
|
17486
18373
|
} catch (error) {
|
|
17487
18374
|
console.error("Error updating plugin settings:", error);
|
|
@@ -22426,6 +23313,9 @@ function renderCollectionFormPage(data) {
|
|
|
22426
23313
|
// src/routes/admin-collections.ts
|
|
22427
23314
|
var adminCollectionsRoutes = new Hono();
|
|
22428
23315
|
adminCollectionsRoutes.use("*", requireAuth());
|
|
23316
|
+
adminCollectionsRoutes.post("*", requireRole(["admin"]));
|
|
23317
|
+
adminCollectionsRoutes.put("*", requireRole(["admin"]));
|
|
23318
|
+
adminCollectionsRoutes.delete("*", requireRole(["admin"]));
|
|
22429
23319
|
adminCollectionsRoutes.get("/", async (c) => {
|
|
22430
23320
|
try {
|
|
22431
23321
|
const user = c.get("user");
|
|
@@ -27936,5 +28826,5 @@ var ROUTES_INFO = {
|
|
|
27936
28826
|
};
|
|
27937
28827
|
|
|
27938
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 };
|
|
27939
|
-
//# sourceMappingURL=chunk-
|
|
27940
|
-
//# sourceMappingURL=chunk-
|
|
28829
|
+
//# sourceMappingURL=chunk-JTNUM7JE.js.map
|
|
28830
|
+
//# sourceMappingURL=chunk-JTNUM7JE.js.map
|