@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,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
3
|
+
var chunkLFAQUR7P_cjs = require('./chunk-LFAQUR7P.cjs');
|
|
4
|
+
var chunk5GO3AMON_cjs = require('./chunk-5GO3AMON.cjs');
|
|
5
|
+
var chunkTWCQVJ6M_cjs = require('./chunk-TWCQVJ6M.cjs');
|
|
6
|
+
var chunkEAJJHE5F_cjs = require('./chunk-EAJJHE5F.cjs');
|
|
7
7
|
var chunkLTKV7AE5_cjs = require('./chunk-LTKV7AE5.cjs');
|
|
8
8
|
var chunk6FHNRRJ3_cjs = require('./chunk-6FHNRRJ3.cjs');
|
|
9
|
-
var
|
|
9
|
+
var chunkE2GKK5HX_cjs = require('./chunk-E2GKK5HX.cjs');
|
|
10
10
|
var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
|
|
11
11
|
var chunkMNWKYY5E_cjs = require('./chunk-MNWKYY5E.cjs');
|
|
12
12
|
var hono = require('hono');
|
|
@@ -121,7 +121,7 @@ apiContentCrudRoutes.get("/:id", async (c) => {
|
|
|
121
121
|
}, 500);
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
|
-
apiContentCrudRoutes.post("/",
|
|
124
|
+
apiContentCrudRoutes.post("/", chunk5GO3AMON_cjs.requireAuth(), chunk5GO3AMON_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
|
|
125
125
|
try {
|
|
126
126
|
const db = c.env.DB;
|
|
127
127
|
const user = c.get("user");
|
|
@@ -162,7 +162,7 @@ apiContentCrudRoutes.post("/", chunkDQZVU3WB_cjs.requireAuth(), async (c) => {
|
|
|
162
162
|
now,
|
|
163
163
|
now
|
|
164
164
|
).run();
|
|
165
|
-
const cache =
|
|
165
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.api);
|
|
166
166
|
await cache.invalidate(`content:list:${collectionId}:*`);
|
|
167
167
|
await cache.invalidate("content-filtered:*");
|
|
168
168
|
const getStmt = db.prepare("SELECT * FROM content WHERE id = ?");
|
|
@@ -187,7 +187,7 @@ apiContentCrudRoutes.post("/", chunkDQZVU3WB_cjs.requireAuth(), async (c) => {
|
|
|
187
187
|
}, 500);
|
|
188
188
|
}
|
|
189
189
|
});
|
|
190
|
-
apiContentCrudRoutes.put("/:id",
|
|
190
|
+
apiContentCrudRoutes.put("/:id", chunk5GO3AMON_cjs.requireAuth(), chunk5GO3AMON_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
|
|
191
191
|
try {
|
|
192
192
|
const id = c.req.param("id");
|
|
193
193
|
const db = c.env.DB;
|
|
@@ -225,7 +225,7 @@ apiContentCrudRoutes.put("/:id", chunkDQZVU3WB_cjs.requireAuth(), async (c) => {
|
|
|
225
225
|
WHERE id = ?
|
|
226
226
|
`);
|
|
227
227
|
await updateStmt.bind(...params).run();
|
|
228
|
-
const cache =
|
|
228
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.api);
|
|
229
229
|
await cache.delete(cache.generateKey("content", id));
|
|
230
230
|
await cache.invalidate(`content:list:${existing.collection_id}:*`);
|
|
231
231
|
await cache.invalidate("content-filtered:*");
|
|
@@ -251,7 +251,7 @@ apiContentCrudRoutes.put("/:id", chunkDQZVU3WB_cjs.requireAuth(), async (c) => {
|
|
|
251
251
|
}, 500);
|
|
252
252
|
}
|
|
253
253
|
});
|
|
254
|
-
apiContentCrudRoutes.delete("/:id",
|
|
254
|
+
apiContentCrudRoutes.delete("/:id", chunk5GO3AMON_cjs.requireAuth(), chunk5GO3AMON_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
|
|
255
255
|
try {
|
|
256
256
|
const id = c.req.param("id");
|
|
257
257
|
const db = c.env.DB;
|
|
@@ -262,7 +262,7 @@ apiContentCrudRoutes.delete("/:id", chunkDQZVU3WB_cjs.requireAuth(), async (c) =
|
|
|
262
262
|
}
|
|
263
263
|
const deleteStmt = db.prepare("DELETE FROM content WHERE id = ?");
|
|
264
264
|
await deleteStmt.bind(id).run();
|
|
265
|
-
const cache =
|
|
265
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.api);
|
|
266
266
|
await cache.delete(cache.generateKey("content", id));
|
|
267
267
|
await cache.invalidate(`content:list:${existing.collection_id}:*`);
|
|
268
268
|
await cache.invalidate("content-filtered:*");
|
|
@@ -287,7 +287,7 @@ apiRoutes.use("*", async (c, next) => {
|
|
|
287
287
|
c.header("X-Response-Time", `${totalTime}ms`);
|
|
288
288
|
});
|
|
289
289
|
apiRoutes.use("*", async (c, next) => {
|
|
290
|
-
const cacheEnabled = await
|
|
290
|
+
const cacheEnabled = await chunk5GO3AMON_cjs.isPluginActive(c.env.DB, "core-cache");
|
|
291
291
|
c.set("cacheEnabled", cacheEnabled);
|
|
292
292
|
await next();
|
|
293
293
|
});
|
|
@@ -724,7 +724,7 @@ apiRoutes.get("/collections", async (c) => {
|
|
|
724
724
|
try {
|
|
725
725
|
const db = c.env.DB;
|
|
726
726
|
const cacheEnabled = c.get("cacheEnabled");
|
|
727
|
-
const cache =
|
|
727
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.api);
|
|
728
728
|
const cacheKey = cache.generateKey("collections", "all");
|
|
729
729
|
if (cacheEnabled) {
|
|
730
730
|
const cacheResult = await cache.getWithSource(cacheKey);
|
|
@@ -750,7 +750,7 @@ apiRoutes.get("/collections", async (c) => {
|
|
|
750
750
|
}
|
|
751
751
|
c.header("X-Cache-Status", "MISS");
|
|
752
752
|
c.header("X-Cache-Source", "database");
|
|
753
|
-
const stmt = db.prepare("SELECT * FROM collections WHERE is_active = 1");
|
|
753
|
+
const stmt = db.prepare("SELECT * FROM collections WHERE is_active = 1 AND (source_type IS NULL OR source_type = 'user')");
|
|
754
754
|
const { results } = await stmt.all();
|
|
755
755
|
const transformedResults = results.map((row) => ({
|
|
756
756
|
...row,
|
|
@@ -778,7 +778,7 @@ apiRoutes.get("/collections", async (c) => {
|
|
|
778
778
|
return c.json({ error: "Failed to fetch collections" }, 500);
|
|
779
779
|
}
|
|
780
780
|
});
|
|
781
|
-
apiRoutes.get("/content",
|
|
781
|
+
apiRoutes.get("/content", chunk5GO3AMON_cjs.optionalAuth(), async (c) => {
|
|
782
782
|
const executionStart = Date.now();
|
|
783
783
|
try {
|
|
784
784
|
const db = c.env.DB;
|
|
@@ -801,13 +801,13 @@ apiRoutes.get("/content", chunkDQZVU3WB_cjs.optionalAuth(), async (c) => {
|
|
|
801
801
|
});
|
|
802
802
|
}
|
|
803
803
|
}
|
|
804
|
-
const filter =
|
|
804
|
+
const filter = chunkE2GKK5HX_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
|
|
805
805
|
const normalizedFilter = normalizePublicContentFilter(filter, c.get("user")?.role);
|
|
806
806
|
if (!normalizedFilter.limit) {
|
|
807
807
|
normalizedFilter.limit = 50;
|
|
808
808
|
}
|
|
809
809
|
normalizedFilter.limit = Math.min(normalizedFilter.limit, 1e3);
|
|
810
|
-
const builder3 = new
|
|
810
|
+
const builder3 = new chunkE2GKK5HX_cjs.QueryFilterBuilder();
|
|
811
811
|
const queryResult = builder3.build("content", normalizedFilter);
|
|
812
812
|
if (queryResult.errors.length > 0) {
|
|
813
813
|
return c.json({
|
|
@@ -816,7 +816,7 @@ apiRoutes.get("/content", chunkDQZVU3WB_cjs.optionalAuth(), async (c) => {
|
|
|
816
816
|
}, 400);
|
|
817
817
|
}
|
|
818
818
|
const cacheEnabled = c.get("cacheEnabled");
|
|
819
|
-
const cache =
|
|
819
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.api);
|
|
820
820
|
const cacheKey = cache.generateKey("content-filtered", JSON.stringify({ filter: normalizedFilter, query: queryResult.sql }));
|
|
821
821
|
if (cacheEnabled) {
|
|
822
822
|
const cacheResult = await cache.getWithSource(cacheKey);
|
|
@@ -879,7 +879,7 @@ apiRoutes.get("/content", chunkDQZVU3WB_cjs.optionalAuth(), async (c) => {
|
|
|
879
879
|
}, 500);
|
|
880
880
|
}
|
|
881
881
|
});
|
|
882
|
-
apiRoutes.get("/collections/:collection/content",
|
|
882
|
+
apiRoutes.get("/collections/:collection/content", chunk5GO3AMON_cjs.optionalAuth(), async (c) => {
|
|
883
883
|
const executionStart = Date.now();
|
|
884
884
|
try {
|
|
885
885
|
const collection = c.req.param("collection");
|
|
@@ -890,7 +890,7 @@ apiRoutes.get("/collections/:collection/content", chunkDQZVU3WB_cjs.optionalAuth
|
|
|
890
890
|
if (!collectionResult) {
|
|
891
891
|
return c.json({ error: "Collection not found" }, 404);
|
|
892
892
|
}
|
|
893
|
-
const filter =
|
|
893
|
+
const filter = chunkE2GKK5HX_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
|
|
894
894
|
const normalizedFilter = normalizePublicContentFilter(filter, c.get("user")?.role);
|
|
895
895
|
if (!normalizedFilter.where) {
|
|
896
896
|
normalizedFilter.where = { and: [] };
|
|
@@ -907,7 +907,7 @@ apiRoutes.get("/collections/:collection/content", chunkDQZVU3WB_cjs.optionalAuth
|
|
|
907
907
|
normalizedFilter.limit = 50;
|
|
908
908
|
}
|
|
909
909
|
normalizedFilter.limit = Math.min(normalizedFilter.limit, 1e3);
|
|
910
|
-
const builder3 = new
|
|
910
|
+
const builder3 = new chunkE2GKK5HX_cjs.QueryFilterBuilder();
|
|
911
911
|
const queryResult = builder3.build("content", normalizedFilter);
|
|
912
912
|
if (queryResult.errors.length > 0) {
|
|
913
913
|
return c.json({
|
|
@@ -916,7 +916,7 @@ apiRoutes.get("/collections/:collection/content", chunkDQZVU3WB_cjs.optionalAuth
|
|
|
916
916
|
}, 400);
|
|
917
917
|
}
|
|
918
918
|
const cacheEnabled = c.get("cacheEnabled");
|
|
919
|
-
const cache =
|
|
919
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.api);
|
|
920
920
|
const cacheKey = cache.generateKey("collection-content-filtered", `${collection}:${JSON.stringify({ filter: normalizedFilter, query: queryResult.sql })}`);
|
|
921
921
|
if (cacheEnabled) {
|
|
922
922
|
const cacheResult = await cache.getWithSource(cacheKey);
|
|
@@ -1028,7 +1028,7 @@ var fileValidationSchema = zod.z.object({
|
|
|
1028
1028
|
// 50MB max
|
|
1029
1029
|
});
|
|
1030
1030
|
var apiMediaRoutes = new hono.Hono();
|
|
1031
|
-
apiMediaRoutes.use("*",
|
|
1031
|
+
apiMediaRoutes.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
1032
1032
|
apiMediaRoutes.post("/upload", async (c) => {
|
|
1033
1033
|
try {
|
|
1034
1034
|
const user = c.get("user");
|
|
@@ -1772,14 +1772,14 @@ apiSystemRoutes.get("/env", (c) => {
|
|
|
1772
1772
|
});
|
|
1773
1773
|
var api_system_default = apiSystemRoutes;
|
|
1774
1774
|
var adminApiRoutes = new hono.Hono();
|
|
1775
|
-
adminApiRoutes.use("*",
|
|
1776
|
-
adminApiRoutes.use("*",
|
|
1775
|
+
adminApiRoutes.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
1776
|
+
adminApiRoutes.use("*", chunk5GO3AMON_cjs.requireRole(["admin", "editor"]));
|
|
1777
1777
|
adminApiRoutes.get("/stats", async (c) => {
|
|
1778
1778
|
try {
|
|
1779
1779
|
const db = c.env.DB;
|
|
1780
1780
|
let collectionsCount = 0;
|
|
1781
1781
|
try {
|
|
1782
|
-
const collectionsStmt = db.prepare("SELECT COUNT(*) as count FROM collections WHERE is_active = 1");
|
|
1782
|
+
const collectionsStmt = db.prepare("SELECT COUNT(*) as count FROM collections WHERE is_active = 1 AND (source_type IS NULL OR source_type = 'user')");
|
|
1783
1783
|
const collectionsResult = await collectionsStmt.first();
|
|
1784
1784
|
collectionsCount = collectionsResult?.count || 0;
|
|
1785
1785
|
} catch (error) {
|
|
@@ -1787,7 +1787,7 @@ adminApiRoutes.get("/stats", async (c) => {
|
|
|
1787
1787
|
}
|
|
1788
1788
|
let contentCount = 0;
|
|
1789
1789
|
try {
|
|
1790
|
-
const contentStmt = db.prepare("SELECT COUNT(*) as count FROM content WHERE deleted_at IS NULL");
|
|
1790
|
+
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')");
|
|
1791
1791
|
const contentResult = await contentStmt.first();
|
|
1792
1792
|
contentCount = contentResult?.count || 0;
|
|
1793
1793
|
} catch (error) {
|
|
@@ -1929,6 +1929,7 @@ adminApiRoutes.get("/collections", async (c) => {
|
|
|
1929
1929
|
SELECT id, name, display_name, description, created_at, updated_at, is_active, managed
|
|
1930
1930
|
FROM collections
|
|
1931
1931
|
WHERE ${includeInactive ? "1=1" : "is_active = 1"}
|
|
1932
|
+
AND (source_type IS NULL OR source_type = 'user')
|
|
1932
1933
|
AND (name LIKE ? OR display_name LIKE ? OR description LIKE ?)
|
|
1933
1934
|
ORDER BY created_at DESC
|
|
1934
1935
|
`);
|
|
@@ -1939,7 +1940,8 @@ adminApiRoutes.get("/collections", async (c) => {
|
|
|
1939
1940
|
stmt = db.prepare(`
|
|
1940
1941
|
SELECT id, name, display_name, description, created_at, updated_at, is_active, managed
|
|
1941
1942
|
FROM collections
|
|
1942
|
-
|
|
1943
|
+
WHERE (source_type IS NULL OR source_type = 'user')
|
|
1944
|
+
${includeInactive ? "" : "AND is_active = 1"}
|
|
1943
1945
|
ORDER BY created_at DESC
|
|
1944
1946
|
`);
|
|
1945
1947
|
const queryResults = await stmt.all();
|
|
@@ -2283,7 +2285,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
|
|
|
2283
2285
|
});
|
|
2284
2286
|
adminApiRoutes.get("/migrations/status", async (c) => {
|
|
2285
2287
|
try {
|
|
2286
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
2288
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-EM2D6EG2.cjs');
|
|
2287
2289
|
const db = c.env.DB;
|
|
2288
2290
|
const migrationService = new MigrationService2(db);
|
|
2289
2291
|
const status = await migrationService.getMigrationStatus();
|
|
@@ -2308,7 +2310,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
2308
2310
|
error: "Unauthorized. Admin access required."
|
|
2309
2311
|
}, 403);
|
|
2310
2312
|
}
|
|
2311
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
2313
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-EM2D6EG2.cjs');
|
|
2312
2314
|
const db = c.env.DB;
|
|
2313
2315
|
const migrationService = new MigrationService2(db);
|
|
2314
2316
|
const result = await migrationService.runPendingMigrations();
|
|
@@ -2327,7 +2329,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
2327
2329
|
});
|
|
2328
2330
|
adminApiRoutes.get("/migrations/validate", async (c) => {
|
|
2329
2331
|
try {
|
|
2330
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
2332
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-EM2D6EG2.cjs');
|
|
2331
2333
|
const db = c.env.DB;
|
|
2332
2334
|
const migrationService = new MigrationService2(db);
|
|
2333
2335
|
const validation = await migrationService.validateSchema();
|
|
@@ -2738,7 +2740,7 @@ var JWT_SECRET_FALLBACK = "your-super-secret-jwt-key-change-in-production";
|
|
|
2738
2740
|
async function setCsrfCookie(c) {
|
|
2739
2741
|
const secret = c.env?.JWT_SECRET || JWT_SECRET_FALLBACK;
|
|
2740
2742
|
const isDev = c.env?.ENVIRONMENT === "development" || !c.env?.ENVIRONMENT;
|
|
2741
|
-
const csrfToken = await
|
|
2743
|
+
const csrfToken = await chunk5GO3AMON_cjs.generateCsrfToken(secret);
|
|
2742
2744
|
cookie.setCookie(c, "csrf_token", csrfToken, {
|
|
2743
2745
|
httpOnly: false,
|
|
2744
2746
|
secure: !isDev,
|
|
@@ -2795,7 +2797,7 @@ var loginSchema = zod.z.object({
|
|
|
2795
2797
|
});
|
|
2796
2798
|
authRoutes.post(
|
|
2797
2799
|
"/register",
|
|
2798
|
-
|
|
2800
|
+
chunk5GO3AMON_cjs.rateLimit({ max: 3, windowMs: 60 * 1e3, keyPrefix: "register" }),
|
|
2799
2801
|
async (c) => {
|
|
2800
2802
|
try {
|
|
2801
2803
|
const db = c.env.DB;
|
|
@@ -2832,7 +2834,7 @@ authRoutes.post(
|
|
|
2832
2834
|
if (existingUser) {
|
|
2833
2835
|
return c.json({ error: "User with this email or username already exists" }, 400);
|
|
2834
2836
|
}
|
|
2835
|
-
const passwordHash = await
|
|
2837
|
+
const passwordHash = await chunk5GO3AMON_cjs.AuthManager.hashPassword(password);
|
|
2836
2838
|
const userId = crypto.randomUUID();
|
|
2837
2839
|
const now = /* @__PURE__ */ new Date();
|
|
2838
2840
|
await db.prepare(`
|
|
@@ -2852,7 +2854,7 @@ authRoutes.post(
|
|
|
2852
2854
|
now.getTime(),
|
|
2853
2855
|
now.getTime()
|
|
2854
2856
|
).run();
|
|
2855
|
-
const token = await
|
|
2857
|
+
const token = await chunk5GO3AMON_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer", c.env.JWT_SECRET);
|
|
2856
2858
|
cookie.setCookie(c, "auth_token", token, {
|
|
2857
2859
|
httpOnly: true,
|
|
2858
2860
|
secure: true,
|
|
@@ -2886,7 +2888,7 @@ authRoutes.post(
|
|
|
2886
2888
|
);
|
|
2887
2889
|
authRoutes.post(
|
|
2888
2890
|
"/login",
|
|
2889
|
-
|
|
2891
|
+
chunk5GO3AMON_cjs.rateLimit({ max: 5, windowMs: 60 * 1e3, keyPrefix: "login" }),
|
|
2890
2892
|
async (c) => {
|
|
2891
2893
|
try {
|
|
2892
2894
|
const body = await c.req.json();
|
|
@@ -2897,7 +2899,7 @@ authRoutes.post(
|
|
|
2897
2899
|
const { email, password } = validation.data;
|
|
2898
2900
|
const db = c.env.DB;
|
|
2899
2901
|
const normalizedEmail = email.toLowerCase();
|
|
2900
|
-
const cache =
|
|
2902
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.user);
|
|
2901
2903
|
let user = await cache.get(cache.generateKey("user", `email:${normalizedEmail}`));
|
|
2902
2904
|
if (!user) {
|
|
2903
2905
|
user = await db.prepare("SELECT * FROM users WHERE email = ? AND is_active = 1").bind(normalizedEmail).first();
|
|
@@ -2909,19 +2911,19 @@ authRoutes.post(
|
|
|
2909
2911
|
if (!user) {
|
|
2910
2912
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
2911
2913
|
}
|
|
2912
|
-
const isValidPassword = await
|
|
2914
|
+
const isValidPassword = await chunk5GO3AMON_cjs.AuthManager.verifyPassword(password, user.password_hash);
|
|
2913
2915
|
if (!isValidPassword) {
|
|
2914
2916
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
2915
2917
|
}
|
|
2916
|
-
if (
|
|
2918
|
+
if (chunk5GO3AMON_cjs.AuthManager.isLegacyHash(user.password_hash)) {
|
|
2917
2919
|
try {
|
|
2918
|
-
const newHash = await
|
|
2920
|
+
const newHash = await chunk5GO3AMON_cjs.AuthManager.hashPassword(password);
|
|
2919
2921
|
await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(newHash, Date.now(), user.id).run();
|
|
2920
2922
|
} catch (rehashError) {
|
|
2921
2923
|
console.error("Password rehash failed (non-fatal):", rehashError);
|
|
2922
2924
|
}
|
|
2923
2925
|
}
|
|
2924
|
-
const token = await
|
|
2926
|
+
const token = await chunk5GO3AMON_cjs.AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET);
|
|
2925
2927
|
cookie.setCookie(c, "auth_token", token, {
|
|
2926
2928
|
httpOnly: true,
|
|
2927
2929
|
secure: true,
|
|
@@ -2974,7 +2976,7 @@ authRoutes.get("/logout", (c) => {
|
|
|
2974
2976
|
clearCsrfCookie(c);
|
|
2975
2977
|
return c.redirect("/auth/login?message=You have been logged out successfully");
|
|
2976
2978
|
});
|
|
2977
|
-
authRoutes.get("/me",
|
|
2979
|
+
authRoutes.get("/me", chunk5GO3AMON_cjs.requireAuth(), async (c) => {
|
|
2978
2980
|
try {
|
|
2979
2981
|
const user = c.get("user");
|
|
2980
2982
|
if (!user) {
|
|
@@ -2991,13 +2993,13 @@ authRoutes.get("/me", chunkDQZVU3WB_cjs.requireAuth(), async (c) => {
|
|
|
2991
2993
|
return c.json({ error: "Failed to get user" }, 500);
|
|
2992
2994
|
}
|
|
2993
2995
|
});
|
|
2994
|
-
authRoutes.post("/refresh",
|
|
2996
|
+
authRoutes.post("/refresh", chunk5GO3AMON_cjs.requireAuth(), async (c) => {
|
|
2995
2997
|
try {
|
|
2996
2998
|
const user = c.get("user");
|
|
2997
2999
|
if (!user) {
|
|
2998
3000
|
return c.json({ error: "Not authenticated" }, 401);
|
|
2999
3001
|
}
|
|
3000
|
-
const token = await
|
|
3002
|
+
const token = await chunk5GO3AMON_cjs.AuthManager.generateToken(user.userId, user.email, user.role, c.env.JWT_SECRET);
|
|
3001
3003
|
cookie.setCookie(c, "auth_token", token, {
|
|
3002
3004
|
httpOnly: true,
|
|
3003
3005
|
secure: true,
|
|
@@ -3014,7 +3016,7 @@ authRoutes.post("/refresh", chunkDQZVU3WB_cjs.requireAuth(), async (c) => {
|
|
|
3014
3016
|
});
|
|
3015
3017
|
authRoutes.post(
|
|
3016
3018
|
"/register/form",
|
|
3017
|
-
|
|
3019
|
+
chunk5GO3AMON_cjs.rateLimit({ max: 3, windowMs: 60 * 1e3, keyPrefix: "register" }),
|
|
3018
3020
|
async (c) => {
|
|
3019
3021
|
try {
|
|
3020
3022
|
const db = c.env.DB;
|
|
@@ -3061,7 +3063,7 @@ authRoutes.post(
|
|
|
3061
3063
|
</div>
|
|
3062
3064
|
`);
|
|
3063
3065
|
}
|
|
3064
|
-
const passwordHash = await
|
|
3066
|
+
const passwordHash = await chunk5GO3AMON_cjs.AuthManager.hashPassword(password);
|
|
3065
3067
|
const role = isFirstUser ? "admin" : "viewer";
|
|
3066
3068
|
const userId = crypto.randomUUID();
|
|
3067
3069
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -3081,7 +3083,7 @@ authRoutes.post(
|
|
|
3081
3083
|
now.getTime(),
|
|
3082
3084
|
now.getTime()
|
|
3083
3085
|
).run();
|
|
3084
|
-
const token = await
|
|
3086
|
+
const token = await chunk5GO3AMON_cjs.AuthManager.generateToken(userId, normalizedEmail, role, c.env.JWT_SECRET);
|
|
3085
3087
|
cookie.setCookie(c, "auth_token", token, {
|
|
3086
3088
|
httpOnly: true,
|
|
3087
3089
|
secure: false,
|
|
@@ -3114,7 +3116,7 @@ authRoutes.post(
|
|
|
3114
3116
|
);
|
|
3115
3117
|
authRoutes.post(
|
|
3116
3118
|
"/login/form",
|
|
3117
|
-
|
|
3119
|
+
chunk5GO3AMON_cjs.rateLimit({ max: 5, windowMs: 60 * 1e3, keyPrefix: "login" }),
|
|
3118
3120
|
async (c) => {
|
|
3119
3121
|
try {
|
|
3120
3122
|
const formData = await c.req.formData();
|
|
@@ -3138,7 +3140,7 @@ authRoutes.post(
|
|
|
3138
3140
|
</div>
|
|
3139
3141
|
`);
|
|
3140
3142
|
}
|
|
3141
|
-
const isValidPassword = await
|
|
3143
|
+
const isValidPassword = await chunk5GO3AMON_cjs.AuthManager.verifyPassword(password, user.password_hash);
|
|
3142
3144
|
if (!isValidPassword) {
|
|
3143
3145
|
return c.html(html.html`
|
|
3144
3146
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
@@ -3146,15 +3148,15 @@ authRoutes.post(
|
|
|
3146
3148
|
</div>
|
|
3147
3149
|
`);
|
|
3148
3150
|
}
|
|
3149
|
-
if (
|
|
3151
|
+
if (chunk5GO3AMON_cjs.AuthManager.isLegacyHash(user.password_hash)) {
|
|
3150
3152
|
try {
|
|
3151
|
-
const newHash = await
|
|
3153
|
+
const newHash = await chunk5GO3AMON_cjs.AuthManager.hashPassword(password);
|
|
3152
3154
|
await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(newHash, Date.now(), user.id).run();
|
|
3153
3155
|
} catch (rehashError) {
|
|
3154
3156
|
console.error("Password rehash failed (non-fatal):", rehashError);
|
|
3155
3157
|
}
|
|
3156
3158
|
}
|
|
3157
|
-
const token = await
|
|
3159
|
+
const token = await chunk5GO3AMON_cjs.AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET);
|
|
3158
3160
|
cookie.setCookie(c, "auth_token", token, {
|
|
3159
3161
|
httpOnly: true,
|
|
3160
3162
|
secure: false,
|
|
@@ -3196,7 +3198,7 @@ authRoutes.post(
|
|
|
3196
3198
|
);
|
|
3197
3199
|
authRoutes.post(
|
|
3198
3200
|
"/seed-admin",
|
|
3199
|
-
|
|
3201
|
+
chunk5GO3AMON_cjs.rateLimit({ max: 2, windowMs: 60 * 1e3, keyPrefix: "seed-admin" }),
|
|
3200
3202
|
async (c) => {
|
|
3201
3203
|
try {
|
|
3202
3204
|
const db = c.env.DB;
|
|
@@ -3218,7 +3220,7 @@ authRoutes.post(
|
|
|
3218
3220
|
`).run();
|
|
3219
3221
|
const existingAdmin = await db.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind("admin@sonicjs.com", "admin").first();
|
|
3220
3222
|
if (existingAdmin) {
|
|
3221
|
-
const passwordHash2 = await
|
|
3223
|
+
const passwordHash2 = await chunk5GO3AMON_cjs.AuthManager.hashPassword("sonicjs!");
|
|
3222
3224
|
await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
|
|
3223
3225
|
return c.json({
|
|
3224
3226
|
message: "Admin user already exists (password updated)",
|
|
@@ -3230,7 +3232,7 @@ authRoutes.post(
|
|
|
3230
3232
|
}
|
|
3231
3233
|
});
|
|
3232
3234
|
}
|
|
3233
|
-
const passwordHash = await
|
|
3235
|
+
const passwordHash = await chunk5GO3AMON_cjs.AuthManager.hashPassword("sonicjs!");
|
|
3234
3236
|
const userId = "admin-user-id";
|
|
3235
3237
|
const now = Date.now();
|
|
3236
3238
|
const adminEmail = "admin@sonicjs.com".toLowerCase();
|
|
@@ -3451,7 +3453,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
3451
3453
|
if (existingUsername) {
|
|
3452
3454
|
return c.json({ error: "Username is already taken" }, 400);
|
|
3453
3455
|
}
|
|
3454
|
-
const passwordHash = await
|
|
3456
|
+
const passwordHash = await chunk5GO3AMON_cjs.AuthManager.hashPassword(password);
|
|
3455
3457
|
const updateStmt = db.prepare(`
|
|
3456
3458
|
UPDATE users SET
|
|
3457
3459
|
username = ?,
|
|
@@ -3470,7 +3472,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
3470
3472
|
Date.now(),
|
|
3471
3473
|
invitedUser.id
|
|
3472
3474
|
).run();
|
|
3473
|
-
const authToken = await
|
|
3475
|
+
const authToken = await chunk5GO3AMON_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role, c.env.JWT_SECRET);
|
|
3474
3476
|
cookie.setCookie(c, "auth_token", authToken, {
|
|
3475
3477
|
httpOnly: true,
|
|
3476
3478
|
secure: true,
|
|
@@ -3487,7 +3489,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
3487
3489
|
});
|
|
3488
3490
|
authRoutes.post(
|
|
3489
3491
|
"/request-password-reset",
|
|
3490
|
-
|
|
3492
|
+
chunk5GO3AMON_cjs.rateLimit({ max: 3, windowMs: 15 * 60 * 1e3, keyPrefix: "password-reset" }),
|
|
3491
3493
|
async (c) => {
|
|
3492
3494
|
try {
|
|
3493
3495
|
const formData = await c.req.formData();
|
|
@@ -3705,7 +3707,7 @@ authRoutes.post("/reset-password", async (c) => {
|
|
|
3705
3707
|
if (Date.now() > user.password_reset_expires) {
|
|
3706
3708
|
return c.json({ error: "Reset token has expired" }, 400);
|
|
3707
3709
|
}
|
|
3708
|
-
const newPasswordHash = await
|
|
3710
|
+
const newPasswordHash = await chunk5GO3AMON_cjs.AuthManager.hashPassword(password);
|
|
3709
3711
|
try {
|
|
3710
3712
|
const historyStmt = db.prepare(`
|
|
3711
3713
|
INSERT INTO password_history (id, user_id, password_hash, created_at)
|
|
@@ -4777,6 +4779,39 @@ function getReadFieldValueScript() {
|
|
|
4777
4779
|
window.__sonicReadFieldValueInit = true;
|
|
4778
4780
|
|
|
4779
4781
|
window.sonicReadFieldValue = function(fieldWrapper) {
|
|
4782
|
+
const getDirectChild = (parent, selector) => {
|
|
4783
|
+
if (!(parent instanceof Element)) return null;
|
|
4784
|
+
return Array.from(parent.children).find(
|
|
4785
|
+
(child) => child instanceof Element && child.matches(selector),
|
|
4786
|
+
) || null;
|
|
4787
|
+
};
|
|
4788
|
+
const getDirectStructuredSubfields = (host) =>
|
|
4789
|
+
Array.from(host.children).filter(
|
|
4790
|
+
(child) => child instanceof Element && child.classList.contains('structured-subfield'),
|
|
4791
|
+
);
|
|
4792
|
+
const getStructuredObjectFieldsHost = (container) => {
|
|
4793
|
+
const directFieldsHost = getDirectChild(container, '[data-structured-object-fields]');
|
|
4794
|
+
if (directFieldsHost) return directFieldsHost;
|
|
4795
|
+
const groupContent = getDirectChild(container, '.field-group-content');
|
|
4796
|
+
const nestedFieldsHost = groupContent
|
|
4797
|
+
? getDirectChild(groupContent, '[data-structured-object-fields]')
|
|
4798
|
+
: null;
|
|
4799
|
+
if (nestedFieldsHost) return nestedFieldsHost;
|
|
4800
|
+
return getDirectChild(container, '[data-array-item-fields]') || container;
|
|
4801
|
+
};
|
|
4802
|
+
const getDirectStructuredObject = (fieldWrapper) => {
|
|
4803
|
+
const directObject = getDirectChild(fieldWrapper, '[data-structured-object]');
|
|
4804
|
+
if (directObject) return directObject;
|
|
4805
|
+
const formGroup = getDirectChild(fieldWrapper, '.form-group');
|
|
4806
|
+
return formGroup ? getDirectChild(formGroup, '[data-structured-object]') : null;
|
|
4807
|
+
};
|
|
4808
|
+
const getDirectStructuredArray = (fieldWrapper) => {
|
|
4809
|
+
const directArray = getDirectChild(fieldWrapper, '[data-structured-array]');
|
|
4810
|
+
if (directArray) return directArray;
|
|
4811
|
+
const formGroup = getDirectChild(fieldWrapper, '.form-group');
|
|
4812
|
+
return formGroup ? getDirectChild(formGroup, '[data-structured-array]') : null;
|
|
4813
|
+
};
|
|
4814
|
+
|
|
4780
4815
|
const fieldType = fieldWrapper.dataset.fieldType;
|
|
4781
4816
|
const select = fieldWrapper.querySelector('select');
|
|
4782
4817
|
const textarea = fieldWrapper.querySelector('textarea');
|
|
@@ -4786,7 +4821,47 @@ function getReadFieldValueScript() {
|
|
|
4786
4821
|
const nonHiddenInput = inputs.find((input) => input.type !== 'hidden' && input.type !== 'checkbox');
|
|
4787
4822
|
const hiddenInput = inputs.find((input) => input.type === 'hidden');
|
|
4788
4823
|
|
|
4824
|
+
const readStructuredFieldsHost = (host) => {
|
|
4825
|
+
const fields = getDirectStructuredSubfields(host);
|
|
4826
|
+
if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
|
|
4827
|
+
return window.sonicReadFieldValue(fields[0]);
|
|
4828
|
+
}
|
|
4829
|
+
return fields.reduce((acc, subfield) => {
|
|
4830
|
+
const fieldName = subfield.dataset.structuredField;
|
|
4831
|
+
if (!fieldName || fieldName === '__value') return acc;
|
|
4832
|
+
acc[fieldName] = window.sonicReadFieldValue(subfield);
|
|
4833
|
+
return acc;
|
|
4834
|
+
}, {});
|
|
4835
|
+
};
|
|
4836
|
+
|
|
4837
|
+
const readStructuredObject = () => {
|
|
4838
|
+
const objectContainer = getDirectStructuredObject(fieldWrapper);
|
|
4839
|
+
if (!objectContainer) return null;
|
|
4840
|
+
const host = getStructuredObjectFieldsHost(objectContainer);
|
|
4841
|
+
return readStructuredFieldsHost(host);
|
|
4842
|
+
};
|
|
4843
|
+
|
|
4844
|
+
const readStructuredArray = () => {
|
|
4845
|
+
const arrayContainer = getDirectStructuredArray(fieldWrapper);
|
|
4846
|
+
if (!arrayContainer) return null;
|
|
4847
|
+
const list = arrayContainer.querySelector('[data-structured-array-list]');
|
|
4848
|
+
if (!list) return [];
|
|
4849
|
+
const items = Array.from(list.querySelectorAll(':scope > .structured-array-item'));
|
|
4850
|
+
return items.map((item) => {
|
|
4851
|
+
const host =
|
|
4852
|
+
item.querySelector(':scope > [data-array-item-fields]') ||
|
|
4853
|
+
item.querySelector('[data-array-item-fields]') ||
|
|
4854
|
+
item;
|
|
4855
|
+
return readStructuredFieldsHost(host);
|
|
4856
|
+
});
|
|
4857
|
+
};
|
|
4858
|
+
|
|
4789
4859
|
if (fieldType === 'object' || fieldType === 'array') {
|
|
4860
|
+
const liveValue = fieldType === 'array' ? readStructuredArray() : readStructuredObject();
|
|
4861
|
+
if (liveValue !== null) {
|
|
4862
|
+
return liveValue;
|
|
4863
|
+
}
|
|
4864
|
+
|
|
4790
4865
|
if (!hiddenInput) {
|
|
4791
4866
|
return fieldType === 'array' ? [] : {};
|
|
4792
4867
|
}
|
|
@@ -4835,6 +4910,15 @@ function getReadFieldValueScript() {
|
|
|
4835
4910
|
</script>
|
|
4836
4911
|
`;
|
|
4837
4912
|
}
|
|
4913
|
+
var STRUCTURED_INDEX_TOKEN = "__INDEX__";
|
|
4914
|
+
var BLOCK_INDEX_TOKEN = "__BLOCK_INDEX__";
|
|
4915
|
+
function sanitizeStructuredGroupId(fieldName) {
|
|
4916
|
+
return `object-${fieldName}`.split(BLOCK_INDEX_TOKEN).map(
|
|
4917
|
+
(blockSegment) => blockSegment.split(STRUCTURED_INDEX_TOKEN).map(
|
|
4918
|
+
(segment) => segment.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
|
|
4919
|
+
).join(STRUCTURED_INDEX_TOKEN)
|
|
4920
|
+
).join(BLOCK_INDEX_TOKEN);
|
|
4921
|
+
}
|
|
4838
4922
|
function isMarkdownEditorFieldType(fieldType) {
|
|
4839
4923
|
return fieldType === "markdown" || fieldType === "mdxeditor" || fieldType === "easymde";
|
|
4840
4924
|
}
|
|
@@ -5415,12 +5499,14 @@ function renderDynamicField(field, options = {}) {
|
|
|
5415
5499
|
|
|
5416
5500
|
${isMultiple ? `
|
|
5417
5501
|
<div class="media-preview-grid grid grid-cols-4 gap-2 mb-2 ${mediaValues.length === 0 ? "hidden" : ""}" id="${fieldId}-preview">
|
|
5418
|
-
${mediaValues.map(
|
|
5502
|
+
${mediaValues.map(
|
|
5503
|
+
(url, idx) => `
|
|
5419
5504
|
<div class="relative media-preview-item" data-url="${url}">
|
|
5420
5505
|
${renderMediaPreview(url, `Media ${idx + 1}`, "w-full h-24 object-cover rounded-lg border border-white/20")}
|
|
5421
5506
|
<button
|
|
5422
5507
|
type="button"
|
|
5423
5508
|
onclick="removeMediaFromMultiple('${fieldId}', '${url}')"
|
|
5509
|
+
data-media-remove="true"
|
|
5424
5510
|
class="absolute top-1 right-1 bg-red-600 text-white rounded-full p-1 hover:bg-red-700"
|
|
5425
5511
|
${disabled ? "disabled" : ""}
|
|
5426
5512
|
>
|
|
@@ -5429,7 +5515,8 @@ function renderDynamicField(field, options = {}) {
|
|
|
5429
5515
|
</svg>
|
|
5430
5516
|
</button>
|
|
5431
5517
|
</div>
|
|
5432
|
-
`
|
|
5518
|
+
`
|
|
5519
|
+
).join("")}
|
|
5433
5520
|
</div>
|
|
5434
5521
|
` : `
|
|
5435
5522
|
<div class="media-preview ${singleValue ? "" : "hidden"}" id="${fieldId}-preview">
|
|
@@ -5453,6 +5540,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5453
5540
|
<button
|
|
5454
5541
|
type="button"
|
|
5455
5542
|
onclick="clearMediaField('${fieldId}')"
|
|
5543
|
+
data-media-remove="true"
|
|
5456
5544
|
class="inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all"
|
|
5457
5545
|
${disabled ? "disabled" : ""}
|
|
5458
5546
|
>
|
|
@@ -5486,7 +5574,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5486
5574
|
}
|
|
5487
5575
|
const showLabel = field.field_type !== "boolean";
|
|
5488
5576
|
return `
|
|
5489
|
-
<div class="form-group">
|
|
5577
|
+
<div class="form-group" data-has-errors="${errors.length > 0 ? "true" : "false"}">
|
|
5490
5578
|
${showLabel ? `
|
|
5491
5579
|
<label for="${fieldId}" class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2">
|
|
5492
5580
|
${escapeHtml3(field.field_label)}
|
|
@@ -5495,7 +5583,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5495
5583
|
` : ""}
|
|
5496
5584
|
${fieldHTML}
|
|
5497
5585
|
${errors.length > 0 ? `
|
|
5498
|
-
<div class="mt-2 text-sm text-pink-600 dark:text-pink-400">
|
|
5586
|
+
<div class="mt-2 text-sm text-pink-600 dark:text-pink-400" data-validation-error-message>
|
|
5499
5587
|
${errors.map((error) => `<div>${escapeHtml3(error)}</div>`).join("")}
|
|
5500
5588
|
</div>
|
|
5501
5589
|
` : ""}
|
|
@@ -5510,8 +5598,8 @@ function renderDynamicField(field, options = {}) {
|
|
|
5510
5598
|
function renderFieldGroup(title, fields, collapsible = false) {
|
|
5511
5599
|
const groupId = title.toLowerCase().replace(/\s+/g, "-");
|
|
5512
5600
|
return `
|
|
5513
|
-
<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">
|
|
5514
|
-
<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(
|
|
5601
|
+
<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)}">
|
|
5602
|
+
<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)"` : ""}>
|
|
5515
5603
|
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
|
|
5516
5604
|
${escapeHtml3(title)}
|
|
5517
5605
|
${collapsible ? `
|
|
@@ -5555,6 +5643,12 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
|
5555
5643
|
>
|
|
5556
5644
|
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(blockValues))}">
|
|
5557
5645
|
|
|
5646
|
+
<div class="flex items-center justify-between border-b border-zinc-950/5 dark:border-white/10 py-4">
|
|
5647
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">
|
|
5648
|
+
${escapeHtml3(field.field_label || "Content Blocks")}
|
|
5649
|
+
</h3>
|
|
5650
|
+
</div>
|
|
5651
|
+
|
|
5558
5652
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5559
5653
|
<div class="flex-1">
|
|
5560
5654
|
<select
|
|
@@ -5585,12 +5679,14 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
|
5585
5679
|
`;
|
|
5586
5680
|
}
|
|
5587
5681
|
function renderStructuredObjectField(field, options, baseClasses, errorClasses) {
|
|
5588
|
-
const { value = {}, pluginStatuses = {} } = options;
|
|
5682
|
+
const { value = {}, pluginStatuses = {}, errors = [] } = options;
|
|
5589
5683
|
const opts = field.field_options || {};
|
|
5590
5684
|
const properties = opts.properties && typeof opts.properties === "object" ? opts.properties : {};
|
|
5591
5685
|
const fieldId = `field-${field.field_name}`;
|
|
5592
5686
|
const fieldName = field.field_name;
|
|
5593
5687
|
const objectValue = normalizeStructuredObjectValue(value);
|
|
5688
|
+
const objectLayout = opts.objectLayout || "nested";
|
|
5689
|
+
const useNestedLayout = objectLayout !== "flat";
|
|
5594
5690
|
const subfields = Object.entries(properties).map(
|
|
5595
5691
|
([propertyName, propertyConfig]) => renderStructuredSubfield(
|
|
5596
5692
|
field,
|
|
@@ -5601,11 +5697,40 @@ function renderStructuredObjectField(field, options, baseClasses, errorClasses)
|
|
|
5601
5697
|
field.field_name
|
|
5602
5698
|
)
|
|
5603
5699
|
).join("");
|
|
5700
|
+
const groupTitle = field.field_label || field.field_name;
|
|
5701
|
+
if (!useNestedLayout) {
|
|
5702
|
+
return `
|
|
5703
|
+
<div class="space-y-4" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
|
|
5704
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
|
|
5705
|
+
<div class="flex items-center justify-between border-b border-zinc-950/5 dark:border-white/10 py-4 first-of-type:pt-0">
|
|
5706
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">
|
|
5707
|
+
${escapeHtml3(groupTitle)}
|
|
5708
|
+
</h3>
|
|
5709
|
+
</div>
|
|
5710
|
+
<div class="space-y-4" data-structured-object-fields>
|
|
5711
|
+
${subfields}
|
|
5712
|
+
</div>
|
|
5713
|
+
</div>
|
|
5714
|
+
${getStructuredFieldScript()}
|
|
5715
|
+
`;
|
|
5716
|
+
}
|
|
5717
|
+
const groupId = sanitizeStructuredGroupId(field.field_name);
|
|
5718
|
+
const isCollapsed = errors.length > 0 ? false : opts.collapsed !== false;
|
|
5604
5719
|
return `
|
|
5605
|
-
<div class="
|
|
5606
|
-
<
|
|
5607
|
-
|
|
5608
|
-
|
|
5720
|
+
<div class="field-group rounded-lg shadow-sm mb-6" data-group-id="${escapeHtml3(groupId)}" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
|
|
5721
|
+
<div class="field-group-header border-b border-zinc-950/5 dark:border-white/10 pr-6 pb-4 cursor-pointer" onclick="toggleFieldGroup(this)">
|
|
5722
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
|
|
5723
|
+
${escapeHtml3(groupTitle)}
|
|
5724
|
+
<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">
|
|
5725
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
5726
|
+
</svg>
|
|
5727
|
+
</h3>
|
|
5728
|
+
</div>
|
|
5729
|
+
<div id="${groupId}-content" class="field-group-content px-6 py-6 space-y-4 ${isCollapsed ? "hidden" : ""}">
|
|
5730
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
|
|
5731
|
+
<div class="space-y-4" data-structured-object-fields>
|
|
5732
|
+
${subfields}
|
|
5733
|
+
</div>
|
|
5609
5734
|
</div>
|
|
5610
5735
|
</div>
|
|
5611
5736
|
${getStructuredFieldScript()}
|
|
@@ -5676,7 +5801,7 @@ function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
|
|
|
5676
5801
|
function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginStatuses, arrayItemTitle) {
|
|
5677
5802
|
const itemFields = renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses);
|
|
5678
5803
|
return `
|
|
5679
|
-
<div class="structured-array-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-
|
|
5804
|
+
<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">
|
|
5680
5805
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5681
5806
|
<div class="flex items-center gap-3">
|
|
5682
5807
|
<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">
|
|
@@ -5689,6 +5814,11 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
|
|
|
5689
5814
|
</div>
|
|
5690
5815
|
</div>
|
|
5691
5816
|
<div class="flex flex-wrap gap-2 text-xs">
|
|
5817
|
+
<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">
|
|
5818
|
+
<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>
|
|
5819
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
|
5820
|
+
</svg>
|
|
5821
|
+
</button>
|
|
5692
5822
|
<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">
|
|
5693
5823
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
5694
5824
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
@@ -5707,7 +5837,7 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
|
|
|
5707
5837
|
</button>
|
|
5708
5838
|
</div>
|
|
5709
5839
|
</div>
|
|
5710
|
-
<div class="mt-4 space-y-4" data-array-item-fields>
|
|
5840
|
+
<div class="mt-4 space-y-4 hidden" data-array-item-fields>
|
|
5711
5841
|
${itemFields}
|
|
5712
5842
|
</div>
|
|
5713
5843
|
</div>
|
|
@@ -5817,7 +5947,7 @@ function normalizeBlocksValue(value, discriminator) {
|
|
|
5817
5947
|
function renderBlockTemplate(field, block, discriminator, pluginStatuses) {
|
|
5818
5948
|
return `
|
|
5819
5949
|
<template data-block-template="${escapeHtml3(block.name)}">
|
|
5820
|
-
${renderBlockCard(field, block, discriminator,
|
|
5950
|
+
${renderBlockCard(field, block, discriminator, BLOCK_INDEX_TOKEN, {}, pluginStatuses)}
|
|
5821
5951
|
</template>
|
|
5822
5952
|
`;
|
|
5823
5953
|
}
|
|
@@ -5859,7 +5989,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5859
5989
|
`;
|
|
5860
5990
|
}).join("");
|
|
5861
5991
|
return `
|
|
5862
|
-
<div class="blocks-item rounded-lg border border-zinc-200 dark:border-white/10
|
|
5992
|
+
<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">
|
|
5863
5993
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5864
5994
|
<div class="flex items-start gap-3">
|
|
5865
5995
|
<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">
|
|
@@ -5867,7 +5997,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5867
5997
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
|
|
5868
5998
|
</svg>
|
|
5869
5999
|
</div>
|
|
5870
|
-
<div>
|
|
6000
|
+
<div class="cursor-pointer" data-action="toggle-block">
|
|
5871
6001
|
<div class="text-sm font-semibold text-zinc-900 dark:text-white">
|
|
5872
6002
|
${escapeHtml3(block.label)}
|
|
5873
6003
|
<span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-block-order-label></span>
|
|
@@ -5876,6 +6006,11 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5876
6006
|
</div>
|
|
5877
6007
|
</div>
|
|
5878
6008
|
<div class="flex flex-wrap gap-2 text-xs">
|
|
6009
|
+
<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">
|
|
6010
|
+
<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>
|
|
6011
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
|
6012
|
+
</svg>
|
|
6013
|
+
</button>
|
|
5879
6014
|
<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">
|
|
5880
6015
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
5881
6016
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
@@ -5894,7 +6029,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5894
6029
|
</button>
|
|
5895
6030
|
</div>
|
|
5896
6031
|
</div>
|
|
5897
|
-
<div class="mt-4 space-y-4">
|
|
6032
|
+
<div class="mt-4 space-y-4 hidden" data-block-content>
|
|
5898
6033
|
${blockFields}
|
|
5899
6034
|
</div>
|
|
5900
6035
|
</div>
|
|
@@ -5928,9 +6063,101 @@ function getStructuredFieldScript() {
|
|
|
5928
6063
|
|
|
5929
6064
|
function initializeStructuredFields() {
|
|
5930
6065
|
const readFieldValue = window.sonicReadFieldValue;
|
|
6066
|
+
const getDirectChild = (parent, selector) => {
|
|
6067
|
+
if (!(parent instanceof Element)) return null;
|
|
6068
|
+
return Array.from(parent.children).find(
|
|
6069
|
+
(child) => child instanceof Element && child.matches(selector),
|
|
6070
|
+
) || null;
|
|
6071
|
+
};
|
|
6072
|
+
const getDirectStructuredSubfields = (host) =>
|
|
6073
|
+
Array.from(host.children).filter(
|
|
6074
|
+
(child) => child instanceof Element && child.classList.contains('structured-subfield'),
|
|
6075
|
+
);
|
|
6076
|
+
const getStructuredValueHost = (container) => {
|
|
6077
|
+
const directObjectHost = getDirectChild(container, '[data-structured-object-fields]');
|
|
6078
|
+
if (directObjectHost) return directObjectHost;
|
|
6079
|
+
const groupContent = getDirectChild(container, '.field-group-content');
|
|
6080
|
+
const nestedObjectHost = groupContent
|
|
6081
|
+
? getDirectChild(groupContent, '[data-structured-object-fields]')
|
|
6082
|
+
: null;
|
|
6083
|
+
if (nestedObjectHost) return nestedObjectHost;
|
|
6084
|
+
return getDirectChild(container, '[data-array-item-fields]') || container;
|
|
6085
|
+
};
|
|
6086
|
+
const getCollectionScope = () => {
|
|
6087
|
+
const url = new URL(window.location.href);
|
|
6088
|
+
const collectionFromQuery = url.searchParams.get('collection');
|
|
6089
|
+
const form = document.getElementById('content-form');
|
|
6090
|
+
const collectionInput = form?.querySelector('input[name="collection_id"]');
|
|
6091
|
+
const collectionFromForm = collectionInput instanceof HTMLInputElement ? collectionInput.value : '';
|
|
6092
|
+
const collectionId = collectionFromQuery || collectionFromForm || '';
|
|
6093
|
+
return window.location.pathname + ':' + collectionId;
|
|
6094
|
+
};
|
|
6095
|
+
|
|
6096
|
+
const getArrayStateKey = (container) => {
|
|
6097
|
+
const fieldName = container.dataset.fieldName || 'unknown';
|
|
6098
|
+
return 'sonic:ui:repeaters:' + getCollectionScope() + ':' + fieldName;
|
|
6099
|
+
};
|
|
6100
|
+
|
|
6101
|
+
const readArrayState = (container) => {
|
|
6102
|
+
try {
|
|
6103
|
+
const raw = sessionStorage.getItem(getArrayStateKey(container));
|
|
6104
|
+
if (!raw) return null;
|
|
6105
|
+
const parsed = JSON.parse(raw);
|
|
6106
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
6107
|
+
} catch {
|
|
6108
|
+
return null;
|
|
6109
|
+
}
|
|
6110
|
+
};
|
|
6111
|
+
|
|
6112
|
+
const writeArrayState = (container, state) => {
|
|
6113
|
+
try {
|
|
6114
|
+
sessionStorage.setItem(getArrayStateKey(container), JSON.stringify(state));
|
|
6115
|
+
} catch {}
|
|
6116
|
+
};
|
|
6117
|
+
|
|
6118
|
+
const setArrayItemExpanded = (item, isExpanded) => {
|
|
6119
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6120
|
+
const icon = item.querySelector('[data-item-toggle-icon]');
|
|
6121
|
+
if (content instanceof HTMLElement) {
|
|
6122
|
+
content.classList.toggle('hidden', !isExpanded);
|
|
6123
|
+
}
|
|
6124
|
+
if (icon instanceof Element) {
|
|
6125
|
+
icon.classList.toggle('-rotate-90', !isExpanded);
|
|
6126
|
+
}
|
|
6127
|
+
};
|
|
6128
|
+
|
|
6129
|
+
const getArrayItems = (container, list) => {
|
|
6130
|
+
if (list) {
|
|
6131
|
+
return Array.from(list.querySelectorAll(':scope > .structured-array-item'));
|
|
6132
|
+
}
|
|
6133
|
+
return Array.from(
|
|
6134
|
+
container.querySelectorAll(':scope > [data-structured-array-list] > .structured-array-item'),
|
|
6135
|
+
);
|
|
6136
|
+
};
|
|
6137
|
+
|
|
6138
|
+
const captureArrayState = (container) => {
|
|
6139
|
+
return getArrayItems(container).map((item) => {
|
|
6140
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6141
|
+
return content instanceof HTMLElement ? !content.classList.contains('hidden') : false;
|
|
6142
|
+
});
|
|
6143
|
+
};
|
|
6144
|
+
|
|
6145
|
+
const applyArrayState = (container, state) => {
|
|
6146
|
+
const items = getArrayItems(container);
|
|
6147
|
+
items.forEach((item, index) => {
|
|
6148
|
+
if (typeof state[index] === 'boolean') {
|
|
6149
|
+
setArrayItemExpanded(item, state[index]);
|
|
6150
|
+
}
|
|
6151
|
+
});
|
|
6152
|
+
};
|
|
6153
|
+
|
|
6154
|
+
const syncArrayState = (container) => {
|
|
6155
|
+
writeArrayState(container, captureArrayState(container));
|
|
6156
|
+
};
|
|
5931
6157
|
|
|
5932
6158
|
const readStructuredValue = (container) => {
|
|
5933
|
-
const
|
|
6159
|
+
const fieldHost = getStructuredValueHost(container);
|
|
6160
|
+
const fields = getDirectStructuredSubfields(fieldHost);
|
|
5934
6161
|
if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
|
|
5935
6162
|
return readFieldValue(fields[0]);
|
|
5936
6163
|
}
|
|
@@ -5944,111 +6171,229 @@ function getStructuredFieldScript() {
|
|
|
5944
6171
|
};
|
|
5945
6172
|
|
|
5946
6173
|
document.querySelectorAll('[data-structured-object]').forEach((container) => {
|
|
6174
|
+
if (container.closest('template')) {
|
|
6175
|
+
return;
|
|
6176
|
+
}
|
|
5947
6177
|
if (container.dataset.structuredInitialized === 'true') {
|
|
5948
6178
|
return;
|
|
5949
6179
|
}
|
|
5950
|
-
container.dataset.
|
|
5951
|
-
|
|
6180
|
+
if (container.dataset.structuredInitializing === 'true') {
|
|
6181
|
+
return;
|
|
6182
|
+
}
|
|
6183
|
+
container.dataset.structuredInitializing = 'true';
|
|
6184
|
+
try {
|
|
6185
|
+
const hiddenInput = container.querySelector('input[type="hidden"]');
|
|
5952
6186
|
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
6187
|
+
const updateHiddenInput = () => {
|
|
6188
|
+
if (!hiddenInput) return;
|
|
6189
|
+
const value = readStructuredValue(container);
|
|
6190
|
+
hiddenInput.value = JSON.stringify(value);
|
|
6191
|
+
};
|
|
5958
6192
|
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
6193
|
+
container.addEventListener('input', updateHiddenInput);
|
|
6194
|
+
container.addEventListener('change', updateHiddenInput);
|
|
6195
|
+
updateHiddenInput();
|
|
6196
|
+
container.dataset.structuredInitialized = 'true';
|
|
6197
|
+
} catch (error) {
|
|
6198
|
+
delete container.dataset.structuredInitialized;
|
|
6199
|
+
console.error('[structured-object] initialization failed', error);
|
|
6200
|
+
} finally {
|
|
6201
|
+
delete container.dataset.structuredInitializing;
|
|
6202
|
+
}
|
|
5962
6203
|
});
|
|
5963
6204
|
|
|
5964
6205
|
document.querySelectorAll('[data-structured-array]').forEach((container) => {
|
|
6206
|
+
if (container.closest('template')) {
|
|
6207
|
+
return;
|
|
6208
|
+
}
|
|
5965
6209
|
if (container.dataset.structuredInitialized === 'true') {
|
|
5966
6210
|
return;
|
|
5967
6211
|
}
|
|
5968
|
-
container.dataset.
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
6212
|
+
if (container.dataset.structuredInitializing === 'true') {
|
|
6213
|
+
return;
|
|
6214
|
+
}
|
|
6215
|
+
container.dataset.structuredInitializing = 'true';
|
|
6216
|
+
try {
|
|
6217
|
+
const list = container.querySelector(':scope > [data-structured-array-list]');
|
|
6218
|
+
const hiddenInput = container.querySelector(':scope > input[type="hidden"]');
|
|
6219
|
+
const template = container.querySelector(':scope > template[data-structured-array-template]');
|
|
6220
|
+
if (
|
|
6221
|
+
template instanceof HTMLTemplateElement &&
|
|
6222
|
+
typeof template.innerHTML === 'string' &&
|
|
6223
|
+
template.innerHTML.trim()
|
|
6224
|
+
) {
|
|
6225
|
+
container.__sonicStructuredArrayTemplate = template.innerHTML;
|
|
6226
|
+
}
|
|
5972
6227
|
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
6228
|
+
const getLiveList = () =>
|
|
6229
|
+
list || container.querySelector(':scope > [data-structured-array-list]');
|
|
6230
|
+
const getLiveHiddenInput = () =>
|
|
6231
|
+
hiddenInput || container.querySelector(':scope > input[type="hidden"]');
|
|
6232
|
+
const getTemplateHtml = () => {
|
|
6233
|
+
if (typeof container.__sonicStructuredArrayTemplate === 'string' &&
|
|
6234
|
+
container.__sonicStructuredArrayTemplate.trim()) {
|
|
6235
|
+
return container.__sonicStructuredArrayTemplate;
|
|
5979
6236
|
}
|
|
5980
6237
|
|
|
5981
|
-
const
|
|
5982
|
-
|
|
5983
|
-
|
|
6238
|
+
const liveTemplate =
|
|
6239
|
+
template instanceof HTMLTemplateElement
|
|
6240
|
+
? template
|
|
6241
|
+
: container.querySelector(':scope > template[data-structured-array-template]');
|
|
6242
|
+
if (
|
|
6243
|
+
liveTemplate instanceof HTMLTemplateElement &&
|
|
6244
|
+
typeof liveTemplate.innerHTML === 'string' &&
|
|
6245
|
+
liveTemplate.innerHTML.trim()
|
|
6246
|
+
) {
|
|
6247
|
+
container.__sonicStructuredArrayTemplate = liveTemplate.innerHTML;
|
|
6248
|
+
return liveTemplate.innerHTML;
|
|
5984
6249
|
}
|
|
6250
|
+
return typeof container.__sonicStructuredArrayTemplate === 'string'
|
|
6251
|
+
? container.__sonicStructuredArrayTemplate
|
|
6252
|
+
: '';
|
|
6253
|
+
};
|
|
5985
6254
|
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
6255
|
+
const updateOrderLabels = () => {
|
|
6256
|
+
const liveList = getLiveList();
|
|
6257
|
+
if (!liveList) return;
|
|
6258
|
+
const items = getArrayItems(container, liveList);
|
|
6259
|
+
items.forEach((item, index) => {
|
|
6260
|
+
const label = item.querySelector('[data-array-order-label]');
|
|
6261
|
+
if (label) {
|
|
6262
|
+
label.textContent = '#'+ (index + 1);
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
const moveUpButton = item.querySelector('[data-action="move-up"]');
|
|
6266
|
+
if (moveUpButton instanceof HTMLButtonElement) {
|
|
6267
|
+
moveUpButton.disabled = index === 0;
|
|
6268
|
+
}
|
|
6269
|
+
|
|
6270
|
+
const moveDownButton = item.querySelector('[data-action="move-down"]');
|
|
6271
|
+
if (moveDownButton instanceof HTMLButtonElement) {
|
|
6272
|
+
moveDownButton.disabled = index === items.length - 1;
|
|
6273
|
+
}
|
|
6274
|
+
});
|
|
6275
|
+
};
|
|
6276
|
+
|
|
6277
|
+
const updateHiddenInput = () => {
|
|
6278
|
+
const liveHiddenInput = getLiveHiddenInput();
|
|
6279
|
+
const liveList = getLiveList();
|
|
6280
|
+
if (!liveHiddenInput || !liveList) return;
|
|
6281
|
+
const items = getArrayItems(container, liveList);
|
|
6282
|
+
const values = items.map((item) => readStructuredValue(item));
|
|
6283
|
+
liveHiddenInput.value = JSON.stringify(values);
|
|
6284
|
+
// Notify parent structured containers after non-input actions (add/remove/move)
|
|
6285
|
+
// so nested array mutations are persisted correctly.
|
|
6286
|
+
liveHiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
6287
|
+
|
|
6288
|
+
const emptyState = liveList.querySelector(':scope > [data-structured-empty]');
|
|
6289
|
+
if (emptyState) {
|
|
6290
|
+
emptyState.style.display = values.length === 0 ? 'block' : 'none';
|
|
5989
6291
|
}
|
|
5990
|
-
|
|
5991
|
-
|
|
6292
|
+
updateOrderLabels();
|
|
6293
|
+
};
|
|
5992
6294
|
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
6295
|
+
const addArrayItem = () => {
|
|
6296
|
+
const liveList = getLiveList();
|
|
6297
|
+
if (!liveList) return;
|
|
6298
|
+
const templateHtml = getTemplateHtml();
|
|
6299
|
+
if (!templateHtml) return;
|
|
6300
|
+
try {
|
|
6301
|
+
const nextIndex = getArrayItems(container, liveList).length;
|
|
6302
|
+
const html = templateHtml.replace(/__INDEX__/g, String(nextIndex));
|
|
6303
|
+
liveList.insertAdjacentHTML('beforeend', html);
|
|
6304
|
+
const newItem = liveList.lastElementChild;
|
|
6305
|
+
if (newItem instanceof HTMLElement) {
|
|
6306
|
+
// Ensure cloned template content can be initialized even if stale
|
|
6307
|
+
// data-structured-initialized attributes were copied.
|
|
6308
|
+
newItem
|
|
6309
|
+
.querySelectorAll('[data-structured-object], [data-structured-array]')
|
|
6310
|
+
.forEach((nestedContainer) => {
|
|
6311
|
+
if (nestedContainer instanceof HTMLElement) {
|
|
6312
|
+
delete nestedContainer.dataset.structuredInitialized;
|
|
6313
|
+
}
|
|
6314
|
+
});
|
|
6315
|
+
setArrayItemExpanded(newItem, true);
|
|
6316
|
+
}
|
|
6317
|
+
if (typeof initializeTinyMCE === 'function') {
|
|
6318
|
+
initializeTinyMCE();
|
|
6319
|
+
}
|
|
6320
|
+
if (typeof window.initializeQuillEditors === 'function') {
|
|
6321
|
+
window.initializeQuillEditors();
|
|
6322
|
+
}
|
|
6323
|
+
if (typeof initializeMDXEditor === 'function') {
|
|
6324
|
+
initializeMDXEditor();
|
|
6325
|
+
}
|
|
6326
|
+
if (typeof window.initializeStructuredFields === 'function') {
|
|
6327
|
+
window.initializeStructuredFields();
|
|
6328
|
+
}
|
|
6329
|
+
updateHiddenInput();
|
|
6330
|
+
syncArrayState(container);
|
|
6331
|
+
} catch (error) {
|
|
6332
|
+
console.error('[structured-array] add-item failed', error);
|
|
6333
|
+
}
|
|
6334
|
+
};
|
|
5998
6335
|
|
|
5999
|
-
const
|
|
6000
|
-
|
|
6001
|
-
|
|
6336
|
+
const topLevelAddButton = container.querySelector(
|
|
6337
|
+
':scope > .flex.items-center.justify-between.gap-3 [data-action="add-item"]',
|
|
6338
|
+
);
|
|
6339
|
+
if (topLevelAddButton instanceof HTMLElement) {
|
|
6340
|
+
topLevelAddButton.addEventListener('click', (event) => {
|
|
6341
|
+
event.preventDefault();
|
|
6342
|
+
event.stopPropagation();
|
|
6343
|
+
addArrayItem();
|
|
6344
|
+
});
|
|
6002
6345
|
}
|
|
6003
|
-
updateOrderLabels();
|
|
6004
|
-
};
|
|
6005
6346
|
|
|
6006
|
-
|
|
6007
|
-
window.initializeDragSortable
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6347
|
+
const dragList = getLiveList();
|
|
6348
|
+
if (typeof window.initializeDragSortable === 'function' && dragList) {
|
|
6349
|
+
window.initializeDragSortable(dragList, {
|
|
6350
|
+
itemSelector: '.structured-array-item',
|
|
6351
|
+
handleSelector: '[data-action="drag-handle"]',
|
|
6352
|
+
onUpdate: () => {
|
|
6353
|
+
updateHiddenInput();
|
|
6354
|
+
syncArrayState(container);
|
|
6355
|
+
}
|
|
6356
|
+
});
|
|
6357
|
+
}
|
|
6013
6358
|
|
|
6014
|
-
|
|
6359
|
+
container.addEventListener('click', (event) => {
|
|
6015
6360
|
const target = event.target;
|
|
6016
6361
|
if (!(target instanceof Element)) return;
|
|
6017
6362
|
const actionButton = target.closest('[data-action]');
|
|
6018
6363
|
if (!actionButton || actionButton.hasAttribute('disabled')) return;
|
|
6364
|
+
const actionOwner = actionButton.closest('[data-structured-array]');
|
|
6365
|
+
if (actionOwner !== container) return;
|
|
6019
6366
|
|
|
6020
|
-
|
|
6367
|
+
const action = actionButton.getAttribute('data-action');
|
|
6021
6368
|
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
const html = template.innerHTML.replace(/__INDEX__/g, String(nextIndex));
|
|
6026
|
-
list.insertAdjacentHTML('beforeend', html);
|
|
6027
|
-
if (typeof initializeTinyMCE === 'function') {
|
|
6028
|
-
initializeTinyMCE();
|
|
6029
|
-
}
|
|
6030
|
-
if (typeof window.initializeQuillEditors === 'function') {
|
|
6031
|
-
window.initializeQuillEditors();
|
|
6032
|
-
}
|
|
6033
|
-
if (typeof initializeMDXEditor === 'function') {
|
|
6034
|
-
initializeMDXEditor();
|
|
6369
|
+
if (action === 'add-item') {
|
|
6370
|
+
addArrayItem();
|
|
6371
|
+
return;
|
|
6035
6372
|
}
|
|
6036
|
-
updateHiddenInput();
|
|
6037
|
-
return;
|
|
6038
|
-
}
|
|
6039
6373
|
|
|
6040
6374
|
const item = actionButton.closest('.structured-array-item');
|
|
6041
|
-
|
|
6375
|
+
const liveList = getLiveList();
|
|
6376
|
+
if (!item || !liveList) return;
|
|
6377
|
+
|
|
6378
|
+
if (action === 'toggle-item') {
|
|
6379
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6380
|
+
if (!(content instanceof HTMLElement)) return;
|
|
6381
|
+
setArrayItemExpanded(item, content.classList.contains('hidden'));
|
|
6382
|
+
syncArrayState(container);
|
|
6383
|
+
return;
|
|
6384
|
+
}
|
|
6042
6385
|
|
|
6043
6386
|
if (action === 'remove-item') {
|
|
6044
6387
|
if (typeof requestRepeaterDelete === 'function') {
|
|
6045
6388
|
requestRepeaterDelete(() => {
|
|
6046
6389
|
item.remove();
|
|
6047
6390
|
updateHiddenInput();
|
|
6391
|
+
syncArrayState(container);
|
|
6048
6392
|
});
|
|
6049
6393
|
} else {
|
|
6050
6394
|
item.remove();
|
|
6051
6395
|
updateHiddenInput();
|
|
6396
|
+
syncArrayState(container);
|
|
6052
6397
|
}
|
|
6053
6398
|
return;
|
|
6054
6399
|
}
|
|
@@ -6056,8 +6401,9 @@ function getStructuredFieldScript() {
|
|
|
6056
6401
|
if (action === 'move-up') {
|
|
6057
6402
|
const previous = item.previousElementSibling;
|
|
6058
6403
|
if (previous) {
|
|
6059
|
-
|
|
6404
|
+
liveList.insertBefore(item, previous);
|
|
6060
6405
|
updateHiddenInput();
|
|
6406
|
+
syncArrayState(container);
|
|
6061
6407
|
}
|
|
6062
6408
|
return;
|
|
6063
6409
|
}
|
|
@@ -6065,29 +6411,43 @@ function getStructuredFieldScript() {
|
|
|
6065
6411
|
if (action === 'move-down') {
|
|
6066
6412
|
const next = item.nextElementSibling;
|
|
6067
6413
|
if (next) {
|
|
6068
|
-
|
|
6414
|
+
liveList.insertBefore(next, item);
|
|
6069
6415
|
updateHiddenInput();
|
|
6416
|
+
syncArrayState(container);
|
|
6070
6417
|
}
|
|
6071
6418
|
}
|
|
6072
|
-
|
|
6419
|
+
});
|
|
6073
6420
|
|
|
6074
|
-
|
|
6421
|
+
container.addEventListener('input', (event) => {
|
|
6075
6422
|
const target = event.target;
|
|
6076
6423
|
if (!(target instanceof Element)) return;
|
|
6077
6424
|
if (target.closest('[data-structured-array-list]')) {
|
|
6078
6425
|
updateHiddenInput();
|
|
6079
6426
|
}
|
|
6080
|
-
|
|
6427
|
+
});
|
|
6081
6428
|
|
|
6082
|
-
|
|
6429
|
+
container.addEventListener('change', (event) => {
|
|
6083
6430
|
const target = event.target;
|
|
6084
6431
|
if (!(target instanceof Element)) return;
|
|
6085
6432
|
if (target.closest('[data-structured-array-list]')) {
|
|
6086
6433
|
updateHiddenInput();
|
|
6087
6434
|
}
|
|
6088
|
-
|
|
6435
|
+
});
|
|
6089
6436
|
|
|
6090
|
-
|
|
6437
|
+
updateHiddenInput();
|
|
6438
|
+
const savedArrayState = readArrayState(container);
|
|
6439
|
+
if (savedArrayState) {
|
|
6440
|
+
applyArrayState(container, savedArrayState);
|
|
6441
|
+
} else {
|
|
6442
|
+
syncArrayState(container);
|
|
6443
|
+
}
|
|
6444
|
+
container.dataset.structuredInitialized = 'true';
|
|
6445
|
+
} catch (error) {
|
|
6446
|
+
delete container.dataset.structuredInitialized;
|
|
6447
|
+
console.error('[structured-array] initialization failed', error);
|
|
6448
|
+
} finally {
|
|
6449
|
+
delete container.dataset.structuredInitializing;
|
|
6450
|
+
}
|
|
6091
6451
|
});
|
|
6092
6452
|
}
|
|
6093
6453
|
|
|
@@ -6102,7 +6462,10 @@ function getStructuredFieldScript() {
|
|
|
6102
6462
|
document.addEventListener('htmx:afterSwap', function() {
|
|
6103
6463
|
setTimeout(initializeStructuredFields, 50);
|
|
6104
6464
|
});
|
|
6105
|
-
} else if (
|
|
6465
|
+
} else if (
|
|
6466
|
+
typeof window.initializeStructuredFields === 'function' &&
|
|
6467
|
+
document.readyState !== 'loading'
|
|
6468
|
+
) {
|
|
6106
6469
|
window.initializeStructuredFields();
|
|
6107
6470
|
}
|
|
6108
6471
|
</script>
|
|
@@ -6114,6 +6477,68 @@ function getBlocksFieldScript() {
|
|
|
6114
6477
|
<script>
|
|
6115
6478
|
if (!window.__sonicBlocksFieldInit) {
|
|
6116
6479
|
window.__sonicBlocksFieldInit = true;
|
|
6480
|
+
const getCollectionScope = () => {
|
|
6481
|
+
const url = new URL(window.location.href);
|
|
6482
|
+
const collectionFromQuery = url.searchParams.get('collection');
|
|
6483
|
+
const form = document.getElementById('content-form');
|
|
6484
|
+
const collectionInput = form?.querySelector('input[name="collection_id"]');
|
|
6485
|
+
const collectionFromForm = collectionInput instanceof HTMLInputElement ? collectionInput.value : '';
|
|
6486
|
+
const collectionId = collectionFromQuery || collectionFromForm || '';
|
|
6487
|
+
return window.location.pathname + ':' + collectionId;
|
|
6488
|
+
};
|
|
6489
|
+
|
|
6490
|
+
const getBlocksStateKey = (container) => {
|
|
6491
|
+
const fieldName = container.dataset.fieldName || 'unknown';
|
|
6492
|
+
return 'sonic:ui:blocks:' + getCollectionScope() + ':' + fieldName;
|
|
6493
|
+
};
|
|
6494
|
+
|
|
6495
|
+
const readBlocksState = (container) => {
|
|
6496
|
+
try {
|
|
6497
|
+
const raw = sessionStorage.getItem(getBlocksStateKey(container));
|
|
6498
|
+
if (!raw) return null;
|
|
6499
|
+
const parsed = JSON.parse(raw);
|
|
6500
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
6501
|
+
} catch {
|
|
6502
|
+
return null;
|
|
6503
|
+
}
|
|
6504
|
+
};
|
|
6505
|
+
|
|
6506
|
+
const writeBlocksState = (container, state) => {
|
|
6507
|
+
try {
|
|
6508
|
+
sessionStorage.setItem(getBlocksStateKey(container), JSON.stringify(state));
|
|
6509
|
+
} catch {}
|
|
6510
|
+
};
|
|
6511
|
+
|
|
6512
|
+
const setBlockExpanded = (item, isExpanded) => {
|
|
6513
|
+
const content = item.querySelector('[data-block-content]');
|
|
6514
|
+
const icon = item.querySelector('[data-block-toggle-icon]');
|
|
6515
|
+
if (content instanceof HTMLElement) {
|
|
6516
|
+
content.classList.toggle('hidden', !isExpanded);
|
|
6517
|
+
}
|
|
6518
|
+
if (icon instanceof Element) {
|
|
6519
|
+
icon.classList.toggle('-rotate-90', !isExpanded);
|
|
6520
|
+
}
|
|
6521
|
+
};
|
|
6522
|
+
|
|
6523
|
+
const captureBlocksState = (container) => {
|
|
6524
|
+
return Array.from(container.querySelectorAll('.blocks-item')).map((item) => {
|
|
6525
|
+
const content = item.querySelector('[data-block-content]');
|
|
6526
|
+
return content instanceof HTMLElement ? !content.classList.contains('hidden') : false;
|
|
6527
|
+
});
|
|
6528
|
+
};
|
|
6529
|
+
|
|
6530
|
+
const applyBlocksState = (container, state) => {
|
|
6531
|
+
const items = Array.from(container.querySelectorAll('.blocks-item'));
|
|
6532
|
+
items.forEach((item, index) => {
|
|
6533
|
+
if (typeof state[index] === 'boolean') {
|
|
6534
|
+
setBlockExpanded(item, state[index]);
|
|
6535
|
+
}
|
|
6536
|
+
});
|
|
6537
|
+
};
|
|
6538
|
+
|
|
6539
|
+
const syncBlocksState = (container) => {
|
|
6540
|
+
writeBlocksState(container, captureBlocksState(container));
|
|
6541
|
+
};
|
|
6117
6542
|
|
|
6118
6543
|
function initializeBlocksFields() {
|
|
6119
6544
|
document.querySelectorAll('.blocks-field').forEach((container) => {
|
|
@@ -6201,7 +6626,10 @@ function getBlocksFieldScript() {
|
|
|
6201
6626
|
window.initializeDragSortable(list, {
|
|
6202
6627
|
itemSelector: '.blocks-item',
|
|
6203
6628
|
handleSelector: '[data-action="drag-handle"]',
|
|
6204
|
-
onUpdate:
|
|
6629
|
+
onUpdate: () => {
|
|
6630
|
+
updateHiddenInput();
|
|
6631
|
+
syncBlocksState(container);
|
|
6632
|
+
}
|
|
6205
6633
|
});
|
|
6206
6634
|
}
|
|
6207
6635
|
|
|
@@ -6223,8 +6651,12 @@ function getBlocksFieldScript() {
|
|
|
6223
6651
|
if (!template) return;
|
|
6224
6652
|
|
|
6225
6653
|
const nextIndex = list.querySelectorAll('.blocks-item').length;
|
|
6226
|
-
const html = template.innerHTML.replace(/
|
|
6654
|
+
const html = template.innerHTML.replace(/__BLOCK_INDEX__/g, String(nextIndex));
|
|
6227
6655
|
list.insertAdjacentHTML('beforeend', html);
|
|
6656
|
+
const newItem = list.lastElementChild;
|
|
6657
|
+
if (newItem instanceof HTMLElement) {
|
|
6658
|
+
setBlockExpanded(newItem, true);
|
|
6659
|
+
}
|
|
6228
6660
|
if (typeSelect) {
|
|
6229
6661
|
typeSelect.value = '';
|
|
6230
6662
|
}
|
|
@@ -6233,21 +6665,32 @@ function getBlocksFieldScript() {
|
|
|
6233
6665
|
window.initializeStructuredFields();
|
|
6234
6666
|
}
|
|
6235
6667
|
updateHiddenInput();
|
|
6668
|
+
syncBlocksState(container);
|
|
6236
6669
|
return;
|
|
6237
6670
|
}
|
|
6238
6671
|
|
|
6239
6672
|
const item = actionButton.closest('.blocks-item');
|
|
6240
6673
|
if (!item || !list) return;
|
|
6241
6674
|
|
|
6675
|
+
if (action === 'toggle-block') {
|
|
6676
|
+
const content = item.querySelector('[data-block-content]');
|
|
6677
|
+
if (!(content instanceof HTMLElement)) return;
|
|
6678
|
+
setBlockExpanded(item, content.classList.contains('hidden'));
|
|
6679
|
+
syncBlocksState(container);
|
|
6680
|
+
return;
|
|
6681
|
+
}
|
|
6682
|
+
|
|
6242
6683
|
if (action === 'remove-block') {
|
|
6243
6684
|
if (typeof requestRepeaterDelete === 'function') {
|
|
6244
6685
|
requestRepeaterDelete(() => {
|
|
6245
6686
|
item.remove();
|
|
6246
6687
|
updateHiddenInput();
|
|
6688
|
+
syncBlocksState(container);
|
|
6247
6689
|
}, 'block');
|
|
6248
6690
|
} else {
|
|
6249
6691
|
item.remove();
|
|
6250
6692
|
updateHiddenInput();
|
|
6693
|
+
syncBlocksState(container);
|
|
6251
6694
|
}
|
|
6252
6695
|
return;
|
|
6253
6696
|
}
|
|
@@ -6257,6 +6700,7 @@ function getBlocksFieldScript() {
|
|
|
6257
6700
|
if (previous) {
|
|
6258
6701
|
list.insertBefore(item, previous);
|
|
6259
6702
|
updateHiddenInput();
|
|
6703
|
+
syncBlocksState(container);
|
|
6260
6704
|
}
|
|
6261
6705
|
return;
|
|
6262
6706
|
}
|
|
@@ -6266,6 +6710,7 @@ function getBlocksFieldScript() {
|
|
|
6266
6710
|
if (next) {
|
|
6267
6711
|
list.insertBefore(next, item);
|
|
6268
6712
|
updateHiddenInput();
|
|
6713
|
+
syncBlocksState(container);
|
|
6269
6714
|
}
|
|
6270
6715
|
}
|
|
6271
6716
|
});
|
|
@@ -6287,6 +6732,12 @@ function getBlocksFieldScript() {
|
|
|
6287
6732
|
});
|
|
6288
6733
|
|
|
6289
6734
|
updateHiddenInput();
|
|
6735
|
+
const savedBlocksState = readBlocksState(container);
|
|
6736
|
+
if (savedBlocksState) {
|
|
6737
|
+
applyBlocksState(container, savedBlocksState);
|
|
6738
|
+
} else {
|
|
6739
|
+
syncBlocksState(container);
|
|
6740
|
+
}
|
|
6290
6741
|
});
|
|
6291
6742
|
}
|
|
6292
6743
|
|
|
@@ -6323,6 +6774,7 @@ chunkLTKV7AE5_cjs.init_admin_layout_catalyst_template();
|
|
|
6323
6774
|
function renderContentFormPage(data) {
|
|
6324
6775
|
const isEdit = data.isEdit || !!data.id;
|
|
6325
6776
|
const title = isEdit ? `Edit: ${data.title || "Content"}` : `New ${data.collection.display_name}`;
|
|
6777
|
+
const hasValidationErrors = Boolean(data.validationErrors && Object.keys(data.validationErrors).length > 0);
|
|
6326
6778
|
const backUrl = data.referrerParams ? `/admin/content?${data.referrerParams}` : `/admin/content?collection=${data.collection.id}`;
|
|
6327
6779
|
const coreFields = data.fields.filter((f) => ["title", "slug", "content"].includes(f.field_name));
|
|
6328
6780
|
const contentFields = data.fields.filter((f) => !["title", "slug", "content"].includes(f.field_name) && !f.field_name.startsWith("meta_"));
|
|
@@ -6411,6 +6863,7 @@ function renderContentFormPage(data) {
|
|
|
6411
6863
|
${isEdit ? `hx-put="/admin/content/${data.id}"` : `hx-post="/admin/content"`}
|
|
6412
6864
|
hx-target="#form-messages"
|
|
6413
6865
|
hx-encoding="multipart/form-data"
|
|
6866
|
+
data-has-validation-errors="${hasValidationErrors ? "true" : "false"}"
|
|
6414
6867
|
class="space-y-6"
|
|
6415
6868
|
>
|
|
6416
6869
|
<input type="hidden" name="collection_id" value="${data.collection.id}">
|
|
@@ -6691,39 +7144,456 @@ function renderContentFormPage(data) {
|
|
|
6691
7144
|
|
|
6692
7145
|
<!-- Dynamic Field Scripts -->
|
|
6693
7146
|
<script>
|
|
7147
|
+
const contentFormCollectionId = ${JSON.stringify(data.collection.id)};
|
|
7148
|
+
|
|
7149
|
+
function getFieldGroupScope() {
|
|
7150
|
+
const url = new URL(window.location.href);
|
|
7151
|
+
const urlCollectionId = url.searchParams.get('collection');
|
|
7152
|
+
const effectiveCollectionId = urlCollectionId || contentFormCollectionId || '';
|
|
7153
|
+
return window.location.pathname + ':' + effectiveCollectionId;
|
|
7154
|
+
}
|
|
7155
|
+
|
|
7156
|
+
function getItemPosition(itemSelector, item) {
|
|
7157
|
+
if (!(item instanceof Element)) return -1;
|
|
7158
|
+
const parent = item.parentElement;
|
|
7159
|
+
if (!parent) return -1;
|
|
7160
|
+
return Array.from(parent.querySelectorAll(':scope > ' + itemSelector)).indexOf(item);
|
|
7161
|
+
}
|
|
7162
|
+
|
|
7163
|
+
function stripIndexedFieldPrefix(fullFieldName, prefix) {
|
|
7164
|
+
if (!fullFieldName || !prefix || !fullFieldName.startsWith(prefix)) {
|
|
7165
|
+
return fullFieldName;
|
|
7166
|
+
}
|
|
7167
|
+
|
|
7168
|
+
const remainder = fullFieldName.slice(prefix.length);
|
|
7169
|
+
const indexMatch = remainder.match(/^(\\d+)(-|__)(.*)$/);
|
|
7170
|
+
if (!indexMatch) {
|
|
7171
|
+
return fullFieldName;
|
|
7172
|
+
}
|
|
7173
|
+
|
|
7174
|
+
return indexMatch[3];
|
|
7175
|
+
}
|
|
7176
|
+
|
|
7177
|
+
function getFieldGroupStorageKey(groupOrId) {
|
|
7178
|
+
const defaultGroupId = typeof groupOrId === 'string' ? groupOrId : (groupOrId?.getAttribute('data-group-id') || 'unknown');
|
|
7179
|
+
const group = typeof groupOrId === 'string'
|
|
7180
|
+
? document.querySelector('.field-group[data-group-id="' + defaultGroupId + '"]')
|
|
7181
|
+
: groupOrId;
|
|
7182
|
+
|
|
7183
|
+
const scopePrefix = 'sonic:ui:objects:' + getFieldGroupScope() + ':';
|
|
7184
|
+
if (!(group instanceof Element)) {
|
|
7185
|
+
return scopePrefix + defaultGroupId;
|
|
7186
|
+
}
|
|
7187
|
+
|
|
7188
|
+
const fullFieldName = group.getAttribute('data-field-name') || '';
|
|
7189
|
+
|
|
7190
|
+
const blocksField = group.closest('.blocks-field');
|
|
7191
|
+
const blockItem = group.closest('.blocks-item');
|
|
7192
|
+
if (blocksField instanceof Element && blockItem instanceof Element) {
|
|
7193
|
+
const blocksFieldName = blocksField.getAttribute('data-field-name') || 'unknown';
|
|
7194
|
+
const blockPosition = getItemPosition('.blocks-item', blockItem);
|
|
7195
|
+
const relativePath = stripIndexedFieldPrefix(fullFieldName, 'block-' + blocksFieldName + '-') || defaultGroupId;
|
|
7196
|
+
return scopePrefix + 'blocks:' + blocksFieldName + ':' + blockPosition + ':' + relativePath;
|
|
7197
|
+
}
|
|
7198
|
+
|
|
7199
|
+
const arrayField = group.closest('[data-structured-array][data-field-name]');
|
|
7200
|
+
const arrayItem = group.closest('.structured-array-item');
|
|
7201
|
+
if (arrayField instanceof Element && arrayItem instanceof Element) {
|
|
7202
|
+
const arrayFieldName = arrayField.getAttribute('data-field-name') || 'unknown';
|
|
7203
|
+
const itemPosition = getItemPosition('.structured-array-item', arrayItem);
|
|
7204
|
+
const relativePath = stripIndexedFieldPrefix(fullFieldName, 'array-' + arrayFieldName + '-') || defaultGroupId;
|
|
7205
|
+
return scopePrefix + 'repeaters:' + arrayFieldName + ':' + itemPosition + ':' + relativePath;
|
|
7206
|
+
}
|
|
7207
|
+
|
|
7208
|
+
return scopePrefix + defaultGroupId;
|
|
7209
|
+
}
|
|
7210
|
+
|
|
7211
|
+
function loadFieldGroupState(group) {
|
|
7212
|
+
try {
|
|
7213
|
+
const value = sessionStorage.getItem(getFieldGroupStorageKey(group));
|
|
7214
|
+
if (value === '1') return true;
|
|
7215
|
+
if (value === '0') return false;
|
|
7216
|
+
} catch {}
|
|
7217
|
+
return null;
|
|
7218
|
+
}
|
|
7219
|
+
|
|
7220
|
+
function saveFieldGroupState(group, isCollapsed) {
|
|
7221
|
+
try {
|
|
7222
|
+
sessionStorage.setItem(getFieldGroupStorageKey(group), isCollapsed ? '1' : '0');
|
|
7223
|
+
} catch {}
|
|
7224
|
+
}
|
|
7225
|
+
|
|
7226
|
+
function resolveFieldGroupElements(groupOrId) {
|
|
7227
|
+
let group = null;
|
|
7228
|
+
|
|
7229
|
+
if (groupOrId instanceof Element) {
|
|
7230
|
+
group = groupOrId.classList.contains('field-group')
|
|
7231
|
+
? groupOrId
|
|
7232
|
+
: groupOrId.closest('.field-group[data-group-id]');
|
|
7233
|
+
} else if (typeof groupOrId === 'string' && groupOrId) {
|
|
7234
|
+
group = document.querySelector('.field-group[data-group-id="' + groupOrId + '"]');
|
|
7235
|
+
}
|
|
7236
|
+
|
|
7237
|
+
let content = null;
|
|
7238
|
+
let icon = null;
|
|
7239
|
+
|
|
7240
|
+
if (group instanceof Element) {
|
|
7241
|
+
content = group.querySelector(':scope > .field-group-content');
|
|
7242
|
+
icon = group.querySelector(':scope > .field-group-header svg[id$="-icon"]');
|
|
7243
|
+
}
|
|
7244
|
+
|
|
7245
|
+
// Legacy fallback for any existing calls still passing string IDs.
|
|
7246
|
+
if (!(content instanceof HTMLElement) && typeof groupOrId === 'string') {
|
|
7247
|
+
content = document.getElementById(groupOrId + '-content');
|
|
7248
|
+
}
|
|
7249
|
+
if (!(icon instanceof Element) && typeof groupOrId === 'string') {
|
|
7250
|
+
icon = document.getElementById(groupOrId + '-icon');
|
|
7251
|
+
}
|
|
7252
|
+
|
|
7253
|
+
if (!(group instanceof Element) && content instanceof Element) {
|
|
7254
|
+
group = content.closest('.field-group[data-group-id]');
|
|
7255
|
+
}
|
|
7256
|
+
|
|
7257
|
+
return { group, content, icon };
|
|
7258
|
+
}
|
|
7259
|
+
|
|
7260
|
+
function applyFieldGroupState(groupOrId, isCollapsed) {
|
|
7261
|
+
const { content, icon } = resolveFieldGroupElements(groupOrId);
|
|
7262
|
+
if (!(content instanceof HTMLElement) || !(icon instanceof Element)) return;
|
|
7263
|
+
content.classList.toggle('hidden', isCollapsed);
|
|
7264
|
+
icon.classList.toggle('-rotate-90', isCollapsed);
|
|
7265
|
+
}
|
|
7266
|
+
|
|
7267
|
+
function restoreFieldGroupStates() {
|
|
7268
|
+
document.querySelectorAll('.field-group[data-group-id]').forEach((group) => {
|
|
7269
|
+
const savedState = loadFieldGroupState(group);
|
|
7270
|
+
if (savedState === null) return;
|
|
7271
|
+
applyFieldGroupState(group, savedState);
|
|
7272
|
+
});
|
|
7273
|
+
}
|
|
7274
|
+
|
|
7275
|
+
function persistAllFieldGroupStates() {
|
|
7276
|
+
document.querySelectorAll('.field-group[data-group-id]').forEach((group) => {
|
|
7277
|
+
const { content } = resolveFieldGroupElements(group);
|
|
7278
|
+
if (!(content instanceof HTMLElement)) return;
|
|
7279
|
+
saveFieldGroupState(group, content.classList.contains('hidden'));
|
|
7280
|
+
});
|
|
7281
|
+
}
|
|
7282
|
+
|
|
7283
|
+
function setValidationHeaderIndicator(container) {
|
|
7284
|
+
if (!(container instanceof Element)) return;
|
|
7285
|
+
let header = null;
|
|
7286
|
+
let markerTarget = null;
|
|
7287
|
+
|
|
7288
|
+
if (container.classList.contains('field-group')) {
|
|
7289
|
+
header = container.querySelector(':scope > .field-group-header');
|
|
7290
|
+
markerTarget = container.querySelector(':scope > .field-group-header h3');
|
|
7291
|
+
} else if (container.classList.contains('structured-array-item')) {
|
|
7292
|
+
header = container.querySelector('[data-action="toggle-item"]');
|
|
7293
|
+
markerTarget = header;
|
|
7294
|
+
} else if (container.classList.contains('blocks-item')) {
|
|
7295
|
+
header = container.querySelector('[data-action="toggle-block"]');
|
|
7296
|
+
markerTarget = header;
|
|
7297
|
+
}
|
|
7298
|
+
|
|
7299
|
+
if (!(header instanceof HTMLElement)) return;
|
|
7300
|
+
if (!(markerTarget instanceof HTMLElement)) {
|
|
7301
|
+
markerTarget = header;
|
|
7302
|
+
}
|
|
7303
|
+
|
|
7304
|
+
header.dataset.validationHeaderError = 'true';
|
|
7305
|
+
header.classList.add('text-pink-700', 'dark:text-pink-300');
|
|
7306
|
+
|
|
7307
|
+
if (!markerTarget.querySelector('[data-validation-indicator]')) {
|
|
7308
|
+
const marker = document.createElement('span');
|
|
7309
|
+
marker.setAttribute('data-validation-indicator', 'true');
|
|
7310
|
+
marker.className = 'ml-2 inline-block h-2 w-2 rounded-full bg-pink-500 align-middle';
|
|
7311
|
+
marker.setAttribute('aria-hidden', 'true');
|
|
7312
|
+
markerTarget.appendChild(marker);
|
|
7313
|
+
}
|
|
7314
|
+
}
|
|
7315
|
+
|
|
7316
|
+
function clearValidationIndicators() {
|
|
7317
|
+
document.querySelectorAll('[data-validation-header-error="true"]').forEach((el) => {
|
|
7318
|
+
if (!(el instanceof HTMLElement)) return;
|
|
7319
|
+
delete el.dataset.validationHeaderError;
|
|
7320
|
+
el.classList.remove('text-pink-700', 'dark:text-pink-300');
|
|
7321
|
+
});
|
|
7322
|
+
|
|
7323
|
+
document.querySelectorAll('[data-validation-indicator]').forEach((el) => el.remove());
|
|
7324
|
+
}
|
|
7325
|
+
|
|
7326
|
+
function expandContainerForValidation(container) {
|
|
7327
|
+
if (!(container instanceof Element)) return;
|
|
7328
|
+
|
|
7329
|
+
if (container.classList.contains('field-group')) {
|
|
7330
|
+
applyFieldGroupState(container, false);
|
|
7331
|
+
return;
|
|
7332
|
+
}
|
|
7333
|
+
|
|
7334
|
+
if (container.classList.contains('structured-array-item')) {
|
|
7335
|
+
const content = container.querySelector('[data-array-item-fields]');
|
|
7336
|
+
const icon = container.querySelector('[data-item-toggle-icon]');
|
|
7337
|
+
if (content instanceof HTMLElement) {
|
|
7338
|
+
content.classList.remove('hidden');
|
|
7339
|
+
}
|
|
7340
|
+
if (icon instanceof Element) {
|
|
7341
|
+
icon.classList.remove('-rotate-90');
|
|
7342
|
+
}
|
|
7343
|
+
return;
|
|
7344
|
+
}
|
|
7345
|
+
|
|
7346
|
+
if (container.classList.contains('blocks-item')) {
|
|
7347
|
+
const content = container.querySelector('[data-block-content]');
|
|
7348
|
+
const icon = container.querySelector('[data-block-toggle-icon]');
|
|
7349
|
+
if (content instanceof HTMLElement) {
|
|
7350
|
+
content.classList.remove('hidden');
|
|
7351
|
+
}
|
|
7352
|
+
if (icon instanceof Element) {
|
|
7353
|
+
icon.classList.remove('-rotate-90');
|
|
7354
|
+
}
|
|
7355
|
+
}
|
|
7356
|
+
}
|
|
7357
|
+
|
|
7358
|
+
function walkErrorContainers(node, expand) {
|
|
7359
|
+
if (!(node instanceof Element)) return;
|
|
7360
|
+
const visited = new Set();
|
|
7361
|
+
let cursor = node;
|
|
7362
|
+
while (cursor) {
|
|
7363
|
+
const candidates = [
|
|
7364
|
+
cursor.closest('.structured-array-item'),
|
|
7365
|
+
cursor.closest('.blocks-item'),
|
|
7366
|
+
cursor.closest('.field-group[data-group-id]')
|
|
7367
|
+
].filter((c) => c instanceof Element && !visited.has(c));
|
|
7368
|
+
|
|
7369
|
+
if (candidates.length === 0) break;
|
|
7370
|
+
|
|
7371
|
+
// Pick nearest ancestor container to preserve "first-error path only".
|
|
7372
|
+
let nearest = candidates[0];
|
|
7373
|
+
let bestDistance = Number.MAX_SAFE_INTEGER;
|
|
7374
|
+
for (const candidate of candidates) {
|
|
7375
|
+
let distance = 0;
|
|
7376
|
+
let walker = cursor;
|
|
7377
|
+
while (walker && walker !== candidate) {
|
|
7378
|
+
walker = walker.parentElement;
|
|
7379
|
+
distance += 1;
|
|
7380
|
+
}
|
|
7381
|
+
if (distance < bestDistance) {
|
|
7382
|
+
bestDistance = distance;
|
|
7383
|
+
nearest = candidate;
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
|
|
7387
|
+
visited.add(nearest);
|
|
7388
|
+
setValidationHeaderIndicator(nearest);
|
|
7389
|
+
if (expand) {
|
|
7390
|
+
expandContainerForValidation(nearest);
|
|
7391
|
+
}
|
|
7392
|
+
cursor = nearest.parentElement;
|
|
7393
|
+
}
|
|
7394
|
+
}
|
|
7395
|
+
|
|
7396
|
+
function getFocusableTargetFromErrorGroup(group) {
|
|
7397
|
+
if (!(group instanceof Element)) return null;
|
|
7398
|
+
return (
|
|
7399
|
+
group.querySelector('input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"]') ||
|
|
7400
|
+
group.querySelector('button:not([disabled])')
|
|
7401
|
+
);
|
|
7402
|
+
}
|
|
7403
|
+
|
|
7404
|
+
function revealServerValidationErrors() {
|
|
7405
|
+
clearValidationIndicators();
|
|
7406
|
+
|
|
7407
|
+
const errorGroups = Array.from(document.querySelectorAll('.form-group[data-has-errors="true"]'));
|
|
7408
|
+
if (errorGroups.length === 0) return;
|
|
7409
|
+
|
|
7410
|
+
// Add indicators for all errored sections, expand only first-error path.
|
|
7411
|
+
errorGroups.forEach((group, index) => {
|
|
7412
|
+
walkErrorContainers(group, index === 0);
|
|
7413
|
+
});
|
|
7414
|
+
|
|
7415
|
+
const firstTarget = getFocusableTargetFromErrorGroup(errorGroups[0]);
|
|
7416
|
+
if (firstTarget instanceof HTMLElement) {
|
|
7417
|
+
firstTarget.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
7418
|
+
firstTarget.focus({ preventScroll: true });
|
|
7419
|
+
}
|
|
7420
|
+
}
|
|
7421
|
+
|
|
7422
|
+
function revealNativeValidationErrors(form) {
|
|
7423
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
7424
|
+
clearValidationIndicators();
|
|
7425
|
+
|
|
7426
|
+
const invalidControls = Array.from(form.querySelectorAll(':invalid'));
|
|
7427
|
+
if (invalidControls.length === 0) return;
|
|
7428
|
+
|
|
7429
|
+
invalidControls.forEach((control, index) => {
|
|
7430
|
+
walkErrorContainers(control, index === 0);
|
|
7431
|
+
});
|
|
7432
|
+
|
|
7433
|
+
const first = invalidControls[0];
|
|
7434
|
+
if (first instanceof HTMLElement) {
|
|
7435
|
+
first.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
7436
|
+
first.focus({ preventScroll: true });
|
|
7437
|
+
}
|
|
7438
|
+
}
|
|
7439
|
+
|
|
6694
7440
|
// Field group toggle
|
|
6695
|
-
function toggleFieldGroup(
|
|
6696
|
-
const content =
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
7441
|
+
function toggleFieldGroup(groupOrTrigger) {
|
|
7442
|
+
const { group, content } = resolveFieldGroupElements(groupOrTrigger);
|
|
7443
|
+
if (!(group instanceof Element)) return;
|
|
7444
|
+
if (!(content instanceof HTMLElement)) return;
|
|
7445
|
+
|
|
7446
|
+
const isCollapsed = !content.classList.contains('hidden');
|
|
7447
|
+
applyFieldGroupState(group, isCollapsed);
|
|
7448
|
+
saveFieldGroupState(group, isCollapsed);
|
|
7449
|
+
}
|
|
7450
|
+
|
|
7451
|
+
if (document.readyState === 'loading') {
|
|
7452
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
7453
|
+
restoreFieldGroupStates();
|
|
7454
|
+
const form = document.getElementById('content-form');
|
|
7455
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7456
|
+
revealServerValidationErrors();
|
|
7457
|
+
}
|
|
7458
|
+
});
|
|
7459
|
+
} else {
|
|
7460
|
+
restoreFieldGroupStates();
|
|
7461
|
+
const form = document.getElementById('content-form');
|
|
7462
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7463
|
+
revealServerValidationErrors();
|
|
6705
7464
|
}
|
|
6706
7465
|
}
|
|
6707
7466
|
|
|
7467
|
+
document.addEventListener('htmx:afterSwap', function() {
|
|
7468
|
+
setTimeout(() => {
|
|
7469
|
+
restoreFieldGroupStates();
|
|
7470
|
+
const form = document.getElementById('content-form');
|
|
7471
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7472
|
+
revealServerValidationErrors();
|
|
7473
|
+
}
|
|
7474
|
+
}, 50);
|
|
7475
|
+
});
|
|
7476
|
+
|
|
7477
|
+
const contentFormEl = document.getElementById('content-form');
|
|
7478
|
+
if (contentFormEl instanceof HTMLFormElement) {
|
|
7479
|
+
contentFormEl.addEventListener('submit', () => {
|
|
7480
|
+
persistAllFieldGroupStates();
|
|
7481
|
+
}, true);
|
|
7482
|
+
}
|
|
7483
|
+
|
|
7484
|
+
window.addEventListener('beforeunload', () => {
|
|
7485
|
+
persistAllFieldGroupStates();
|
|
7486
|
+
});
|
|
7487
|
+
|
|
7488
|
+
document.addEventListener('visibilitychange', () => {
|
|
7489
|
+
if (document.visibilityState === 'hidden') {
|
|
7490
|
+
persistAllFieldGroupStates();
|
|
7491
|
+
}
|
|
7492
|
+
});
|
|
7493
|
+
|
|
7494
|
+
let pendingNativeValidationReveal = false;
|
|
7495
|
+
document.addEventListener('invalid', function(event) {
|
|
7496
|
+
const target = event.target;
|
|
7497
|
+
if (!(target instanceof Element)) return;
|
|
7498
|
+
const form = target.closest('form');
|
|
7499
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
7500
|
+
|
|
7501
|
+
if (pendingNativeValidationReveal) return;
|
|
7502
|
+
pendingNativeValidationReveal = true;
|
|
7503
|
+
|
|
7504
|
+
// Expand only first invalid path synchronously so the browser can focus it
|
|
7505
|
+
// and avoid "invalid form control is not focusable" errors.
|
|
7506
|
+
walkErrorContainers(target, true);
|
|
7507
|
+
|
|
7508
|
+
setTimeout(() => {
|
|
7509
|
+
pendingNativeValidationReveal = false;
|
|
7510
|
+
revealNativeValidationErrors(form);
|
|
7511
|
+
}, 0);
|
|
7512
|
+
}, true);
|
|
7513
|
+
|
|
6708
7514
|
// Media field functions
|
|
6709
|
-
|
|
7515
|
+
function notifyFieldChange(input) {
|
|
7516
|
+
if (!input) return;
|
|
7517
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
7518
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
7519
|
+
}
|
|
7520
|
+
|
|
7521
|
+
function getActiveMediaModal() {
|
|
7522
|
+
const modal = document.getElementById('media-selector-modal');
|
|
7523
|
+
return modal instanceof HTMLElement ? modal : null;
|
|
7524
|
+
}
|
|
7525
|
+
|
|
7526
|
+
function getMediaFieldElements(fieldId) {
|
|
7527
|
+
if (!fieldId) {
|
|
7528
|
+
return {
|
|
7529
|
+
fieldId: '',
|
|
7530
|
+
hiddenInput: null,
|
|
7531
|
+
preview: null,
|
|
7532
|
+
mediaField: null,
|
|
7533
|
+
actionsDiv: null,
|
|
7534
|
+
};
|
|
7535
|
+
}
|
|
7536
|
+
|
|
7537
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
7538
|
+
const preview = document.getElementById(fieldId + '-preview');
|
|
7539
|
+
const mediaField = hiddenInput?.closest('.media-field-container') || null;
|
|
7540
|
+
const actionsDiv = mediaField?.querySelector('.media-actions') || null;
|
|
7541
|
+
|
|
7542
|
+
return {
|
|
7543
|
+
fieldId,
|
|
7544
|
+
hiddenInput,
|
|
7545
|
+
preview,
|
|
7546
|
+
mediaField,
|
|
7547
|
+
actionsDiv,
|
|
7548
|
+
};
|
|
7549
|
+
}
|
|
7550
|
+
|
|
7551
|
+
function getActiveMediaTarget() {
|
|
7552
|
+
const modal = getActiveMediaModal();
|
|
7553
|
+
const fieldId = modal?.dataset.targetFieldId || '';
|
|
7554
|
+
return {
|
|
7555
|
+
modal,
|
|
7556
|
+
originalValue: modal?.dataset.originalValue || '',
|
|
7557
|
+
...getMediaFieldElements(fieldId),
|
|
7558
|
+
};
|
|
7559
|
+
}
|
|
7560
|
+
|
|
7561
|
+
function ensureSingleMediaRemoveButton(fieldId, actionsDiv) {
|
|
7562
|
+
if (!(actionsDiv instanceof HTMLElement)) return;
|
|
7563
|
+
const existingRemoveButton = actionsDiv.querySelector('[data-media-remove="true"]');
|
|
7564
|
+
if (existingRemoveButton) return;
|
|
7565
|
+
|
|
7566
|
+
const removeBtn = document.createElement('button');
|
|
7567
|
+
removeBtn.type = 'button';
|
|
7568
|
+
removeBtn.setAttribute('data-media-remove', 'true');
|
|
7569
|
+
removeBtn.onclick = () => clearMediaField(fieldId);
|
|
7570
|
+
removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
|
|
7571
|
+
removeBtn.textContent = 'Remove';
|
|
7572
|
+
actionsDiv.appendChild(removeBtn);
|
|
7573
|
+
}
|
|
6710
7574
|
|
|
6711
7575
|
function openMediaSelector(fieldId) {
|
|
6712
|
-
|
|
7576
|
+
const existingModal = getActiveMediaModal();
|
|
7577
|
+
if (existingModal) {
|
|
7578
|
+
existingModal.remove();
|
|
7579
|
+
}
|
|
7580
|
+
|
|
6713
7581
|
// Store the original value in case user cancels
|
|
6714
|
-
const originalValue =
|
|
7582
|
+
const originalValue = getMediaFieldElements(fieldId).hiddenInput?.value || '';
|
|
6715
7583
|
|
|
6716
7584
|
// Open media library modal
|
|
6717
7585
|
const modal = document.createElement('div');
|
|
6718
7586
|
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50';
|
|
6719
7587
|
modal.id = 'media-selector-modal';
|
|
7588
|
+
modal.dataset.targetFieldId = fieldId;
|
|
7589
|
+
modal.dataset.originalValue = originalValue;
|
|
6720
7590
|
modal.innerHTML = \`
|
|
6721
7591
|
<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">
|
|
6722
7592
|
<h3 class="text-lg font-semibold text-zinc-950 dark:text-white mb-4">Select Media</h3>
|
|
6723
7593
|
<div id="media-grid-container" hx-get="/admin/media/selector" hx-trigger="load"></div>
|
|
6724
7594
|
<div class="mt-4 flex justify-end space-x-2">
|
|
6725
7595
|
<button
|
|
6726
|
-
onclick="cancelMediaSelection(
|
|
7596
|
+
onclick="cancelMediaSelection()"
|
|
6727
7597
|
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">
|
|
6728
7598
|
Cancel
|
|
6729
7599
|
</button>
|
|
@@ -6743,23 +7613,23 @@ function renderContentFormPage(data) {
|
|
|
6743
7613
|
}
|
|
6744
7614
|
|
|
6745
7615
|
function closeMediaSelector() {
|
|
6746
|
-
const modal =
|
|
7616
|
+
const modal = getActiveMediaModal();
|
|
6747
7617
|
if (modal) {
|
|
6748
7618
|
modal.remove();
|
|
6749
7619
|
}
|
|
6750
|
-
currentMediaFieldId = null;
|
|
6751
7620
|
}
|
|
6752
7621
|
|
|
6753
|
-
function cancelMediaSelection(
|
|
7622
|
+
function cancelMediaSelection() {
|
|
7623
|
+
const { hiddenInput, preview, originalValue } = getActiveMediaTarget();
|
|
7624
|
+
|
|
6754
7625
|
// Restore original value
|
|
6755
|
-
const hiddenInput = document.getElementById(fieldId);
|
|
6756
7626
|
if (hiddenInput) {
|
|
6757
7627
|
hiddenInput.value = originalValue;
|
|
7628
|
+
notifyFieldChange(hiddenInput);
|
|
6758
7629
|
}
|
|
6759
7630
|
|
|
6760
7631
|
// If original value was empty, hide the preview and show select button
|
|
6761
7632
|
if (!originalValue) {
|
|
6762
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6763
7633
|
if (preview) {
|
|
6764
7634
|
preview.classList.add('hidden');
|
|
6765
7635
|
}
|
|
@@ -6770,11 +7640,11 @@ function renderContentFormPage(data) {
|
|
|
6770
7640
|
}
|
|
6771
7641
|
|
|
6772
7642
|
function clearMediaField(fieldId) {
|
|
6773
|
-
const hiddenInput =
|
|
6774
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
7643
|
+
const { hiddenInput, preview, actionsDiv } = getMediaFieldElements(fieldId);
|
|
6775
7644
|
|
|
6776
7645
|
if (hiddenInput) {
|
|
6777
7646
|
hiddenInput.value = '';
|
|
7647
|
+
notifyFieldChange(hiddenInput);
|
|
6778
7648
|
}
|
|
6779
7649
|
|
|
6780
7650
|
if (preview) {
|
|
@@ -6784,25 +7654,34 @@ function renderContentFormPage(data) {
|
|
|
6784
7654
|
}
|
|
6785
7655
|
preview.classList.add('hidden');
|
|
6786
7656
|
}
|
|
7657
|
+
|
|
7658
|
+
const removeButton = actionsDiv?.querySelector('[data-media-remove="true"]');
|
|
7659
|
+
if (removeButton) {
|
|
7660
|
+
removeButton.remove();
|
|
7661
|
+
}
|
|
6787
7662
|
}
|
|
6788
7663
|
|
|
6789
7664
|
// Global function to remove a single media from multiple selection
|
|
6790
7665
|
window.removeMediaFromMultiple = function(fieldId, urlToRemove) {
|
|
6791
|
-
const hiddenInput =
|
|
7666
|
+
const { hiddenInput, preview } = getMediaFieldElements(fieldId);
|
|
6792
7667
|
if (!hiddenInput) return;
|
|
6793
7668
|
|
|
6794
7669
|
const values = hiddenInput.value.split(',').filter(url => url !== urlToRemove);
|
|
6795
7670
|
hiddenInput.value = values.join(',');
|
|
7671
|
+
notifyFieldChange(hiddenInput);
|
|
6796
7672
|
|
|
6797
7673
|
// Remove preview item
|
|
6798
|
-
const previewItem =
|
|
7674
|
+
const previewItem =
|
|
7675
|
+
preview &&
|
|
7676
|
+
Array.from(preview.querySelectorAll('[data-url]')).find(
|
|
7677
|
+
(item) => item.getAttribute('data-url') === urlToRemove,
|
|
7678
|
+
);
|
|
6799
7679
|
if (previewItem) {
|
|
6800
7680
|
previewItem.remove();
|
|
6801
7681
|
}
|
|
6802
7682
|
|
|
6803
7683
|
// Hide preview grid if empty
|
|
6804
7684
|
if (values.length === 0) {
|
|
6805
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6806
7685
|
if (preview) {
|
|
6807
7686
|
preview.classList.add('hidden');
|
|
6808
7687
|
}
|
|
@@ -6811,39 +7690,24 @@ function renderContentFormPage(data) {
|
|
|
6811
7690
|
|
|
6812
7691
|
// Global function called by media selector buttons
|
|
6813
7692
|
window.selectMediaFile = function(mediaId, mediaUrl, filename) {
|
|
6814
|
-
|
|
7693
|
+
const { fieldId, hiddenInput, preview, actionsDiv } = getActiveMediaTarget();
|
|
7694
|
+
if (!fieldId || !hiddenInput) {
|
|
6815
7695
|
console.error('No field ID set for media selection');
|
|
6816
7696
|
return;
|
|
6817
7697
|
}
|
|
6818
7698
|
|
|
6819
|
-
const fieldId = currentMediaFieldId;
|
|
6820
|
-
|
|
6821
7699
|
// Set the hidden input value to the media URL (not ID)
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
hiddenInput.value = mediaUrl;
|
|
6825
|
-
}
|
|
7700
|
+
hiddenInput.value = mediaUrl;
|
|
7701
|
+
notifyFieldChange(hiddenInput);
|
|
6826
7702
|
|
|
6827
7703
|
// Update the preview
|
|
6828
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6829
7704
|
if (preview) {
|
|
6830
7705
|
preview.innerHTML = \`<img src="\${mediaUrl}" alt="\${filename}" class="w-32 h-32 object-cover rounded-lg border border-white/20">\`;
|
|
6831
7706
|
preview.classList.remove('hidden');
|
|
6832
7707
|
}
|
|
6833
7708
|
|
|
6834
7709
|
// Show the remove button by finding the media actions container and updating it
|
|
6835
|
-
|
|
6836
|
-
if (mediaField) {
|
|
6837
|
-
const actionsDiv = mediaField.querySelector('.media-actions');
|
|
6838
|
-
if (actionsDiv && !actionsDiv.querySelector('button:has-text("Remove")')) {
|
|
6839
|
-
const removeBtn = document.createElement('button');
|
|
6840
|
-
removeBtn.type = 'button';
|
|
6841
|
-
removeBtn.onclick = () => clearMediaField(fieldId);
|
|
6842
|
-
removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
|
|
6843
|
-
removeBtn.textContent = 'Remove';
|
|
6844
|
-
actionsDiv.appendChild(removeBtn);
|
|
6845
|
-
}
|
|
6846
|
-
}
|
|
7710
|
+
ensureSingleMediaRemoveButton(fieldId, actionsDiv);
|
|
6847
7711
|
|
|
6848
7712
|
// DON'T close the modal - let user click OK button
|
|
6849
7713
|
// Visual feedback: highlight the selected item
|
|
@@ -6857,7 +7721,9 @@ function renderContentFormPage(data) {
|
|
|
6857
7721
|
};
|
|
6858
7722
|
|
|
6859
7723
|
function setMediaField(fieldId, mediaUrl) {
|
|
6860
|
-
document.getElementById(fieldId)
|
|
7724
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
7725
|
+
hiddenInput.value = mediaUrl;
|
|
7726
|
+
notifyFieldChange(hiddenInput);
|
|
6861
7727
|
const preview = document.getElementById(fieldId + '-preview');
|
|
6862
7728
|
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">\`;
|
|
6863
7729
|
preview.classList.remove('hidden');
|
|
@@ -8304,9 +9170,9 @@ function parseFieldValue(field, formData, options = {}) {
|
|
|
8304
9170
|
const { skipValidation = false } = options;
|
|
8305
9171
|
const value = formData.get(field.field_name);
|
|
8306
9172
|
const errors = [];
|
|
8307
|
-
const blocksConfig =
|
|
9173
|
+
const blocksConfig = chunkE2GKK5HX_cjs.getBlocksFieldConfig(field.field_options);
|
|
8308
9174
|
if (blocksConfig) {
|
|
8309
|
-
const parsed =
|
|
9175
|
+
const parsed = chunkE2GKK5HX_cjs.parseBlocksValue(value, blocksConfig);
|
|
8310
9176
|
if (!skipValidation && field.is_required && parsed.value.length === 0) {
|
|
8311
9177
|
parsed.errors.push(`${field.field_label} is required`);
|
|
8312
9178
|
}
|
|
@@ -8416,9 +9282,9 @@ function extractFieldData(fields, formData, options = {}) {
|
|
|
8416
9282
|
}
|
|
8417
9283
|
return { data, errors };
|
|
8418
9284
|
}
|
|
8419
|
-
adminContentRoutes.use("*",
|
|
9285
|
+
adminContentRoutes.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
8420
9286
|
async function getCollectionFields(db, collectionId) {
|
|
8421
|
-
const cache =
|
|
9287
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.collection);
|
|
8422
9288
|
return cache.getOrSet(
|
|
8423
9289
|
cache.generateKey("fields", collectionId),
|
|
8424
9290
|
async () => {
|
|
@@ -8467,7 +9333,7 @@ async function getCollectionFields(db, collectionId) {
|
|
|
8467
9333
|
);
|
|
8468
9334
|
}
|
|
8469
9335
|
async function getCollection(db, collectionId) {
|
|
8470
|
-
const cache =
|
|
9336
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.collection);
|
|
8471
9337
|
return cache.getOrSet(
|
|
8472
9338
|
cache.generateKey("collection", collectionId),
|
|
8473
9339
|
async () => {
|
|
@@ -8495,7 +9361,7 @@ adminContentRoutes.get("/", async (c) => {
|
|
|
8495
9361
|
const status = url.searchParams.get("status") || "all";
|
|
8496
9362
|
const search = url.searchParams.get("search") || "";
|
|
8497
9363
|
const offset = (page - 1) * limit;
|
|
8498
|
-
const collectionsStmt = db.prepare("SELECT id, name, display_name FROM collections WHERE is_active = 1 ORDER BY display_name");
|
|
9364
|
+
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");
|
|
8499
9365
|
const { results: collectionsResults } = await collectionsStmt.all();
|
|
8500
9366
|
const models = (collectionsResults || []).map((row) => ({
|
|
8501
9367
|
name: row.name,
|
|
@@ -8503,6 +9369,7 @@ adminContentRoutes.get("/", async (c) => {
|
|
|
8503
9369
|
}));
|
|
8504
9370
|
const conditions = [];
|
|
8505
9371
|
const params = [];
|
|
9372
|
+
conditions.push("(col.source_type IS NULL OR col.source_type = 'user')");
|
|
8506
9373
|
if (status !== "deleted") {
|
|
8507
9374
|
conditions.push("c.status != 'deleted'");
|
|
8508
9375
|
}
|
|
@@ -8631,7 +9498,7 @@ adminContentRoutes.get("/new", async (c) => {
|
|
|
8631
9498
|
const collectionId = url.searchParams.get("collection");
|
|
8632
9499
|
if (!collectionId) {
|
|
8633
9500
|
const db2 = c.env.DB;
|
|
8634
|
-
const collectionsStmt = db2.prepare("SELECT id, name, display_name, description FROM collections WHERE is_active = 1 ORDER BY display_name");
|
|
9501
|
+
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");
|
|
8635
9502
|
const { results } = await collectionsStmt.all();
|
|
8636
9503
|
const collections = (results || []).map((row) => ({
|
|
8637
9504
|
id: row.id,
|
|
@@ -8692,21 +9559,21 @@ adminContentRoutes.get("/new", async (c) => {
|
|
|
8692
9559
|
const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
|
|
8693
9560
|
let tinymceSettings;
|
|
8694
9561
|
if (tinymceEnabled) {
|
|
8695
|
-
const pluginService = new
|
|
9562
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
8696
9563
|
const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
|
|
8697
9564
|
tinymceSettings = tinymcePlugin2?.settings;
|
|
8698
9565
|
}
|
|
8699
9566
|
const quillEnabled = await isPluginActive2(db, "quill-editor");
|
|
8700
9567
|
let quillSettings;
|
|
8701
9568
|
if (quillEnabled) {
|
|
8702
|
-
const pluginService = new
|
|
9569
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
8703
9570
|
const quillPlugin = await pluginService.getPlugin("quill-editor");
|
|
8704
9571
|
quillSettings = quillPlugin?.settings;
|
|
8705
9572
|
}
|
|
8706
9573
|
const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
|
|
8707
9574
|
let mdxeditorSettings;
|
|
8708
9575
|
if (mdxeditorEnabled) {
|
|
8709
|
-
const pluginService = new
|
|
9576
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
8710
9577
|
const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
|
|
8711
9578
|
mdxeditorSettings = mdxeditorPlugin?.settings;
|
|
8712
9579
|
}
|
|
@@ -8756,7 +9623,7 @@ adminContentRoutes.get("/:id/edit", async (c) => {
|
|
|
8756
9623
|
const db = c.env.DB;
|
|
8757
9624
|
const url = new URL(c.req.url);
|
|
8758
9625
|
const referrerParams = url.searchParams.get("ref") || "";
|
|
8759
|
-
const cache =
|
|
9626
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.content);
|
|
8760
9627
|
const content = await cache.getOrSet(
|
|
8761
9628
|
cache.generateKey("content", id),
|
|
8762
9629
|
async () => {
|
|
@@ -8797,21 +9664,21 @@ adminContentRoutes.get("/:id/edit", async (c) => {
|
|
|
8797
9664
|
const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
|
|
8798
9665
|
let tinymceSettings;
|
|
8799
9666
|
if (tinymceEnabled) {
|
|
8800
|
-
const pluginService = new
|
|
9667
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
8801
9668
|
const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
|
|
8802
9669
|
tinymceSettings = tinymcePlugin2?.settings;
|
|
8803
9670
|
}
|
|
8804
9671
|
const quillEnabled = await isPluginActive2(db, "quill-editor");
|
|
8805
9672
|
let quillSettings;
|
|
8806
9673
|
if (quillEnabled) {
|
|
8807
|
-
const pluginService = new
|
|
9674
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
8808
9675
|
const quillPlugin = await pluginService.getPlugin("quill-editor");
|
|
8809
9676
|
quillSettings = quillPlugin?.settings;
|
|
8810
9677
|
}
|
|
8811
9678
|
const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
|
|
8812
9679
|
let mdxeditorSettings;
|
|
8813
9680
|
if (mdxeditorEnabled) {
|
|
8814
|
-
const pluginService = new
|
|
9681
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
8815
9682
|
const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
|
|
8816
9683
|
mdxeditorSettings = mdxeditorPlugin?.settings;
|
|
8817
9684
|
}
|
|
@@ -8932,7 +9799,7 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
8932
9799
|
now,
|
|
8933
9800
|
now
|
|
8934
9801
|
).run();
|
|
8935
|
-
const cache =
|
|
9802
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.content);
|
|
8936
9803
|
await cache.invalidate(`content:list:${collectionId}:*`);
|
|
8937
9804
|
const versionStmt = db.prepare(`
|
|
8938
9805
|
INSERT INTO content_versions (id, content_id, version, data, author_id, created_at)
|
|
@@ -9051,7 +9918,7 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
9051
9918
|
now,
|
|
9052
9919
|
id
|
|
9053
9920
|
).run();
|
|
9054
|
-
const cache =
|
|
9921
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.content);
|
|
9055
9922
|
await cache.delete(cache.generateKey("content", id));
|
|
9056
9923
|
await cache.invalidate(`content:list:${existingContent.collection_id}:*`);
|
|
9057
9924
|
const existingData = JSON.parse(existingContent.data || "{}");
|
|
@@ -9106,7 +9973,7 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
9106
9973
|
`);
|
|
9107
9974
|
}
|
|
9108
9975
|
});
|
|
9109
|
-
adminContentRoutes.post("/preview",
|
|
9976
|
+
adminContentRoutes.post("/preview", chunk5GO3AMON_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
|
|
9110
9977
|
try {
|
|
9111
9978
|
const formData = await c.req.formData();
|
|
9112
9979
|
const collectionId = formData.get("collection_id");
|
|
@@ -9328,7 +10195,7 @@ adminContentRoutes.post("/bulk-action", async (c) => {
|
|
|
9328
10195
|
} else {
|
|
9329
10196
|
return c.json({ success: false, error: "Invalid action" });
|
|
9330
10197
|
}
|
|
9331
|
-
const cache =
|
|
10198
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.content);
|
|
9332
10199
|
for (const contentId of ids) {
|
|
9333
10200
|
await cache.delete(cache.generateKey("content", contentId));
|
|
9334
10201
|
}
|
|
@@ -9356,7 +10223,7 @@ adminContentRoutes.delete("/:id", async (c) => {
|
|
|
9356
10223
|
WHERE id = ?
|
|
9357
10224
|
`);
|
|
9358
10225
|
await deleteStmt.bind(now, id).run();
|
|
9359
|
-
const cache =
|
|
10226
|
+
const cache = chunkLFAQUR7P_cjs.getCacheService(chunkLFAQUR7P_cjs.CACHE_CONFIGS.content);
|
|
9360
10227
|
await cache.delete(cache.generateKey("content", id));
|
|
9361
10228
|
await cache.invalidate("content:list:*");
|
|
9362
10229
|
return c.html(`
|
|
@@ -9484,7 +10351,7 @@ adminContentRoutes.post("/:id/restore/:version", async (c) => {
|
|
|
9484
10351
|
return c.json({ success: false, error: "Failed to restore version" });
|
|
9485
10352
|
}
|
|
9486
10353
|
});
|
|
9487
|
-
adminContentRoutes.get("/:id/version/:version/preview",
|
|
10354
|
+
adminContentRoutes.get("/:id/version/:version/preview", chunk5GO3AMON_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
|
|
9488
10355
|
try {
|
|
9489
10356
|
const id = c.req.param("id");
|
|
9490
10357
|
const version = parseInt(c.req.param("version") || "0");
|
|
@@ -11451,7 +12318,14 @@ function renderUsersListPage(data) {
|
|
|
11451
12318
|
|
|
11452
12319
|
// src/routes/admin-users.ts
|
|
11453
12320
|
var userRoutes = new hono.Hono();
|
|
11454
|
-
userRoutes.use("*",
|
|
12321
|
+
userRoutes.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
12322
|
+
userRoutes.use("/users/*", chunk5GO3AMON_cjs.requireRole(["admin"]));
|
|
12323
|
+
userRoutes.use("/users", chunk5GO3AMON_cjs.requireRole(["admin"]));
|
|
12324
|
+
userRoutes.use("/invite-user", chunk5GO3AMON_cjs.requireRole(["admin"]));
|
|
12325
|
+
userRoutes.use("/resend-invitation/*", chunk5GO3AMON_cjs.requireRole(["admin"]));
|
|
12326
|
+
userRoutes.use("/cancel-invitation/*", chunk5GO3AMON_cjs.requireRole(["admin"]));
|
|
12327
|
+
userRoutes.use("/activity-logs", chunk5GO3AMON_cjs.requireRole(["admin"]));
|
|
12328
|
+
userRoutes.use("/activity-logs/*", chunk5GO3AMON_cjs.requireRole(["admin"]));
|
|
11455
12329
|
userRoutes.get("/", (c) => {
|
|
11456
12330
|
return c.redirect("/admin/dashboard");
|
|
11457
12331
|
});
|
|
@@ -11606,7 +12480,7 @@ userRoutes.put("/profile", async (c) => {
|
|
|
11606
12480
|
Date.now(),
|
|
11607
12481
|
user.userId
|
|
11608
12482
|
).run();
|
|
11609
|
-
await
|
|
12483
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
11610
12484
|
db,
|
|
11611
12485
|
user.userId,
|
|
11612
12486
|
"profile.update",
|
|
@@ -11669,7 +12543,7 @@ userRoutes.post("/profile/avatar", async (c) => {
|
|
|
11669
12543
|
SELECT first_name, last_name FROM users WHERE id = ?
|
|
11670
12544
|
`);
|
|
11671
12545
|
const userData = await userStmt.bind(user.userId).first();
|
|
11672
|
-
await
|
|
12546
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
11673
12547
|
db,
|
|
11674
12548
|
user.userId,
|
|
11675
12549
|
"profile.avatar_update",
|
|
@@ -11740,7 +12614,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
11740
12614
|
dismissible: true
|
|
11741
12615
|
}));
|
|
11742
12616
|
}
|
|
11743
|
-
const validPassword = await
|
|
12617
|
+
const validPassword = await chunk5GO3AMON_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
|
|
11744
12618
|
if (!validPassword) {
|
|
11745
12619
|
return c.html(renderAlert2({
|
|
11746
12620
|
type: "error",
|
|
@@ -11748,7 +12622,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
11748
12622
|
dismissible: true
|
|
11749
12623
|
}));
|
|
11750
12624
|
}
|
|
11751
|
-
const newPasswordHash = await
|
|
12625
|
+
const newPasswordHash = await chunk5GO3AMON_cjs.AuthManager.hashPassword(newPassword);
|
|
11752
12626
|
const historyStmt = db.prepare(`
|
|
11753
12627
|
INSERT INTO password_history (id, user_id, password_hash, created_at)
|
|
11754
12628
|
VALUES (?, ?, ?, ?)
|
|
@@ -11764,7 +12638,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
11764
12638
|
WHERE id = ?
|
|
11765
12639
|
`);
|
|
11766
12640
|
await updateStmt.bind(newPasswordHash, Date.now(), user.userId).run();
|
|
11767
|
-
await
|
|
12641
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
11768
12642
|
db,
|
|
11769
12643
|
user.userId,
|
|
11770
12644
|
"profile.password_change",
|
|
@@ -11831,7 +12705,7 @@ userRoutes.get("/users", async (c) => {
|
|
|
11831
12705
|
`);
|
|
11832
12706
|
const countResult = await countStmt.bind(...params).first();
|
|
11833
12707
|
const totalUsers = countResult?.total || 0;
|
|
11834
|
-
await
|
|
12708
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
11835
12709
|
db,
|
|
11836
12710
|
user.userId,
|
|
11837
12711
|
"users.list_view",
|
|
@@ -11939,7 +12813,9 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
11939
12813
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
11940
12814
|
const phone = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
11941
12815
|
const bio = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
11942
|
-
const
|
|
12816
|
+
const roleInput = formData.get("role")?.toString() || "viewer";
|
|
12817
|
+
const validRoles = ["admin", "editor", "author", "viewer"];
|
|
12818
|
+
const role = validRoles.includes(roleInput) ? roleInput : "viewer";
|
|
11943
12819
|
const password = formData.get("password")?.toString() || "";
|
|
11944
12820
|
const confirmPassword = formData.get("confirm_password")?.toString() || "";
|
|
11945
12821
|
const isActive = formData.get("is_active") === "1";
|
|
@@ -11985,7 +12861,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
11985
12861
|
dismissible: true
|
|
11986
12862
|
}));
|
|
11987
12863
|
}
|
|
11988
|
-
const passwordHash = await
|
|
12864
|
+
const passwordHash = await chunk5GO3AMON_cjs.AuthManager.hashPassword(password);
|
|
11989
12865
|
const userId = crypto.randomUUID();
|
|
11990
12866
|
const createStmt = db.prepare(`
|
|
11991
12867
|
INSERT INTO users (
|
|
@@ -12008,7 +12884,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
12008
12884
|
Date.now(),
|
|
12009
12885
|
Date.now()
|
|
12010
12886
|
).run();
|
|
12011
|
-
await
|
|
12887
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12012
12888
|
db,
|
|
12013
12889
|
user.userId,
|
|
12014
12890
|
"user!.create",
|
|
@@ -12046,7 +12922,7 @@ userRoutes.get("/users/:id", async (c) => {
|
|
|
12046
12922
|
if (!userRecord) {
|
|
12047
12923
|
return c.json({ error: "User not found" }, 404);
|
|
12048
12924
|
}
|
|
12049
|
-
await
|
|
12925
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12050
12926
|
db,
|
|
12051
12927
|
user.userId,
|
|
12052
12928
|
"user!.view",
|
|
@@ -12159,7 +13035,9 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
12159
13035
|
const username = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("username")?.toString());
|
|
12160
13036
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
12161
13037
|
const phone = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
12162
|
-
const
|
|
13038
|
+
const roleInput = formData.get("role")?.toString() || "viewer";
|
|
13039
|
+
const validRoles = ["admin", "editor", "author", "viewer"];
|
|
13040
|
+
const role = validRoles.includes(roleInput) ? roleInput : "viewer";
|
|
12163
13041
|
const isActive = formData.get("is_active") === "1";
|
|
12164
13042
|
const emailVerified = formData.get("email_verified") === "1";
|
|
12165
13043
|
const profileDisplayName = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("profile_display_name")?.toString()) || null;
|
|
@@ -12271,7 +13149,7 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
12271
13149
|
).run();
|
|
12272
13150
|
}
|
|
12273
13151
|
}
|
|
12274
|
-
await
|
|
13152
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12275
13153
|
db,
|
|
12276
13154
|
user.userId,
|
|
12277
13155
|
"user.update",
|
|
@@ -12316,7 +13194,7 @@ userRoutes.post("/users/:id/toggle", async (c) => {
|
|
|
12316
13194
|
UPDATE users SET is_active = ?, updated_at = ? WHERE id = ?
|
|
12317
13195
|
`);
|
|
12318
13196
|
await toggleStmt.bind(active ? 1 : 0, Date.now(), userId).run();
|
|
12319
|
-
await
|
|
13197
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12320
13198
|
db,
|
|
12321
13199
|
user.userId,
|
|
12322
13200
|
active ? "user.activate" : "user.deactivate",
|
|
@@ -12357,7 +13235,7 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
12357
13235
|
DELETE FROM users WHERE id = ?
|
|
12358
13236
|
`);
|
|
12359
13237
|
await deleteStmt.bind(userId).run();
|
|
12360
|
-
await
|
|
13238
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12361
13239
|
db,
|
|
12362
13240
|
user.userId,
|
|
12363
13241
|
"user!.hard_delete",
|
|
@@ -12376,7 +13254,7 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
12376
13254
|
UPDATE users SET is_active = 0, updated_at = ? WHERE id = ?
|
|
12377
13255
|
`);
|
|
12378
13256
|
await deleteStmt.bind(Date.now(), userId).run();
|
|
12379
|
-
await
|
|
13257
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12380
13258
|
db,
|
|
12381
13259
|
user.userId,
|
|
12382
13260
|
"user!.soft_delete",
|
|
@@ -12442,7 +13320,7 @@ userRoutes.post("/invite-user", async (c) => {
|
|
|
12442
13320
|
Date.now(),
|
|
12443
13321
|
Date.now()
|
|
12444
13322
|
).run();
|
|
12445
|
-
await
|
|
13323
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12446
13324
|
db,
|
|
12447
13325
|
user.userId,
|
|
12448
13326
|
"user!.invite_sent",
|
|
@@ -12499,7 +13377,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
|
|
|
12499
13377
|
Date.now(),
|
|
12500
13378
|
userId
|
|
12501
13379
|
).run();
|
|
12502
|
-
await
|
|
13380
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12503
13381
|
db,
|
|
12504
13382
|
user.userId,
|
|
12505
13383
|
"user!.invitation_resent",
|
|
@@ -12535,7 +13413,7 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
|
|
|
12535
13413
|
}
|
|
12536
13414
|
const deleteStmt = db.prepare(`DELETE FROM users WHERE id = ?`);
|
|
12537
13415
|
await deleteStmt.bind(userId).run();
|
|
12538
|
-
await
|
|
13416
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12539
13417
|
db,
|
|
12540
13418
|
user.userId,
|
|
12541
13419
|
"user!.invitation_cancelled",
|
|
@@ -12618,7 +13496,7 @@ userRoutes.get("/activity-logs", async (c) => {
|
|
|
12618
13496
|
...log,
|
|
12619
13497
|
details: log.details ? JSON.parse(log.details) : null
|
|
12620
13498
|
}));
|
|
12621
|
-
await
|
|
13499
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12622
13500
|
db,
|
|
12623
13501
|
user.userId,
|
|
12624
13502
|
"activity.logs_viewed",
|
|
@@ -12725,7 +13603,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
|
|
|
12725
13603
|
csvRows.push(row.join(","));
|
|
12726
13604
|
}
|
|
12727
13605
|
const csvContent = csvRows.join("\n");
|
|
12728
|
-
await
|
|
13606
|
+
await chunk5GO3AMON_cjs.logActivity(
|
|
12729
13607
|
db,
|
|
12730
13608
|
user.userId,
|
|
12731
13609
|
"activity.logs_exported",
|
|
@@ -14064,7 +14942,7 @@ var fileValidationSchema2 = zod.z.object({
|
|
|
14064
14942
|
// 50MB max
|
|
14065
14943
|
});
|
|
14066
14944
|
var adminMediaRoutes = new hono.Hono();
|
|
14067
|
-
adminMediaRoutes.use("*",
|
|
14945
|
+
adminMediaRoutes.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
14068
14946
|
adminMediaRoutes.get("/", async (c) => {
|
|
14069
14947
|
try {
|
|
14070
14948
|
const user = c.get("user");
|
|
@@ -14650,7 +15528,7 @@ adminMediaRoutes.put("/:id", async (c) => {
|
|
|
14650
15528
|
`);
|
|
14651
15529
|
}
|
|
14652
15530
|
});
|
|
14653
|
-
adminMediaRoutes.delete("/cleanup",
|
|
15531
|
+
adminMediaRoutes.delete("/cleanup", chunk5GO3AMON_cjs.requireRole("admin"), async (c) => {
|
|
14654
15532
|
try {
|
|
14655
15533
|
const db = c.env.DB;
|
|
14656
15534
|
const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
|
|
@@ -16873,7 +17751,7 @@ function renderEmailSettingsContent(plugin, settings) {
|
|
|
16873
17751
|
|
|
16874
17752
|
// src/routes/admin-plugins.ts
|
|
16875
17753
|
var adminPluginRoutes = new hono.Hono();
|
|
16876
|
-
adminPluginRoutes.use("*",
|
|
17754
|
+
adminPluginRoutes.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
16877
17755
|
var AVAILABLE_PLUGINS = [
|
|
16878
17756
|
{
|
|
16879
17757
|
id: "third-party-faq",
|
|
@@ -17000,7 +17878,7 @@ adminPluginRoutes.get("/", async (c) => {
|
|
|
17000
17878
|
if (user?.role !== "admin") {
|
|
17001
17879
|
return c.text("Access denied", 403);
|
|
17002
17880
|
}
|
|
17003
|
-
const pluginService = new
|
|
17881
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
17004
17882
|
let installedPlugins = [];
|
|
17005
17883
|
let stats = { total: 0, active: 0, inactive: 0, errors: 0, uninstalled: 0 };
|
|
17006
17884
|
try {
|
|
@@ -17076,7 +17954,7 @@ adminPluginRoutes.get("/:id", async (c) => {
|
|
|
17076
17954
|
if (user?.role !== "admin") {
|
|
17077
17955
|
return c.redirect("/admin/plugins");
|
|
17078
17956
|
}
|
|
17079
|
-
const pluginService = new
|
|
17957
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
17080
17958
|
const plugin = await pluginService.getPlugin(pluginId);
|
|
17081
17959
|
if (!plugin) {
|
|
17082
17960
|
return c.text("Plugin not found", 404);
|
|
@@ -17160,7 +18038,7 @@ adminPluginRoutes.post("/:id/activate", async (c) => {
|
|
|
17160
18038
|
if (user?.role !== "admin") {
|
|
17161
18039
|
return c.json({ error: "Access denied" }, 403);
|
|
17162
18040
|
}
|
|
17163
|
-
const pluginService = new
|
|
18041
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
17164
18042
|
await pluginService.activatePlugin(pluginId);
|
|
17165
18043
|
return c.json({ success: true });
|
|
17166
18044
|
} catch (error) {
|
|
@@ -17177,7 +18055,7 @@ adminPluginRoutes.post("/:id/deactivate", async (c) => {
|
|
|
17177
18055
|
if (user?.role !== "admin") {
|
|
17178
18056
|
return c.json({ error: "Access denied" }, 403);
|
|
17179
18057
|
}
|
|
17180
|
-
const pluginService = new
|
|
18058
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
17181
18059
|
await pluginService.deactivatePlugin(pluginId);
|
|
17182
18060
|
return c.json({ success: true });
|
|
17183
18061
|
} catch (error) {
|
|
@@ -17194,7 +18072,7 @@ adminPluginRoutes.post("/install", async (c) => {
|
|
|
17194
18072
|
return c.json({ error: "Access denied" }, 403);
|
|
17195
18073
|
}
|
|
17196
18074
|
const body = await c.req.json();
|
|
17197
|
-
const pluginService = new
|
|
18075
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
17198
18076
|
if (body.name === "faq-plugin") {
|
|
17199
18077
|
const faqPlugin = await pluginService.installPlugin({
|
|
17200
18078
|
id: "third-party-faq",
|
|
@@ -17464,7 +18342,7 @@ adminPluginRoutes.post("/:id/uninstall", async (c) => {
|
|
|
17464
18342
|
if (user?.role !== "admin") {
|
|
17465
18343
|
return c.json({ error: "Access denied" }, 403);
|
|
17466
18344
|
}
|
|
17467
|
-
const pluginService = new
|
|
18345
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
17468
18346
|
await pluginService.uninstallPlugin(pluginId);
|
|
17469
18347
|
return c.json({ success: true });
|
|
17470
18348
|
} catch (error) {
|
|
@@ -17482,8 +18360,20 @@ adminPluginRoutes.post("/:id/settings", async (c) => {
|
|
|
17482
18360
|
return c.json({ error: "Access denied" }, 403);
|
|
17483
18361
|
}
|
|
17484
18362
|
const settings = await c.req.json();
|
|
17485
|
-
const pluginService = new
|
|
18363
|
+
const pluginService = new chunkTWCQVJ6M_cjs.PluginService(db);
|
|
17486
18364
|
await pluginService.updatePluginSettings(pluginId, settings);
|
|
18365
|
+
if (pluginId === "core-auth") {
|
|
18366
|
+
try {
|
|
18367
|
+
const cacheKv = c.env.CACHE_KV;
|
|
18368
|
+
if (cacheKv) {
|
|
18369
|
+
await cacheKv.delete("auth:settings");
|
|
18370
|
+
await cacheKv.delete("auth:registration-enabled");
|
|
18371
|
+
console.log("[AuthSettings] Cache cleared after updating authentication settings");
|
|
18372
|
+
}
|
|
18373
|
+
} catch (cacheError) {
|
|
18374
|
+
console.error("[AuthSettings] Failed to clear cache:", cacheError);
|
|
18375
|
+
}
|
|
18376
|
+
}
|
|
17487
18377
|
return c.json({ success: true });
|
|
17488
18378
|
} catch (error) {
|
|
17489
18379
|
console.error("Error updating plugin settings:", error);
|
|
@@ -18278,11 +19168,11 @@ function renderLogConfigPage(data) {
|
|
|
18278
19168
|
|
|
18279
19169
|
// src/routes/admin-logs.ts
|
|
18280
19170
|
var adminLogsRoutes = new hono.Hono();
|
|
18281
|
-
adminLogsRoutes.use("*",
|
|
19171
|
+
adminLogsRoutes.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
18282
19172
|
adminLogsRoutes.get("/", async (c) => {
|
|
18283
19173
|
try {
|
|
18284
19174
|
const user = c.get("user");
|
|
18285
|
-
const logger =
|
|
19175
|
+
const logger = chunkLFAQUR7P_cjs.getLogger(c.env.DB);
|
|
18286
19176
|
const query = c.req.query();
|
|
18287
19177
|
const page = parseInt(query.page || "1");
|
|
18288
19178
|
const limit = parseInt(query.limit || "50");
|
|
@@ -18362,7 +19252,7 @@ adminLogsRoutes.get("/:id", async (c) => {
|
|
|
18362
19252
|
try {
|
|
18363
19253
|
const id = c.req.param("id");
|
|
18364
19254
|
const user = c.get("user");
|
|
18365
|
-
const logger =
|
|
19255
|
+
const logger = chunkLFAQUR7P_cjs.getLogger(c.env.DB);
|
|
18366
19256
|
const { logs } = await logger.getLogs({
|
|
18367
19257
|
limit: 1,
|
|
18368
19258
|
offset: 0,
|
|
@@ -18399,7 +19289,7 @@ adminLogsRoutes.get("/:id", async (c) => {
|
|
|
18399
19289
|
adminLogsRoutes.get("/config", async (c) => {
|
|
18400
19290
|
try {
|
|
18401
19291
|
const user = c.get("user");
|
|
18402
|
-
const logger =
|
|
19292
|
+
const logger = chunkLFAQUR7P_cjs.getLogger(c.env.DB);
|
|
18403
19293
|
const configs = await logger.getAllConfigs();
|
|
18404
19294
|
const pageData = {
|
|
18405
19295
|
configs,
|
|
@@ -18423,7 +19313,7 @@ adminLogsRoutes.post("/config/:category", async (c) => {
|
|
|
18423
19313
|
const level = formData.get("level");
|
|
18424
19314
|
const retention = parseInt(formData.get("retention"));
|
|
18425
19315
|
const maxSize = parseInt(formData.get("max_size"));
|
|
18426
|
-
const logger =
|
|
19316
|
+
const logger = chunkLFAQUR7P_cjs.getLogger(c.env.DB);
|
|
18427
19317
|
await logger.updateConfig(category, {
|
|
18428
19318
|
enabled,
|
|
18429
19319
|
level,
|
|
@@ -18452,7 +19342,7 @@ adminLogsRoutes.get("/export", async (c) => {
|
|
|
18452
19342
|
const category = query.category;
|
|
18453
19343
|
const startDate = query.start_date;
|
|
18454
19344
|
const endDate = query.end_date;
|
|
18455
|
-
const logger =
|
|
19345
|
+
const logger = chunkLFAQUR7P_cjs.getLogger(c.env.DB);
|
|
18456
19346
|
const filter = {
|
|
18457
19347
|
limit: 1e4,
|
|
18458
19348
|
// Export up to 10k logs
|
|
@@ -18533,7 +19423,7 @@ adminLogsRoutes.post("/cleanup", async (c) => {
|
|
|
18533
19423
|
error: "Unauthorized. Admin access required."
|
|
18534
19424
|
}, 403);
|
|
18535
19425
|
}
|
|
18536
|
-
const logger =
|
|
19426
|
+
const logger = chunkLFAQUR7P_cjs.getLogger(c.env.DB);
|
|
18537
19427
|
await logger.cleanupByRetention();
|
|
18538
19428
|
return c.html(html.html`
|
|
18539
19429
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
|
|
@@ -18555,7 +19445,7 @@ adminLogsRoutes.post("/search", async (c) => {
|
|
|
18555
19445
|
const search = formData.get("search");
|
|
18556
19446
|
const level = formData.get("level");
|
|
18557
19447
|
const category = formData.get("category");
|
|
18558
|
-
const logger =
|
|
19448
|
+
const logger = chunkLFAQUR7P_cjs.getLogger(c.env.DB);
|
|
18559
19449
|
const filter = {
|
|
18560
19450
|
limit: 20,
|
|
18561
19451
|
offset: 0,
|
|
@@ -20606,9 +21496,9 @@ function renderStorageUsage(databaseSizeBytes, mediaSizeBytes) {
|
|
|
20606
21496
|
}
|
|
20607
21497
|
|
|
20608
21498
|
// src/routes/admin-dashboard.ts
|
|
20609
|
-
var VERSION =
|
|
21499
|
+
var VERSION = chunkE2GKK5HX_cjs.getCoreVersion();
|
|
20610
21500
|
var router = new hono.Hono();
|
|
20611
|
-
router.use("*",
|
|
21501
|
+
router.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
20612
21502
|
router.get("/", async (c) => {
|
|
20613
21503
|
const user = c.get("user");
|
|
20614
21504
|
try {
|
|
@@ -20639,7 +21529,7 @@ router.get("/stats", async (c) => {
|
|
|
20639
21529
|
const db = c.env.DB;
|
|
20640
21530
|
let collectionsCount = 0;
|
|
20641
21531
|
try {
|
|
20642
|
-
const collectionsStmt = db.prepare("SELECT COUNT(*) as count FROM collections WHERE is_active = 1");
|
|
21532
|
+
const collectionsStmt = db.prepare("SELECT COUNT(*) as count FROM collections WHERE is_active = 1 AND (source_type IS NULL OR source_type = 'user')");
|
|
20643
21533
|
const collectionsResult = await collectionsStmt.first();
|
|
20644
21534
|
collectionsCount = collectionsResult?.count || 0;
|
|
20645
21535
|
} catch (error) {
|
|
@@ -20647,7 +21537,7 @@ router.get("/stats", async (c) => {
|
|
|
20647
21537
|
}
|
|
20648
21538
|
let contentCount = 0;
|
|
20649
21539
|
try {
|
|
20650
|
-
const contentStmt = db.prepare("SELECT COUNT(*) as count FROM content");
|
|
21540
|
+
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')");
|
|
20651
21541
|
const contentResult = await contentStmt.first();
|
|
20652
21542
|
contentCount = contentResult?.count || 0;
|
|
20653
21543
|
} catch (error) {
|
|
@@ -22427,7 +23317,10 @@ function renderCollectionFormPage(data) {
|
|
|
22427
23317
|
|
|
22428
23318
|
// src/routes/admin-collections.ts
|
|
22429
23319
|
var adminCollectionsRoutes = new hono.Hono();
|
|
22430
|
-
adminCollectionsRoutes.use("*",
|
|
23320
|
+
adminCollectionsRoutes.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
23321
|
+
adminCollectionsRoutes.post("*", chunk5GO3AMON_cjs.requireRole(["admin"]));
|
|
23322
|
+
adminCollectionsRoutes.put("*", chunk5GO3AMON_cjs.requireRole(["admin"]));
|
|
23323
|
+
adminCollectionsRoutes.delete("*", chunk5GO3AMON_cjs.requireRole(["admin"]));
|
|
22431
23324
|
adminCollectionsRoutes.get("/", async (c) => {
|
|
22432
23325
|
try {
|
|
22433
23326
|
const user = c.get("user");
|
|
@@ -22441,6 +23334,7 @@ adminCollectionsRoutes.get("/", async (c) => {
|
|
|
22441
23334
|
SELECT id, name, display_name, description, created_at, managed, schema
|
|
22442
23335
|
FROM collections
|
|
22443
23336
|
WHERE is_active = 1
|
|
23337
|
+
AND (source_type IS NULL OR source_type = 'user')
|
|
22444
23338
|
AND (name LIKE ? OR display_name LIKE ? OR description LIKE ?)
|
|
22445
23339
|
ORDER BY created_at DESC
|
|
22446
23340
|
`);
|
|
@@ -22448,7 +23342,7 @@ adminCollectionsRoutes.get("/", async (c) => {
|
|
|
22448
23342
|
const queryResults = await stmt.bind(searchParam, searchParam, searchParam).all();
|
|
22449
23343
|
results = queryResults.results;
|
|
22450
23344
|
} else {
|
|
22451
|
-
stmt = db.prepare("SELECT id, name, display_name, description, created_at, managed, schema FROM collections WHERE is_active = 1 ORDER BY created_at DESC");
|
|
23345
|
+
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");
|
|
22452
23346
|
const queryResults = await stmt.all();
|
|
22453
23347
|
results = queryResults.results;
|
|
22454
23348
|
}
|
|
@@ -24622,7 +25516,7 @@ function renderDatabaseToolsSettings(settings) {
|
|
|
24622
25516
|
|
|
24623
25517
|
// src/routes/admin-settings.ts
|
|
24624
25518
|
var adminSettingsRoutes = new hono.Hono();
|
|
24625
|
-
adminSettingsRoutes.use("*",
|
|
25519
|
+
adminSettingsRoutes.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
24626
25520
|
function getMockSettings(user) {
|
|
24627
25521
|
return {
|
|
24628
25522
|
general: {
|
|
@@ -24687,7 +25581,7 @@ adminSettingsRoutes.get("/", (c) => {
|
|
|
24687
25581
|
adminSettingsRoutes.get("/general", async (c) => {
|
|
24688
25582
|
const user = c.get("user");
|
|
24689
25583
|
const db = c.env.DB;
|
|
24690
|
-
const settingsService = new
|
|
25584
|
+
const settingsService = new chunkLFAQUR7P_cjs.SettingsService(db);
|
|
24691
25585
|
const generalSettings = await settingsService.getGeneralSettings(user?.email);
|
|
24692
25586
|
const mockSettings = getMockSettings(user);
|
|
24693
25587
|
mockSettings.general = generalSettings;
|
|
@@ -24790,7 +25684,7 @@ adminSettingsRoutes.get("/database-tools", (c) => {
|
|
|
24790
25684
|
adminSettingsRoutes.get("/api/migrations/status", async (c) => {
|
|
24791
25685
|
try {
|
|
24792
25686
|
const db = c.env.DB;
|
|
24793
|
-
const migrationService = new
|
|
25687
|
+
const migrationService = new chunkEAJJHE5F_cjs.MigrationService(db);
|
|
24794
25688
|
const status = await migrationService.getMigrationStatus();
|
|
24795
25689
|
return c.json({
|
|
24796
25690
|
success: true,
|
|
@@ -24814,7 +25708,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
|
|
|
24814
25708
|
}, 403);
|
|
24815
25709
|
}
|
|
24816
25710
|
const db = c.env.DB;
|
|
24817
|
-
const migrationService = new
|
|
25711
|
+
const migrationService = new chunkEAJJHE5F_cjs.MigrationService(db);
|
|
24818
25712
|
const result = await migrationService.runPendingMigrations();
|
|
24819
25713
|
return c.json({
|
|
24820
25714
|
success: result.success,
|
|
@@ -24832,7 +25726,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
|
|
|
24832
25726
|
adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
|
|
24833
25727
|
try {
|
|
24834
25728
|
const db = c.env.DB;
|
|
24835
|
-
const migrationService = new
|
|
25729
|
+
const migrationService = new chunkEAJJHE5F_cjs.MigrationService(db);
|
|
24836
25730
|
const validation = await migrationService.validateSchema();
|
|
24837
25731
|
return c.json({
|
|
24838
25732
|
success: true,
|
|
@@ -25001,7 +25895,7 @@ adminSettingsRoutes.post("/general", async (c) => {
|
|
|
25001
25895
|
}
|
|
25002
25896
|
const formData = await c.req.formData();
|
|
25003
25897
|
const db = c.env.DB;
|
|
25004
|
-
const settingsService = new
|
|
25898
|
+
const settingsService = new chunkLFAQUR7P_cjs.SettingsService(db);
|
|
25005
25899
|
const settings = {
|
|
25006
25900
|
siteName: formData.get("siteName"),
|
|
25007
25901
|
siteDescription: formData.get("siteDescription"),
|
|
@@ -26722,7 +27616,7 @@ function renderFormCreatePage(data) {
|
|
|
26722
27616
|
|
|
26723
27617
|
// src/routes/admin-forms.ts
|
|
26724
27618
|
var adminFormsRoutes = new hono.Hono();
|
|
26725
|
-
adminFormsRoutes.use("*",
|
|
27619
|
+
adminFormsRoutes.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
26726
27620
|
adminFormsRoutes.get("/", async (c) => {
|
|
26727
27621
|
try {
|
|
26728
27622
|
const user = c.get("user");
|
|
@@ -27520,14 +28414,36 @@ publicFormsRoutes.post("/:identifier/submit", async (c) => {
|
|
|
27520
28414
|
now
|
|
27521
28415
|
).run();
|
|
27522
28416
|
await db.prepare(`
|
|
27523
|
-
UPDATE forms
|
|
28417
|
+
UPDATE forms
|
|
27524
28418
|
SET submission_count = submission_count + 1,
|
|
27525
28419
|
updated_at = ?
|
|
27526
28420
|
WHERE id = ?
|
|
27527
28421
|
`).bind(now, form.id).run();
|
|
28422
|
+
let contentId = null;
|
|
28423
|
+
try {
|
|
28424
|
+
contentId = await chunkTWCQVJ6M_cjs.createContentFromSubmission(
|
|
28425
|
+
db,
|
|
28426
|
+
sanitizedData,
|
|
28427
|
+
{ id: form.id, name: form.name, display_name: form.display_name },
|
|
28428
|
+
submissionId,
|
|
28429
|
+
{
|
|
28430
|
+
ipAddress: c.req.header("cf-connecting-ip") || null,
|
|
28431
|
+
userAgent: c.req.header("user-agent") || null,
|
|
28432
|
+
userEmail: sanitizedData?.email || null,
|
|
28433
|
+
userId: null
|
|
28434
|
+
// anonymous submission
|
|
28435
|
+
}
|
|
28436
|
+
);
|
|
28437
|
+
if (!contentId) {
|
|
28438
|
+
console.warn("[FormSubmit] Content creation returned null for submission:", submissionId);
|
|
28439
|
+
}
|
|
28440
|
+
} catch (contentError) {
|
|
28441
|
+
console.error("[FormSubmit] Error creating content from submission:", contentError);
|
|
28442
|
+
}
|
|
27528
28443
|
return c.json({
|
|
27529
28444
|
success: true,
|
|
27530
28445
|
submissionId,
|
|
28446
|
+
contentId,
|
|
27531
28447
|
message: "Form submitted successfully"
|
|
27532
28448
|
});
|
|
27533
28449
|
} catch (error) {
|
|
@@ -27679,7 +28595,7 @@ function renderAPIReferencePage(data) {
|
|
|
27679
28595
|
>
|
|
27680
28596
|
<option value="">All Categories</option>
|
|
27681
28597
|
${categories.map((category) => {
|
|
27682
|
-
const info =
|
|
28598
|
+
const info = chunkLFAQUR7P_cjs.CATEGORY_INFO[category];
|
|
27683
28599
|
const title = info ? info.title : category;
|
|
27684
28600
|
return `<option value="${category}">${title}</option>`;
|
|
27685
28601
|
}).join("\n ")}
|
|
@@ -27696,7 +28612,7 @@ function renderAPIReferencePage(data) {
|
|
|
27696
28612
|
<!-- API Categories -->
|
|
27697
28613
|
<div class="space-y-6">
|
|
27698
28614
|
${Object.entries(endpointsByCategory).map(([category, endpoints]) => {
|
|
27699
|
-
const info =
|
|
28615
|
+
const info = chunkLFAQUR7P_cjs.CATEGORY_INFO[category] || { title: category, description: "", icon: "📋" };
|
|
27700
28616
|
return `
|
|
27701
28617
|
<div class="api-category" data-category="${category}">
|
|
27702
28618
|
<div class="rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 overflow-hidden">
|
|
@@ -27873,14 +28789,14 @@ function renderAPIReferencePage(data) {
|
|
|
27873
28789
|
}
|
|
27874
28790
|
|
|
27875
28791
|
// src/routes/admin-api-reference.ts
|
|
27876
|
-
var VERSION2 =
|
|
28792
|
+
var VERSION2 = chunkE2GKK5HX_cjs.getCoreVersion();
|
|
27877
28793
|
var router2 = new hono.Hono();
|
|
27878
|
-
router2.use("*",
|
|
28794
|
+
router2.use("*", chunk5GO3AMON_cjs.requireAuth());
|
|
27879
28795
|
router2.get("/", async (c) => {
|
|
27880
28796
|
const user = c.get("user");
|
|
27881
28797
|
try {
|
|
27882
|
-
const app2 =
|
|
27883
|
-
const endpoints =
|
|
28798
|
+
const app2 = chunkLFAQUR7P_cjs.getAppInstance();
|
|
28799
|
+
const endpoints = chunkLFAQUR7P_cjs.buildRouteList(app2);
|
|
27884
28800
|
const pageData = {
|
|
27885
28801
|
endpoints,
|
|
27886
28802
|
user: user ? {
|
|
@@ -27962,5 +28878,5 @@ exports.router = router;
|
|
|
27962
28878
|
exports.router2 = router2;
|
|
27963
28879
|
exports.test_cleanup_default = test_cleanup_default;
|
|
27964
28880
|
exports.userRoutes = userRoutes;
|
|
27965
|
-
//# sourceMappingURL=chunk-
|
|
27966
|
-
//# sourceMappingURL=chunk-
|
|
28881
|
+
//# sourceMappingURL=chunk-HGKBMUYY.cjs.map
|
|
28882
|
+
//# sourceMappingURL=chunk-HGKBMUYY.cjs.map
|