@sonicjs-cms/core 2.9.0 → 2.10.1
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-DQZVU3WB.cjs → chunk-5GO3AMON.cjs} +13 -7
- package/dist/chunk-5GO3AMON.cjs.map +1 -0
- package/dist/{chunk-YFJJU26H.js → chunk-BUPNX3ZM.js} +375 -3
- package/dist/chunk-BUPNX3ZM.js.map +1 -0
- package/dist/{chunk-SHU7Q66Q.cjs → chunk-E2GKK5HX.cjs} +7 -3
- package/dist/chunk-E2GKK5HX.cjs.map +1 -0
- package/dist/{chunk-LDFMYRG6.cjs → chunk-EAJJHE5F.cjs} +9 -2
- package/dist/chunk-EAJJHE5F.cjs.map +1 -0
- package/dist/{chunk-STTZVLY2.js → chunk-FW5CGNM2.js} +9 -2
- package/dist/chunk-FW5CGNM2.js.map +1 -0
- package/dist/{chunk-KSB6FXOP.cjs → chunk-HGKBMUYY.cjs} +1194 -278
- package/dist/chunk-HGKBMUYY.cjs.map +1 -0
- package/dist/{chunk-25YNV4RK.js → chunk-JFMBYQTC.js} +10 -4
- package/dist/chunk-JFMBYQTC.js.map +1 -0
- package/dist/{chunk-64APW3DW.cjs → chunk-LFAQUR7P.cjs} +9 -2
- package/dist/chunk-LFAQUR7P.cjs.map +1 -0
- package/dist/{chunk-2JGQKF7B.js → chunk-SDAGUFOF.js} +1079 -163
- package/dist/chunk-SDAGUFOF.js.map +1 -0
- package/dist/{chunk-MPT5PA6U.cjs → chunk-TWCQVJ6M.cjs} +381 -2
- package/dist/chunk-TWCQVJ6M.cjs.map +1 -0
- package/dist/{chunk-7JMMLHPQ.js → chunk-VJCLJH3X.js} +9 -2
- package/dist/chunk-VJCLJH3X.js.map +1 -0
- package/dist/{chunk-3FHMXGLF.js → chunk-YXTFJPMN.js} +7 -3
- package/dist/chunk-YXTFJPMN.js.map +1 -0
- 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 +170 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +29 -29
- package/dist/middleware.js +3 -3
- package/dist/migrations-ADK6YNM2.js +4 -0
- package/dist/{migrations-SZSR3C3G.js.map → migrations-ADK6YNM2.js.map} +1 -1
- package/dist/migrations-EM2D6EG2.cjs +13 -0
- package/dist/{migrations-QQWGDWGB.cjs.map → migrations-EM2D6EG2.cjs.map} +1 -1
- package/dist/{plugin-bootstrap-BAz7NY0H.d.cts → plugin-bootstrap-B8PXeGj_.d.cts} +230 -2
- package/dist/{plugin-bootstrap-Cz3-bj8X.d.ts → plugin-bootstrap-CD63DZ-p.d.ts} +230 -2
- package/dist/routes.cjs +29 -29
- package/dist/routes.js +6 -6
- package/dist/services.cjs +60 -32
- package/dist/services.d.cts +2 -2
- package/dist/services.d.ts +2 -2
- package/dist/services.js +3 -3
- 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/migrations/033_form_content_integration.sql +19 -0
- package/package.json +5 -1
- package/dist/chunk-25YNV4RK.js.map +0 -1
- package/dist/chunk-2JGQKF7B.js.map +0 -1
- package/dist/chunk-3FHMXGLF.js.map +0 -1
- package/dist/chunk-64APW3DW.cjs.map +0 -1
- package/dist/chunk-7JMMLHPQ.js.map +0 -1
- package/dist/chunk-DQZVU3WB.cjs.map +0 -1
- package/dist/chunk-KSB6FXOP.cjs.map +0 -1
- package/dist/chunk-LDFMYRG6.cjs.map +0 -1
- package/dist/chunk-MPT5PA6U.cjs.map +0 -1
- package/dist/chunk-SHU7Q66Q.cjs.map +0 -1
- package/dist/chunk-STTZVLY2.js.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
|
-
import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService, getAppInstance, buildRouteList, CATEGORY_INFO } 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-VJCLJH3X.js';
|
|
2
|
+
import { requireAuth, requireRole, isPluginActive, optionalAuth, rateLimit, AuthManager, logActivity, generateCsrfToken } from './chunk-JFMBYQTC.js';
|
|
3
|
+
import { PluginService, createContentFromSubmission } from './chunk-BUPNX3ZM.js';
|
|
4
|
+
import { MigrationService } from './chunk-FW5CGNM2.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-YXTFJPMN.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;
|
|
@@ -748,7 +748,7 @@ apiRoutes.get("/collections", async (c) => {
|
|
|
748
748
|
}
|
|
749
749
|
c.header("X-Cache-Status", "MISS");
|
|
750
750
|
c.header("X-Cache-Source", "database");
|
|
751
|
-
const stmt = db.prepare("SELECT * FROM collections WHERE is_active = 1");
|
|
751
|
+
const stmt = db.prepare("SELECT * FROM collections WHERE is_active = 1 AND (source_type IS NULL OR source_type = 'user')");
|
|
752
752
|
const { results } = await stmt.all();
|
|
753
753
|
const transformedResults = results.map((row) => ({
|
|
754
754
|
...row,
|
|
@@ -1777,7 +1777,7 @@ adminApiRoutes.get("/stats", async (c) => {
|
|
|
1777
1777
|
const db = c.env.DB;
|
|
1778
1778
|
let collectionsCount = 0;
|
|
1779
1779
|
try {
|
|
1780
|
-
const collectionsStmt = db.prepare("SELECT COUNT(*) as count FROM collections WHERE is_active = 1");
|
|
1780
|
+
const collectionsStmt = db.prepare("SELECT COUNT(*) as count FROM collections WHERE is_active = 1 AND (source_type IS NULL OR source_type = 'user')");
|
|
1781
1781
|
const collectionsResult = await collectionsStmt.first();
|
|
1782
1782
|
collectionsCount = collectionsResult?.count || 0;
|
|
1783
1783
|
} catch (error) {
|
|
@@ -1785,7 +1785,7 @@ adminApiRoutes.get("/stats", async (c) => {
|
|
|
1785
1785
|
}
|
|
1786
1786
|
let contentCount = 0;
|
|
1787
1787
|
try {
|
|
1788
|
-
const contentStmt = db.prepare("SELECT COUNT(*) as count FROM content WHERE deleted_at IS NULL");
|
|
1788
|
+
const contentStmt = db.prepare("SELECT COUNT(*) as count FROM content c JOIN collections col ON c.collection_id = col.id WHERE c.deleted_at IS NULL AND (col.source_type IS NULL OR col.source_type = 'user')");
|
|
1789
1789
|
const contentResult = await contentStmt.first();
|
|
1790
1790
|
contentCount = contentResult?.count || 0;
|
|
1791
1791
|
} catch (error) {
|
|
@@ -1927,6 +1927,7 @@ adminApiRoutes.get("/collections", async (c) => {
|
|
|
1927
1927
|
SELECT id, name, display_name, description, created_at, updated_at, is_active, managed
|
|
1928
1928
|
FROM collections
|
|
1929
1929
|
WHERE ${includeInactive ? "1=1" : "is_active = 1"}
|
|
1930
|
+
AND (source_type IS NULL OR source_type = 'user')
|
|
1930
1931
|
AND (name LIKE ? OR display_name LIKE ? OR description LIKE ?)
|
|
1931
1932
|
ORDER BY created_at DESC
|
|
1932
1933
|
`);
|
|
@@ -1937,7 +1938,8 @@ adminApiRoutes.get("/collections", async (c) => {
|
|
|
1937
1938
|
stmt = db.prepare(`
|
|
1938
1939
|
SELECT id, name, display_name, description, created_at, updated_at, is_active, managed
|
|
1939
1940
|
FROM collections
|
|
1940
|
-
|
|
1941
|
+
WHERE (source_type IS NULL OR source_type = 'user')
|
|
1942
|
+
${includeInactive ? "" : "AND is_active = 1"}
|
|
1941
1943
|
ORDER BY created_at DESC
|
|
1942
1944
|
`);
|
|
1943
1945
|
const queryResults = await stmt.all();
|
|
@@ -2281,7 +2283,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
|
|
|
2281
2283
|
});
|
|
2282
2284
|
adminApiRoutes.get("/migrations/status", async (c) => {
|
|
2283
2285
|
try {
|
|
2284
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
2286
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-ADK6YNM2.js');
|
|
2285
2287
|
const db = c.env.DB;
|
|
2286
2288
|
const migrationService = new MigrationService2(db);
|
|
2287
2289
|
const status = await migrationService.getMigrationStatus();
|
|
@@ -2306,7 +2308,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
2306
2308
|
error: "Unauthorized. Admin access required."
|
|
2307
2309
|
}, 403);
|
|
2308
2310
|
}
|
|
2309
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
2311
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-ADK6YNM2.js');
|
|
2310
2312
|
const db = c.env.DB;
|
|
2311
2313
|
const migrationService = new MigrationService2(db);
|
|
2312
2314
|
const result = await migrationService.runPendingMigrations();
|
|
@@ -2325,7 +2327,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
2325
2327
|
});
|
|
2326
2328
|
adminApiRoutes.get("/migrations/validate", async (c) => {
|
|
2327
2329
|
try {
|
|
2328
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
2330
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-ADK6YNM2.js');
|
|
2329
2331
|
const db = c.env.DB;
|
|
2330
2332
|
const migrationService = new MigrationService2(db);
|
|
2331
2333
|
const validation = await migrationService.validateSchema();
|
|
@@ -4775,6 +4777,39 @@ function getReadFieldValueScript() {
|
|
|
4775
4777
|
window.__sonicReadFieldValueInit = true;
|
|
4776
4778
|
|
|
4777
4779
|
window.sonicReadFieldValue = function(fieldWrapper) {
|
|
4780
|
+
const getDirectChild = (parent, selector) => {
|
|
4781
|
+
if (!(parent instanceof Element)) return null;
|
|
4782
|
+
return Array.from(parent.children).find(
|
|
4783
|
+
(child) => child instanceof Element && child.matches(selector),
|
|
4784
|
+
) || null;
|
|
4785
|
+
};
|
|
4786
|
+
const getDirectStructuredSubfields = (host) =>
|
|
4787
|
+
Array.from(host.children).filter(
|
|
4788
|
+
(child) => child instanceof Element && child.classList.contains('structured-subfield'),
|
|
4789
|
+
);
|
|
4790
|
+
const getStructuredObjectFieldsHost = (container) => {
|
|
4791
|
+
const directFieldsHost = getDirectChild(container, '[data-structured-object-fields]');
|
|
4792
|
+
if (directFieldsHost) return directFieldsHost;
|
|
4793
|
+
const groupContent = getDirectChild(container, '.field-group-content');
|
|
4794
|
+
const nestedFieldsHost = groupContent
|
|
4795
|
+
? getDirectChild(groupContent, '[data-structured-object-fields]')
|
|
4796
|
+
: null;
|
|
4797
|
+
if (nestedFieldsHost) return nestedFieldsHost;
|
|
4798
|
+
return getDirectChild(container, '[data-array-item-fields]') || container;
|
|
4799
|
+
};
|
|
4800
|
+
const getDirectStructuredObject = (fieldWrapper) => {
|
|
4801
|
+
const directObject = getDirectChild(fieldWrapper, '[data-structured-object]');
|
|
4802
|
+
if (directObject) return directObject;
|
|
4803
|
+
const formGroup = getDirectChild(fieldWrapper, '.form-group');
|
|
4804
|
+
return formGroup ? getDirectChild(formGroup, '[data-structured-object]') : null;
|
|
4805
|
+
};
|
|
4806
|
+
const getDirectStructuredArray = (fieldWrapper) => {
|
|
4807
|
+
const directArray = getDirectChild(fieldWrapper, '[data-structured-array]');
|
|
4808
|
+
if (directArray) return directArray;
|
|
4809
|
+
const formGroup = getDirectChild(fieldWrapper, '.form-group');
|
|
4810
|
+
return formGroup ? getDirectChild(formGroup, '[data-structured-array]') : null;
|
|
4811
|
+
};
|
|
4812
|
+
|
|
4778
4813
|
const fieldType = fieldWrapper.dataset.fieldType;
|
|
4779
4814
|
const select = fieldWrapper.querySelector('select');
|
|
4780
4815
|
const textarea = fieldWrapper.querySelector('textarea');
|
|
@@ -4784,7 +4819,47 @@ function getReadFieldValueScript() {
|
|
|
4784
4819
|
const nonHiddenInput = inputs.find((input) => input.type !== 'hidden' && input.type !== 'checkbox');
|
|
4785
4820
|
const hiddenInput = inputs.find((input) => input.type === 'hidden');
|
|
4786
4821
|
|
|
4822
|
+
const readStructuredFieldsHost = (host) => {
|
|
4823
|
+
const fields = getDirectStructuredSubfields(host);
|
|
4824
|
+
if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
|
|
4825
|
+
return window.sonicReadFieldValue(fields[0]);
|
|
4826
|
+
}
|
|
4827
|
+
return fields.reduce((acc, subfield) => {
|
|
4828
|
+
const fieldName = subfield.dataset.structuredField;
|
|
4829
|
+
if (!fieldName || fieldName === '__value') return acc;
|
|
4830
|
+
acc[fieldName] = window.sonicReadFieldValue(subfield);
|
|
4831
|
+
return acc;
|
|
4832
|
+
}, {});
|
|
4833
|
+
};
|
|
4834
|
+
|
|
4835
|
+
const readStructuredObject = () => {
|
|
4836
|
+
const objectContainer = getDirectStructuredObject(fieldWrapper);
|
|
4837
|
+
if (!objectContainer) return null;
|
|
4838
|
+
const host = getStructuredObjectFieldsHost(objectContainer);
|
|
4839
|
+
return readStructuredFieldsHost(host);
|
|
4840
|
+
};
|
|
4841
|
+
|
|
4842
|
+
const readStructuredArray = () => {
|
|
4843
|
+
const arrayContainer = getDirectStructuredArray(fieldWrapper);
|
|
4844
|
+
if (!arrayContainer) return null;
|
|
4845
|
+
const list = arrayContainer.querySelector('[data-structured-array-list]');
|
|
4846
|
+
if (!list) return [];
|
|
4847
|
+
const items = Array.from(list.querySelectorAll(':scope > .structured-array-item'));
|
|
4848
|
+
return items.map((item) => {
|
|
4849
|
+
const host =
|
|
4850
|
+
item.querySelector(':scope > [data-array-item-fields]') ||
|
|
4851
|
+
item.querySelector('[data-array-item-fields]') ||
|
|
4852
|
+
item;
|
|
4853
|
+
return readStructuredFieldsHost(host);
|
|
4854
|
+
});
|
|
4855
|
+
};
|
|
4856
|
+
|
|
4787
4857
|
if (fieldType === 'object' || fieldType === 'array') {
|
|
4858
|
+
const liveValue = fieldType === 'array' ? readStructuredArray() : readStructuredObject();
|
|
4859
|
+
if (liveValue !== null) {
|
|
4860
|
+
return liveValue;
|
|
4861
|
+
}
|
|
4862
|
+
|
|
4788
4863
|
if (!hiddenInput) {
|
|
4789
4864
|
return fieldType === 'array' ? [] : {};
|
|
4790
4865
|
}
|
|
@@ -4833,6 +4908,15 @@ function getReadFieldValueScript() {
|
|
|
4833
4908
|
</script>
|
|
4834
4909
|
`;
|
|
4835
4910
|
}
|
|
4911
|
+
var STRUCTURED_INDEX_TOKEN = "__INDEX__";
|
|
4912
|
+
var BLOCK_INDEX_TOKEN = "__BLOCK_INDEX__";
|
|
4913
|
+
function sanitizeStructuredGroupId(fieldName) {
|
|
4914
|
+
return `object-${fieldName}`.split(BLOCK_INDEX_TOKEN).map(
|
|
4915
|
+
(blockSegment) => blockSegment.split(STRUCTURED_INDEX_TOKEN).map(
|
|
4916
|
+
(segment) => segment.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
|
|
4917
|
+
).join(STRUCTURED_INDEX_TOKEN)
|
|
4918
|
+
).join(BLOCK_INDEX_TOKEN);
|
|
4919
|
+
}
|
|
4836
4920
|
function isMarkdownEditorFieldType(fieldType) {
|
|
4837
4921
|
return fieldType === "markdown" || fieldType === "mdxeditor" || fieldType === "easymde";
|
|
4838
4922
|
}
|
|
@@ -5413,12 +5497,14 @@ function renderDynamicField(field, options = {}) {
|
|
|
5413
5497
|
|
|
5414
5498
|
${isMultiple ? `
|
|
5415
5499
|
<div class="media-preview-grid grid grid-cols-4 gap-2 mb-2 ${mediaValues.length === 0 ? "hidden" : ""}" id="${fieldId}-preview">
|
|
5416
|
-
${mediaValues.map(
|
|
5500
|
+
${mediaValues.map(
|
|
5501
|
+
(url, idx) => `
|
|
5417
5502
|
<div class="relative media-preview-item" data-url="${url}">
|
|
5418
5503
|
${renderMediaPreview(url, `Media ${idx + 1}`, "w-full h-24 object-cover rounded-lg border border-white/20")}
|
|
5419
5504
|
<button
|
|
5420
5505
|
type="button"
|
|
5421
5506
|
onclick="removeMediaFromMultiple('${fieldId}', '${url}')"
|
|
5507
|
+
data-media-remove="true"
|
|
5422
5508
|
class="absolute top-1 right-1 bg-red-600 text-white rounded-full p-1 hover:bg-red-700"
|
|
5423
5509
|
${disabled ? "disabled" : ""}
|
|
5424
5510
|
>
|
|
@@ -5427,7 +5513,8 @@ function renderDynamicField(field, options = {}) {
|
|
|
5427
5513
|
</svg>
|
|
5428
5514
|
</button>
|
|
5429
5515
|
</div>
|
|
5430
|
-
`
|
|
5516
|
+
`
|
|
5517
|
+
).join("")}
|
|
5431
5518
|
</div>
|
|
5432
5519
|
` : `
|
|
5433
5520
|
<div class="media-preview ${singleValue ? "" : "hidden"}" id="${fieldId}-preview">
|
|
@@ -5451,6 +5538,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5451
5538
|
<button
|
|
5452
5539
|
type="button"
|
|
5453
5540
|
onclick="clearMediaField('${fieldId}')"
|
|
5541
|
+
data-media-remove="true"
|
|
5454
5542
|
class="inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all"
|
|
5455
5543
|
${disabled ? "disabled" : ""}
|
|
5456
5544
|
>
|
|
@@ -5484,7 +5572,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5484
5572
|
}
|
|
5485
5573
|
const showLabel = field.field_type !== "boolean";
|
|
5486
5574
|
return `
|
|
5487
|
-
<div class="form-group">
|
|
5575
|
+
<div class="form-group" data-has-errors="${errors.length > 0 ? "true" : "false"}">
|
|
5488
5576
|
${showLabel ? `
|
|
5489
5577
|
<label for="${fieldId}" class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2">
|
|
5490
5578
|
${escapeHtml3(field.field_label)}
|
|
@@ -5493,7 +5581,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5493
5581
|
` : ""}
|
|
5494
5582
|
${fieldHTML}
|
|
5495
5583
|
${errors.length > 0 ? `
|
|
5496
|
-
<div class="mt-2 text-sm text-pink-600 dark:text-pink-400">
|
|
5584
|
+
<div class="mt-2 text-sm text-pink-600 dark:text-pink-400" data-validation-error-message>
|
|
5497
5585
|
${errors.map((error) => `<div>${escapeHtml3(error)}</div>`).join("")}
|
|
5498
5586
|
</div>
|
|
5499
5587
|
` : ""}
|
|
@@ -5508,8 +5596,8 @@ function renderDynamicField(field, options = {}) {
|
|
|
5508
5596
|
function renderFieldGroup(title, fields, collapsible = false) {
|
|
5509
5597
|
const groupId = title.toLowerCase().replace(/\s+/g, "-");
|
|
5510
5598
|
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(
|
|
5599
|
+
<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)}">
|
|
5600
|
+
<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
5601
|
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
|
|
5514
5602
|
${escapeHtml3(title)}
|
|
5515
5603
|
${collapsible ? `
|
|
@@ -5553,6 +5641,12 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
|
5553
5641
|
>
|
|
5554
5642
|
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(blockValues))}">
|
|
5555
5643
|
|
|
5644
|
+
<div class="flex items-center justify-between border-b border-zinc-950/5 dark:border-white/10 py-4">
|
|
5645
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">
|
|
5646
|
+
${escapeHtml3(field.field_label || "Content Blocks")}
|
|
5647
|
+
</h3>
|
|
5648
|
+
</div>
|
|
5649
|
+
|
|
5556
5650
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5557
5651
|
<div class="flex-1">
|
|
5558
5652
|
<select
|
|
@@ -5583,12 +5677,14 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
|
5583
5677
|
`;
|
|
5584
5678
|
}
|
|
5585
5679
|
function renderStructuredObjectField(field, options, baseClasses, errorClasses) {
|
|
5586
|
-
const { value = {}, pluginStatuses = {} } = options;
|
|
5680
|
+
const { value = {}, pluginStatuses = {}, errors = [] } = options;
|
|
5587
5681
|
const opts = field.field_options || {};
|
|
5588
5682
|
const properties = opts.properties && typeof opts.properties === "object" ? opts.properties : {};
|
|
5589
5683
|
const fieldId = `field-${field.field_name}`;
|
|
5590
5684
|
const fieldName = field.field_name;
|
|
5591
5685
|
const objectValue = normalizeStructuredObjectValue(value);
|
|
5686
|
+
const objectLayout = opts.objectLayout || "nested";
|
|
5687
|
+
const useNestedLayout = objectLayout !== "flat";
|
|
5592
5688
|
const subfields = Object.entries(properties).map(
|
|
5593
5689
|
([propertyName, propertyConfig]) => renderStructuredSubfield(
|
|
5594
5690
|
field,
|
|
@@ -5599,11 +5695,40 @@ function renderStructuredObjectField(field, options, baseClasses, errorClasses)
|
|
|
5599
5695
|
field.field_name
|
|
5600
5696
|
)
|
|
5601
5697
|
).join("");
|
|
5698
|
+
const groupTitle = field.field_label || field.field_name;
|
|
5699
|
+
if (!useNestedLayout) {
|
|
5700
|
+
return `
|
|
5701
|
+
<div class="space-y-4" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
|
|
5702
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
|
|
5703
|
+
<div class="flex items-center justify-between border-b border-zinc-950/5 dark:border-white/10 py-4 first-of-type:pt-0">
|
|
5704
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">
|
|
5705
|
+
${escapeHtml3(groupTitle)}
|
|
5706
|
+
</h3>
|
|
5707
|
+
</div>
|
|
5708
|
+
<div class="space-y-4" data-structured-object-fields>
|
|
5709
|
+
${subfields}
|
|
5710
|
+
</div>
|
|
5711
|
+
</div>
|
|
5712
|
+
${getStructuredFieldScript()}
|
|
5713
|
+
`;
|
|
5714
|
+
}
|
|
5715
|
+
const groupId = sanitizeStructuredGroupId(field.field_name);
|
|
5716
|
+
const isCollapsed = errors.length > 0 ? false : opts.collapsed !== false;
|
|
5602
5717
|
return `
|
|
5603
|
-
<div class="
|
|
5604
|
-
<
|
|
5605
|
-
|
|
5606
|
-
|
|
5718
|
+
<div class="field-group rounded-lg shadow-sm mb-6" data-group-id="${escapeHtml3(groupId)}" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
|
|
5719
|
+
<div class="field-group-header border-b border-zinc-950/5 dark:border-white/10 pr-6 pb-4 cursor-pointer" onclick="toggleFieldGroup(this)">
|
|
5720
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
|
|
5721
|
+
${escapeHtml3(groupTitle)}
|
|
5722
|
+
<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">
|
|
5723
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
5724
|
+
</svg>
|
|
5725
|
+
</h3>
|
|
5726
|
+
</div>
|
|
5727
|
+
<div id="${groupId}-content" class="field-group-content px-6 py-6 space-y-4 ${isCollapsed ? "hidden" : ""}">
|
|
5728
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
|
|
5729
|
+
<div class="space-y-4" data-structured-object-fields>
|
|
5730
|
+
${subfields}
|
|
5731
|
+
</div>
|
|
5607
5732
|
</div>
|
|
5608
5733
|
</div>
|
|
5609
5734
|
${getStructuredFieldScript()}
|
|
@@ -5674,7 +5799,7 @@ function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
|
|
|
5674
5799
|
function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginStatuses, arrayItemTitle) {
|
|
5675
5800
|
const itemFields = renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses);
|
|
5676
5801
|
return `
|
|
5677
|
-
<div class="structured-array-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-
|
|
5802
|
+
<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
5803
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5679
5804
|
<div class="flex items-center gap-3">
|
|
5680
5805
|
<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 +5812,11 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
|
|
|
5687
5812
|
</div>
|
|
5688
5813
|
</div>
|
|
5689
5814
|
<div class="flex flex-wrap gap-2 text-xs">
|
|
5815
|
+
<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">
|
|
5816
|
+
<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>
|
|
5817
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
|
5818
|
+
</svg>
|
|
5819
|
+
</button>
|
|
5690
5820
|
<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
5821
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
5692
5822
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
@@ -5705,7 +5835,7 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
|
|
|
5705
5835
|
</button>
|
|
5706
5836
|
</div>
|
|
5707
5837
|
</div>
|
|
5708
|
-
<div class="mt-4 space-y-4" data-array-item-fields>
|
|
5838
|
+
<div class="mt-4 space-y-4 hidden" data-array-item-fields>
|
|
5709
5839
|
${itemFields}
|
|
5710
5840
|
</div>
|
|
5711
5841
|
</div>
|
|
@@ -5815,7 +5945,7 @@ function normalizeBlocksValue(value, discriminator) {
|
|
|
5815
5945
|
function renderBlockTemplate(field, block, discriminator, pluginStatuses) {
|
|
5816
5946
|
return `
|
|
5817
5947
|
<template data-block-template="${escapeHtml3(block.name)}">
|
|
5818
|
-
${renderBlockCard(field, block, discriminator,
|
|
5948
|
+
${renderBlockCard(field, block, discriminator, BLOCK_INDEX_TOKEN, {}, pluginStatuses)}
|
|
5819
5949
|
</template>
|
|
5820
5950
|
`;
|
|
5821
5951
|
}
|
|
@@ -5857,7 +5987,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5857
5987
|
`;
|
|
5858
5988
|
}).join("");
|
|
5859
5989
|
return `
|
|
5860
|
-
<div class="blocks-item rounded-lg border border-zinc-200 dark:border-white/10
|
|
5990
|
+
<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
5991
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5862
5992
|
<div class="flex items-start gap-3">
|
|
5863
5993
|
<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 +5995,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5865
5995
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
|
|
5866
5996
|
</svg>
|
|
5867
5997
|
</div>
|
|
5868
|
-
<div>
|
|
5998
|
+
<div class="cursor-pointer" data-action="toggle-block">
|
|
5869
5999
|
<div class="text-sm font-semibold text-zinc-900 dark:text-white">
|
|
5870
6000
|
${escapeHtml3(block.label)}
|
|
5871
6001
|
<span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-block-order-label></span>
|
|
@@ -5874,6 +6004,11 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5874
6004
|
</div>
|
|
5875
6005
|
</div>
|
|
5876
6006
|
<div class="flex flex-wrap gap-2 text-xs">
|
|
6007
|
+
<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">
|
|
6008
|
+
<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>
|
|
6009
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
|
6010
|
+
</svg>
|
|
6011
|
+
</button>
|
|
5877
6012
|
<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
6013
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
5879
6014
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
@@ -5892,7 +6027,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5892
6027
|
</button>
|
|
5893
6028
|
</div>
|
|
5894
6029
|
</div>
|
|
5895
|
-
<div class="mt-4 space-y-4">
|
|
6030
|
+
<div class="mt-4 space-y-4 hidden" data-block-content>
|
|
5896
6031
|
${blockFields}
|
|
5897
6032
|
</div>
|
|
5898
6033
|
</div>
|
|
@@ -5926,9 +6061,101 @@ function getStructuredFieldScript() {
|
|
|
5926
6061
|
|
|
5927
6062
|
function initializeStructuredFields() {
|
|
5928
6063
|
const readFieldValue = window.sonicReadFieldValue;
|
|
6064
|
+
const getDirectChild = (parent, selector) => {
|
|
6065
|
+
if (!(parent instanceof Element)) return null;
|
|
6066
|
+
return Array.from(parent.children).find(
|
|
6067
|
+
(child) => child instanceof Element && child.matches(selector),
|
|
6068
|
+
) || null;
|
|
6069
|
+
};
|
|
6070
|
+
const getDirectStructuredSubfields = (host) =>
|
|
6071
|
+
Array.from(host.children).filter(
|
|
6072
|
+
(child) => child instanceof Element && child.classList.contains('structured-subfield'),
|
|
6073
|
+
);
|
|
6074
|
+
const getStructuredValueHost = (container) => {
|
|
6075
|
+
const directObjectHost = getDirectChild(container, '[data-structured-object-fields]');
|
|
6076
|
+
if (directObjectHost) return directObjectHost;
|
|
6077
|
+
const groupContent = getDirectChild(container, '.field-group-content');
|
|
6078
|
+
const nestedObjectHost = groupContent
|
|
6079
|
+
? getDirectChild(groupContent, '[data-structured-object-fields]')
|
|
6080
|
+
: null;
|
|
6081
|
+
if (nestedObjectHost) return nestedObjectHost;
|
|
6082
|
+
return getDirectChild(container, '[data-array-item-fields]') || container;
|
|
6083
|
+
};
|
|
6084
|
+
const getCollectionScope = () => {
|
|
6085
|
+
const url = new URL(window.location.href);
|
|
6086
|
+
const collectionFromQuery = url.searchParams.get('collection');
|
|
6087
|
+
const form = document.getElementById('content-form');
|
|
6088
|
+
const collectionInput = form?.querySelector('input[name="collection_id"]');
|
|
6089
|
+
const collectionFromForm = collectionInput instanceof HTMLInputElement ? collectionInput.value : '';
|
|
6090
|
+
const collectionId = collectionFromQuery || collectionFromForm || '';
|
|
6091
|
+
return window.location.pathname + ':' + collectionId;
|
|
6092
|
+
};
|
|
6093
|
+
|
|
6094
|
+
const getArrayStateKey = (container) => {
|
|
6095
|
+
const fieldName = container.dataset.fieldName || 'unknown';
|
|
6096
|
+
return 'sonic:ui:repeaters:' + getCollectionScope() + ':' + fieldName;
|
|
6097
|
+
};
|
|
6098
|
+
|
|
6099
|
+
const readArrayState = (container) => {
|
|
6100
|
+
try {
|
|
6101
|
+
const raw = sessionStorage.getItem(getArrayStateKey(container));
|
|
6102
|
+
if (!raw) return null;
|
|
6103
|
+
const parsed = JSON.parse(raw);
|
|
6104
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
6105
|
+
} catch {
|
|
6106
|
+
return null;
|
|
6107
|
+
}
|
|
6108
|
+
};
|
|
6109
|
+
|
|
6110
|
+
const writeArrayState = (container, state) => {
|
|
6111
|
+
try {
|
|
6112
|
+
sessionStorage.setItem(getArrayStateKey(container), JSON.stringify(state));
|
|
6113
|
+
} catch {}
|
|
6114
|
+
};
|
|
6115
|
+
|
|
6116
|
+
const setArrayItemExpanded = (item, isExpanded) => {
|
|
6117
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6118
|
+
const icon = item.querySelector('[data-item-toggle-icon]');
|
|
6119
|
+
if (content instanceof HTMLElement) {
|
|
6120
|
+
content.classList.toggle('hidden', !isExpanded);
|
|
6121
|
+
}
|
|
6122
|
+
if (icon instanceof Element) {
|
|
6123
|
+
icon.classList.toggle('-rotate-90', !isExpanded);
|
|
6124
|
+
}
|
|
6125
|
+
};
|
|
6126
|
+
|
|
6127
|
+
const getArrayItems = (container, list) => {
|
|
6128
|
+
if (list) {
|
|
6129
|
+
return Array.from(list.querySelectorAll(':scope > .structured-array-item'));
|
|
6130
|
+
}
|
|
6131
|
+
return Array.from(
|
|
6132
|
+
container.querySelectorAll(':scope > [data-structured-array-list] > .structured-array-item'),
|
|
6133
|
+
);
|
|
6134
|
+
};
|
|
6135
|
+
|
|
6136
|
+
const captureArrayState = (container) => {
|
|
6137
|
+
return getArrayItems(container).map((item) => {
|
|
6138
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6139
|
+
return content instanceof HTMLElement ? !content.classList.contains('hidden') : false;
|
|
6140
|
+
});
|
|
6141
|
+
};
|
|
6142
|
+
|
|
6143
|
+
const applyArrayState = (container, state) => {
|
|
6144
|
+
const items = getArrayItems(container);
|
|
6145
|
+
items.forEach((item, index) => {
|
|
6146
|
+
if (typeof state[index] === 'boolean') {
|
|
6147
|
+
setArrayItemExpanded(item, state[index]);
|
|
6148
|
+
}
|
|
6149
|
+
});
|
|
6150
|
+
};
|
|
6151
|
+
|
|
6152
|
+
const syncArrayState = (container) => {
|
|
6153
|
+
writeArrayState(container, captureArrayState(container));
|
|
6154
|
+
};
|
|
5929
6155
|
|
|
5930
6156
|
const readStructuredValue = (container) => {
|
|
5931
|
-
const
|
|
6157
|
+
const fieldHost = getStructuredValueHost(container);
|
|
6158
|
+
const fields = getDirectStructuredSubfields(fieldHost);
|
|
5932
6159
|
if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
|
|
5933
6160
|
return readFieldValue(fields[0]);
|
|
5934
6161
|
}
|
|
@@ -5942,111 +6169,229 @@ function getStructuredFieldScript() {
|
|
|
5942
6169
|
};
|
|
5943
6170
|
|
|
5944
6171
|
document.querySelectorAll('[data-structured-object]').forEach((container) => {
|
|
6172
|
+
if (container.closest('template')) {
|
|
6173
|
+
return;
|
|
6174
|
+
}
|
|
5945
6175
|
if (container.dataset.structuredInitialized === 'true') {
|
|
5946
6176
|
return;
|
|
5947
6177
|
}
|
|
5948
|
-
container.dataset.
|
|
5949
|
-
|
|
6178
|
+
if (container.dataset.structuredInitializing === 'true') {
|
|
6179
|
+
return;
|
|
6180
|
+
}
|
|
6181
|
+
container.dataset.structuredInitializing = 'true';
|
|
6182
|
+
try {
|
|
6183
|
+
const hiddenInput = container.querySelector('input[type="hidden"]');
|
|
5950
6184
|
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6185
|
+
const updateHiddenInput = () => {
|
|
6186
|
+
if (!hiddenInput) return;
|
|
6187
|
+
const value = readStructuredValue(container);
|
|
6188
|
+
hiddenInput.value = JSON.stringify(value);
|
|
6189
|
+
};
|
|
5956
6190
|
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
6191
|
+
container.addEventListener('input', updateHiddenInput);
|
|
6192
|
+
container.addEventListener('change', updateHiddenInput);
|
|
6193
|
+
updateHiddenInput();
|
|
6194
|
+
container.dataset.structuredInitialized = 'true';
|
|
6195
|
+
} catch (error) {
|
|
6196
|
+
delete container.dataset.structuredInitialized;
|
|
6197
|
+
console.error('[structured-object] initialization failed', error);
|
|
6198
|
+
} finally {
|
|
6199
|
+
delete container.dataset.structuredInitializing;
|
|
6200
|
+
}
|
|
5960
6201
|
});
|
|
5961
6202
|
|
|
5962
6203
|
document.querySelectorAll('[data-structured-array]').forEach((container) => {
|
|
6204
|
+
if (container.closest('template')) {
|
|
6205
|
+
return;
|
|
6206
|
+
}
|
|
5963
6207
|
if (container.dataset.structuredInitialized === 'true') {
|
|
5964
6208
|
return;
|
|
5965
6209
|
}
|
|
5966
|
-
container.dataset.
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
6210
|
+
if (container.dataset.structuredInitializing === 'true') {
|
|
6211
|
+
return;
|
|
6212
|
+
}
|
|
6213
|
+
container.dataset.structuredInitializing = 'true';
|
|
6214
|
+
try {
|
|
6215
|
+
const list = container.querySelector(':scope > [data-structured-array-list]');
|
|
6216
|
+
const hiddenInput = container.querySelector(':scope > input[type="hidden"]');
|
|
6217
|
+
const template = container.querySelector(':scope > template[data-structured-array-template]');
|
|
6218
|
+
if (
|
|
6219
|
+
template instanceof HTMLTemplateElement &&
|
|
6220
|
+
typeof template.innerHTML === 'string' &&
|
|
6221
|
+
template.innerHTML.trim()
|
|
6222
|
+
) {
|
|
6223
|
+
container.__sonicStructuredArrayTemplate = template.innerHTML;
|
|
6224
|
+
}
|
|
5970
6225
|
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
6226
|
+
const getLiveList = () =>
|
|
6227
|
+
list || container.querySelector(':scope > [data-structured-array-list]');
|
|
6228
|
+
const getLiveHiddenInput = () =>
|
|
6229
|
+
hiddenInput || container.querySelector(':scope > input[type="hidden"]');
|
|
6230
|
+
const getTemplateHtml = () => {
|
|
6231
|
+
if (typeof container.__sonicStructuredArrayTemplate === 'string' &&
|
|
6232
|
+
container.__sonicStructuredArrayTemplate.trim()) {
|
|
6233
|
+
return container.__sonicStructuredArrayTemplate;
|
|
5977
6234
|
}
|
|
5978
6235
|
|
|
5979
|
-
const
|
|
5980
|
-
|
|
5981
|
-
|
|
6236
|
+
const liveTemplate =
|
|
6237
|
+
template instanceof HTMLTemplateElement
|
|
6238
|
+
? template
|
|
6239
|
+
: container.querySelector(':scope > template[data-structured-array-template]');
|
|
6240
|
+
if (
|
|
6241
|
+
liveTemplate instanceof HTMLTemplateElement &&
|
|
6242
|
+
typeof liveTemplate.innerHTML === 'string' &&
|
|
6243
|
+
liveTemplate.innerHTML.trim()
|
|
6244
|
+
) {
|
|
6245
|
+
container.__sonicStructuredArrayTemplate = liveTemplate.innerHTML;
|
|
6246
|
+
return liveTemplate.innerHTML;
|
|
5982
6247
|
}
|
|
6248
|
+
return typeof container.__sonicStructuredArrayTemplate === 'string'
|
|
6249
|
+
? container.__sonicStructuredArrayTemplate
|
|
6250
|
+
: '';
|
|
6251
|
+
};
|
|
5983
6252
|
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
6253
|
+
const updateOrderLabels = () => {
|
|
6254
|
+
const liveList = getLiveList();
|
|
6255
|
+
if (!liveList) return;
|
|
6256
|
+
const items = getArrayItems(container, liveList);
|
|
6257
|
+
items.forEach((item, index) => {
|
|
6258
|
+
const label = item.querySelector('[data-array-order-label]');
|
|
6259
|
+
if (label) {
|
|
6260
|
+
label.textContent = '#'+ (index + 1);
|
|
6261
|
+
}
|
|
6262
|
+
|
|
6263
|
+
const moveUpButton = item.querySelector('[data-action="move-up"]');
|
|
6264
|
+
if (moveUpButton instanceof HTMLButtonElement) {
|
|
6265
|
+
moveUpButton.disabled = index === 0;
|
|
6266
|
+
}
|
|
6267
|
+
|
|
6268
|
+
const moveDownButton = item.querySelector('[data-action="move-down"]');
|
|
6269
|
+
if (moveDownButton instanceof HTMLButtonElement) {
|
|
6270
|
+
moveDownButton.disabled = index === items.length - 1;
|
|
6271
|
+
}
|
|
6272
|
+
});
|
|
6273
|
+
};
|
|
6274
|
+
|
|
6275
|
+
const updateHiddenInput = () => {
|
|
6276
|
+
const liveHiddenInput = getLiveHiddenInput();
|
|
6277
|
+
const liveList = getLiveList();
|
|
6278
|
+
if (!liveHiddenInput || !liveList) return;
|
|
6279
|
+
const items = getArrayItems(container, liveList);
|
|
6280
|
+
const values = items.map((item) => readStructuredValue(item));
|
|
6281
|
+
liveHiddenInput.value = JSON.stringify(values);
|
|
6282
|
+
// Notify parent structured containers after non-input actions (add/remove/move)
|
|
6283
|
+
// so nested array mutations are persisted correctly.
|
|
6284
|
+
liveHiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
6285
|
+
|
|
6286
|
+
const emptyState = liveList.querySelector(':scope > [data-structured-empty]');
|
|
6287
|
+
if (emptyState) {
|
|
6288
|
+
emptyState.style.display = values.length === 0 ? 'block' : 'none';
|
|
5987
6289
|
}
|
|
5988
|
-
|
|
5989
|
-
|
|
6290
|
+
updateOrderLabels();
|
|
6291
|
+
};
|
|
5990
6292
|
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
6293
|
+
const addArrayItem = () => {
|
|
6294
|
+
const liveList = getLiveList();
|
|
6295
|
+
if (!liveList) return;
|
|
6296
|
+
const templateHtml = getTemplateHtml();
|
|
6297
|
+
if (!templateHtml) return;
|
|
6298
|
+
try {
|
|
6299
|
+
const nextIndex = getArrayItems(container, liveList).length;
|
|
6300
|
+
const html = templateHtml.replace(/__INDEX__/g, String(nextIndex));
|
|
6301
|
+
liveList.insertAdjacentHTML('beforeend', html);
|
|
6302
|
+
const newItem = liveList.lastElementChild;
|
|
6303
|
+
if (newItem instanceof HTMLElement) {
|
|
6304
|
+
// Ensure cloned template content can be initialized even if stale
|
|
6305
|
+
// data-structured-initialized attributes were copied.
|
|
6306
|
+
newItem
|
|
6307
|
+
.querySelectorAll('[data-structured-object], [data-structured-array]')
|
|
6308
|
+
.forEach((nestedContainer) => {
|
|
6309
|
+
if (nestedContainer instanceof HTMLElement) {
|
|
6310
|
+
delete nestedContainer.dataset.structuredInitialized;
|
|
6311
|
+
}
|
|
6312
|
+
});
|
|
6313
|
+
setArrayItemExpanded(newItem, true);
|
|
6314
|
+
}
|
|
6315
|
+
if (typeof initializeTinyMCE === 'function') {
|
|
6316
|
+
initializeTinyMCE();
|
|
6317
|
+
}
|
|
6318
|
+
if (typeof window.initializeQuillEditors === 'function') {
|
|
6319
|
+
window.initializeQuillEditors();
|
|
6320
|
+
}
|
|
6321
|
+
if (typeof initializeMDXEditor === 'function') {
|
|
6322
|
+
initializeMDXEditor();
|
|
6323
|
+
}
|
|
6324
|
+
if (typeof window.initializeStructuredFields === 'function') {
|
|
6325
|
+
window.initializeStructuredFields();
|
|
6326
|
+
}
|
|
6327
|
+
updateHiddenInput();
|
|
6328
|
+
syncArrayState(container);
|
|
6329
|
+
} catch (error) {
|
|
6330
|
+
console.error('[structured-array] add-item failed', error);
|
|
6331
|
+
}
|
|
6332
|
+
};
|
|
5996
6333
|
|
|
5997
|
-
const
|
|
5998
|
-
|
|
5999
|
-
|
|
6334
|
+
const topLevelAddButton = container.querySelector(
|
|
6335
|
+
':scope > .flex.items-center.justify-between.gap-3 [data-action="add-item"]',
|
|
6336
|
+
);
|
|
6337
|
+
if (topLevelAddButton instanceof HTMLElement) {
|
|
6338
|
+
topLevelAddButton.addEventListener('click', (event) => {
|
|
6339
|
+
event.preventDefault();
|
|
6340
|
+
event.stopPropagation();
|
|
6341
|
+
addArrayItem();
|
|
6342
|
+
});
|
|
6000
6343
|
}
|
|
6001
|
-
updateOrderLabels();
|
|
6002
|
-
};
|
|
6003
6344
|
|
|
6004
|
-
|
|
6005
|
-
window.initializeDragSortable
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6345
|
+
const dragList = getLiveList();
|
|
6346
|
+
if (typeof window.initializeDragSortable === 'function' && dragList) {
|
|
6347
|
+
window.initializeDragSortable(dragList, {
|
|
6348
|
+
itemSelector: '.structured-array-item',
|
|
6349
|
+
handleSelector: '[data-action="drag-handle"]',
|
|
6350
|
+
onUpdate: () => {
|
|
6351
|
+
updateHiddenInput();
|
|
6352
|
+
syncArrayState(container);
|
|
6353
|
+
}
|
|
6354
|
+
});
|
|
6355
|
+
}
|
|
6011
6356
|
|
|
6012
|
-
|
|
6357
|
+
container.addEventListener('click', (event) => {
|
|
6013
6358
|
const target = event.target;
|
|
6014
6359
|
if (!(target instanceof Element)) return;
|
|
6015
6360
|
const actionButton = target.closest('[data-action]');
|
|
6016
6361
|
if (!actionButton || actionButton.hasAttribute('disabled')) return;
|
|
6362
|
+
const actionOwner = actionButton.closest('[data-structured-array]');
|
|
6363
|
+
if (actionOwner !== container) return;
|
|
6017
6364
|
|
|
6018
|
-
|
|
6365
|
+
const action = actionButton.getAttribute('data-action');
|
|
6019
6366
|
|
|
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();
|
|
6367
|
+
if (action === 'add-item') {
|
|
6368
|
+
addArrayItem();
|
|
6369
|
+
return;
|
|
6033
6370
|
}
|
|
6034
|
-
updateHiddenInput();
|
|
6035
|
-
return;
|
|
6036
|
-
}
|
|
6037
6371
|
|
|
6038
6372
|
const item = actionButton.closest('.structured-array-item');
|
|
6039
|
-
|
|
6373
|
+
const liveList = getLiveList();
|
|
6374
|
+
if (!item || !liveList) return;
|
|
6375
|
+
|
|
6376
|
+
if (action === 'toggle-item') {
|
|
6377
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6378
|
+
if (!(content instanceof HTMLElement)) return;
|
|
6379
|
+
setArrayItemExpanded(item, content.classList.contains('hidden'));
|
|
6380
|
+
syncArrayState(container);
|
|
6381
|
+
return;
|
|
6382
|
+
}
|
|
6040
6383
|
|
|
6041
6384
|
if (action === 'remove-item') {
|
|
6042
6385
|
if (typeof requestRepeaterDelete === 'function') {
|
|
6043
6386
|
requestRepeaterDelete(() => {
|
|
6044
6387
|
item.remove();
|
|
6045
6388
|
updateHiddenInput();
|
|
6389
|
+
syncArrayState(container);
|
|
6046
6390
|
});
|
|
6047
6391
|
} else {
|
|
6048
6392
|
item.remove();
|
|
6049
6393
|
updateHiddenInput();
|
|
6394
|
+
syncArrayState(container);
|
|
6050
6395
|
}
|
|
6051
6396
|
return;
|
|
6052
6397
|
}
|
|
@@ -6054,8 +6399,9 @@ function getStructuredFieldScript() {
|
|
|
6054
6399
|
if (action === 'move-up') {
|
|
6055
6400
|
const previous = item.previousElementSibling;
|
|
6056
6401
|
if (previous) {
|
|
6057
|
-
|
|
6402
|
+
liveList.insertBefore(item, previous);
|
|
6058
6403
|
updateHiddenInput();
|
|
6404
|
+
syncArrayState(container);
|
|
6059
6405
|
}
|
|
6060
6406
|
return;
|
|
6061
6407
|
}
|
|
@@ -6063,29 +6409,43 @@ function getStructuredFieldScript() {
|
|
|
6063
6409
|
if (action === 'move-down') {
|
|
6064
6410
|
const next = item.nextElementSibling;
|
|
6065
6411
|
if (next) {
|
|
6066
|
-
|
|
6412
|
+
liveList.insertBefore(next, item);
|
|
6067
6413
|
updateHiddenInput();
|
|
6414
|
+
syncArrayState(container);
|
|
6068
6415
|
}
|
|
6069
6416
|
}
|
|
6070
|
-
|
|
6417
|
+
});
|
|
6071
6418
|
|
|
6072
|
-
|
|
6419
|
+
container.addEventListener('input', (event) => {
|
|
6073
6420
|
const target = event.target;
|
|
6074
6421
|
if (!(target instanceof Element)) return;
|
|
6075
6422
|
if (target.closest('[data-structured-array-list]')) {
|
|
6076
6423
|
updateHiddenInput();
|
|
6077
6424
|
}
|
|
6078
|
-
|
|
6425
|
+
});
|
|
6079
6426
|
|
|
6080
|
-
|
|
6427
|
+
container.addEventListener('change', (event) => {
|
|
6081
6428
|
const target = event.target;
|
|
6082
6429
|
if (!(target instanceof Element)) return;
|
|
6083
6430
|
if (target.closest('[data-structured-array-list]')) {
|
|
6084
6431
|
updateHiddenInput();
|
|
6085
6432
|
}
|
|
6086
|
-
|
|
6433
|
+
});
|
|
6087
6434
|
|
|
6088
|
-
|
|
6435
|
+
updateHiddenInput();
|
|
6436
|
+
const savedArrayState = readArrayState(container);
|
|
6437
|
+
if (savedArrayState) {
|
|
6438
|
+
applyArrayState(container, savedArrayState);
|
|
6439
|
+
} else {
|
|
6440
|
+
syncArrayState(container);
|
|
6441
|
+
}
|
|
6442
|
+
container.dataset.structuredInitialized = 'true';
|
|
6443
|
+
} catch (error) {
|
|
6444
|
+
delete container.dataset.structuredInitialized;
|
|
6445
|
+
console.error('[structured-array] initialization failed', error);
|
|
6446
|
+
} finally {
|
|
6447
|
+
delete container.dataset.structuredInitializing;
|
|
6448
|
+
}
|
|
6089
6449
|
});
|
|
6090
6450
|
}
|
|
6091
6451
|
|
|
@@ -6100,7 +6460,10 @@ function getStructuredFieldScript() {
|
|
|
6100
6460
|
document.addEventListener('htmx:afterSwap', function() {
|
|
6101
6461
|
setTimeout(initializeStructuredFields, 50);
|
|
6102
6462
|
});
|
|
6103
|
-
} else if (
|
|
6463
|
+
} else if (
|
|
6464
|
+
typeof window.initializeStructuredFields === 'function' &&
|
|
6465
|
+
document.readyState !== 'loading'
|
|
6466
|
+
) {
|
|
6104
6467
|
window.initializeStructuredFields();
|
|
6105
6468
|
}
|
|
6106
6469
|
</script>
|
|
@@ -6112,6 +6475,68 @@ function getBlocksFieldScript() {
|
|
|
6112
6475
|
<script>
|
|
6113
6476
|
if (!window.__sonicBlocksFieldInit) {
|
|
6114
6477
|
window.__sonicBlocksFieldInit = true;
|
|
6478
|
+
const getCollectionScope = () => {
|
|
6479
|
+
const url = new URL(window.location.href);
|
|
6480
|
+
const collectionFromQuery = url.searchParams.get('collection');
|
|
6481
|
+
const form = document.getElementById('content-form');
|
|
6482
|
+
const collectionInput = form?.querySelector('input[name="collection_id"]');
|
|
6483
|
+
const collectionFromForm = collectionInput instanceof HTMLInputElement ? collectionInput.value : '';
|
|
6484
|
+
const collectionId = collectionFromQuery || collectionFromForm || '';
|
|
6485
|
+
return window.location.pathname + ':' + collectionId;
|
|
6486
|
+
};
|
|
6487
|
+
|
|
6488
|
+
const getBlocksStateKey = (container) => {
|
|
6489
|
+
const fieldName = container.dataset.fieldName || 'unknown';
|
|
6490
|
+
return 'sonic:ui:blocks:' + getCollectionScope() + ':' + fieldName;
|
|
6491
|
+
};
|
|
6492
|
+
|
|
6493
|
+
const readBlocksState = (container) => {
|
|
6494
|
+
try {
|
|
6495
|
+
const raw = sessionStorage.getItem(getBlocksStateKey(container));
|
|
6496
|
+
if (!raw) return null;
|
|
6497
|
+
const parsed = JSON.parse(raw);
|
|
6498
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
6499
|
+
} catch {
|
|
6500
|
+
return null;
|
|
6501
|
+
}
|
|
6502
|
+
};
|
|
6503
|
+
|
|
6504
|
+
const writeBlocksState = (container, state) => {
|
|
6505
|
+
try {
|
|
6506
|
+
sessionStorage.setItem(getBlocksStateKey(container), JSON.stringify(state));
|
|
6507
|
+
} catch {}
|
|
6508
|
+
};
|
|
6509
|
+
|
|
6510
|
+
const setBlockExpanded = (item, isExpanded) => {
|
|
6511
|
+
const content = item.querySelector('[data-block-content]');
|
|
6512
|
+
const icon = item.querySelector('[data-block-toggle-icon]');
|
|
6513
|
+
if (content instanceof HTMLElement) {
|
|
6514
|
+
content.classList.toggle('hidden', !isExpanded);
|
|
6515
|
+
}
|
|
6516
|
+
if (icon instanceof Element) {
|
|
6517
|
+
icon.classList.toggle('-rotate-90', !isExpanded);
|
|
6518
|
+
}
|
|
6519
|
+
};
|
|
6520
|
+
|
|
6521
|
+
const captureBlocksState = (container) => {
|
|
6522
|
+
return Array.from(container.querySelectorAll('.blocks-item')).map((item) => {
|
|
6523
|
+
const content = item.querySelector('[data-block-content]');
|
|
6524
|
+
return content instanceof HTMLElement ? !content.classList.contains('hidden') : false;
|
|
6525
|
+
});
|
|
6526
|
+
};
|
|
6527
|
+
|
|
6528
|
+
const applyBlocksState = (container, state) => {
|
|
6529
|
+
const items = Array.from(container.querySelectorAll('.blocks-item'));
|
|
6530
|
+
items.forEach((item, index) => {
|
|
6531
|
+
if (typeof state[index] === 'boolean') {
|
|
6532
|
+
setBlockExpanded(item, state[index]);
|
|
6533
|
+
}
|
|
6534
|
+
});
|
|
6535
|
+
};
|
|
6536
|
+
|
|
6537
|
+
const syncBlocksState = (container) => {
|
|
6538
|
+
writeBlocksState(container, captureBlocksState(container));
|
|
6539
|
+
};
|
|
6115
6540
|
|
|
6116
6541
|
function initializeBlocksFields() {
|
|
6117
6542
|
document.querySelectorAll('.blocks-field').forEach((container) => {
|
|
@@ -6199,7 +6624,10 @@ function getBlocksFieldScript() {
|
|
|
6199
6624
|
window.initializeDragSortable(list, {
|
|
6200
6625
|
itemSelector: '.blocks-item',
|
|
6201
6626
|
handleSelector: '[data-action="drag-handle"]',
|
|
6202
|
-
onUpdate:
|
|
6627
|
+
onUpdate: () => {
|
|
6628
|
+
updateHiddenInput();
|
|
6629
|
+
syncBlocksState(container);
|
|
6630
|
+
}
|
|
6203
6631
|
});
|
|
6204
6632
|
}
|
|
6205
6633
|
|
|
@@ -6221,8 +6649,12 @@ function getBlocksFieldScript() {
|
|
|
6221
6649
|
if (!template) return;
|
|
6222
6650
|
|
|
6223
6651
|
const nextIndex = list.querySelectorAll('.blocks-item').length;
|
|
6224
|
-
const html = template.innerHTML.replace(/
|
|
6652
|
+
const html = template.innerHTML.replace(/__BLOCK_INDEX__/g, String(nextIndex));
|
|
6225
6653
|
list.insertAdjacentHTML('beforeend', html);
|
|
6654
|
+
const newItem = list.lastElementChild;
|
|
6655
|
+
if (newItem instanceof HTMLElement) {
|
|
6656
|
+
setBlockExpanded(newItem, true);
|
|
6657
|
+
}
|
|
6226
6658
|
if (typeSelect) {
|
|
6227
6659
|
typeSelect.value = '';
|
|
6228
6660
|
}
|
|
@@ -6231,21 +6663,32 @@ function getBlocksFieldScript() {
|
|
|
6231
6663
|
window.initializeStructuredFields();
|
|
6232
6664
|
}
|
|
6233
6665
|
updateHiddenInput();
|
|
6666
|
+
syncBlocksState(container);
|
|
6234
6667
|
return;
|
|
6235
6668
|
}
|
|
6236
6669
|
|
|
6237
6670
|
const item = actionButton.closest('.blocks-item');
|
|
6238
6671
|
if (!item || !list) return;
|
|
6239
6672
|
|
|
6673
|
+
if (action === 'toggle-block') {
|
|
6674
|
+
const content = item.querySelector('[data-block-content]');
|
|
6675
|
+
if (!(content instanceof HTMLElement)) return;
|
|
6676
|
+
setBlockExpanded(item, content.classList.contains('hidden'));
|
|
6677
|
+
syncBlocksState(container);
|
|
6678
|
+
return;
|
|
6679
|
+
}
|
|
6680
|
+
|
|
6240
6681
|
if (action === 'remove-block') {
|
|
6241
6682
|
if (typeof requestRepeaterDelete === 'function') {
|
|
6242
6683
|
requestRepeaterDelete(() => {
|
|
6243
6684
|
item.remove();
|
|
6244
6685
|
updateHiddenInput();
|
|
6686
|
+
syncBlocksState(container);
|
|
6245
6687
|
}, 'block');
|
|
6246
6688
|
} else {
|
|
6247
6689
|
item.remove();
|
|
6248
6690
|
updateHiddenInput();
|
|
6691
|
+
syncBlocksState(container);
|
|
6249
6692
|
}
|
|
6250
6693
|
return;
|
|
6251
6694
|
}
|
|
@@ -6255,6 +6698,7 @@ function getBlocksFieldScript() {
|
|
|
6255
6698
|
if (previous) {
|
|
6256
6699
|
list.insertBefore(item, previous);
|
|
6257
6700
|
updateHiddenInput();
|
|
6701
|
+
syncBlocksState(container);
|
|
6258
6702
|
}
|
|
6259
6703
|
return;
|
|
6260
6704
|
}
|
|
@@ -6264,6 +6708,7 @@ function getBlocksFieldScript() {
|
|
|
6264
6708
|
if (next) {
|
|
6265
6709
|
list.insertBefore(next, item);
|
|
6266
6710
|
updateHiddenInput();
|
|
6711
|
+
syncBlocksState(container);
|
|
6267
6712
|
}
|
|
6268
6713
|
}
|
|
6269
6714
|
});
|
|
@@ -6285,6 +6730,12 @@ function getBlocksFieldScript() {
|
|
|
6285
6730
|
});
|
|
6286
6731
|
|
|
6287
6732
|
updateHiddenInput();
|
|
6733
|
+
const savedBlocksState = readBlocksState(container);
|
|
6734
|
+
if (savedBlocksState) {
|
|
6735
|
+
applyBlocksState(container, savedBlocksState);
|
|
6736
|
+
} else {
|
|
6737
|
+
syncBlocksState(container);
|
|
6738
|
+
}
|
|
6288
6739
|
});
|
|
6289
6740
|
}
|
|
6290
6741
|
|
|
@@ -6321,6 +6772,7 @@ init_admin_layout_catalyst_template();
|
|
|
6321
6772
|
function renderContentFormPage(data) {
|
|
6322
6773
|
const isEdit = data.isEdit || !!data.id;
|
|
6323
6774
|
const title = isEdit ? `Edit: ${data.title || "Content"}` : `New ${data.collection.display_name}`;
|
|
6775
|
+
const hasValidationErrors = Boolean(data.validationErrors && Object.keys(data.validationErrors).length > 0);
|
|
6324
6776
|
const backUrl = data.referrerParams ? `/admin/content?${data.referrerParams}` : `/admin/content?collection=${data.collection.id}`;
|
|
6325
6777
|
const coreFields = data.fields.filter((f) => ["title", "slug", "content"].includes(f.field_name));
|
|
6326
6778
|
const contentFields = data.fields.filter((f) => !["title", "slug", "content"].includes(f.field_name) && !f.field_name.startsWith("meta_"));
|
|
@@ -6409,6 +6861,7 @@ function renderContentFormPage(data) {
|
|
|
6409
6861
|
${isEdit ? `hx-put="/admin/content/${data.id}"` : `hx-post="/admin/content"`}
|
|
6410
6862
|
hx-target="#form-messages"
|
|
6411
6863
|
hx-encoding="multipart/form-data"
|
|
6864
|
+
data-has-validation-errors="${hasValidationErrors ? "true" : "false"}"
|
|
6412
6865
|
class="space-y-6"
|
|
6413
6866
|
>
|
|
6414
6867
|
<input type="hidden" name="collection_id" value="${data.collection.id}">
|
|
@@ -6689,39 +7142,456 @@ function renderContentFormPage(data) {
|
|
|
6689
7142
|
|
|
6690
7143
|
<!-- Dynamic Field Scripts -->
|
|
6691
7144
|
<script>
|
|
7145
|
+
const contentFormCollectionId = ${JSON.stringify(data.collection.id)};
|
|
7146
|
+
|
|
7147
|
+
function getFieldGroupScope() {
|
|
7148
|
+
const url = new URL(window.location.href);
|
|
7149
|
+
const urlCollectionId = url.searchParams.get('collection');
|
|
7150
|
+
const effectiveCollectionId = urlCollectionId || contentFormCollectionId || '';
|
|
7151
|
+
return window.location.pathname + ':' + effectiveCollectionId;
|
|
7152
|
+
}
|
|
7153
|
+
|
|
7154
|
+
function getItemPosition(itemSelector, item) {
|
|
7155
|
+
if (!(item instanceof Element)) return -1;
|
|
7156
|
+
const parent = item.parentElement;
|
|
7157
|
+
if (!parent) return -1;
|
|
7158
|
+
return Array.from(parent.querySelectorAll(':scope > ' + itemSelector)).indexOf(item);
|
|
7159
|
+
}
|
|
7160
|
+
|
|
7161
|
+
function stripIndexedFieldPrefix(fullFieldName, prefix) {
|
|
7162
|
+
if (!fullFieldName || !prefix || !fullFieldName.startsWith(prefix)) {
|
|
7163
|
+
return fullFieldName;
|
|
7164
|
+
}
|
|
7165
|
+
|
|
7166
|
+
const remainder = fullFieldName.slice(prefix.length);
|
|
7167
|
+
const indexMatch = remainder.match(/^(\\d+)(-|__)(.*)$/);
|
|
7168
|
+
if (!indexMatch) {
|
|
7169
|
+
return fullFieldName;
|
|
7170
|
+
}
|
|
7171
|
+
|
|
7172
|
+
return indexMatch[3];
|
|
7173
|
+
}
|
|
7174
|
+
|
|
7175
|
+
function getFieldGroupStorageKey(groupOrId) {
|
|
7176
|
+
const defaultGroupId = typeof groupOrId === 'string' ? groupOrId : (groupOrId?.getAttribute('data-group-id') || 'unknown');
|
|
7177
|
+
const group = typeof groupOrId === 'string'
|
|
7178
|
+
? document.querySelector('.field-group[data-group-id="' + defaultGroupId + '"]')
|
|
7179
|
+
: groupOrId;
|
|
7180
|
+
|
|
7181
|
+
const scopePrefix = 'sonic:ui:objects:' + getFieldGroupScope() + ':';
|
|
7182
|
+
if (!(group instanceof Element)) {
|
|
7183
|
+
return scopePrefix + defaultGroupId;
|
|
7184
|
+
}
|
|
7185
|
+
|
|
7186
|
+
const fullFieldName = group.getAttribute('data-field-name') || '';
|
|
7187
|
+
|
|
7188
|
+
const blocksField = group.closest('.blocks-field');
|
|
7189
|
+
const blockItem = group.closest('.blocks-item');
|
|
7190
|
+
if (blocksField instanceof Element && blockItem instanceof Element) {
|
|
7191
|
+
const blocksFieldName = blocksField.getAttribute('data-field-name') || 'unknown';
|
|
7192
|
+
const blockPosition = getItemPosition('.blocks-item', blockItem);
|
|
7193
|
+
const relativePath = stripIndexedFieldPrefix(fullFieldName, 'block-' + blocksFieldName + '-') || defaultGroupId;
|
|
7194
|
+
return scopePrefix + 'blocks:' + blocksFieldName + ':' + blockPosition + ':' + relativePath;
|
|
7195
|
+
}
|
|
7196
|
+
|
|
7197
|
+
const arrayField = group.closest('[data-structured-array][data-field-name]');
|
|
7198
|
+
const arrayItem = group.closest('.structured-array-item');
|
|
7199
|
+
if (arrayField instanceof Element && arrayItem instanceof Element) {
|
|
7200
|
+
const arrayFieldName = arrayField.getAttribute('data-field-name') || 'unknown';
|
|
7201
|
+
const itemPosition = getItemPosition('.structured-array-item', arrayItem);
|
|
7202
|
+
const relativePath = stripIndexedFieldPrefix(fullFieldName, 'array-' + arrayFieldName + '-') || defaultGroupId;
|
|
7203
|
+
return scopePrefix + 'repeaters:' + arrayFieldName + ':' + itemPosition + ':' + relativePath;
|
|
7204
|
+
}
|
|
7205
|
+
|
|
7206
|
+
return scopePrefix + defaultGroupId;
|
|
7207
|
+
}
|
|
7208
|
+
|
|
7209
|
+
function loadFieldGroupState(group) {
|
|
7210
|
+
try {
|
|
7211
|
+
const value = sessionStorage.getItem(getFieldGroupStorageKey(group));
|
|
7212
|
+
if (value === '1') return true;
|
|
7213
|
+
if (value === '0') return false;
|
|
7214
|
+
} catch {}
|
|
7215
|
+
return null;
|
|
7216
|
+
}
|
|
7217
|
+
|
|
7218
|
+
function saveFieldGroupState(group, isCollapsed) {
|
|
7219
|
+
try {
|
|
7220
|
+
sessionStorage.setItem(getFieldGroupStorageKey(group), isCollapsed ? '1' : '0');
|
|
7221
|
+
} catch {}
|
|
7222
|
+
}
|
|
7223
|
+
|
|
7224
|
+
function resolveFieldGroupElements(groupOrId) {
|
|
7225
|
+
let group = null;
|
|
7226
|
+
|
|
7227
|
+
if (groupOrId instanceof Element) {
|
|
7228
|
+
group = groupOrId.classList.contains('field-group')
|
|
7229
|
+
? groupOrId
|
|
7230
|
+
: groupOrId.closest('.field-group[data-group-id]');
|
|
7231
|
+
} else if (typeof groupOrId === 'string' && groupOrId) {
|
|
7232
|
+
group = document.querySelector('.field-group[data-group-id="' + groupOrId + '"]');
|
|
7233
|
+
}
|
|
7234
|
+
|
|
7235
|
+
let content = null;
|
|
7236
|
+
let icon = null;
|
|
7237
|
+
|
|
7238
|
+
if (group instanceof Element) {
|
|
7239
|
+
content = group.querySelector(':scope > .field-group-content');
|
|
7240
|
+
icon = group.querySelector(':scope > .field-group-header svg[id$="-icon"]');
|
|
7241
|
+
}
|
|
7242
|
+
|
|
7243
|
+
// Legacy fallback for any existing calls still passing string IDs.
|
|
7244
|
+
if (!(content instanceof HTMLElement) && typeof groupOrId === 'string') {
|
|
7245
|
+
content = document.getElementById(groupOrId + '-content');
|
|
7246
|
+
}
|
|
7247
|
+
if (!(icon instanceof Element) && typeof groupOrId === 'string') {
|
|
7248
|
+
icon = document.getElementById(groupOrId + '-icon');
|
|
7249
|
+
}
|
|
7250
|
+
|
|
7251
|
+
if (!(group instanceof Element) && content instanceof Element) {
|
|
7252
|
+
group = content.closest('.field-group[data-group-id]');
|
|
7253
|
+
}
|
|
7254
|
+
|
|
7255
|
+
return { group, content, icon };
|
|
7256
|
+
}
|
|
7257
|
+
|
|
7258
|
+
function applyFieldGroupState(groupOrId, isCollapsed) {
|
|
7259
|
+
const { content, icon } = resolveFieldGroupElements(groupOrId);
|
|
7260
|
+
if (!(content instanceof HTMLElement) || !(icon instanceof Element)) return;
|
|
7261
|
+
content.classList.toggle('hidden', isCollapsed);
|
|
7262
|
+
icon.classList.toggle('-rotate-90', isCollapsed);
|
|
7263
|
+
}
|
|
7264
|
+
|
|
7265
|
+
function restoreFieldGroupStates() {
|
|
7266
|
+
document.querySelectorAll('.field-group[data-group-id]').forEach((group) => {
|
|
7267
|
+
const savedState = loadFieldGroupState(group);
|
|
7268
|
+
if (savedState === null) return;
|
|
7269
|
+
applyFieldGroupState(group, savedState);
|
|
7270
|
+
});
|
|
7271
|
+
}
|
|
7272
|
+
|
|
7273
|
+
function persistAllFieldGroupStates() {
|
|
7274
|
+
document.querySelectorAll('.field-group[data-group-id]').forEach((group) => {
|
|
7275
|
+
const { content } = resolveFieldGroupElements(group);
|
|
7276
|
+
if (!(content instanceof HTMLElement)) return;
|
|
7277
|
+
saveFieldGroupState(group, content.classList.contains('hidden'));
|
|
7278
|
+
});
|
|
7279
|
+
}
|
|
7280
|
+
|
|
7281
|
+
function setValidationHeaderIndicator(container) {
|
|
7282
|
+
if (!(container instanceof Element)) return;
|
|
7283
|
+
let header = null;
|
|
7284
|
+
let markerTarget = null;
|
|
7285
|
+
|
|
7286
|
+
if (container.classList.contains('field-group')) {
|
|
7287
|
+
header = container.querySelector(':scope > .field-group-header');
|
|
7288
|
+
markerTarget = container.querySelector(':scope > .field-group-header h3');
|
|
7289
|
+
} else if (container.classList.contains('structured-array-item')) {
|
|
7290
|
+
header = container.querySelector('[data-action="toggle-item"]');
|
|
7291
|
+
markerTarget = header;
|
|
7292
|
+
} else if (container.classList.contains('blocks-item')) {
|
|
7293
|
+
header = container.querySelector('[data-action="toggle-block"]');
|
|
7294
|
+
markerTarget = header;
|
|
7295
|
+
}
|
|
7296
|
+
|
|
7297
|
+
if (!(header instanceof HTMLElement)) return;
|
|
7298
|
+
if (!(markerTarget instanceof HTMLElement)) {
|
|
7299
|
+
markerTarget = header;
|
|
7300
|
+
}
|
|
7301
|
+
|
|
7302
|
+
header.dataset.validationHeaderError = 'true';
|
|
7303
|
+
header.classList.add('text-pink-700', 'dark:text-pink-300');
|
|
7304
|
+
|
|
7305
|
+
if (!markerTarget.querySelector('[data-validation-indicator]')) {
|
|
7306
|
+
const marker = document.createElement('span');
|
|
7307
|
+
marker.setAttribute('data-validation-indicator', 'true');
|
|
7308
|
+
marker.className = 'ml-2 inline-block h-2 w-2 rounded-full bg-pink-500 align-middle';
|
|
7309
|
+
marker.setAttribute('aria-hidden', 'true');
|
|
7310
|
+
markerTarget.appendChild(marker);
|
|
7311
|
+
}
|
|
7312
|
+
}
|
|
7313
|
+
|
|
7314
|
+
function clearValidationIndicators() {
|
|
7315
|
+
document.querySelectorAll('[data-validation-header-error="true"]').forEach((el) => {
|
|
7316
|
+
if (!(el instanceof HTMLElement)) return;
|
|
7317
|
+
delete el.dataset.validationHeaderError;
|
|
7318
|
+
el.classList.remove('text-pink-700', 'dark:text-pink-300');
|
|
7319
|
+
});
|
|
7320
|
+
|
|
7321
|
+
document.querySelectorAll('[data-validation-indicator]').forEach((el) => el.remove());
|
|
7322
|
+
}
|
|
7323
|
+
|
|
7324
|
+
function expandContainerForValidation(container) {
|
|
7325
|
+
if (!(container instanceof Element)) return;
|
|
7326
|
+
|
|
7327
|
+
if (container.classList.contains('field-group')) {
|
|
7328
|
+
applyFieldGroupState(container, false);
|
|
7329
|
+
return;
|
|
7330
|
+
}
|
|
7331
|
+
|
|
7332
|
+
if (container.classList.contains('structured-array-item')) {
|
|
7333
|
+
const content = container.querySelector('[data-array-item-fields]');
|
|
7334
|
+
const icon = container.querySelector('[data-item-toggle-icon]');
|
|
7335
|
+
if (content instanceof HTMLElement) {
|
|
7336
|
+
content.classList.remove('hidden');
|
|
7337
|
+
}
|
|
7338
|
+
if (icon instanceof Element) {
|
|
7339
|
+
icon.classList.remove('-rotate-90');
|
|
7340
|
+
}
|
|
7341
|
+
return;
|
|
7342
|
+
}
|
|
7343
|
+
|
|
7344
|
+
if (container.classList.contains('blocks-item')) {
|
|
7345
|
+
const content = container.querySelector('[data-block-content]');
|
|
7346
|
+
const icon = container.querySelector('[data-block-toggle-icon]');
|
|
7347
|
+
if (content instanceof HTMLElement) {
|
|
7348
|
+
content.classList.remove('hidden');
|
|
7349
|
+
}
|
|
7350
|
+
if (icon instanceof Element) {
|
|
7351
|
+
icon.classList.remove('-rotate-90');
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7355
|
+
|
|
7356
|
+
function walkErrorContainers(node, expand) {
|
|
7357
|
+
if (!(node instanceof Element)) return;
|
|
7358
|
+
const visited = new Set();
|
|
7359
|
+
let cursor = node;
|
|
7360
|
+
while (cursor) {
|
|
7361
|
+
const candidates = [
|
|
7362
|
+
cursor.closest('.structured-array-item'),
|
|
7363
|
+
cursor.closest('.blocks-item'),
|
|
7364
|
+
cursor.closest('.field-group[data-group-id]')
|
|
7365
|
+
].filter((c) => c instanceof Element && !visited.has(c));
|
|
7366
|
+
|
|
7367
|
+
if (candidates.length === 0) break;
|
|
7368
|
+
|
|
7369
|
+
// Pick nearest ancestor container to preserve "first-error path only".
|
|
7370
|
+
let nearest = candidates[0];
|
|
7371
|
+
let bestDistance = Number.MAX_SAFE_INTEGER;
|
|
7372
|
+
for (const candidate of candidates) {
|
|
7373
|
+
let distance = 0;
|
|
7374
|
+
let walker = cursor;
|
|
7375
|
+
while (walker && walker !== candidate) {
|
|
7376
|
+
walker = walker.parentElement;
|
|
7377
|
+
distance += 1;
|
|
7378
|
+
}
|
|
7379
|
+
if (distance < bestDistance) {
|
|
7380
|
+
bestDistance = distance;
|
|
7381
|
+
nearest = candidate;
|
|
7382
|
+
}
|
|
7383
|
+
}
|
|
7384
|
+
|
|
7385
|
+
visited.add(nearest);
|
|
7386
|
+
setValidationHeaderIndicator(nearest);
|
|
7387
|
+
if (expand) {
|
|
7388
|
+
expandContainerForValidation(nearest);
|
|
7389
|
+
}
|
|
7390
|
+
cursor = nearest.parentElement;
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7393
|
+
|
|
7394
|
+
function getFocusableTargetFromErrorGroup(group) {
|
|
7395
|
+
if (!(group instanceof Element)) return null;
|
|
7396
|
+
return (
|
|
7397
|
+
group.querySelector('input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"]') ||
|
|
7398
|
+
group.querySelector('button:not([disabled])')
|
|
7399
|
+
);
|
|
7400
|
+
}
|
|
7401
|
+
|
|
7402
|
+
function revealServerValidationErrors() {
|
|
7403
|
+
clearValidationIndicators();
|
|
7404
|
+
|
|
7405
|
+
const errorGroups = Array.from(document.querySelectorAll('.form-group[data-has-errors="true"]'));
|
|
7406
|
+
if (errorGroups.length === 0) return;
|
|
7407
|
+
|
|
7408
|
+
// Add indicators for all errored sections, expand only first-error path.
|
|
7409
|
+
errorGroups.forEach((group, index) => {
|
|
7410
|
+
walkErrorContainers(group, index === 0);
|
|
7411
|
+
});
|
|
7412
|
+
|
|
7413
|
+
const firstTarget = getFocusableTargetFromErrorGroup(errorGroups[0]);
|
|
7414
|
+
if (firstTarget instanceof HTMLElement) {
|
|
7415
|
+
firstTarget.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
7416
|
+
firstTarget.focus({ preventScroll: true });
|
|
7417
|
+
}
|
|
7418
|
+
}
|
|
7419
|
+
|
|
7420
|
+
function revealNativeValidationErrors(form) {
|
|
7421
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
7422
|
+
clearValidationIndicators();
|
|
7423
|
+
|
|
7424
|
+
const invalidControls = Array.from(form.querySelectorAll(':invalid'));
|
|
7425
|
+
if (invalidControls.length === 0) return;
|
|
7426
|
+
|
|
7427
|
+
invalidControls.forEach((control, index) => {
|
|
7428
|
+
walkErrorContainers(control, index === 0);
|
|
7429
|
+
});
|
|
7430
|
+
|
|
7431
|
+
const first = invalidControls[0];
|
|
7432
|
+
if (first instanceof HTMLElement) {
|
|
7433
|
+
first.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
7434
|
+
first.focus({ preventScroll: true });
|
|
7435
|
+
}
|
|
7436
|
+
}
|
|
7437
|
+
|
|
6692
7438
|
// Field group toggle
|
|
6693
|
-
function toggleFieldGroup(
|
|
6694
|
-
const content =
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
7439
|
+
function toggleFieldGroup(groupOrTrigger) {
|
|
7440
|
+
const { group, content } = resolveFieldGroupElements(groupOrTrigger);
|
|
7441
|
+
if (!(group instanceof Element)) return;
|
|
7442
|
+
if (!(content instanceof HTMLElement)) return;
|
|
7443
|
+
|
|
7444
|
+
const isCollapsed = !content.classList.contains('hidden');
|
|
7445
|
+
applyFieldGroupState(group, isCollapsed);
|
|
7446
|
+
saveFieldGroupState(group, isCollapsed);
|
|
7447
|
+
}
|
|
7448
|
+
|
|
7449
|
+
if (document.readyState === 'loading') {
|
|
7450
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
7451
|
+
restoreFieldGroupStates();
|
|
7452
|
+
const form = document.getElementById('content-form');
|
|
7453
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7454
|
+
revealServerValidationErrors();
|
|
7455
|
+
}
|
|
7456
|
+
});
|
|
7457
|
+
} else {
|
|
7458
|
+
restoreFieldGroupStates();
|
|
7459
|
+
const form = document.getElementById('content-form');
|
|
7460
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7461
|
+
revealServerValidationErrors();
|
|
6703
7462
|
}
|
|
6704
7463
|
}
|
|
6705
7464
|
|
|
7465
|
+
document.addEventListener('htmx:afterSwap', function() {
|
|
7466
|
+
setTimeout(() => {
|
|
7467
|
+
restoreFieldGroupStates();
|
|
7468
|
+
const form = document.getElementById('content-form');
|
|
7469
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7470
|
+
revealServerValidationErrors();
|
|
7471
|
+
}
|
|
7472
|
+
}, 50);
|
|
7473
|
+
});
|
|
7474
|
+
|
|
7475
|
+
const contentFormEl = document.getElementById('content-form');
|
|
7476
|
+
if (contentFormEl instanceof HTMLFormElement) {
|
|
7477
|
+
contentFormEl.addEventListener('submit', () => {
|
|
7478
|
+
persistAllFieldGroupStates();
|
|
7479
|
+
}, true);
|
|
7480
|
+
}
|
|
7481
|
+
|
|
7482
|
+
window.addEventListener('beforeunload', () => {
|
|
7483
|
+
persistAllFieldGroupStates();
|
|
7484
|
+
});
|
|
7485
|
+
|
|
7486
|
+
document.addEventListener('visibilitychange', () => {
|
|
7487
|
+
if (document.visibilityState === 'hidden') {
|
|
7488
|
+
persistAllFieldGroupStates();
|
|
7489
|
+
}
|
|
7490
|
+
});
|
|
7491
|
+
|
|
7492
|
+
let pendingNativeValidationReveal = false;
|
|
7493
|
+
document.addEventListener('invalid', function(event) {
|
|
7494
|
+
const target = event.target;
|
|
7495
|
+
if (!(target instanceof Element)) return;
|
|
7496
|
+
const form = target.closest('form');
|
|
7497
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
7498
|
+
|
|
7499
|
+
if (pendingNativeValidationReveal) return;
|
|
7500
|
+
pendingNativeValidationReveal = true;
|
|
7501
|
+
|
|
7502
|
+
// Expand only first invalid path synchronously so the browser can focus it
|
|
7503
|
+
// and avoid "invalid form control is not focusable" errors.
|
|
7504
|
+
walkErrorContainers(target, true);
|
|
7505
|
+
|
|
7506
|
+
setTimeout(() => {
|
|
7507
|
+
pendingNativeValidationReveal = false;
|
|
7508
|
+
revealNativeValidationErrors(form);
|
|
7509
|
+
}, 0);
|
|
7510
|
+
}, true);
|
|
7511
|
+
|
|
6706
7512
|
// Media field functions
|
|
6707
|
-
|
|
7513
|
+
function notifyFieldChange(input) {
|
|
7514
|
+
if (!input) return;
|
|
7515
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
7516
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
7517
|
+
}
|
|
7518
|
+
|
|
7519
|
+
function getActiveMediaModal() {
|
|
7520
|
+
const modal = document.getElementById('media-selector-modal');
|
|
7521
|
+
return modal instanceof HTMLElement ? modal : null;
|
|
7522
|
+
}
|
|
7523
|
+
|
|
7524
|
+
function getMediaFieldElements(fieldId) {
|
|
7525
|
+
if (!fieldId) {
|
|
7526
|
+
return {
|
|
7527
|
+
fieldId: '',
|
|
7528
|
+
hiddenInput: null,
|
|
7529
|
+
preview: null,
|
|
7530
|
+
mediaField: null,
|
|
7531
|
+
actionsDiv: null,
|
|
7532
|
+
};
|
|
7533
|
+
}
|
|
7534
|
+
|
|
7535
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
7536
|
+
const preview = document.getElementById(fieldId + '-preview');
|
|
7537
|
+
const mediaField = hiddenInput?.closest('.media-field-container') || null;
|
|
7538
|
+
const actionsDiv = mediaField?.querySelector('.media-actions') || null;
|
|
7539
|
+
|
|
7540
|
+
return {
|
|
7541
|
+
fieldId,
|
|
7542
|
+
hiddenInput,
|
|
7543
|
+
preview,
|
|
7544
|
+
mediaField,
|
|
7545
|
+
actionsDiv,
|
|
7546
|
+
};
|
|
7547
|
+
}
|
|
7548
|
+
|
|
7549
|
+
function getActiveMediaTarget() {
|
|
7550
|
+
const modal = getActiveMediaModal();
|
|
7551
|
+
const fieldId = modal?.dataset.targetFieldId || '';
|
|
7552
|
+
return {
|
|
7553
|
+
modal,
|
|
7554
|
+
originalValue: modal?.dataset.originalValue || '',
|
|
7555
|
+
...getMediaFieldElements(fieldId),
|
|
7556
|
+
};
|
|
7557
|
+
}
|
|
7558
|
+
|
|
7559
|
+
function ensureSingleMediaRemoveButton(fieldId, actionsDiv) {
|
|
7560
|
+
if (!(actionsDiv instanceof HTMLElement)) return;
|
|
7561
|
+
const existingRemoveButton = actionsDiv.querySelector('[data-media-remove="true"]');
|
|
7562
|
+
if (existingRemoveButton) return;
|
|
7563
|
+
|
|
7564
|
+
const removeBtn = document.createElement('button');
|
|
7565
|
+
removeBtn.type = 'button';
|
|
7566
|
+
removeBtn.setAttribute('data-media-remove', 'true');
|
|
7567
|
+
removeBtn.onclick = () => clearMediaField(fieldId);
|
|
7568
|
+
removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
|
|
7569
|
+
removeBtn.textContent = 'Remove';
|
|
7570
|
+
actionsDiv.appendChild(removeBtn);
|
|
7571
|
+
}
|
|
6708
7572
|
|
|
6709
7573
|
function openMediaSelector(fieldId) {
|
|
6710
|
-
|
|
7574
|
+
const existingModal = getActiveMediaModal();
|
|
7575
|
+
if (existingModal) {
|
|
7576
|
+
existingModal.remove();
|
|
7577
|
+
}
|
|
7578
|
+
|
|
6711
7579
|
// Store the original value in case user cancels
|
|
6712
|
-
const originalValue =
|
|
7580
|
+
const originalValue = getMediaFieldElements(fieldId).hiddenInput?.value || '';
|
|
6713
7581
|
|
|
6714
7582
|
// Open media library modal
|
|
6715
7583
|
const modal = document.createElement('div');
|
|
6716
7584
|
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50';
|
|
6717
7585
|
modal.id = 'media-selector-modal';
|
|
7586
|
+
modal.dataset.targetFieldId = fieldId;
|
|
7587
|
+
modal.dataset.originalValue = originalValue;
|
|
6718
7588
|
modal.innerHTML = \`
|
|
6719
7589
|
<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
7590
|
<h3 class="text-lg font-semibold text-zinc-950 dark:text-white mb-4">Select Media</h3>
|
|
6721
7591
|
<div id="media-grid-container" hx-get="/admin/media/selector" hx-trigger="load"></div>
|
|
6722
7592
|
<div class="mt-4 flex justify-end space-x-2">
|
|
6723
7593
|
<button
|
|
6724
|
-
onclick="cancelMediaSelection(
|
|
7594
|
+
onclick="cancelMediaSelection()"
|
|
6725
7595
|
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
7596
|
Cancel
|
|
6727
7597
|
</button>
|
|
@@ -6741,23 +7611,23 @@ function renderContentFormPage(data) {
|
|
|
6741
7611
|
}
|
|
6742
7612
|
|
|
6743
7613
|
function closeMediaSelector() {
|
|
6744
|
-
const modal =
|
|
7614
|
+
const modal = getActiveMediaModal();
|
|
6745
7615
|
if (modal) {
|
|
6746
7616
|
modal.remove();
|
|
6747
7617
|
}
|
|
6748
|
-
currentMediaFieldId = null;
|
|
6749
7618
|
}
|
|
6750
7619
|
|
|
6751
|
-
function cancelMediaSelection(
|
|
7620
|
+
function cancelMediaSelection() {
|
|
7621
|
+
const { hiddenInput, preview, originalValue } = getActiveMediaTarget();
|
|
7622
|
+
|
|
6752
7623
|
// Restore original value
|
|
6753
|
-
const hiddenInput = document.getElementById(fieldId);
|
|
6754
7624
|
if (hiddenInput) {
|
|
6755
7625
|
hiddenInput.value = originalValue;
|
|
7626
|
+
notifyFieldChange(hiddenInput);
|
|
6756
7627
|
}
|
|
6757
7628
|
|
|
6758
7629
|
// If original value was empty, hide the preview and show select button
|
|
6759
7630
|
if (!originalValue) {
|
|
6760
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6761
7631
|
if (preview) {
|
|
6762
7632
|
preview.classList.add('hidden');
|
|
6763
7633
|
}
|
|
@@ -6768,11 +7638,11 @@ function renderContentFormPage(data) {
|
|
|
6768
7638
|
}
|
|
6769
7639
|
|
|
6770
7640
|
function clearMediaField(fieldId) {
|
|
6771
|
-
const hiddenInput =
|
|
6772
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
7641
|
+
const { hiddenInput, preview, actionsDiv } = getMediaFieldElements(fieldId);
|
|
6773
7642
|
|
|
6774
7643
|
if (hiddenInput) {
|
|
6775
7644
|
hiddenInput.value = '';
|
|
7645
|
+
notifyFieldChange(hiddenInput);
|
|
6776
7646
|
}
|
|
6777
7647
|
|
|
6778
7648
|
if (preview) {
|
|
@@ -6782,25 +7652,34 @@ function renderContentFormPage(data) {
|
|
|
6782
7652
|
}
|
|
6783
7653
|
preview.classList.add('hidden');
|
|
6784
7654
|
}
|
|
7655
|
+
|
|
7656
|
+
const removeButton = actionsDiv?.querySelector('[data-media-remove="true"]');
|
|
7657
|
+
if (removeButton) {
|
|
7658
|
+
removeButton.remove();
|
|
7659
|
+
}
|
|
6785
7660
|
}
|
|
6786
7661
|
|
|
6787
7662
|
// Global function to remove a single media from multiple selection
|
|
6788
7663
|
window.removeMediaFromMultiple = function(fieldId, urlToRemove) {
|
|
6789
|
-
const hiddenInput =
|
|
7664
|
+
const { hiddenInput, preview } = getMediaFieldElements(fieldId);
|
|
6790
7665
|
if (!hiddenInput) return;
|
|
6791
7666
|
|
|
6792
7667
|
const values = hiddenInput.value.split(',').filter(url => url !== urlToRemove);
|
|
6793
7668
|
hiddenInput.value = values.join(',');
|
|
7669
|
+
notifyFieldChange(hiddenInput);
|
|
6794
7670
|
|
|
6795
7671
|
// Remove preview item
|
|
6796
|
-
const previewItem =
|
|
7672
|
+
const previewItem =
|
|
7673
|
+
preview &&
|
|
7674
|
+
Array.from(preview.querySelectorAll('[data-url]')).find(
|
|
7675
|
+
(item) => item.getAttribute('data-url') === urlToRemove,
|
|
7676
|
+
);
|
|
6797
7677
|
if (previewItem) {
|
|
6798
7678
|
previewItem.remove();
|
|
6799
7679
|
}
|
|
6800
7680
|
|
|
6801
7681
|
// Hide preview grid if empty
|
|
6802
7682
|
if (values.length === 0) {
|
|
6803
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6804
7683
|
if (preview) {
|
|
6805
7684
|
preview.classList.add('hidden');
|
|
6806
7685
|
}
|
|
@@ -6809,39 +7688,24 @@ function renderContentFormPage(data) {
|
|
|
6809
7688
|
|
|
6810
7689
|
// Global function called by media selector buttons
|
|
6811
7690
|
window.selectMediaFile = function(mediaId, mediaUrl, filename) {
|
|
6812
|
-
|
|
7691
|
+
const { fieldId, hiddenInput, preview, actionsDiv } = getActiveMediaTarget();
|
|
7692
|
+
if (!fieldId || !hiddenInput) {
|
|
6813
7693
|
console.error('No field ID set for media selection');
|
|
6814
7694
|
return;
|
|
6815
7695
|
}
|
|
6816
7696
|
|
|
6817
|
-
const fieldId = currentMediaFieldId;
|
|
6818
|
-
|
|
6819
7697
|
// Set the hidden input value to the media URL (not ID)
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
hiddenInput.value = mediaUrl;
|
|
6823
|
-
}
|
|
7698
|
+
hiddenInput.value = mediaUrl;
|
|
7699
|
+
notifyFieldChange(hiddenInput);
|
|
6824
7700
|
|
|
6825
7701
|
// Update the preview
|
|
6826
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6827
7702
|
if (preview) {
|
|
6828
7703
|
preview.innerHTML = \`<img src="\${mediaUrl}" alt="\${filename}" class="w-32 h-32 object-cover rounded-lg border border-white/20">\`;
|
|
6829
7704
|
preview.classList.remove('hidden');
|
|
6830
7705
|
}
|
|
6831
7706
|
|
|
6832
7707
|
// 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
|
-
}
|
|
7708
|
+
ensureSingleMediaRemoveButton(fieldId, actionsDiv);
|
|
6845
7709
|
|
|
6846
7710
|
// DON'T close the modal - let user click OK button
|
|
6847
7711
|
// Visual feedback: highlight the selected item
|
|
@@ -6855,7 +7719,9 @@ function renderContentFormPage(data) {
|
|
|
6855
7719
|
};
|
|
6856
7720
|
|
|
6857
7721
|
function setMediaField(fieldId, mediaUrl) {
|
|
6858
|
-
document.getElementById(fieldId)
|
|
7722
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
7723
|
+
hiddenInput.value = mediaUrl;
|
|
7724
|
+
notifyFieldChange(hiddenInput);
|
|
6859
7725
|
const preview = document.getElementById(fieldId + '-preview');
|
|
6860
7726
|
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
7727
|
preview.classList.remove('hidden');
|
|
@@ -8493,7 +9359,7 @@ adminContentRoutes.get("/", async (c) => {
|
|
|
8493
9359
|
const status = url.searchParams.get("status") || "all";
|
|
8494
9360
|
const search = url.searchParams.get("search") || "";
|
|
8495
9361
|
const offset = (page - 1) * limit;
|
|
8496
|
-
const collectionsStmt = db.prepare("SELECT id, name, display_name FROM collections WHERE is_active = 1 ORDER BY display_name");
|
|
9362
|
+
const collectionsStmt = db.prepare("SELECT id, name, display_name FROM collections WHERE is_active = 1 AND (source_type IS NULL OR source_type = 'user') ORDER BY display_name");
|
|
8497
9363
|
const { results: collectionsResults } = await collectionsStmt.all();
|
|
8498
9364
|
const models = (collectionsResults || []).map((row) => ({
|
|
8499
9365
|
name: row.name,
|
|
@@ -8501,6 +9367,7 @@ adminContentRoutes.get("/", async (c) => {
|
|
|
8501
9367
|
}));
|
|
8502
9368
|
const conditions = [];
|
|
8503
9369
|
const params = [];
|
|
9370
|
+
conditions.push("(col.source_type IS NULL OR col.source_type = 'user')");
|
|
8504
9371
|
if (status !== "deleted") {
|
|
8505
9372
|
conditions.push("c.status != 'deleted'");
|
|
8506
9373
|
}
|
|
@@ -8629,7 +9496,7 @@ adminContentRoutes.get("/new", async (c) => {
|
|
|
8629
9496
|
const collectionId = url.searchParams.get("collection");
|
|
8630
9497
|
if (!collectionId) {
|
|
8631
9498
|
const db2 = c.env.DB;
|
|
8632
|
-
const collectionsStmt = db2.prepare("SELECT id, name, display_name, description FROM collections WHERE is_active = 1 ORDER BY display_name");
|
|
9499
|
+
const collectionsStmt = db2.prepare("SELECT id, name, display_name, description FROM collections WHERE is_active = 1 AND (source_type IS NULL OR source_type = 'user') ORDER BY display_name");
|
|
8633
9500
|
const { results } = await collectionsStmt.all();
|
|
8634
9501
|
const collections = (results || []).map((row) => ({
|
|
8635
9502
|
id: row.id,
|
|
@@ -11450,6 +12317,13 @@ function renderUsersListPage(data) {
|
|
|
11450
12317
|
// src/routes/admin-users.ts
|
|
11451
12318
|
var userRoutes = new Hono();
|
|
11452
12319
|
userRoutes.use("*", requireAuth());
|
|
12320
|
+
userRoutes.use("/users/*", requireRole(["admin"]));
|
|
12321
|
+
userRoutes.use("/users", requireRole(["admin"]));
|
|
12322
|
+
userRoutes.use("/invite-user", requireRole(["admin"]));
|
|
12323
|
+
userRoutes.use("/resend-invitation/*", requireRole(["admin"]));
|
|
12324
|
+
userRoutes.use("/cancel-invitation/*", requireRole(["admin"]));
|
|
12325
|
+
userRoutes.use("/activity-logs", requireRole(["admin"]));
|
|
12326
|
+
userRoutes.use("/activity-logs/*", requireRole(["admin"]));
|
|
11453
12327
|
userRoutes.get("/", (c) => {
|
|
11454
12328
|
return c.redirect("/admin/dashboard");
|
|
11455
12329
|
});
|
|
@@ -11937,7 +12811,9 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
11937
12811
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
11938
12812
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
11939
12813
|
const bio = sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
11940
|
-
const
|
|
12814
|
+
const roleInput = formData.get("role")?.toString() || "viewer";
|
|
12815
|
+
const validRoles = ["admin", "editor", "author", "viewer"];
|
|
12816
|
+
const role = validRoles.includes(roleInput) ? roleInput : "viewer";
|
|
11941
12817
|
const password = formData.get("password")?.toString() || "";
|
|
11942
12818
|
const confirmPassword = formData.get("confirm_password")?.toString() || "";
|
|
11943
12819
|
const isActive = formData.get("is_active") === "1";
|
|
@@ -12157,7 +13033,9 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
12157
13033
|
const username = sanitizeInput(formData.get("username")?.toString());
|
|
12158
13034
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
12159
13035
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
12160
|
-
const
|
|
13036
|
+
const roleInput = formData.get("role")?.toString() || "viewer";
|
|
13037
|
+
const validRoles = ["admin", "editor", "author", "viewer"];
|
|
13038
|
+
const role = validRoles.includes(roleInput) ? roleInput : "viewer";
|
|
12161
13039
|
const isActive = formData.get("is_active") === "1";
|
|
12162
13040
|
const emailVerified = formData.get("email_verified") === "1";
|
|
12163
13041
|
const profileDisplayName = sanitizeInput(formData.get("profile_display_name")?.toString()) || null;
|
|
@@ -17482,6 +18360,18 @@ adminPluginRoutes.post("/:id/settings", async (c) => {
|
|
|
17482
18360
|
const settings = await c.req.json();
|
|
17483
18361
|
const pluginService = new PluginService(db);
|
|
17484
18362
|
await pluginService.updatePluginSettings(pluginId, settings);
|
|
18363
|
+
if (pluginId === "core-auth") {
|
|
18364
|
+
try {
|
|
18365
|
+
const cacheKv = c.env.CACHE_KV;
|
|
18366
|
+
if (cacheKv) {
|
|
18367
|
+
await cacheKv.delete("auth:settings");
|
|
18368
|
+
await cacheKv.delete("auth:registration-enabled");
|
|
18369
|
+
console.log("[AuthSettings] Cache cleared after updating authentication settings");
|
|
18370
|
+
}
|
|
18371
|
+
} catch (cacheError) {
|
|
18372
|
+
console.error("[AuthSettings] Failed to clear cache:", cacheError);
|
|
18373
|
+
}
|
|
18374
|
+
}
|
|
17485
18375
|
return c.json({ success: true });
|
|
17486
18376
|
} catch (error) {
|
|
17487
18377
|
console.error("Error updating plugin settings:", error);
|
|
@@ -20637,7 +21527,7 @@ router.get("/stats", async (c) => {
|
|
|
20637
21527
|
const db = c.env.DB;
|
|
20638
21528
|
let collectionsCount = 0;
|
|
20639
21529
|
try {
|
|
20640
|
-
const collectionsStmt = db.prepare("SELECT COUNT(*) as count FROM collections WHERE is_active = 1");
|
|
21530
|
+
const collectionsStmt = db.prepare("SELECT COUNT(*) as count FROM collections WHERE is_active = 1 AND (source_type IS NULL OR source_type = 'user')");
|
|
20641
21531
|
const collectionsResult = await collectionsStmt.first();
|
|
20642
21532
|
collectionsCount = collectionsResult?.count || 0;
|
|
20643
21533
|
} catch (error) {
|
|
@@ -20645,7 +21535,7 @@ router.get("/stats", async (c) => {
|
|
|
20645
21535
|
}
|
|
20646
21536
|
let contentCount = 0;
|
|
20647
21537
|
try {
|
|
20648
|
-
const contentStmt = db.prepare("SELECT COUNT(*) as count FROM content");
|
|
21538
|
+
const contentStmt = db.prepare("SELECT COUNT(*) as count FROM content c JOIN collections col ON c.collection_id = col.id WHERE (col.source_type IS NULL OR col.source_type = 'user')");
|
|
20649
21539
|
const contentResult = await contentStmt.first();
|
|
20650
21540
|
contentCount = contentResult?.count || 0;
|
|
20651
21541
|
} catch (error) {
|
|
@@ -22426,6 +23316,9 @@ function renderCollectionFormPage(data) {
|
|
|
22426
23316
|
// src/routes/admin-collections.ts
|
|
22427
23317
|
var adminCollectionsRoutes = new Hono();
|
|
22428
23318
|
adminCollectionsRoutes.use("*", requireAuth());
|
|
23319
|
+
adminCollectionsRoutes.post("*", requireRole(["admin"]));
|
|
23320
|
+
adminCollectionsRoutes.put("*", requireRole(["admin"]));
|
|
23321
|
+
adminCollectionsRoutes.delete("*", requireRole(["admin"]));
|
|
22429
23322
|
adminCollectionsRoutes.get("/", async (c) => {
|
|
22430
23323
|
try {
|
|
22431
23324
|
const user = c.get("user");
|
|
@@ -22439,6 +23332,7 @@ adminCollectionsRoutes.get("/", async (c) => {
|
|
|
22439
23332
|
SELECT id, name, display_name, description, created_at, managed, schema
|
|
22440
23333
|
FROM collections
|
|
22441
23334
|
WHERE is_active = 1
|
|
23335
|
+
AND (source_type IS NULL OR source_type = 'user')
|
|
22442
23336
|
AND (name LIKE ? OR display_name LIKE ? OR description LIKE ?)
|
|
22443
23337
|
ORDER BY created_at DESC
|
|
22444
23338
|
`);
|
|
@@ -22446,7 +23340,7 @@ adminCollectionsRoutes.get("/", async (c) => {
|
|
|
22446
23340
|
const queryResults = await stmt.bind(searchParam, searchParam, searchParam).all();
|
|
22447
23341
|
results = queryResults.results;
|
|
22448
23342
|
} else {
|
|
22449
|
-
stmt = db.prepare("SELECT id, name, display_name, description, created_at, managed, schema FROM collections WHERE is_active = 1 ORDER BY created_at DESC");
|
|
23343
|
+
stmt = db.prepare("SELECT id, name, display_name, description, created_at, managed, schema FROM collections WHERE is_active = 1 AND (source_type IS NULL OR source_type = 'user') ORDER BY created_at DESC");
|
|
22450
23344
|
const queryResults = await stmt.all();
|
|
22451
23345
|
results = queryResults.results;
|
|
22452
23346
|
}
|
|
@@ -27518,14 +28412,36 @@ publicFormsRoutes.post("/:identifier/submit", async (c) => {
|
|
|
27518
28412
|
now
|
|
27519
28413
|
).run();
|
|
27520
28414
|
await db.prepare(`
|
|
27521
|
-
UPDATE forms
|
|
28415
|
+
UPDATE forms
|
|
27522
28416
|
SET submission_count = submission_count + 1,
|
|
27523
28417
|
updated_at = ?
|
|
27524
28418
|
WHERE id = ?
|
|
27525
28419
|
`).bind(now, form.id).run();
|
|
28420
|
+
let contentId = null;
|
|
28421
|
+
try {
|
|
28422
|
+
contentId = await createContentFromSubmission(
|
|
28423
|
+
db,
|
|
28424
|
+
sanitizedData,
|
|
28425
|
+
{ id: form.id, name: form.name, display_name: form.display_name },
|
|
28426
|
+
submissionId,
|
|
28427
|
+
{
|
|
28428
|
+
ipAddress: c.req.header("cf-connecting-ip") || null,
|
|
28429
|
+
userAgent: c.req.header("user-agent") || null,
|
|
28430
|
+
userEmail: sanitizedData?.email || null,
|
|
28431
|
+
userId: null
|
|
28432
|
+
// anonymous submission
|
|
28433
|
+
}
|
|
28434
|
+
);
|
|
28435
|
+
if (!contentId) {
|
|
28436
|
+
console.warn("[FormSubmit] Content creation returned null for submission:", submissionId);
|
|
28437
|
+
}
|
|
28438
|
+
} catch (contentError) {
|
|
28439
|
+
console.error("[FormSubmit] Error creating content from submission:", contentError);
|
|
28440
|
+
}
|
|
27526
28441
|
return c.json({
|
|
27527
28442
|
success: true,
|
|
27528
28443
|
submissionId,
|
|
28444
|
+
contentId,
|
|
27529
28445
|
message: "Form submitted successfully"
|
|
27530
28446
|
});
|
|
27531
28447
|
} catch (error) {
|
|
@@ -27936,5 +28852,5 @@ var ROUTES_INFO = {
|
|
|
27936
28852
|
};
|
|
27937
28853
|
|
|
27938
28854
|
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-
|
|
28855
|
+
//# sourceMappingURL=chunk-SDAGUFOF.js.map
|
|
28856
|
+
//# sourceMappingURL=chunk-SDAGUFOF.js.map
|