@sonicjs-cms/core 2.9.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-YFJJU26H.js → chunk-27AOVQTR.js} +10 -2
- package/dist/chunk-27AOVQTR.js.map +1 -0
- package/dist/{chunk-25YNV4RK.js → chunk-4TTMQQC7.js} +4 -4
- package/dist/{chunk-25YNV4RK.js.map → chunk-4TTMQQC7.js.map} +1 -1
- package/dist/{chunk-STTZVLY2.js → chunk-6O3RJV3C.js} +2 -2
- package/dist/{chunk-STTZVLY2.js.map → chunk-6O3RJV3C.js.map} +1 -1
- package/dist/{chunk-SHU7Q66Q.cjs → chunk-EKPLKUZT.cjs} +7 -3
- package/dist/chunk-EKPLKUZT.cjs.map +1 -0
- package/dist/{chunk-MPT5PA6U.cjs → chunk-IIBRG5S5.cjs} +10 -2
- package/dist/chunk-IIBRG5S5.cjs.map +1 -0
- package/dist/{chunk-DQZVU3WB.cjs → chunk-IT2TC4ZD.cjs} +7 -7
- package/dist/{chunk-DQZVU3WB.cjs.map → chunk-IT2TC4ZD.cjs.map} +1 -1
- package/dist/{chunk-3FHMXGLF.js → chunk-IZWNIUJI.js} +7 -3
- package/dist/chunk-IZWNIUJI.js.map +1 -0
- package/dist/{chunk-2JGQKF7B.js → chunk-JTNUM7JE.js} +1042 -152
- package/dist/chunk-JTNUM7JE.js.map +1 -0
- package/dist/{chunk-KSB6FXOP.cjs → chunk-RCA6R6VE.cjs} +1130 -240
- package/dist/chunk-RCA6R6VE.cjs.map +1 -0
- package/dist/{chunk-LDFMYRG6.cjs → chunk-ZMVWMJ3S.cjs} +2 -2
- package/dist/{chunk-LDFMYRG6.cjs.map → chunk-ZMVWMJ3S.cjs.map} +1 -1
- package/dist/{collection-config-DckWhkdL.d.cts → collection-config-B4PG-AaF.d.cts} +2 -0
- package/dist/{collection-config-DckWhkdL.d.ts → collection-config-B4PG-AaF.d.ts} +2 -0
- package/dist/index.cjs +97 -97
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -8
- package/dist/middleware.cjs +29 -29
- package/dist/middleware.js +3 -3
- package/dist/migrations-N2C2VPJU.js +4 -0
- package/dist/{migrations-SZSR3C3G.js.map → migrations-N2C2VPJU.js.map} +1 -1
- package/dist/migrations-ONIAY6GK.cjs +13 -0
- package/dist/{migrations-QQWGDWGB.cjs.map → migrations-ONIAY6GK.cjs.map} +1 -1
- package/dist/{plugin-bootstrap-BAz7NY0H.d.cts → plugin-bootstrap-WmpvYM5w.d.ts} +1 -1
- package/dist/{plugin-bootstrap-Cz3-bj8X.d.ts → plugin-bootstrap-fpG98Otb.d.cts} +1 -1
- package/dist/routes.cjs +28 -28
- package/dist/routes.js +5 -5
- package/dist/services.cjs +16 -16
- package/dist/services.d.cts +2 -2
- package/dist/services.d.ts +2 -2
- package/dist/services.js +2 -2
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils.cjs +11 -11
- package/dist/utils.d.cts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/package.json +5 -1
- package/dist/chunk-2JGQKF7B.js.map +0 -1
- package/dist/chunk-3FHMXGLF.js.map +0 -1
- package/dist/chunk-KSB6FXOP.cjs.map +0 -1
- package/dist/chunk-MPT5PA6U.cjs.map +0 -1
- package/dist/chunk-SHU7Q66Q.cjs.map +0 -1
- package/dist/chunk-YFJJU26H.js.map +0 -1
- package/dist/migrations-QQWGDWGB.cjs +0 -13
- package/dist/migrations-SZSR3C3G.js +0 -4
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunk64APW3DW_cjs = require('./chunk-64APW3DW.cjs');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
4
|
+
var chunkIT2TC4ZD_cjs = require('./chunk-IT2TC4ZD.cjs');
|
|
5
|
+
var chunkIIBRG5S5_cjs = require('./chunk-IIBRG5S5.cjs');
|
|
6
|
+
var chunkZMVWMJ3S_cjs = require('./chunk-ZMVWMJ3S.cjs');
|
|
7
7
|
var chunkLTKV7AE5_cjs = require('./chunk-LTKV7AE5.cjs');
|
|
8
8
|
var chunk6FHNRRJ3_cjs = require('./chunk-6FHNRRJ3.cjs');
|
|
9
|
-
var
|
|
9
|
+
var chunkEKPLKUZT_cjs = require('./chunk-EKPLKUZT.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("/", chunkIT2TC4ZD_cjs.requireAuth(), chunkIT2TC4ZD_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
|
|
125
125
|
try {
|
|
126
126
|
const db = c.env.DB;
|
|
127
127
|
const user = c.get("user");
|
|
@@ -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", chunkIT2TC4ZD_cjs.requireAuth(), chunkIT2TC4ZD_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;
|
|
@@ -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", chunkIT2TC4ZD_cjs.requireAuth(), chunkIT2TC4ZD_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;
|
|
@@ -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 chunkIT2TC4ZD_cjs.isPluginActive(c.env.DB, "core-cache");
|
|
291
291
|
c.set("cacheEnabled", cacheEnabled);
|
|
292
292
|
await next();
|
|
293
293
|
});
|
|
@@ -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", chunkIT2TC4ZD_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 = chunkEKPLKUZT_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 chunkEKPLKUZT_cjs.QueryFilterBuilder();
|
|
811
811
|
const queryResult = builder3.build("content", normalizedFilter);
|
|
812
812
|
if (queryResult.errors.length > 0) {
|
|
813
813
|
return c.json({
|
|
@@ -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", chunkIT2TC4ZD_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 = chunkEKPLKUZT_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 chunkEKPLKUZT_cjs.QueryFilterBuilder();
|
|
911
911
|
const queryResult = builder3.build("content", normalizedFilter);
|
|
912
912
|
if (queryResult.errors.length > 0) {
|
|
913
913
|
return c.json({
|
|
@@ -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("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
1032
1032
|
apiMediaRoutes.post("/upload", async (c) => {
|
|
1033
1033
|
try {
|
|
1034
1034
|
const user = c.get("user");
|
|
@@ -1772,8 +1772,8 @@ 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("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
1776
|
+
adminApiRoutes.use("*", chunkIT2TC4ZD_cjs.requireRole(["admin", "editor"]));
|
|
1777
1777
|
adminApiRoutes.get("/stats", async (c) => {
|
|
1778
1778
|
try {
|
|
1779
1779
|
const db = c.env.DB;
|
|
@@ -2283,7 +2283,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
|
|
|
2283
2283
|
});
|
|
2284
2284
|
adminApiRoutes.get("/migrations/status", async (c) => {
|
|
2285
2285
|
try {
|
|
2286
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
2286
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-ONIAY6GK.cjs');
|
|
2287
2287
|
const db = c.env.DB;
|
|
2288
2288
|
const migrationService = new MigrationService2(db);
|
|
2289
2289
|
const status = await migrationService.getMigrationStatus();
|
|
@@ -2308,7 +2308,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
2308
2308
|
error: "Unauthorized. Admin access required."
|
|
2309
2309
|
}, 403);
|
|
2310
2310
|
}
|
|
2311
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
2311
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-ONIAY6GK.cjs');
|
|
2312
2312
|
const db = c.env.DB;
|
|
2313
2313
|
const migrationService = new MigrationService2(db);
|
|
2314
2314
|
const result = await migrationService.runPendingMigrations();
|
|
@@ -2327,7 +2327,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
2327
2327
|
});
|
|
2328
2328
|
adminApiRoutes.get("/migrations/validate", async (c) => {
|
|
2329
2329
|
try {
|
|
2330
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
2330
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-ONIAY6GK.cjs');
|
|
2331
2331
|
const db = c.env.DB;
|
|
2332
2332
|
const migrationService = new MigrationService2(db);
|
|
2333
2333
|
const validation = await migrationService.validateSchema();
|
|
@@ -2738,7 +2738,7 @@ var JWT_SECRET_FALLBACK = "your-super-secret-jwt-key-change-in-production";
|
|
|
2738
2738
|
async function setCsrfCookie(c) {
|
|
2739
2739
|
const secret = c.env?.JWT_SECRET || JWT_SECRET_FALLBACK;
|
|
2740
2740
|
const isDev = c.env?.ENVIRONMENT === "development" || !c.env?.ENVIRONMENT;
|
|
2741
|
-
const csrfToken = await
|
|
2741
|
+
const csrfToken = await chunkIT2TC4ZD_cjs.generateCsrfToken(secret);
|
|
2742
2742
|
cookie.setCookie(c, "csrf_token", csrfToken, {
|
|
2743
2743
|
httpOnly: false,
|
|
2744
2744
|
secure: !isDev,
|
|
@@ -2795,7 +2795,7 @@ var loginSchema = zod.z.object({
|
|
|
2795
2795
|
});
|
|
2796
2796
|
authRoutes.post(
|
|
2797
2797
|
"/register",
|
|
2798
|
-
|
|
2798
|
+
chunkIT2TC4ZD_cjs.rateLimit({ max: 3, windowMs: 60 * 1e3, keyPrefix: "register" }),
|
|
2799
2799
|
async (c) => {
|
|
2800
2800
|
try {
|
|
2801
2801
|
const db = c.env.DB;
|
|
@@ -2832,7 +2832,7 @@ authRoutes.post(
|
|
|
2832
2832
|
if (existingUser) {
|
|
2833
2833
|
return c.json({ error: "User with this email or username already exists" }, 400);
|
|
2834
2834
|
}
|
|
2835
|
-
const passwordHash = await
|
|
2835
|
+
const passwordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
|
|
2836
2836
|
const userId = crypto.randomUUID();
|
|
2837
2837
|
const now = /* @__PURE__ */ new Date();
|
|
2838
2838
|
await db.prepare(`
|
|
@@ -2852,7 +2852,7 @@ authRoutes.post(
|
|
|
2852
2852
|
now.getTime(),
|
|
2853
2853
|
now.getTime()
|
|
2854
2854
|
).run();
|
|
2855
|
-
const token = await
|
|
2855
|
+
const token = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer", c.env.JWT_SECRET);
|
|
2856
2856
|
cookie.setCookie(c, "auth_token", token, {
|
|
2857
2857
|
httpOnly: true,
|
|
2858
2858
|
secure: true,
|
|
@@ -2886,7 +2886,7 @@ authRoutes.post(
|
|
|
2886
2886
|
);
|
|
2887
2887
|
authRoutes.post(
|
|
2888
2888
|
"/login",
|
|
2889
|
-
|
|
2889
|
+
chunkIT2TC4ZD_cjs.rateLimit({ max: 5, windowMs: 60 * 1e3, keyPrefix: "login" }),
|
|
2890
2890
|
async (c) => {
|
|
2891
2891
|
try {
|
|
2892
2892
|
const body = await c.req.json();
|
|
@@ -2909,19 +2909,19 @@ authRoutes.post(
|
|
|
2909
2909
|
if (!user) {
|
|
2910
2910
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
2911
2911
|
}
|
|
2912
|
-
const isValidPassword = await
|
|
2912
|
+
const isValidPassword = await chunkIT2TC4ZD_cjs.AuthManager.verifyPassword(password, user.password_hash);
|
|
2913
2913
|
if (!isValidPassword) {
|
|
2914
2914
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
2915
2915
|
}
|
|
2916
|
-
if (
|
|
2916
|
+
if (chunkIT2TC4ZD_cjs.AuthManager.isLegacyHash(user.password_hash)) {
|
|
2917
2917
|
try {
|
|
2918
|
-
const newHash = await
|
|
2918
|
+
const newHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
|
|
2919
2919
|
await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(newHash, Date.now(), user.id).run();
|
|
2920
2920
|
} catch (rehashError) {
|
|
2921
2921
|
console.error("Password rehash failed (non-fatal):", rehashError);
|
|
2922
2922
|
}
|
|
2923
2923
|
}
|
|
2924
|
-
const token = await
|
|
2924
|
+
const token = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET);
|
|
2925
2925
|
cookie.setCookie(c, "auth_token", token, {
|
|
2926
2926
|
httpOnly: true,
|
|
2927
2927
|
secure: true,
|
|
@@ -2974,7 +2974,7 @@ authRoutes.get("/logout", (c) => {
|
|
|
2974
2974
|
clearCsrfCookie(c);
|
|
2975
2975
|
return c.redirect("/auth/login?message=You have been logged out successfully");
|
|
2976
2976
|
});
|
|
2977
|
-
authRoutes.get("/me",
|
|
2977
|
+
authRoutes.get("/me", chunkIT2TC4ZD_cjs.requireAuth(), async (c) => {
|
|
2978
2978
|
try {
|
|
2979
2979
|
const user = c.get("user");
|
|
2980
2980
|
if (!user) {
|
|
@@ -2991,13 +2991,13 @@ authRoutes.get("/me", chunkDQZVU3WB_cjs.requireAuth(), async (c) => {
|
|
|
2991
2991
|
return c.json({ error: "Failed to get user" }, 500);
|
|
2992
2992
|
}
|
|
2993
2993
|
});
|
|
2994
|
-
authRoutes.post("/refresh",
|
|
2994
|
+
authRoutes.post("/refresh", chunkIT2TC4ZD_cjs.requireAuth(), async (c) => {
|
|
2995
2995
|
try {
|
|
2996
2996
|
const user = c.get("user");
|
|
2997
2997
|
if (!user) {
|
|
2998
2998
|
return c.json({ error: "Not authenticated" }, 401);
|
|
2999
2999
|
}
|
|
3000
|
-
const token = await
|
|
3000
|
+
const token = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(user.userId, user.email, user.role, c.env.JWT_SECRET);
|
|
3001
3001
|
cookie.setCookie(c, "auth_token", token, {
|
|
3002
3002
|
httpOnly: true,
|
|
3003
3003
|
secure: true,
|
|
@@ -3014,7 +3014,7 @@ authRoutes.post("/refresh", chunkDQZVU3WB_cjs.requireAuth(), async (c) => {
|
|
|
3014
3014
|
});
|
|
3015
3015
|
authRoutes.post(
|
|
3016
3016
|
"/register/form",
|
|
3017
|
-
|
|
3017
|
+
chunkIT2TC4ZD_cjs.rateLimit({ max: 3, windowMs: 60 * 1e3, keyPrefix: "register" }),
|
|
3018
3018
|
async (c) => {
|
|
3019
3019
|
try {
|
|
3020
3020
|
const db = c.env.DB;
|
|
@@ -3061,7 +3061,7 @@ authRoutes.post(
|
|
|
3061
3061
|
</div>
|
|
3062
3062
|
`);
|
|
3063
3063
|
}
|
|
3064
|
-
const passwordHash = await
|
|
3064
|
+
const passwordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
|
|
3065
3065
|
const role = isFirstUser ? "admin" : "viewer";
|
|
3066
3066
|
const userId = crypto.randomUUID();
|
|
3067
3067
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -3081,7 +3081,7 @@ authRoutes.post(
|
|
|
3081
3081
|
now.getTime(),
|
|
3082
3082
|
now.getTime()
|
|
3083
3083
|
).run();
|
|
3084
|
-
const token = await
|
|
3084
|
+
const token = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(userId, normalizedEmail, role, c.env.JWT_SECRET);
|
|
3085
3085
|
cookie.setCookie(c, "auth_token", token, {
|
|
3086
3086
|
httpOnly: true,
|
|
3087
3087
|
secure: false,
|
|
@@ -3114,7 +3114,7 @@ authRoutes.post(
|
|
|
3114
3114
|
);
|
|
3115
3115
|
authRoutes.post(
|
|
3116
3116
|
"/login/form",
|
|
3117
|
-
|
|
3117
|
+
chunkIT2TC4ZD_cjs.rateLimit({ max: 5, windowMs: 60 * 1e3, keyPrefix: "login" }),
|
|
3118
3118
|
async (c) => {
|
|
3119
3119
|
try {
|
|
3120
3120
|
const formData = await c.req.formData();
|
|
@@ -3138,7 +3138,7 @@ authRoutes.post(
|
|
|
3138
3138
|
</div>
|
|
3139
3139
|
`);
|
|
3140
3140
|
}
|
|
3141
|
-
const isValidPassword = await
|
|
3141
|
+
const isValidPassword = await chunkIT2TC4ZD_cjs.AuthManager.verifyPassword(password, user.password_hash);
|
|
3142
3142
|
if (!isValidPassword) {
|
|
3143
3143
|
return c.html(html.html`
|
|
3144
3144
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
@@ -3146,15 +3146,15 @@ authRoutes.post(
|
|
|
3146
3146
|
</div>
|
|
3147
3147
|
`);
|
|
3148
3148
|
}
|
|
3149
|
-
if (
|
|
3149
|
+
if (chunkIT2TC4ZD_cjs.AuthManager.isLegacyHash(user.password_hash)) {
|
|
3150
3150
|
try {
|
|
3151
|
-
const newHash = await
|
|
3151
|
+
const newHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
|
|
3152
3152
|
await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(newHash, Date.now(), user.id).run();
|
|
3153
3153
|
} catch (rehashError) {
|
|
3154
3154
|
console.error("Password rehash failed (non-fatal):", rehashError);
|
|
3155
3155
|
}
|
|
3156
3156
|
}
|
|
3157
|
-
const token = await
|
|
3157
|
+
const token = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(user.id, user.email, user.role, c.env.JWT_SECRET);
|
|
3158
3158
|
cookie.setCookie(c, "auth_token", token, {
|
|
3159
3159
|
httpOnly: true,
|
|
3160
3160
|
secure: false,
|
|
@@ -3196,7 +3196,7 @@ authRoutes.post(
|
|
|
3196
3196
|
);
|
|
3197
3197
|
authRoutes.post(
|
|
3198
3198
|
"/seed-admin",
|
|
3199
|
-
|
|
3199
|
+
chunkIT2TC4ZD_cjs.rateLimit({ max: 2, windowMs: 60 * 1e3, keyPrefix: "seed-admin" }),
|
|
3200
3200
|
async (c) => {
|
|
3201
3201
|
try {
|
|
3202
3202
|
const db = c.env.DB;
|
|
@@ -3218,7 +3218,7 @@ authRoutes.post(
|
|
|
3218
3218
|
`).run();
|
|
3219
3219
|
const existingAdmin = await db.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind("admin@sonicjs.com", "admin").first();
|
|
3220
3220
|
if (existingAdmin) {
|
|
3221
|
-
const passwordHash2 = await
|
|
3221
|
+
const passwordHash2 = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword("sonicjs!");
|
|
3222
3222
|
await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
|
|
3223
3223
|
return c.json({
|
|
3224
3224
|
message: "Admin user already exists (password updated)",
|
|
@@ -3230,7 +3230,7 @@ authRoutes.post(
|
|
|
3230
3230
|
}
|
|
3231
3231
|
});
|
|
3232
3232
|
}
|
|
3233
|
-
const passwordHash = await
|
|
3233
|
+
const passwordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword("sonicjs!");
|
|
3234
3234
|
const userId = "admin-user-id";
|
|
3235
3235
|
const now = Date.now();
|
|
3236
3236
|
const adminEmail = "admin@sonicjs.com".toLowerCase();
|
|
@@ -3451,7 +3451,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
3451
3451
|
if (existingUsername) {
|
|
3452
3452
|
return c.json({ error: "Username is already taken" }, 400);
|
|
3453
3453
|
}
|
|
3454
|
-
const passwordHash = await
|
|
3454
|
+
const passwordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
|
|
3455
3455
|
const updateStmt = db.prepare(`
|
|
3456
3456
|
UPDATE users SET
|
|
3457
3457
|
username = ?,
|
|
@@ -3470,7 +3470,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
3470
3470
|
Date.now(),
|
|
3471
3471
|
invitedUser.id
|
|
3472
3472
|
).run();
|
|
3473
|
-
const authToken = await
|
|
3473
|
+
const authToken = await chunkIT2TC4ZD_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role, c.env.JWT_SECRET);
|
|
3474
3474
|
cookie.setCookie(c, "auth_token", authToken, {
|
|
3475
3475
|
httpOnly: true,
|
|
3476
3476
|
secure: true,
|
|
@@ -3487,7 +3487,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
3487
3487
|
});
|
|
3488
3488
|
authRoutes.post(
|
|
3489
3489
|
"/request-password-reset",
|
|
3490
|
-
|
|
3490
|
+
chunkIT2TC4ZD_cjs.rateLimit({ max: 3, windowMs: 15 * 60 * 1e3, keyPrefix: "password-reset" }),
|
|
3491
3491
|
async (c) => {
|
|
3492
3492
|
try {
|
|
3493
3493
|
const formData = await c.req.formData();
|
|
@@ -3705,7 +3705,7 @@ authRoutes.post("/reset-password", async (c) => {
|
|
|
3705
3705
|
if (Date.now() > user.password_reset_expires) {
|
|
3706
3706
|
return c.json({ error: "Reset token has expired" }, 400);
|
|
3707
3707
|
}
|
|
3708
|
-
const newPasswordHash = await
|
|
3708
|
+
const newPasswordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
|
|
3709
3709
|
try {
|
|
3710
3710
|
const historyStmt = db.prepare(`
|
|
3711
3711
|
INSERT INTO password_history (id, user_id, password_hash, created_at)
|
|
@@ -4777,6 +4777,39 @@ function getReadFieldValueScript() {
|
|
|
4777
4777
|
window.__sonicReadFieldValueInit = true;
|
|
4778
4778
|
|
|
4779
4779
|
window.sonicReadFieldValue = function(fieldWrapper) {
|
|
4780
|
+
const getDirectChild = (parent, selector) => {
|
|
4781
|
+
if (!(parent instanceof Element)) return null;
|
|
4782
|
+
return Array.from(parent.children).find(
|
|
4783
|
+
(child) => child instanceof Element && child.matches(selector),
|
|
4784
|
+
) || null;
|
|
4785
|
+
};
|
|
4786
|
+
const getDirectStructuredSubfields = (host) =>
|
|
4787
|
+
Array.from(host.children).filter(
|
|
4788
|
+
(child) => child instanceof Element && child.classList.contains('structured-subfield'),
|
|
4789
|
+
);
|
|
4790
|
+
const getStructuredObjectFieldsHost = (container) => {
|
|
4791
|
+
const directFieldsHost = getDirectChild(container, '[data-structured-object-fields]');
|
|
4792
|
+
if (directFieldsHost) return directFieldsHost;
|
|
4793
|
+
const groupContent = getDirectChild(container, '.field-group-content');
|
|
4794
|
+
const nestedFieldsHost = groupContent
|
|
4795
|
+
? getDirectChild(groupContent, '[data-structured-object-fields]')
|
|
4796
|
+
: null;
|
|
4797
|
+
if (nestedFieldsHost) return nestedFieldsHost;
|
|
4798
|
+
return getDirectChild(container, '[data-array-item-fields]') || container;
|
|
4799
|
+
};
|
|
4800
|
+
const getDirectStructuredObject = (fieldWrapper) => {
|
|
4801
|
+
const directObject = getDirectChild(fieldWrapper, '[data-structured-object]');
|
|
4802
|
+
if (directObject) return directObject;
|
|
4803
|
+
const formGroup = getDirectChild(fieldWrapper, '.form-group');
|
|
4804
|
+
return formGroup ? getDirectChild(formGroup, '[data-structured-object]') : null;
|
|
4805
|
+
};
|
|
4806
|
+
const getDirectStructuredArray = (fieldWrapper) => {
|
|
4807
|
+
const directArray = getDirectChild(fieldWrapper, '[data-structured-array]');
|
|
4808
|
+
if (directArray) return directArray;
|
|
4809
|
+
const formGroup = getDirectChild(fieldWrapper, '.form-group');
|
|
4810
|
+
return formGroup ? getDirectChild(formGroup, '[data-structured-array]') : null;
|
|
4811
|
+
};
|
|
4812
|
+
|
|
4780
4813
|
const fieldType = fieldWrapper.dataset.fieldType;
|
|
4781
4814
|
const select = fieldWrapper.querySelector('select');
|
|
4782
4815
|
const textarea = fieldWrapper.querySelector('textarea');
|
|
@@ -4786,7 +4819,47 @@ function getReadFieldValueScript() {
|
|
|
4786
4819
|
const nonHiddenInput = inputs.find((input) => input.type !== 'hidden' && input.type !== 'checkbox');
|
|
4787
4820
|
const hiddenInput = inputs.find((input) => input.type === 'hidden');
|
|
4788
4821
|
|
|
4822
|
+
const readStructuredFieldsHost = (host) => {
|
|
4823
|
+
const fields = getDirectStructuredSubfields(host);
|
|
4824
|
+
if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
|
|
4825
|
+
return window.sonicReadFieldValue(fields[0]);
|
|
4826
|
+
}
|
|
4827
|
+
return fields.reduce((acc, subfield) => {
|
|
4828
|
+
const fieldName = subfield.dataset.structuredField;
|
|
4829
|
+
if (!fieldName || fieldName === '__value') return acc;
|
|
4830
|
+
acc[fieldName] = window.sonicReadFieldValue(subfield);
|
|
4831
|
+
return acc;
|
|
4832
|
+
}, {});
|
|
4833
|
+
};
|
|
4834
|
+
|
|
4835
|
+
const readStructuredObject = () => {
|
|
4836
|
+
const objectContainer = getDirectStructuredObject(fieldWrapper);
|
|
4837
|
+
if (!objectContainer) return null;
|
|
4838
|
+
const host = getStructuredObjectFieldsHost(objectContainer);
|
|
4839
|
+
return readStructuredFieldsHost(host);
|
|
4840
|
+
};
|
|
4841
|
+
|
|
4842
|
+
const readStructuredArray = () => {
|
|
4843
|
+
const arrayContainer = getDirectStructuredArray(fieldWrapper);
|
|
4844
|
+
if (!arrayContainer) return null;
|
|
4845
|
+
const list = arrayContainer.querySelector('[data-structured-array-list]');
|
|
4846
|
+
if (!list) return [];
|
|
4847
|
+
const items = Array.from(list.querySelectorAll(':scope > .structured-array-item'));
|
|
4848
|
+
return items.map((item) => {
|
|
4849
|
+
const host =
|
|
4850
|
+
item.querySelector(':scope > [data-array-item-fields]') ||
|
|
4851
|
+
item.querySelector('[data-array-item-fields]') ||
|
|
4852
|
+
item;
|
|
4853
|
+
return readStructuredFieldsHost(host);
|
|
4854
|
+
});
|
|
4855
|
+
};
|
|
4856
|
+
|
|
4789
4857
|
if (fieldType === 'object' || fieldType === 'array') {
|
|
4858
|
+
const liveValue = fieldType === 'array' ? readStructuredArray() : readStructuredObject();
|
|
4859
|
+
if (liveValue !== null) {
|
|
4860
|
+
return liveValue;
|
|
4861
|
+
}
|
|
4862
|
+
|
|
4790
4863
|
if (!hiddenInput) {
|
|
4791
4864
|
return fieldType === 'array' ? [] : {};
|
|
4792
4865
|
}
|
|
@@ -4835,6 +4908,15 @@ function getReadFieldValueScript() {
|
|
|
4835
4908
|
</script>
|
|
4836
4909
|
`;
|
|
4837
4910
|
}
|
|
4911
|
+
var STRUCTURED_INDEX_TOKEN = "__INDEX__";
|
|
4912
|
+
var BLOCK_INDEX_TOKEN = "__BLOCK_INDEX__";
|
|
4913
|
+
function sanitizeStructuredGroupId(fieldName) {
|
|
4914
|
+
return `object-${fieldName}`.split(BLOCK_INDEX_TOKEN).map(
|
|
4915
|
+
(blockSegment) => blockSegment.split(STRUCTURED_INDEX_TOKEN).map(
|
|
4916
|
+
(segment) => segment.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
|
|
4917
|
+
).join(STRUCTURED_INDEX_TOKEN)
|
|
4918
|
+
).join(BLOCK_INDEX_TOKEN);
|
|
4919
|
+
}
|
|
4838
4920
|
function isMarkdownEditorFieldType(fieldType) {
|
|
4839
4921
|
return fieldType === "markdown" || fieldType === "mdxeditor" || fieldType === "easymde";
|
|
4840
4922
|
}
|
|
@@ -5415,12 +5497,14 @@ function renderDynamicField(field, options = {}) {
|
|
|
5415
5497
|
|
|
5416
5498
|
${isMultiple ? `
|
|
5417
5499
|
<div class="media-preview-grid grid grid-cols-4 gap-2 mb-2 ${mediaValues.length === 0 ? "hidden" : ""}" id="${fieldId}-preview">
|
|
5418
|
-
${mediaValues.map(
|
|
5500
|
+
${mediaValues.map(
|
|
5501
|
+
(url, idx) => `
|
|
5419
5502
|
<div class="relative media-preview-item" data-url="${url}">
|
|
5420
5503
|
${renderMediaPreview(url, `Media ${idx + 1}`, "w-full h-24 object-cover rounded-lg border border-white/20")}
|
|
5421
5504
|
<button
|
|
5422
5505
|
type="button"
|
|
5423
5506
|
onclick="removeMediaFromMultiple('${fieldId}', '${url}')"
|
|
5507
|
+
data-media-remove="true"
|
|
5424
5508
|
class="absolute top-1 right-1 bg-red-600 text-white rounded-full p-1 hover:bg-red-700"
|
|
5425
5509
|
${disabled ? "disabled" : ""}
|
|
5426
5510
|
>
|
|
@@ -5429,7 +5513,8 @@ function renderDynamicField(field, options = {}) {
|
|
|
5429
5513
|
</svg>
|
|
5430
5514
|
</button>
|
|
5431
5515
|
</div>
|
|
5432
|
-
`
|
|
5516
|
+
`
|
|
5517
|
+
).join("")}
|
|
5433
5518
|
</div>
|
|
5434
5519
|
` : `
|
|
5435
5520
|
<div class="media-preview ${singleValue ? "" : "hidden"}" id="${fieldId}-preview">
|
|
@@ -5453,6 +5538,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5453
5538
|
<button
|
|
5454
5539
|
type="button"
|
|
5455
5540
|
onclick="clearMediaField('${fieldId}')"
|
|
5541
|
+
data-media-remove="true"
|
|
5456
5542
|
class="inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all"
|
|
5457
5543
|
${disabled ? "disabled" : ""}
|
|
5458
5544
|
>
|
|
@@ -5486,7 +5572,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5486
5572
|
}
|
|
5487
5573
|
const showLabel = field.field_type !== "boolean";
|
|
5488
5574
|
return `
|
|
5489
|
-
<div class="form-group">
|
|
5575
|
+
<div class="form-group" data-has-errors="${errors.length > 0 ? "true" : "false"}">
|
|
5490
5576
|
${showLabel ? `
|
|
5491
5577
|
<label for="${fieldId}" class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2">
|
|
5492
5578
|
${escapeHtml3(field.field_label)}
|
|
@@ -5495,7 +5581,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5495
5581
|
` : ""}
|
|
5496
5582
|
${fieldHTML}
|
|
5497
5583
|
${errors.length > 0 ? `
|
|
5498
|
-
<div class="mt-2 text-sm text-pink-600 dark:text-pink-400">
|
|
5584
|
+
<div class="mt-2 text-sm text-pink-600 dark:text-pink-400" data-validation-error-message>
|
|
5499
5585
|
${errors.map((error) => `<div>${escapeHtml3(error)}</div>`).join("")}
|
|
5500
5586
|
</div>
|
|
5501
5587
|
` : ""}
|
|
@@ -5510,8 +5596,8 @@ function renderDynamicField(field, options = {}) {
|
|
|
5510
5596
|
function renderFieldGroup(title, fields, collapsible = false) {
|
|
5511
5597
|
const groupId = title.toLowerCase().replace(/\s+/g, "-");
|
|
5512
5598
|
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(
|
|
5599
|
+
<div class="field-group rounded-lg bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 mb-6" data-group-id="${escapeHtml3(groupId)}">
|
|
5600
|
+
<div class="field-group-header border-b border-zinc-950/5 dark:border-white/10 px-6 py-4 ${collapsible ? "cursor-pointer" : ""}" ${collapsible ? `onclick="toggleFieldGroup(this)"` : ""}>
|
|
5515
5601
|
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
|
|
5516
5602
|
${escapeHtml3(title)}
|
|
5517
5603
|
${collapsible ? `
|
|
@@ -5555,6 +5641,12 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
|
5555
5641
|
>
|
|
5556
5642
|
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(blockValues))}">
|
|
5557
5643
|
|
|
5644
|
+
<div class="flex items-center justify-between border-b border-zinc-950/5 dark:border-white/10 py-4">
|
|
5645
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">
|
|
5646
|
+
${escapeHtml3(field.field_label || "Content Blocks")}
|
|
5647
|
+
</h3>
|
|
5648
|
+
</div>
|
|
5649
|
+
|
|
5558
5650
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5559
5651
|
<div class="flex-1">
|
|
5560
5652
|
<select
|
|
@@ -5585,12 +5677,14 @@ function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
|
5585
5677
|
`;
|
|
5586
5678
|
}
|
|
5587
5679
|
function renderStructuredObjectField(field, options, baseClasses, errorClasses) {
|
|
5588
|
-
const { value = {}, pluginStatuses = {} } = options;
|
|
5680
|
+
const { value = {}, pluginStatuses = {}, errors = [] } = options;
|
|
5589
5681
|
const opts = field.field_options || {};
|
|
5590
5682
|
const properties = opts.properties && typeof opts.properties === "object" ? opts.properties : {};
|
|
5591
5683
|
const fieldId = `field-${field.field_name}`;
|
|
5592
5684
|
const fieldName = field.field_name;
|
|
5593
5685
|
const objectValue = normalizeStructuredObjectValue(value);
|
|
5686
|
+
const objectLayout = opts.objectLayout || "nested";
|
|
5687
|
+
const useNestedLayout = objectLayout !== "flat";
|
|
5594
5688
|
const subfields = Object.entries(properties).map(
|
|
5595
5689
|
([propertyName, propertyConfig]) => renderStructuredSubfield(
|
|
5596
5690
|
field,
|
|
@@ -5601,11 +5695,40 @@ function renderStructuredObjectField(field, options, baseClasses, errorClasses)
|
|
|
5601
5695
|
field.field_name
|
|
5602
5696
|
)
|
|
5603
5697
|
).join("");
|
|
5698
|
+
const groupTitle = field.field_label || field.field_name;
|
|
5699
|
+
if (!useNestedLayout) {
|
|
5700
|
+
return `
|
|
5701
|
+
<div class="space-y-4" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
|
|
5702
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
|
|
5703
|
+
<div class="flex items-center justify-between border-b border-zinc-950/5 dark:border-white/10 py-4 first-of-type:pt-0">
|
|
5704
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white">
|
|
5705
|
+
${escapeHtml3(groupTitle)}
|
|
5706
|
+
</h3>
|
|
5707
|
+
</div>
|
|
5708
|
+
<div class="space-y-4" data-structured-object-fields>
|
|
5709
|
+
${subfields}
|
|
5710
|
+
</div>
|
|
5711
|
+
</div>
|
|
5712
|
+
${getStructuredFieldScript()}
|
|
5713
|
+
`;
|
|
5714
|
+
}
|
|
5715
|
+
const groupId = sanitizeStructuredGroupId(field.field_name);
|
|
5716
|
+
const isCollapsed = errors.length > 0 ? false : opts.collapsed !== false;
|
|
5604
5717
|
return `
|
|
5605
|
-
<div class="
|
|
5606
|
-
<
|
|
5607
|
-
|
|
5608
|
-
|
|
5718
|
+
<div class="field-group rounded-lg shadow-sm mb-6" data-group-id="${escapeHtml3(groupId)}" data-structured-object data-field-name="${escapeHtml3(fieldName)}">
|
|
5719
|
+
<div class="field-group-header border-b border-zinc-950/5 dark:border-white/10 pr-6 pb-4 cursor-pointer" onclick="toggleFieldGroup(this)">
|
|
5720
|
+
<h3 class="text-base/7 font-semibold text-zinc-950 dark:text-white flex items-center">
|
|
5721
|
+
${escapeHtml3(groupTitle)}
|
|
5722
|
+
<svg id="${groupId}-icon" class="w-5 h-5 ml-2 transform transition-transform ${isCollapsed ? "-rotate-90" : ""} text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5723
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
5724
|
+
</svg>
|
|
5725
|
+
</h3>
|
|
5726
|
+
</div>
|
|
5727
|
+
<div id="${groupId}-content" class="field-group-content px-6 py-6 space-y-4 ${isCollapsed ? "hidden" : ""}">
|
|
5728
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml3(JSON.stringify(objectValue))}">
|
|
5729
|
+
<div class="space-y-4" data-structured-object-fields>
|
|
5730
|
+
${subfields}
|
|
5731
|
+
</div>
|
|
5609
5732
|
</div>
|
|
5610
5733
|
</div>
|
|
5611
5734
|
${getStructuredFieldScript()}
|
|
@@ -5676,7 +5799,7 @@ function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
|
|
|
5676
5799
|
function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginStatuses, arrayItemTitle) {
|
|
5677
5800
|
const itemFields = renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses);
|
|
5678
5801
|
return `
|
|
5679
|
-
<div class="structured-array-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-
|
|
5802
|
+
<div class="structured-array-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-zinc-600/5 p-4 shadow-lg shadow-zinc-950/20" data-array-index="${escapeHtml3(index)}" draggable="true">
|
|
5680
5803
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5681
5804
|
<div class="flex items-center gap-3">
|
|
5682
5805
|
<div class="drag-handle cursor-move text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-400" data-action="drag-handle" title="Drag to reorder">
|
|
@@ -5689,6 +5812,11 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
|
|
|
5689
5812
|
</div>
|
|
5690
5813
|
</div>
|
|
5691
5814
|
<div class="flex flex-wrap gap-2 text-xs">
|
|
5815
|
+
<button type="button" data-action="toggle-item" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10" aria-label="Expand item" title="Expand">
|
|
5816
|
+
<svg class="h-4 w-4 transition-transform -rotate-90 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" data-item-toggle-icon>
|
|
5817
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
|
5818
|
+
</svg>
|
|
5819
|
+
</button>
|
|
5692
5820
|
<button type="button" data-action="move-up" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent dark:disabled:hover:bg-transparent" aria-label="Move item up" title="Move up">
|
|
5693
5821
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
5694
5822
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
@@ -5707,7 +5835,7 @@ function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginSt
|
|
|
5707
5835
|
</button>
|
|
5708
5836
|
</div>
|
|
5709
5837
|
</div>
|
|
5710
|
-
<div class="mt-4 space-y-4" data-array-item-fields>
|
|
5838
|
+
<div class="mt-4 space-y-4 hidden" data-array-item-fields>
|
|
5711
5839
|
${itemFields}
|
|
5712
5840
|
</div>
|
|
5713
5841
|
</div>
|
|
@@ -5817,7 +5945,7 @@ function normalizeBlocksValue(value, discriminator) {
|
|
|
5817
5945
|
function renderBlockTemplate(field, block, discriminator, pluginStatuses) {
|
|
5818
5946
|
return `
|
|
5819
5947
|
<template data-block-template="${escapeHtml3(block.name)}">
|
|
5820
|
-
${renderBlockCard(field, block, discriminator,
|
|
5948
|
+
${renderBlockCard(field, block, discriminator, BLOCK_INDEX_TOKEN, {}, pluginStatuses)}
|
|
5821
5949
|
</template>
|
|
5822
5950
|
`;
|
|
5823
5951
|
}
|
|
@@ -5859,7 +5987,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5859
5987
|
`;
|
|
5860
5988
|
}).join("");
|
|
5861
5989
|
return `
|
|
5862
|
-
<div class="blocks-item rounded-lg border border-zinc-200 dark:border-white/10
|
|
5990
|
+
<div class="blocks-item rounded-lg border border-zinc-200 dark:border-white/10 dark:bg-zinc-600/5 p-4 shadow-lg shadow-zinc-950/20" data-block-type="${escapeHtml3(block.name)}" data-block-discriminator="${escapeHtml3(discriminator)}" draggable="true">
|
|
5863
5991
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
5864
5992
|
<div class="flex items-start gap-3">
|
|
5865
5993
|
<div class="drag-handle cursor-move text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-400" data-action="drag-handle" title="Drag to reorder">
|
|
@@ -5867,7 +5995,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5867
5995
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
|
|
5868
5996
|
</svg>
|
|
5869
5997
|
</div>
|
|
5870
|
-
<div>
|
|
5998
|
+
<div class="cursor-pointer" data-action="toggle-block">
|
|
5871
5999
|
<div class="text-sm font-semibold text-zinc-900 dark:text-white">
|
|
5872
6000
|
${escapeHtml3(block.label)}
|
|
5873
6001
|
<span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-block-order-label></span>
|
|
@@ -5876,6 +6004,11 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5876
6004
|
</div>
|
|
5877
6005
|
</div>
|
|
5878
6006
|
<div class="flex flex-wrap gap-2 text-xs">
|
|
6007
|
+
<button type="button" data-action="toggle-block" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10" aria-label="Expand block" title="Expand">
|
|
6008
|
+
<svg class="h-4 w-4 transition-transform -rotate-90 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" data-block-toggle-icon>
|
|
6009
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
|
6010
|
+
</svg>
|
|
6011
|
+
</button>
|
|
5879
6012
|
<button type="button" data-action="move-up" class="inline-flex items-center justify-center rounded-md border border-zinc-200 px-2 py-1 text-zinc-600 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/10 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent dark:disabled:hover:bg-transparent" aria-label="Move block up" title="Move up">
|
|
5880
6013
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
5881
6014
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
@@ -5894,7 +6027,7 @@ function renderBlockCard(field, block, discriminator, index, data, pluginStatuse
|
|
|
5894
6027
|
</button>
|
|
5895
6028
|
</div>
|
|
5896
6029
|
</div>
|
|
5897
|
-
<div class="mt-4 space-y-4">
|
|
6030
|
+
<div class="mt-4 space-y-4 hidden" data-block-content>
|
|
5898
6031
|
${blockFields}
|
|
5899
6032
|
</div>
|
|
5900
6033
|
</div>
|
|
@@ -5928,9 +6061,101 @@ function getStructuredFieldScript() {
|
|
|
5928
6061
|
|
|
5929
6062
|
function initializeStructuredFields() {
|
|
5930
6063
|
const readFieldValue = window.sonicReadFieldValue;
|
|
6064
|
+
const getDirectChild = (parent, selector) => {
|
|
6065
|
+
if (!(parent instanceof Element)) return null;
|
|
6066
|
+
return Array.from(parent.children).find(
|
|
6067
|
+
(child) => child instanceof Element && child.matches(selector),
|
|
6068
|
+
) || null;
|
|
6069
|
+
};
|
|
6070
|
+
const getDirectStructuredSubfields = (host) =>
|
|
6071
|
+
Array.from(host.children).filter(
|
|
6072
|
+
(child) => child instanceof Element && child.classList.contains('structured-subfield'),
|
|
6073
|
+
);
|
|
6074
|
+
const getStructuredValueHost = (container) => {
|
|
6075
|
+
const directObjectHost = getDirectChild(container, '[data-structured-object-fields]');
|
|
6076
|
+
if (directObjectHost) return directObjectHost;
|
|
6077
|
+
const groupContent = getDirectChild(container, '.field-group-content');
|
|
6078
|
+
const nestedObjectHost = groupContent
|
|
6079
|
+
? getDirectChild(groupContent, '[data-structured-object-fields]')
|
|
6080
|
+
: null;
|
|
6081
|
+
if (nestedObjectHost) return nestedObjectHost;
|
|
6082
|
+
return getDirectChild(container, '[data-array-item-fields]') || container;
|
|
6083
|
+
};
|
|
6084
|
+
const getCollectionScope = () => {
|
|
6085
|
+
const url = new URL(window.location.href);
|
|
6086
|
+
const collectionFromQuery = url.searchParams.get('collection');
|
|
6087
|
+
const form = document.getElementById('content-form');
|
|
6088
|
+
const collectionInput = form?.querySelector('input[name="collection_id"]');
|
|
6089
|
+
const collectionFromForm = collectionInput instanceof HTMLInputElement ? collectionInput.value : '';
|
|
6090
|
+
const collectionId = collectionFromQuery || collectionFromForm || '';
|
|
6091
|
+
return window.location.pathname + ':' + collectionId;
|
|
6092
|
+
};
|
|
6093
|
+
|
|
6094
|
+
const getArrayStateKey = (container) => {
|
|
6095
|
+
const fieldName = container.dataset.fieldName || 'unknown';
|
|
6096
|
+
return 'sonic:ui:repeaters:' + getCollectionScope() + ':' + fieldName;
|
|
6097
|
+
};
|
|
6098
|
+
|
|
6099
|
+
const readArrayState = (container) => {
|
|
6100
|
+
try {
|
|
6101
|
+
const raw = sessionStorage.getItem(getArrayStateKey(container));
|
|
6102
|
+
if (!raw) return null;
|
|
6103
|
+
const parsed = JSON.parse(raw);
|
|
6104
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
6105
|
+
} catch {
|
|
6106
|
+
return null;
|
|
6107
|
+
}
|
|
6108
|
+
};
|
|
6109
|
+
|
|
6110
|
+
const writeArrayState = (container, state) => {
|
|
6111
|
+
try {
|
|
6112
|
+
sessionStorage.setItem(getArrayStateKey(container), JSON.stringify(state));
|
|
6113
|
+
} catch {}
|
|
6114
|
+
};
|
|
6115
|
+
|
|
6116
|
+
const setArrayItemExpanded = (item, isExpanded) => {
|
|
6117
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6118
|
+
const icon = item.querySelector('[data-item-toggle-icon]');
|
|
6119
|
+
if (content instanceof HTMLElement) {
|
|
6120
|
+
content.classList.toggle('hidden', !isExpanded);
|
|
6121
|
+
}
|
|
6122
|
+
if (icon instanceof Element) {
|
|
6123
|
+
icon.classList.toggle('-rotate-90', !isExpanded);
|
|
6124
|
+
}
|
|
6125
|
+
};
|
|
6126
|
+
|
|
6127
|
+
const getArrayItems = (container, list) => {
|
|
6128
|
+
if (list) {
|
|
6129
|
+
return Array.from(list.querySelectorAll(':scope > .structured-array-item'));
|
|
6130
|
+
}
|
|
6131
|
+
return Array.from(
|
|
6132
|
+
container.querySelectorAll(':scope > [data-structured-array-list] > .structured-array-item'),
|
|
6133
|
+
);
|
|
6134
|
+
};
|
|
6135
|
+
|
|
6136
|
+
const captureArrayState = (container) => {
|
|
6137
|
+
return getArrayItems(container).map((item) => {
|
|
6138
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6139
|
+
return content instanceof HTMLElement ? !content.classList.contains('hidden') : false;
|
|
6140
|
+
});
|
|
6141
|
+
};
|
|
6142
|
+
|
|
6143
|
+
const applyArrayState = (container, state) => {
|
|
6144
|
+
const items = getArrayItems(container);
|
|
6145
|
+
items.forEach((item, index) => {
|
|
6146
|
+
if (typeof state[index] === 'boolean') {
|
|
6147
|
+
setArrayItemExpanded(item, state[index]);
|
|
6148
|
+
}
|
|
6149
|
+
});
|
|
6150
|
+
};
|
|
6151
|
+
|
|
6152
|
+
const syncArrayState = (container) => {
|
|
6153
|
+
writeArrayState(container, captureArrayState(container));
|
|
6154
|
+
};
|
|
5931
6155
|
|
|
5932
6156
|
const readStructuredValue = (container) => {
|
|
5933
|
-
const
|
|
6157
|
+
const fieldHost = getStructuredValueHost(container);
|
|
6158
|
+
const fields = getDirectStructuredSubfields(fieldHost);
|
|
5934
6159
|
if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
|
|
5935
6160
|
return readFieldValue(fields[0]);
|
|
5936
6161
|
}
|
|
@@ -5944,111 +6169,229 @@ function getStructuredFieldScript() {
|
|
|
5944
6169
|
};
|
|
5945
6170
|
|
|
5946
6171
|
document.querySelectorAll('[data-structured-object]').forEach((container) => {
|
|
6172
|
+
if (container.closest('template')) {
|
|
6173
|
+
return;
|
|
6174
|
+
}
|
|
5947
6175
|
if (container.dataset.structuredInitialized === 'true') {
|
|
5948
6176
|
return;
|
|
5949
6177
|
}
|
|
5950
|
-
container.dataset.
|
|
5951
|
-
|
|
6178
|
+
if (container.dataset.structuredInitializing === 'true') {
|
|
6179
|
+
return;
|
|
6180
|
+
}
|
|
6181
|
+
container.dataset.structuredInitializing = 'true';
|
|
6182
|
+
try {
|
|
6183
|
+
const hiddenInput = container.querySelector('input[type="hidden"]');
|
|
5952
6184
|
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
6185
|
+
const updateHiddenInput = () => {
|
|
6186
|
+
if (!hiddenInput) return;
|
|
6187
|
+
const value = readStructuredValue(container);
|
|
6188
|
+
hiddenInput.value = JSON.stringify(value);
|
|
6189
|
+
};
|
|
5958
6190
|
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
6191
|
+
container.addEventListener('input', updateHiddenInput);
|
|
6192
|
+
container.addEventListener('change', updateHiddenInput);
|
|
6193
|
+
updateHiddenInput();
|
|
6194
|
+
container.dataset.structuredInitialized = 'true';
|
|
6195
|
+
} catch (error) {
|
|
6196
|
+
delete container.dataset.structuredInitialized;
|
|
6197
|
+
console.error('[structured-object] initialization failed', error);
|
|
6198
|
+
} finally {
|
|
6199
|
+
delete container.dataset.structuredInitializing;
|
|
6200
|
+
}
|
|
5962
6201
|
});
|
|
5963
6202
|
|
|
5964
6203
|
document.querySelectorAll('[data-structured-array]').forEach((container) => {
|
|
6204
|
+
if (container.closest('template')) {
|
|
6205
|
+
return;
|
|
6206
|
+
}
|
|
5965
6207
|
if (container.dataset.structuredInitialized === 'true') {
|
|
5966
6208
|
return;
|
|
5967
6209
|
}
|
|
5968
|
-
container.dataset.
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
6210
|
+
if (container.dataset.structuredInitializing === 'true') {
|
|
6211
|
+
return;
|
|
6212
|
+
}
|
|
6213
|
+
container.dataset.structuredInitializing = 'true';
|
|
6214
|
+
try {
|
|
6215
|
+
const list = container.querySelector(':scope > [data-structured-array-list]');
|
|
6216
|
+
const hiddenInput = container.querySelector(':scope > input[type="hidden"]');
|
|
6217
|
+
const template = container.querySelector(':scope > template[data-structured-array-template]');
|
|
6218
|
+
if (
|
|
6219
|
+
template instanceof HTMLTemplateElement &&
|
|
6220
|
+
typeof template.innerHTML === 'string' &&
|
|
6221
|
+
template.innerHTML.trim()
|
|
6222
|
+
) {
|
|
6223
|
+
container.__sonicStructuredArrayTemplate = template.innerHTML;
|
|
6224
|
+
}
|
|
5972
6225
|
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
6226
|
+
const getLiveList = () =>
|
|
6227
|
+
list || container.querySelector(':scope > [data-structured-array-list]');
|
|
6228
|
+
const getLiveHiddenInput = () =>
|
|
6229
|
+
hiddenInput || container.querySelector(':scope > input[type="hidden"]');
|
|
6230
|
+
const getTemplateHtml = () => {
|
|
6231
|
+
if (typeof container.__sonicStructuredArrayTemplate === 'string' &&
|
|
6232
|
+
container.__sonicStructuredArrayTemplate.trim()) {
|
|
6233
|
+
return container.__sonicStructuredArrayTemplate;
|
|
5979
6234
|
}
|
|
5980
6235
|
|
|
5981
|
-
const
|
|
5982
|
-
|
|
5983
|
-
|
|
6236
|
+
const liveTemplate =
|
|
6237
|
+
template instanceof HTMLTemplateElement
|
|
6238
|
+
? template
|
|
6239
|
+
: container.querySelector(':scope > template[data-structured-array-template]');
|
|
6240
|
+
if (
|
|
6241
|
+
liveTemplate instanceof HTMLTemplateElement &&
|
|
6242
|
+
typeof liveTemplate.innerHTML === 'string' &&
|
|
6243
|
+
liveTemplate.innerHTML.trim()
|
|
6244
|
+
) {
|
|
6245
|
+
container.__sonicStructuredArrayTemplate = liveTemplate.innerHTML;
|
|
6246
|
+
return liveTemplate.innerHTML;
|
|
5984
6247
|
}
|
|
6248
|
+
return typeof container.__sonicStructuredArrayTemplate === 'string'
|
|
6249
|
+
? container.__sonicStructuredArrayTemplate
|
|
6250
|
+
: '';
|
|
6251
|
+
};
|
|
5985
6252
|
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
6253
|
+
const updateOrderLabels = () => {
|
|
6254
|
+
const liveList = getLiveList();
|
|
6255
|
+
if (!liveList) return;
|
|
6256
|
+
const items = getArrayItems(container, liveList);
|
|
6257
|
+
items.forEach((item, index) => {
|
|
6258
|
+
const label = item.querySelector('[data-array-order-label]');
|
|
6259
|
+
if (label) {
|
|
6260
|
+
label.textContent = '#'+ (index + 1);
|
|
6261
|
+
}
|
|
6262
|
+
|
|
6263
|
+
const moveUpButton = item.querySelector('[data-action="move-up"]');
|
|
6264
|
+
if (moveUpButton instanceof HTMLButtonElement) {
|
|
6265
|
+
moveUpButton.disabled = index === 0;
|
|
6266
|
+
}
|
|
6267
|
+
|
|
6268
|
+
const moveDownButton = item.querySelector('[data-action="move-down"]');
|
|
6269
|
+
if (moveDownButton instanceof HTMLButtonElement) {
|
|
6270
|
+
moveDownButton.disabled = index === items.length - 1;
|
|
6271
|
+
}
|
|
6272
|
+
});
|
|
6273
|
+
};
|
|
6274
|
+
|
|
6275
|
+
const updateHiddenInput = () => {
|
|
6276
|
+
const liveHiddenInput = getLiveHiddenInput();
|
|
6277
|
+
const liveList = getLiveList();
|
|
6278
|
+
if (!liveHiddenInput || !liveList) return;
|
|
6279
|
+
const items = getArrayItems(container, liveList);
|
|
6280
|
+
const values = items.map((item) => readStructuredValue(item));
|
|
6281
|
+
liveHiddenInput.value = JSON.stringify(values);
|
|
6282
|
+
// Notify parent structured containers after non-input actions (add/remove/move)
|
|
6283
|
+
// so nested array mutations are persisted correctly.
|
|
6284
|
+
liveHiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
6285
|
+
|
|
6286
|
+
const emptyState = liveList.querySelector(':scope > [data-structured-empty]');
|
|
6287
|
+
if (emptyState) {
|
|
6288
|
+
emptyState.style.display = values.length === 0 ? 'block' : 'none';
|
|
5989
6289
|
}
|
|
5990
|
-
|
|
5991
|
-
|
|
6290
|
+
updateOrderLabels();
|
|
6291
|
+
};
|
|
5992
6292
|
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
6293
|
+
const addArrayItem = () => {
|
|
6294
|
+
const liveList = getLiveList();
|
|
6295
|
+
if (!liveList) return;
|
|
6296
|
+
const templateHtml = getTemplateHtml();
|
|
6297
|
+
if (!templateHtml) return;
|
|
6298
|
+
try {
|
|
6299
|
+
const nextIndex = getArrayItems(container, liveList).length;
|
|
6300
|
+
const html = templateHtml.replace(/__INDEX__/g, String(nextIndex));
|
|
6301
|
+
liveList.insertAdjacentHTML('beforeend', html);
|
|
6302
|
+
const newItem = liveList.lastElementChild;
|
|
6303
|
+
if (newItem instanceof HTMLElement) {
|
|
6304
|
+
// Ensure cloned template content can be initialized even if stale
|
|
6305
|
+
// data-structured-initialized attributes were copied.
|
|
6306
|
+
newItem
|
|
6307
|
+
.querySelectorAll('[data-structured-object], [data-structured-array]')
|
|
6308
|
+
.forEach((nestedContainer) => {
|
|
6309
|
+
if (nestedContainer instanceof HTMLElement) {
|
|
6310
|
+
delete nestedContainer.dataset.structuredInitialized;
|
|
6311
|
+
}
|
|
6312
|
+
});
|
|
6313
|
+
setArrayItemExpanded(newItem, true);
|
|
6314
|
+
}
|
|
6315
|
+
if (typeof initializeTinyMCE === 'function') {
|
|
6316
|
+
initializeTinyMCE();
|
|
6317
|
+
}
|
|
6318
|
+
if (typeof window.initializeQuillEditors === 'function') {
|
|
6319
|
+
window.initializeQuillEditors();
|
|
6320
|
+
}
|
|
6321
|
+
if (typeof initializeMDXEditor === 'function') {
|
|
6322
|
+
initializeMDXEditor();
|
|
6323
|
+
}
|
|
6324
|
+
if (typeof window.initializeStructuredFields === 'function') {
|
|
6325
|
+
window.initializeStructuredFields();
|
|
6326
|
+
}
|
|
6327
|
+
updateHiddenInput();
|
|
6328
|
+
syncArrayState(container);
|
|
6329
|
+
} catch (error) {
|
|
6330
|
+
console.error('[structured-array] add-item failed', error);
|
|
6331
|
+
}
|
|
6332
|
+
};
|
|
5998
6333
|
|
|
5999
|
-
const
|
|
6000
|
-
|
|
6001
|
-
|
|
6334
|
+
const topLevelAddButton = container.querySelector(
|
|
6335
|
+
':scope > .flex.items-center.justify-between.gap-3 [data-action="add-item"]',
|
|
6336
|
+
);
|
|
6337
|
+
if (topLevelAddButton instanceof HTMLElement) {
|
|
6338
|
+
topLevelAddButton.addEventListener('click', (event) => {
|
|
6339
|
+
event.preventDefault();
|
|
6340
|
+
event.stopPropagation();
|
|
6341
|
+
addArrayItem();
|
|
6342
|
+
});
|
|
6002
6343
|
}
|
|
6003
|
-
updateOrderLabels();
|
|
6004
|
-
};
|
|
6005
6344
|
|
|
6006
|
-
|
|
6007
|
-
window.initializeDragSortable
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6345
|
+
const dragList = getLiveList();
|
|
6346
|
+
if (typeof window.initializeDragSortable === 'function' && dragList) {
|
|
6347
|
+
window.initializeDragSortable(dragList, {
|
|
6348
|
+
itemSelector: '.structured-array-item',
|
|
6349
|
+
handleSelector: '[data-action="drag-handle"]',
|
|
6350
|
+
onUpdate: () => {
|
|
6351
|
+
updateHiddenInput();
|
|
6352
|
+
syncArrayState(container);
|
|
6353
|
+
}
|
|
6354
|
+
});
|
|
6355
|
+
}
|
|
6013
6356
|
|
|
6014
|
-
|
|
6357
|
+
container.addEventListener('click', (event) => {
|
|
6015
6358
|
const target = event.target;
|
|
6016
6359
|
if (!(target instanceof Element)) return;
|
|
6017
6360
|
const actionButton = target.closest('[data-action]');
|
|
6018
6361
|
if (!actionButton || actionButton.hasAttribute('disabled')) return;
|
|
6362
|
+
const actionOwner = actionButton.closest('[data-structured-array]');
|
|
6363
|
+
if (actionOwner !== container) return;
|
|
6019
6364
|
|
|
6020
|
-
|
|
6365
|
+
const action = actionButton.getAttribute('data-action');
|
|
6021
6366
|
|
|
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();
|
|
6367
|
+
if (action === 'add-item') {
|
|
6368
|
+
addArrayItem();
|
|
6369
|
+
return;
|
|
6035
6370
|
}
|
|
6036
|
-
updateHiddenInput();
|
|
6037
|
-
return;
|
|
6038
|
-
}
|
|
6039
6371
|
|
|
6040
6372
|
const item = actionButton.closest('.structured-array-item');
|
|
6041
|
-
|
|
6373
|
+
const liveList = getLiveList();
|
|
6374
|
+
if (!item || !liveList) return;
|
|
6375
|
+
|
|
6376
|
+
if (action === 'toggle-item') {
|
|
6377
|
+
const content = item.querySelector('[data-array-item-fields]');
|
|
6378
|
+
if (!(content instanceof HTMLElement)) return;
|
|
6379
|
+
setArrayItemExpanded(item, content.classList.contains('hidden'));
|
|
6380
|
+
syncArrayState(container);
|
|
6381
|
+
return;
|
|
6382
|
+
}
|
|
6042
6383
|
|
|
6043
6384
|
if (action === 'remove-item') {
|
|
6044
6385
|
if (typeof requestRepeaterDelete === 'function') {
|
|
6045
6386
|
requestRepeaterDelete(() => {
|
|
6046
6387
|
item.remove();
|
|
6047
6388
|
updateHiddenInput();
|
|
6389
|
+
syncArrayState(container);
|
|
6048
6390
|
});
|
|
6049
6391
|
} else {
|
|
6050
6392
|
item.remove();
|
|
6051
6393
|
updateHiddenInput();
|
|
6394
|
+
syncArrayState(container);
|
|
6052
6395
|
}
|
|
6053
6396
|
return;
|
|
6054
6397
|
}
|
|
@@ -6056,8 +6399,9 @@ function getStructuredFieldScript() {
|
|
|
6056
6399
|
if (action === 'move-up') {
|
|
6057
6400
|
const previous = item.previousElementSibling;
|
|
6058
6401
|
if (previous) {
|
|
6059
|
-
|
|
6402
|
+
liveList.insertBefore(item, previous);
|
|
6060
6403
|
updateHiddenInput();
|
|
6404
|
+
syncArrayState(container);
|
|
6061
6405
|
}
|
|
6062
6406
|
return;
|
|
6063
6407
|
}
|
|
@@ -6065,29 +6409,43 @@ function getStructuredFieldScript() {
|
|
|
6065
6409
|
if (action === 'move-down') {
|
|
6066
6410
|
const next = item.nextElementSibling;
|
|
6067
6411
|
if (next) {
|
|
6068
|
-
|
|
6412
|
+
liveList.insertBefore(next, item);
|
|
6069
6413
|
updateHiddenInput();
|
|
6414
|
+
syncArrayState(container);
|
|
6070
6415
|
}
|
|
6071
6416
|
}
|
|
6072
|
-
|
|
6417
|
+
});
|
|
6073
6418
|
|
|
6074
|
-
|
|
6419
|
+
container.addEventListener('input', (event) => {
|
|
6075
6420
|
const target = event.target;
|
|
6076
6421
|
if (!(target instanceof Element)) return;
|
|
6077
6422
|
if (target.closest('[data-structured-array-list]')) {
|
|
6078
6423
|
updateHiddenInput();
|
|
6079
6424
|
}
|
|
6080
|
-
|
|
6425
|
+
});
|
|
6081
6426
|
|
|
6082
|
-
|
|
6427
|
+
container.addEventListener('change', (event) => {
|
|
6083
6428
|
const target = event.target;
|
|
6084
6429
|
if (!(target instanceof Element)) return;
|
|
6085
6430
|
if (target.closest('[data-structured-array-list]')) {
|
|
6086
6431
|
updateHiddenInput();
|
|
6087
6432
|
}
|
|
6088
|
-
|
|
6433
|
+
});
|
|
6089
6434
|
|
|
6090
|
-
|
|
6435
|
+
updateHiddenInput();
|
|
6436
|
+
const savedArrayState = readArrayState(container);
|
|
6437
|
+
if (savedArrayState) {
|
|
6438
|
+
applyArrayState(container, savedArrayState);
|
|
6439
|
+
} else {
|
|
6440
|
+
syncArrayState(container);
|
|
6441
|
+
}
|
|
6442
|
+
container.dataset.structuredInitialized = 'true';
|
|
6443
|
+
} catch (error) {
|
|
6444
|
+
delete container.dataset.structuredInitialized;
|
|
6445
|
+
console.error('[structured-array] initialization failed', error);
|
|
6446
|
+
} finally {
|
|
6447
|
+
delete container.dataset.structuredInitializing;
|
|
6448
|
+
}
|
|
6091
6449
|
});
|
|
6092
6450
|
}
|
|
6093
6451
|
|
|
@@ -6102,7 +6460,10 @@ function getStructuredFieldScript() {
|
|
|
6102
6460
|
document.addEventListener('htmx:afterSwap', function() {
|
|
6103
6461
|
setTimeout(initializeStructuredFields, 50);
|
|
6104
6462
|
});
|
|
6105
|
-
} else if (
|
|
6463
|
+
} else if (
|
|
6464
|
+
typeof window.initializeStructuredFields === 'function' &&
|
|
6465
|
+
document.readyState !== 'loading'
|
|
6466
|
+
) {
|
|
6106
6467
|
window.initializeStructuredFields();
|
|
6107
6468
|
}
|
|
6108
6469
|
</script>
|
|
@@ -6114,6 +6475,68 @@ function getBlocksFieldScript() {
|
|
|
6114
6475
|
<script>
|
|
6115
6476
|
if (!window.__sonicBlocksFieldInit) {
|
|
6116
6477
|
window.__sonicBlocksFieldInit = true;
|
|
6478
|
+
const getCollectionScope = () => {
|
|
6479
|
+
const url = new URL(window.location.href);
|
|
6480
|
+
const collectionFromQuery = url.searchParams.get('collection');
|
|
6481
|
+
const form = document.getElementById('content-form');
|
|
6482
|
+
const collectionInput = form?.querySelector('input[name="collection_id"]');
|
|
6483
|
+
const collectionFromForm = collectionInput instanceof HTMLInputElement ? collectionInput.value : '';
|
|
6484
|
+
const collectionId = collectionFromQuery || collectionFromForm || '';
|
|
6485
|
+
return window.location.pathname + ':' + collectionId;
|
|
6486
|
+
};
|
|
6487
|
+
|
|
6488
|
+
const getBlocksStateKey = (container) => {
|
|
6489
|
+
const fieldName = container.dataset.fieldName || 'unknown';
|
|
6490
|
+
return 'sonic:ui:blocks:' + getCollectionScope() + ':' + fieldName;
|
|
6491
|
+
};
|
|
6492
|
+
|
|
6493
|
+
const readBlocksState = (container) => {
|
|
6494
|
+
try {
|
|
6495
|
+
const raw = sessionStorage.getItem(getBlocksStateKey(container));
|
|
6496
|
+
if (!raw) return null;
|
|
6497
|
+
const parsed = JSON.parse(raw);
|
|
6498
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
6499
|
+
} catch {
|
|
6500
|
+
return null;
|
|
6501
|
+
}
|
|
6502
|
+
};
|
|
6503
|
+
|
|
6504
|
+
const writeBlocksState = (container, state) => {
|
|
6505
|
+
try {
|
|
6506
|
+
sessionStorage.setItem(getBlocksStateKey(container), JSON.stringify(state));
|
|
6507
|
+
} catch {}
|
|
6508
|
+
};
|
|
6509
|
+
|
|
6510
|
+
const setBlockExpanded = (item, isExpanded) => {
|
|
6511
|
+
const content = item.querySelector('[data-block-content]');
|
|
6512
|
+
const icon = item.querySelector('[data-block-toggle-icon]');
|
|
6513
|
+
if (content instanceof HTMLElement) {
|
|
6514
|
+
content.classList.toggle('hidden', !isExpanded);
|
|
6515
|
+
}
|
|
6516
|
+
if (icon instanceof Element) {
|
|
6517
|
+
icon.classList.toggle('-rotate-90', !isExpanded);
|
|
6518
|
+
}
|
|
6519
|
+
};
|
|
6520
|
+
|
|
6521
|
+
const captureBlocksState = (container) => {
|
|
6522
|
+
return Array.from(container.querySelectorAll('.blocks-item')).map((item) => {
|
|
6523
|
+
const content = item.querySelector('[data-block-content]');
|
|
6524
|
+
return content instanceof HTMLElement ? !content.classList.contains('hidden') : false;
|
|
6525
|
+
});
|
|
6526
|
+
};
|
|
6527
|
+
|
|
6528
|
+
const applyBlocksState = (container, state) => {
|
|
6529
|
+
const items = Array.from(container.querySelectorAll('.blocks-item'));
|
|
6530
|
+
items.forEach((item, index) => {
|
|
6531
|
+
if (typeof state[index] === 'boolean') {
|
|
6532
|
+
setBlockExpanded(item, state[index]);
|
|
6533
|
+
}
|
|
6534
|
+
});
|
|
6535
|
+
};
|
|
6536
|
+
|
|
6537
|
+
const syncBlocksState = (container) => {
|
|
6538
|
+
writeBlocksState(container, captureBlocksState(container));
|
|
6539
|
+
};
|
|
6117
6540
|
|
|
6118
6541
|
function initializeBlocksFields() {
|
|
6119
6542
|
document.querySelectorAll('.blocks-field').forEach((container) => {
|
|
@@ -6201,7 +6624,10 @@ function getBlocksFieldScript() {
|
|
|
6201
6624
|
window.initializeDragSortable(list, {
|
|
6202
6625
|
itemSelector: '.blocks-item',
|
|
6203
6626
|
handleSelector: '[data-action="drag-handle"]',
|
|
6204
|
-
onUpdate:
|
|
6627
|
+
onUpdate: () => {
|
|
6628
|
+
updateHiddenInput();
|
|
6629
|
+
syncBlocksState(container);
|
|
6630
|
+
}
|
|
6205
6631
|
});
|
|
6206
6632
|
}
|
|
6207
6633
|
|
|
@@ -6223,8 +6649,12 @@ function getBlocksFieldScript() {
|
|
|
6223
6649
|
if (!template) return;
|
|
6224
6650
|
|
|
6225
6651
|
const nextIndex = list.querySelectorAll('.blocks-item').length;
|
|
6226
|
-
const html = template.innerHTML.replace(/
|
|
6652
|
+
const html = template.innerHTML.replace(/__BLOCK_INDEX__/g, String(nextIndex));
|
|
6227
6653
|
list.insertAdjacentHTML('beforeend', html);
|
|
6654
|
+
const newItem = list.lastElementChild;
|
|
6655
|
+
if (newItem instanceof HTMLElement) {
|
|
6656
|
+
setBlockExpanded(newItem, true);
|
|
6657
|
+
}
|
|
6228
6658
|
if (typeSelect) {
|
|
6229
6659
|
typeSelect.value = '';
|
|
6230
6660
|
}
|
|
@@ -6233,21 +6663,32 @@ function getBlocksFieldScript() {
|
|
|
6233
6663
|
window.initializeStructuredFields();
|
|
6234
6664
|
}
|
|
6235
6665
|
updateHiddenInput();
|
|
6666
|
+
syncBlocksState(container);
|
|
6236
6667
|
return;
|
|
6237
6668
|
}
|
|
6238
6669
|
|
|
6239
6670
|
const item = actionButton.closest('.blocks-item');
|
|
6240
6671
|
if (!item || !list) return;
|
|
6241
6672
|
|
|
6673
|
+
if (action === 'toggle-block') {
|
|
6674
|
+
const content = item.querySelector('[data-block-content]');
|
|
6675
|
+
if (!(content instanceof HTMLElement)) return;
|
|
6676
|
+
setBlockExpanded(item, content.classList.contains('hidden'));
|
|
6677
|
+
syncBlocksState(container);
|
|
6678
|
+
return;
|
|
6679
|
+
}
|
|
6680
|
+
|
|
6242
6681
|
if (action === 'remove-block') {
|
|
6243
6682
|
if (typeof requestRepeaterDelete === 'function') {
|
|
6244
6683
|
requestRepeaterDelete(() => {
|
|
6245
6684
|
item.remove();
|
|
6246
6685
|
updateHiddenInput();
|
|
6686
|
+
syncBlocksState(container);
|
|
6247
6687
|
}, 'block');
|
|
6248
6688
|
} else {
|
|
6249
6689
|
item.remove();
|
|
6250
6690
|
updateHiddenInput();
|
|
6691
|
+
syncBlocksState(container);
|
|
6251
6692
|
}
|
|
6252
6693
|
return;
|
|
6253
6694
|
}
|
|
@@ -6257,6 +6698,7 @@ function getBlocksFieldScript() {
|
|
|
6257
6698
|
if (previous) {
|
|
6258
6699
|
list.insertBefore(item, previous);
|
|
6259
6700
|
updateHiddenInput();
|
|
6701
|
+
syncBlocksState(container);
|
|
6260
6702
|
}
|
|
6261
6703
|
return;
|
|
6262
6704
|
}
|
|
@@ -6266,6 +6708,7 @@ function getBlocksFieldScript() {
|
|
|
6266
6708
|
if (next) {
|
|
6267
6709
|
list.insertBefore(next, item);
|
|
6268
6710
|
updateHiddenInput();
|
|
6711
|
+
syncBlocksState(container);
|
|
6269
6712
|
}
|
|
6270
6713
|
}
|
|
6271
6714
|
});
|
|
@@ -6287,6 +6730,12 @@ function getBlocksFieldScript() {
|
|
|
6287
6730
|
});
|
|
6288
6731
|
|
|
6289
6732
|
updateHiddenInput();
|
|
6733
|
+
const savedBlocksState = readBlocksState(container);
|
|
6734
|
+
if (savedBlocksState) {
|
|
6735
|
+
applyBlocksState(container, savedBlocksState);
|
|
6736
|
+
} else {
|
|
6737
|
+
syncBlocksState(container);
|
|
6738
|
+
}
|
|
6290
6739
|
});
|
|
6291
6740
|
}
|
|
6292
6741
|
|
|
@@ -6323,6 +6772,7 @@ chunkLTKV7AE5_cjs.init_admin_layout_catalyst_template();
|
|
|
6323
6772
|
function renderContentFormPage(data) {
|
|
6324
6773
|
const isEdit = data.isEdit || !!data.id;
|
|
6325
6774
|
const title = isEdit ? `Edit: ${data.title || "Content"}` : `New ${data.collection.display_name}`;
|
|
6775
|
+
const hasValidationErrors = Boolean(data.validationErrors && Object.keys(data.validationErrors).length > 0);
|
|
6326
6776
|
const backUrl = data.referrerParams ? `/admin/content?${data.referrerParams}` : `/admin/content?collection=${data.collection.id}`;
|
|
6327
6777
|
const coreFields = data.fields.filter((f) => ["title", "slug", "content"].includes(f.field_name));
|
|
6328
6778
|
const contentFields = data.fields.filter((f) => !["title", "slug", "content"].includes(f.field_name) && !f.field_name.startsWith("meta_"));
|
|
@@ -6411,6 +6861,7 @@ function renderContentFormPage(data) {
|
|
|
6411
6861
|
${isEdit ? `hx-put="/admin/content/${data.id}"` : `hx-post="/admin/content"`}
|
|
6412
6862
|
hx-target="#form-messages"
|
|
6413
6863
|
hx-encoding="multipart/form-data"
|
|
6864
|
+
data-has-validation-errors="${hasValidationErrors ? "true" : "false"}"
|
|
6414
6865
|
class="space-y-6"
|
|
6415
6866
|
>
|
|
6416
6867
|
<input type="hidden" name="collection_id" value="${data.collection.id}">
|
|
@@ -6691,39 +7142,456 @@ function renderContentFormPage(data) {
|
|
|
6691
7142
|
|
|
6692
7143
|
<!-- Dynamic Field Scripts -->
|
|
6693
7144
|
<script>
|
|
7145
|
+
const contentFormCollectionId = ${JSON.stringify(data.collection.id)};
|
|
7146
|
+
|
|
7147
|
+
function getFieldGroupScope() {
|
|
7148
|
+
const url = new URL(window.location.href);
|
|
7149
|
+
const urlCollectionId = url.searchParams.get('collection');
|
|
7150
|
+
const effectiveCollectionId = urlCollectionId || contentFormCollectionId || '';
|
|
7151
|
+
return window.location.pathname + ':' + effectiveCollectionId;
|
|
7152
|
+
}
|
|
7153
|
+
|
|
7154
|
+
function getItemPosition(itemSelector, item) {
|
|
7155
|
+
if (!(item instanceof Element)) return -1;
|
|
7156
|
+
const parent = item.parentElement;
|
|
7157
|
+
if (!parent) return -1;
|
|
7158
|
+
return Array.from(parent.querySelectorAll(':scope > ' + itemSelector)).indexOf(item);
|
|
7159
|
+
}
|
|
7160
|
+
|
|
7161
|
+
function stripIndexedFieldPrefix(fullFieldName, prefix) {
|
|
7162
|
+
if (!fullFieldName || !prefix || !fullFieldName.startsWith(prefix)) {
|
|
7163
|
+
return fullFieldName;
|
|
7164
|
+
}
|
|
7165
|
+
|
|
7166
|
+
const remainder = fullFieldName.slice(prefix.length);
|
|
7167
|
+
const indexMatch = remainder.match(/^(\\d+)(-|__)(.*)$/);
|
|
7168
|
+
if (!indexMatch) {
|
|
7169
|
+
return fullFieldName;
|
|
7170
|
+
}
|
|
7171
|
+
|
|
7172
|
+
return indexMatch[3];
|
|
7173
|
+
}
|
|
7174
|
+
|
|
7175
|
+
function getFieldGroupStorageKey(groupOrId) {
|
|
7176
|
+
const defaultGroupId = typeof groupOrId === 'string' ? groupOrId : (groupOrId?.getAttribute('data-group-id') || 'unknown');
|
|
7177
|
+
const group = typeof groupOrId === 'string'
|
|
7178
|
+
? document.querySelector('.field-group[data-group-id="' + defaultGroupId + '"]')
|
|
7179
|
+
: groupOrId;
|
|
7180
|
+
|
|
7181
|
+
const scopePrefix = 'sonic:ui:objects:' + getFieldGroupScope() + ':';
|
|
7182
|
+
if (!(group instanceof Element)) {
|
|
7183
|
+
return scopePrefix + defaultGroupId;
|
|
7184
|
+
}
|
|
7185
|
+
|
|
7186
|
+
const fullFieldName = group.getAttribute('data-field-name') || '';
|
|
7187
|
+
|
|
7188
|
+
const blocksField = group.closest('.blocks-field');
|
|
7189
|
+
const blockItem = group.closest('.blocks-item');
|
|
7190
|
+
if (blocksField instanceof Element && blockItem instanceof Element) {
|
|
7191
|
+
const blocksFieldName = blocksField.getAttribute('data-field-name') || 'unknown';
|
|
7192
|
+
const blockPosition = getItemPosition('.blocks-item', blockItem);
|
|
7193
|
+
const relativePath = stripIndexedFieldPrefix(fullFieldName, 'block-' + blocksFieldName + '-') || defaultGroupId;
|
|
7194
|
+
return scopePrefix + 'blocks:' + blocksFieldName + ':' + blockPosition + ':' + relativePath;
|
|
7195
|
+
}
|
|
7196
|
+
|
|
7197
|
+
const arrayField = group.closest('[data-structured-array][data-field-name]');
|
|
7198
|
+
const arrayItem = group.closest('.structured-array-item');
|
|
7199
|
+
if (arrayField instanceof Element && arrayItem instanceof Element) {
|
|
7200
|
+
const arrayFieldName = arrayField.getAttribute('data-field-name') || 'unknown';
|
|
7201
|
+
const itemPosition = getItemPosition('.structured-array-item', arrayItem);
|
|
7202
|
+
const relativePath = stripIndexedFieldPrefix(fullFieldName, 'array-' + arrayFieldName + '-') || defaultGroupId;
|
|
7203
|
+
return scopePrefix + 'repeaters:' + arrayFieldName + ':' + itemPosition + ':' + relativePath;
|
|
7204
|
+
}
|
|
7205
|
+
|
|
7206
|
+
return scopePrefix + defaultGroupId;
|
|
7207
|
+
}
|
|
7208
|
+
|
|
7209
|
+
function loadFieldGroupState(group) {
|
|
7210
|
+
try {
|
|
7211
|
+
const value = sessionStorage.getItem(getFieldGroupStorageKey(group));
|
|
7212
|
+
if (value === '1') return true;
|
|
7213
|
+
if (value === '0') return false;
|
|
7214
|
+
} catch {}
|
|
7215
|
+
return null;
|
|
7216
|
+
}
|
|
7217
|
+
|
|
7218
|
+
function saveFieldGroupState(group, isCollapsed) {
|
|
7219
|
+
try {
|
|
7220
|
+
sessionStorage.setItem(getFieldGroupStorageKey(group), isCollapsed ? '1' : '0');
|
|
7221
|
+
} catch {}
|
|
7222
|
+
}
|
|
7223
|
+
|
|
7224
|
+
function resolveFieldGroupElements(groupOrId) {
|
|
7225
|
+
let group = null;
|
|
7226
|
+
|
|
7227
|
+
if (groupOrId instanceof Element) {
|
|
7228
|
+
group = groupOrId.classList.contains('field-group')
|
|
7229
|
+
? groupOrId
|
|
7230
|
+
: groupOrId.closest('.field-group[data-group-id]');
|
|
7231
|
+
} else if (typeof groupOrId === 'string' && groupOrId) {
|
|
7232
|
+
group = document.querySelector('.field-group[data-group-id="' + groupOrId + '"]');
|
|
7233
|
+
}
|
|
7234
|
+
|
|
7235
|
+
let content = null;
|
|
7236
|
+
let icon = null;
|
|
7237
|
+
|
|
7238
|
+
if (group instanceof Element) {
|
|
7239
|
+
content = group.querySelector(':scope > .field-group-content');
|
|
7240
|
+
icon = group.querySelector(':scope > .field-group-header svg[id$="-icon"]');
|
|
7241
|
+
}
|
|
7242
|
+
|
|
7243
|
+
// Legacy fallback for any existing calls still passing string IDs.
|
|
7244
|
+
if (!(content instanceof HTMLElement) && typeof groupOrId === 'string') {
|
|
7245
|
+
content = document.getElementById(groupOrId + '-content');
|
|
7246
|
+
}
|
|
7247
|
+
if (!(icon instanceof Element) && typeof groupOrId === 'string') {
|
|
7248
|
+
icon = document.getElementById(groupOrId + '-icon');
|
|
7249
|
+
}
|
|
7250
|
+
|
|
7251
|
+
if (!(group instanceof Element) && content instanceof Element) {
|
|
7252
|
+
group = content.closest('.field-group[data-group-id]');
|
|
7253
|
+
}
|
|
7254
|
+
|
|
7255
|
+
return { group, content, icon };
|
|
7256
|
+
}
|
|
7257
|
+
|
|
7258
|
+
function applyFieldGroupState(groupOrId, isCollapsed) {
|
|
7259
|
+
const { content, icon } = resolveFieldGroupElements(groupOrId);
|
|
7260
|
+
if (!(content instanceof HTMLElement) || !(icon instanceof Element)) return;
|
|
7261
|
+
content.classList.toggle('hidden', isCollapsed);
|
|
7262
|
+
icon.classList.toggle('-rotate-90', isCollapsed);
|
|
7263
|
+
}
|
|
7264
|
+
|
|
7265
|
+
function restoreFieldGroupStates() {
|
|
7266
|
+
document.querySelectorAll('.field-group[data-group-id]').forEach((group) => {
|
|
7267
|
+
const savedState = loadFieldGroupState(group);
|
|
7268
|
+
if (savedState === null) return;
|
|
7269
|
+
applyFieldGroupState(group, savedState);
|
|
7270
|
+
});
|
|
7271
|
+
}
|
|
7272
|
+
|
|
7273
|
+
function persistAllFieldGroupStates() {
|
|
7274
|
+
document.querySelectorAll('.field-group[data-group-id]').forEach((group) => {
|
|
7275
|
+
const { content } = resolveFieldGroupElements(group);
|
|
7276
|
+
if (!(content instanceof HTMLElement)) return;
|
|
7277
|
+
saveFieldGroupState(group, content.classList.contains('hidden'));
|
|
7278
|
+
});
|
|
7279
|
+
}
|
|
7280
|
+
|
|
7281
|
+
function setValidationHeaderIndicator(container) {
|
|
7282
|
+
if (!(container instanceof Element)) return;
|
|
7283
|
+
let header = null;
|
|
7284
|
+
let markerTarget = null;
|
|
7285
|
+
|
|
7286
|
+
if (container.classList.contains('field-group')) {
|
|
7287
|
+
header = container.querySelector(':scope > .field-group-header');
|
|
7288
|
+
markerTarget = container.querySelector(':scope > .field-group-header h3');
|
|
7289
|
+
} else if (container.classList.contains('structured-array-item')) {
|
|
7290
|
+
header = container.querySelector('[data-action="toggle-item"]');
|
|
7291
|
+
markerTarget = header;
|
|
7292
|
+
} else if (container.classList.contains('blocks-item')) {
|
|
7293
|
+
header = container.querySelector('[data-action="toggle-block"]');
|
|
7294
|
+
markerTarget = header;
|
|
7295
|
+
}
|
|
7296
|
+
|
|
7297
|
+
if (!(header instanceof HTMLElement)) return;
|
|
7298
|
+
if (!(markerTarget instanceof HTMLElement)) {
|
|
7299
|
+
markerTarget = header;
|
|
7300
|
+
}
|
|
7301
|
+
|
|
7302
|
+
header.dataset.validationHeaderError = 'true';
|
|
7303
|
+
header.classList.add('text-pink-700', 'dark:text-pink-300');
|
|
7304
|
+
|
|
7305
|
+
if (!markerTarget.querySelector('[data-validation-indicator]')) {
|
|
7306
|
+
const marker = document.createElement('span');
|
|
7307
|
+
marker.setAttribute('data-validation-indicator', 'true');
|
|
7308
|
+
marker.className = 'ml-2 inline-block h-2 w-2 rounded-full bg-pink-500 align-middle';
|
|
7309
|
+
marker.setAttribute('aria-hidden', 'true');
|
|
7310
|
+
markerTarget.appendChild(marker);
|
|
7311
|
+
}
|
|
7312
|
+
}
|
|
7313
|
+
|
|
7314
|
+
function clearValidationIndicators() {
|
|
7315
|
+
document.querySelectorAll('[data-validation-header-error="true"]').forEach((el) => {
|
|
7316
|
+
if (!(el instanceof HTMLElement)) return;
|
|
7317
|
+
delete el.dataset.validationHeaderError;
|
|
7318
|
+
el.classList.remove('text-pink-700', 'dark:text-pink-300');
|
|
7319
|
+
});
|
|
7320
|
+
|
|
7321
|
+
document.querySelectorAll('[data-validation-indicator]').forEach((el) => el.remove());
|
|
7322
|
+
}
|
|
7323
|
+
|
|
7324
|
+
function expandContainerForValidation(container) {
|
|
7325
|
+
if (!(container instanceof Element)) return;
|
|
7326
|
+
|
|
7327
|
+
if (container.classList.contains('field-group')) {
|
|
7328
|
+
applyFieldGroupState(container, false);
|
|
7329
|
+
return;
|
|
7330
|
+
}
|
|
7331
|
+
|
|
7332
|
+
if (container.classList.contains('structured-array-item')) {
|
|
7333
|
+
const content = container.querySelector('[data-array-item-fields]');
|
|
7334
|
+
const icon = container.querySelector('[data-item-toggle-icon]');
|
|
7335
|
+
if (content instanceof HTMLElement) {
|
|
7336
|
+
content.classList.remove('hidden');
|
|
7337
|
+
}
|
|
7338
|
+
if (icon instanceof Element) {
|
|
7339
|
+
icon.classList.remove('-rotate-90');
|
|
7340
|
+
}
|
|
7341
|
+
return;
|
|
7342
|
+
}
|
|
7343
|
+
|
|
7344
|
+
if (container.classList.contains('blocks-item')) {
|
|
7345
|
+
const content = container.querySelector('[data-block-content]');
|
|
7346
|
+
const icon = container.querySelector('[data-block-toggle-icon]');
|
|
7347
|
+
if (content instanceof HTMLElement) {
|
|
7348
|
+
content.classList.remove('hidden');
|
|
7349
|
+
}
|
|
7350
|
+
if (icon instanceof Element) {
|
|
7351
|
+
icon.classList.remove('-rotate-90');
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7355
|
+
|
|
7356
|
+
function walkErrorContainers(node, expand) {
|
|
7357
|
+
if (!(node instanceof Element)) return;
|
|
7358
|
+
const visited = new Set();
|
|
7359
|
+
let cursor = node;
|
|
7360
|
+
while (cursor) {
|
|
7361
|
+
const candidates = [
|
|
7362
|
+
cursor.closest('.structured-array-item'),
|
|
7363
|
+
cursor.closest('.blocks-item'),
|
|
7364
|
+
cursor.closest('.field-group[data-group-id]')
|
|
7365
|
+
].filter((c) => c instanceof Element && !visited.has(c));
|
|
7366
|
+
|
|
7367
|
+
if (candidates.length === 0) break;
|
|
7368
|
+
|
|
7369
|
+
// Pick nearest ancestor container to preserve "first-error path only".
|
|
7370
|
+
let nearest = candidates[0];
|
|
7371
|
+
let bestDistance = Number.MAX_SAFE_INTEGER;
|
|
7372
|
+
for (const candidate of candidates) {
|
|
7373
|
+
let distance = 0;
|
|
7374
|
+
let walker = cursor;
|
|
7375
|
+
while (walker && walker !== candidate) {
|
|
7376
|
+
walker = walker.parentElement;
|
|
7377
|
+
distance += 1;
|
|
7378
|
+
}
|
|
7379
|
+
if (distance < bestDistance) {
|
|
7380
|
+
bestDistance = distance;
|
|
7381
|
+
nearest = candidate;
|
|
7382
|
+
}
|
|
7383
|
+
}
|
|
7384
|
+
|
|
7385
|
+
visited.add(nearest);
|
|
7386
|
+
setValidationHeaderIndicator(nearest);
|
|
7387
|
+
if (expand) {
|
|
7388
|
+
expandContainerForValidation(nearest);
|
|
7389
|
+
}
|
|
7390
|
+
cursor = nearest.parentElement;
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7393
|
+
|
|
7394
|
+
function getFocusableTargetFromErrorGroup(group) {
|
|
7395
|
+
if (!(group instanceof Element)) return null;
|
|
7396
|
+
return (
|
|
7397
|
+
group.querySelector('input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"]') ||
|
|
7398
|
+
group.querySelector('button:not([disabled])')
|
|
7399
|
+
);
|
|
7400
|
+
}
|
|
7401
|
+
|
|
7402
|
+
function revealServerValidationErrors() {
|
|
7403
|
+
clearValidationIndicators();
|
|
7404
|
+
|
|
7405
|
+
const errorGroups = Array.from(document.querySelectorAll('.form-group[data-has-errors="true"]'));
|
|
7406
|
+
if (errorGroups.length === 0) return;
|
|
7407
|
+
|
|
7408
|
+
// Add indicators for all errored sections, expand only first-error path.
|
|
7409
|
+
errorGroups.forEach((group, index) => {
|
|
7410
|
+
walkErrorContainers(group, index === 0);
|
|
7411
|
+
});
|
|
7412
|
+
|
|
7413
|
+
const firstTarget = getFocusableTargetFromErrorGroup(errorGroups[0]);
|
|
7414
|
+
if (firstTarget instanceof HTMLElement) {
|
|
7415
|
+
firstTarget.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
7416
|
+
firstTarget.focus({ preventScroll: true });
|
|
7417
|
+
}
|
|
7418
|
+
}
|
|
7419
|
+
|
|
7420
|
+
function revealNativeValidationErrors(form) {
|
|
7421
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
7422
|
+
clearValidationIndicators();
|
|
7423
|
+
|
|
7424
|
+
const invalidControls = Array.from(form.querySelectorAll(':invalid'));
|
|
7425
|
+
if (invalidControls.length === 0) return;
|
|
7426
|
+
|
|
7427
|
+
invalidControls.forEach((control, index) => {
|
|
7428
|
+
walkErrorContainers(control, index === 0);
|
|
7429
|
+
});
|
|
7430
|
+
|
|
7431
|
+
const first = invalidControls[0];
|
|
7432
|
+
if (first instanceof HTMLElement) {
|
|
7433
|
+
first.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
7434
|
+
first.focus({ preventScroll: true });
|
|
7435
|
+
}
|
|
7436
|
+
}
|
|
7437
|
+
|
|
6694
7438
|
// Field group toggle
|
|
6695
|
-
function toggleFieldGroup(
|
|
6696
|
-
const content =
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
7439
|
+
function toggleFieldGroup(groupOrTrigger) {
|
|
7440
|
+
const { group, content } = resolveFieldGroupElements(groupOrTrigger);
|
|
7441
|
+
if (!(group instanceof Element)) return;
|
|
7442
|
+
if (!(content instanceof HTMLElement)) return;
|
|
7443
|
+
|
|
7444
|
+
const isCollapsed = !content.classList.contains('hidden');
|
|
7445
|
+
applyFieldGroupState(group, isCollapsed);
|
|
7446
|
+
saveFieldGroupState(group, isCollapsed);
|
|
7447
|
+
}
|
|
7448
|
+
|
|
7449
|
+
if (document.readyState === 'loading') {
|
|
7450
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
7451
|
+
restoreFieldGroupStates();
|
|
7452
|
+
const form = document.getElementById('content-form');
|
|
7453
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7454
|
+
revealServerValidationErrors();
|
|
7455
|
+
}
|
|
7456
|
+
});
|
|
7457
|
+
} else {
|
|
7458
|
+
restoreFieldGroupStates();
|
|
7459
|
+
const form = document.getElementById('content-form');
|
|
7460
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7461
|
+
revealServerValidationErrors();
|
|
6705
7462
|
}
|
|
6706
7463
|
}
|
|
6707
7464
|
|
|
7465
|
+
document.addEventListener('htmx:afterSwap', function() {
|
|
7466
|
+
setTimeout(() => {
|
|
7467
|
+
restoreFieldGroupStates();
|
|
7468
|
+
const form = document.getElementById('content-form');
|
|
7469
|
+
if (form?.getAttribute('data-has-validation-errors') === 'true') {
|
|
7470
|
+
revealServerValidationErrors();
|
|
7471
|
+
}
|
|
7472
|
+
}, 50);
|
|
7473
|
+
});
|
|
7474
|
+
|
|
7475
|
+
const contentFormEl = document.getElementById('content-form');
|
|
7476
|
+
if (contentFormEl instanceof HTMLFormElement) {
|
|
7477
|
+
contentFormEl.addEventListener('submit', () => {
|
|
7478
|
+
persistAllFieldGroupStates();
|
|
7479
|
+
}, true);
|
|
7480
|
+
}
|
|
7481
|
+
|
|
7482
|
+
window.addEventListener('beforeunload', () => {
|
|
7483
|
+
persistAllFieldGroupStates();
|
|
7484
|
+
});
|
|
7485
|
+
|
|
7486
|
+
document.addEventListener('visibilitychange', () => {
|
|
7487
|
+
if (document.visibilityState === 'hidden') {
|
|
7488
|
+
persistAllFieldGroupStates();
|
|
7489
|
+
}
|
|
7490
|
+
});
|
|
7491
|
+
|
|
7492
|
+
let pendingNativeValidationReveal = false;
|
|
7493
|
+
document.addEventListener('invalid', function(event) {
|
|
7494
|
+
const target = event.target;
|
|
7495
|
+
if (!(target instanceof Element)) return;
|
|
7496
|
+
const form = target.closest('form');
|
|
7497
|
+
if (!(form instanceof HTMLFormElement)) return;
|
|
7498
|
+
|
|
7499
|
+
if (pendingNativeValidationReveal) return;
|
|
7500
|
+
pendingNativeValidationReveal = true;
|
|
7501
|
+
|
|
7502
|
+
// Expand only first invalid path synchronously so the browser can focus it
|
|
7503
|
+
// and avoid "invalid form control is not focusable" errors.
|
|
7504
|
+
walkErrorContainers(target, true);
|
|
7505
|
+
|
|
7506
|
+
setTimeout(() => {
|
|
7507
|
+
pendingNativeValidationReveal = false;
|
|
7508
|
+
revealNativeValidationErrors(form);
|
|
7509
|
+
}, 0);
|
|
7510
|
+
}, true);
|
|
7511
|
+
|
|
6708
7512
|
// Media field functions
|
|
6709
|
-
|
|
7513
|
+
function notifyFieldChange(input) {
|
|
7514
|
+
if (!input) return;
|
|
7515
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
7516
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
7517
|
+
}
|
|
7518
|
+
|
|
7519
|
+
function getActiveMediaModal() {
|
|
7520
|
+
const modal = document.getElementById('media-selector-modal');
|
|
7521
|
+
return modal instanceof HTMLElement ? modal : null;
|
|
7522
|
+
}
|
|
7523
|
+
|
|
7524
|
+
function getMediaFieldElements(fieldId) {
|
|
7525
|
+
if (!fieldId) {
|
|
7526
|
+
return {
|
|
7527
|
+
fieldId: '',
|
|
7528
|
+
hiddenInput: null,
|
|
7529
|
+
preview: null,
|
|
7530
|
+
mediaField: null,
|
|
7531
|
+
actionsDiv: null,
|
|
7532
|
+
};
|
|
7533
|
+
}
|
|
7534
|
+
|
|
7535
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
7536
|
+
const preview = document.getElementById(fieldId + '-preview');
|
|
7537
|
+
const mediaField = hiddenInput?.closest('.media-field-container') || null;
|
|
7538
|
+
const actionsDiv = mediaField?.querySelector('.media-actions') || null;
|
|
7539
|
+
|
|
7540
|
+
return {
|
|
7541
|
+
fieldId,
|
|
7542
|
+
hiddenInput,
|
|
7543
|
+
preview,
|
|
7544
|
+
mediaField,
|
|
7545
|
+
actionsDiv,
|
|
7546
|
+
};
|
|
7547
|
+
}
|
|
7548
|
+
|
|
7549
|
+
function getActiveMediaTarget() {
|
|
7550
|
+
const modal = getActiveMediaModal();
|
|
7551
|
+
const fieldId = modal?.dataset.targetFieldId || '';
|
|
7552
|
+
return {
|
|
7553
|
+
modal,
|
|
7554
|
+
originalValue: modal?.dataset.originalValue || '',
|
|
7555
|
+
...getMediaFieldElements(fieldId),
|
|
7556
|
+
};
|
|
7557
|
+
}
|
|
7558
|
+
|
|
7559
|
+
function ensureSingleMediaRemoveButton(fieldId, actionsDiv) {
|
|
7560
|
+
if (!(actionsDiv instanceof HTMLElement)) return;
|
|
7561
|
+
const existingRemoveButton = actionsDiv.querySelector('[data-media-remove="true"]');
|
|
7562
|
+
if (existingRemoveButton) return;
|
|
7563
|
+
|
|
7564
|
+
const removeBtn = document.createElement('button');
|
|
7565
|
+
removeBtn.type = 'button';
|
|
7566
|
+
removeBtn.setAttribute('data-media-remove', 'true');
|
|
7567
|
+
removeBtn.onclick = () => clearMediaField(fieldId);
|
|
7568
|
+
removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
|
|
7569
|
+
removeBtn.textContent = 'Remove';
|
|
7570
|
+
actionsDiv.appendChild(removeBtn);
|
|
7571
|
+
}
|
|
6710
7572
|
|
|
6711
7573
|
function openMediaSelector(fieldId) {
|
|
6712
|
-
|
|
7574
|
+
const existingModal = getActiveMediaModal();
|
|
7575
|
+
if (existingModal) {
|
|
7576
|
+
existingModal.remove();
|
|
7577
|
+
}
|
|
7578
|
+
|
|
6713
7579
|
// Store the original value in case user cancels
|
|
6714
|
-
const originalValue =
|
|
7580
|
+
const originalValue = getMediaFieldElements(fieldId).hiddenInput?.value || '';
|
|
6715
7581
|
|
|
6716
7582
|
// Open media library modal
|
|
6717
7583
|
const modal = document.createElement('div');
|
|
6718
7584
|
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50';
|
|
6719
7585
|
modal.id = 'media-selector-modal';
|
|
7586
|
+
modal.dataset.targetFieldId = fieldId;
|
|
7587
|
+
modal.dataset.originalValue = originalValue;
|
|
6720
7588
|
modal.innerHTML = \`
|
|
6721
7589
|
<div class="rounded-xl bg-white dark:bg-zinc-900 shadow-xl ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
|
|
6722
7590
|
<h3 class="text-lg font-semibold text-zinc-950 dark:text-white mb-4">Select Media</h3>
|
|
6723
7591
|
<div id="media-grid-container" hx-get="/admin/media/selector" hx-trigger="load"></div>
|
|
6724
7592
|
<div class="mt-4 flex justify-end space-x-2">
|
|
6725
7593
|
<button
|
|
6726
|
-
onclick="cancelMediaSelection(
|
|
7594
|
+
onclick="cancelMediaSelection()"
|
|
6727
7595
|
class="rounded-lg bg-white dark:bg-zinc-800 px-4 py-2 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
|
|
6728
7596
|
Cancel
|
|
6729
7597
|
</button>
|
|
@@ -6743,23 +7611,23 @@ function renderContentFormPage(data) {
|
|
|
6743
7611
|
}
|
|
6744
7612
|
|
|
6745
7613
|
function closeMediaSelector() {
|
|
6746
|
-
const modal =
|
|
7614
|
+
const modal = getActiveMediaModal();
|
|
6747
7615
|
if (modal) {
|
|
6748
7616
|
modal.remove();
|
|
6749
7617
|
}
|
|
6750
|
-
currentMediaFieldId = null;
|
|
6751
7618
|
}
|
|
6752
7619
|
|
|
6753
|
-
function cancelMediaSelection(
|
|
7620
|
+
function cancelMediaSelection() {
|
|
7621
|
+
const { hiddenInput, preview, originalValue } = getActiveMediaTarget();
|
|
7622
|
+
|
|
6754
7623
|
// Restore original value
|
|
6755
|
-
const hiddenInput = document.getElementById(fieldId);
|
|
6756
7624
|
if (hiddenInput) {
|
|
6757
7625
|
hiddenInput.value = originalValue;
|
|
7626
|
+
notifyFieldChange(hiddenInput);
|
|
6758
7627
|
}
|
|
6759
7628
|
|
|
6760
7629
|
// If original value was empty, hide the preview and show select button
|
|
6761
7630
|
if (!originalValue) {
|
|
6762
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6763
7631
|
if (preview) {
|
|
6764
7632
|
preview.classList.add('hidden');
|
|
6765
7633
|
}
|
|
@@ -6770,11 +7638,11 @@ function renderContentFormPage(data) {
|
|
|
6770
7638
|
}
|
|
6771
7639
|
|
|
6772
7640
|
function clearMediaField(fieldId) {
|
|
6773
|
-
const hiddenInput =
|
|
6774
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
7641
|
+
const { hiddenInput, preview, actionsDiv } = getMediaFieldElements(fieldId);
|
|
6775
7642
|
|
|
6776
7643
|
if (hiddenInput) {
|
|
6777
7644
|
hiddenInput.value = '';
|
|
7645
|
+
notifyFieldChange(hiddenInput);
|
|
6778
7646
|
}
|
|
6779
7647
|
|
|
6780
7648
|
if (preview) {
|
|
@@ -6784,25 +7652,34 @@ function renderContentFormPage(data) {
|
|
|
6784
7652
|
}
|
|
6785
7653
|
preview.classList.add('hidden');
|
|
6786
7654
|
}
|
|
7655
|
+
|
|
7656
|
+
const removeButton = actionsDiv?.querySelector('[data-media-remove="true"]');
|
|
7657
|
+
if (removeButton) {
|
|
7658
|
+
removeButton.remove();
|
|
7659
|
+
}
|
|
6787
7660
|
}
|
|
6788
7661
|
|
|
6789
7662
|
// Global function to remove a single media from multiple selection
|
|
6790
7663
|
window.removeMediaFromMultiple = function(fieldId, urlToRemove) {
|
|
6791
|
-
const hiddenInput =
|
|
7664
|
+
const { hiddenInput, preview } = getMediaFieldElements(fieldId);
|
|
6792
7665
|
if (!hiddenInput) return;
|
|
6793
7666
|
|
|
6794
7667
|
const values = hiddenInput.value.split(',').filter(url => url !== urlToRemove);
|
|
6795
7668
|
hiddenInput.value = values.join(',');
|
|
7669
|
+
notifyFieldChange(hiddenInput);
|
|
6796
7670
|
|
|
6797
7671
|
// Remove preview item
|
|
6798
|
-
const previewItem =
|
|
7672
|
+
const previewItem =
|
|
7673
|
+
preview &&
|
|
7674
|
+
Array.from(preview.querySelectorAll('[data-url]')).find(
|
|
7675
|
+
(item) => item.getAttribute('data-url') === urlToRemove,
|
|
7676
|
+
);
|
|
6799
7677
|
if (previewItem) {
|
|
6800
7678
|
previewItem.remove();
|
|
6801
7679
|
}
|
|
6802
7680
|
|
|
6803
7681
|
// Hide preview grid if empty
|
|
6804
7682
|
if (values.length === 0) {
|
|
6805
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6806
7683
|
if (preview) {
|
|
6807
7684
|
preview.classList.add('hidden');
|
|
6808
7685
|
}
|
|
@@ -6811,39 +7688,24 @@ function renderContentFormPage(data) {
|
|
|
6811
7688
|
|
|
6812
7689
|
// Global function called by media selector buttons
|
|
6813
7690
|
window.selectMediaFile = function(mediaId, mediaUrl, filename) {
|
|
6814
|
-
|
|
7691
|
+
const { fieldId, hiddenInput, preview, actionsDiv } = getActiveMediaTarget();
|
|
7692
|
+
if (!fieldId || !hiddenInput) {
|
|
6815
7693
|
console.error('No field ID set for media selection');
|
|
6816
7694
|
return;
|
|
6817
7695
|
}
|
|
6818
7696
|
|
|
6819
|
-
const fieldId = currentMediaFieldId;
|
|
6820
|
-
|
|
6821
7697
|
// Set the hidden input value to the media URL (not ID)
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
hiddenInput.value = mediaUrl;
|
|
6825
|
-
}
|
|
7698
|
+
hiddenInput.value = mediaUrl;
|
|
7699
|
+
notifyFieldChange(hiddenInput);
|
|
6826
7700
|
|
|
6827
7701
|
// Update the preview
|
|
6828
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
6829
7702
|
if (preview) {
|
|
6830
7703
|
preview.innerHTML = \`<img src="\${mediaUrl}" alt="\${filename}" class="w-32 h-32 object-cover rounded-lg border border-white/20">\`;
|
|
6831
7704
|
preview.classList.remove('hidden');
|
|
6832
7705
|
}
|
|
6833
7706
|
|
|
6834
7707
|
// 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
|
-
}
|
|
7708
|
+
ensureSingleMediaRemoveButton(fieldId, actionsDiv);
|
|
6847
7709
|
|
|
6848
7710
|
// DON'T close the modal - let user click OK button
|
|
6849
7711
|
// Visual feedback: highlight the selected item
|
|
@@ -6857,7 +7719,9 @@ function renderContentFormPage(data) {
|
|
|
6857
7719
|
};
|
|
6858
7720
|
|
|
6859
7721
|
function setMediaField(fieldId, mediaUrl) {
|
|
6860
|
-
document.getElementById(fieldId)
|
|
7722
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
7723
|
+
hiddenInput.value = mediaUrl;
|
|
7724
|
+
notifyFieldChange(hiddenInput);
|
|
6861
7725
|
const preview = document.getElementById(fieldId + '-preview');
|
|
6862
7726
|
preview.innerHTML = \`<img src="\${mediaUrl}" alt="Selected media" class="w-32 h-32 object-cover rounded-lg ring-1 ring-zinc-950/10 dark:ring-white/10">\`;
|
|
6863
7727
|
preview.classList.remove('hidden');
|
|
@@ -8304,9 +9168,9 @@ function parseFieldValue(field, formData, options = {}) {
|
|
|
8304
9168
|
const { skipValidation = false } = options;
|
|
8305
9169
|
const value = formData.get(field.field_name);
|
|
8306
9170
|
const errors = [];
|
|
8307
|
-
const blocksConfig =
|
|
9171
|
+
const blocksConfig = chunkEKPLKUZT_cjs.getBlocksFieldConfig(field.field_options);
|
|
8308
9172
|
if (blocksConfig) {
|
|
8309
|
-
const parsed =
|
|
9173
|
+
const parsed = chunkEKPLKUZT_cjs.parseBlocksValue(value, blocksConfig);
|
|
8310
9174
|
if (!skipValidation && field.is_required && parsed.value.length === 0) {
|
|
8311
9175
|
parsed.errors.push(`${field.field_label} is required`);
|
|
8312
9176
|
}
|
|
@@ -8416,7 +9280,7 @@ function extractFieldData(fields, formData, options = {}) {
|
|
|
8416
9280
|
}
|
|
8417
9281
|
return { data, errors };
|
|
8418
9282
|
}
|
|
8419
|
-
adminContentRoutes.use("*",
|
|
9283
|
+
adminContentRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
8420
9284
|
async function getCollectionFields(db, collectionId) {
|
|
8421
9285
|
const cache = chunk64APW3DW_cjs.getCacheService(chunk64APW3DW_cjs.CACHE_CONFIGS.collection);
|
|
8422
9286
|
return cache.getOrSet(
|
|
@@ -8692,21 +9556,21 @@ adminContentRoutes.get("/new", async (c) => {
|
|
|
8692
9556
|
const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
|
|
8693
9557
|
let tinymceSettings;
|
|
8694
9558
|
if (tinymceEnabled) {
|
|
8695
|
-
const pluginService = new
|
|
9559
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
8696
9560
|
const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
|
|
8697
9561
|
tinymceSettings = tinymcePlugin2?.settings;
|
|
8698
9562
|
}
|
|
8699
9563
|
const quillEnabled = await isPluginActive2(db, "quill-editor");
|
|
8700
9564
|
let quillSettings;
|
|
8701
9565
|
if (quillEnabled) {
|
|
8702
|
-
const pluginService = new
|
|
9566
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
8703
9567
|
const quillPlugin = await pluginService.getPlugin("quill-editor");
|
|
8704
9568
|
quillSettings = quillPlugin?.settings;
|
|
8705
9569
|
}
|
|
8706
9570
|
const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
|
|
8707
9571
|
let mdxeditorSettings;
|
|
8708
9572
|
if (mdxeditorEnabled) {
|
|
8709
|
-
const pluginService = new
|
|
9573
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
8710
9574
|
const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
|
|
8711
9575
|
mdxeditorSettings = mdxeditorPlugin?.settings;
|
|
8712
9576
|
}
|
|
@@ -8797,21 +9661,21 @@ adminContentRoutes.get("/:id/edit", async (c) => {
|
|
|
8797
9661
|
const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
|
|
8798
9662
|
let tinymceSettings;
|
|
8799
9663
|
if (tinymceEnabled) {
|
|
8800
|
-
const pluginService = new
|
|
9664
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
8801
9665
|
const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
|
|
8802
9666
|
tinymceSettings = tinymcePlugin2?.settings;
|
|
8803
9667
|
}
|
|
8804
9668
|
const quillEnabled = await isPluginActive2(db, "quill-editor");
|
|
8805
9669
|
let quillSettings;
|
|
8806
9670
|
if (quillEnabled) {
|
|
8807
|
-
const pluginService = new
|
|
9671
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
8808
9672
|
const quillPlugin = await pluginService.getPlugin("quill-editor");
|
|
8809
9673
|
quillSettings = quillPlugin?.settings;
|
|
8810
9674
|
}
|
|
8811
9675
|
const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
|
|
8812
9676
|
let mdxeditorSettings;
|
|
8813
9677
|
if (mdxeditorEnabled) {
|
|
8814
|
-
const pluginService = new
|
|
9678
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
8815
9679
|
const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
|
|
8816
9680
|
mdxeditorSettings = mdxeditorPlugin?.settings;
|
|
8817
9681
|
}
|
|
@@ -9106,7 +9970,7 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
9106
9970
|
`);
|
|
9107
9971
|
}
|
|
9108
9972
|
});
|
|
9109
|
-
adminContentRoutes.post("/preview",
|
|
9973
|
+
adminContentRoutes.post("/preview", chunkIT2TC4ZD_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
|
|
9110
9974
|
try {
|
|
9111
9975
|
const formData = await c.req.formData();
|
|
9112
9976
|
const collectionId = formData.get("collection_id");
|
|
@@ -9484,7 +10348,7 @@ adminContentRoutes.post("/:id/restore/:version", async (c) => {
|
|
|
9484
10348
|
return c.json({ success: false, error: "Failed to restore version" });
|
|
9485
10349
|
}
|
|
9486
10350
|
});
|
|
9487
|
-
adminContentRoutes.get("/:id/version/:version/preview",
|
|
10351
|
+
adminContentRoutes.get("/:id/version/:version/preview", chunkIT2TC4ZD_cjs.requireRole(["admin", "editor", "author"]), async (c) => {
|
|
9488
10352
|
try {
|
|
9489
10353
|
const id = c.req.param("id");
|
|
9490
10354
|
const version = parseInt(c.req.param("version") || "0");
|
|
@@ -11451,7 +12315,14 @@ function renderUsersListPage(data) {
|
|
|
11451
12315
|
|
|
11452
12316
|
// src/routes/admin-users.ts
|
|
11453
12317
|
var userRoutes = new hono.Hono();
|
|
11454
|
-
userRoutes.use("*",
|
|
12318
|
+
userRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
12319
|
+
userRoutes.use("/users/*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
|
|
12320
|
+
userRoutes.use("/users", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
|
|
12321
|
+
userRoutes.use("/invite-user", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
|
|
12322
|
+
userRoutes.use("/resend-invitation/*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
|
|
12323
|
+
userRoutes.use("/cancel-invitation/*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
|
|
12324
|
+
userRoutes.use("/activity-logs", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
|
|
12325
|
+
userRoutes.use("/activity-logs/*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
|
|
11455
12326
|
userRoutes.get("/", (c) => {
|
|
11456
12327
|
return c.redirect("/admin/dashboard");
|
|
11457
12328
|
});
|
|
@@ -11606,7 +12477,7 @@ userRoutes.put("/profile", async (c) => {
|
|
|
11606
12477
|
Date.now(),
|
|
11607
12478
|
user.userId
|
|
11608
12479
|
).run();
|
|
11609
|
-
await
|
|
12480
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
11610
12481
|
db,
|
|
11611
12482
|
user.userId,
|
|
11612
12483
|
"profile.update",
|
|
@@ -11669,7 +12540,7 @@ userRoutes.post("/profile/avatar", async (c) => {
|
|
|
11669
12540
|
SELECT first_name, last_name FROM users WHERE id = ?
|
|
11670
12541
|
`);
|
|
11671
12542
|
const userData = await userStmt.bind(user.userId).first();
|
|
11672
|
-
await
|
|
12543
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
11673
12544
|
db,
|
|
11674
12545
|
user.userId,
|
|
11675
12546
|
"profile.avatar_update",
|
|
@@ -11740,7 +12611,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
11740
12611
|
dismissible: true
|
|
11741
12612
|
}));
|
|
11742
12613
|
}
|
|
11743
|
-
const validPassword = await
|
|
12614
|
+
const validPassword = await chunkIT2TC4ZD_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
|
|
11744
12615
|
if (!validPassword) {
|
|
11745
12616
|
return c.html(renderAlert2({
|
|
11746
12617
|
type: "error",
|
|
@@ -11748,7 +12619,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
11748
12619
|
dismissible: true
|
|
11749
12620
|
}));
|
|
11750
12621
|
}
|
|
11751
|
-
const newPasswordHash = await
|
|
12622
|
+
const newPasswordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(newPassword);
|
|
11752
12623
|
const historyStmt = db.prepare(`
|
|
11753
12624
|
INSERT INTO password_history (id, user_id, password_hash, created_at)
|
|
11754
12625
|
VALUES (?, ?, ?, ?)
|
|
@@ -11764,7 +12635,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
11764
12635
|
WHERE id = ?
|
|
11765
12636
|
`);
|
|
11766
12637
|
await updateStmt.bind(newPasswordHash, Date.now(), user.userId).run();
|
|
11767
|
-
await
|
|
12638
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
11768
12639
|
db,
|
|
11769
12640
|
user.userId,
|
|
11770
12641
|
"profile.password_change",
|
|
@@ -11831,7 +12702,7 @@ userRoutes.get("/users", async (c) => {
|
|
|
11831
12702
|
`);
|
|
11832
12703
|
const countResult = await countStmt.bind(...params).first();
|
|
11833
12704
|
const totalUsers = countResult?.total || 0;
|
|
11834
|
-
await
|
|
12705
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
11835
12706
|
db,
|
|
11836
12707
|
user.userId,
|
|
11837
12708
|
"users.list_view",
|
|
@@ -11939,7 +12810,9 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
11939
12810
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
11940
12811
|
const phone = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
11941
12812
|
const bio = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
11942
|
-
const
|
|
12813
|
+
const roleInput = formData.get("role")?.toString() || "viewer";
|
|
12814
|
+
const validRoles = ["admin", "editor", "author", "viewer"];
|
|
12815
|
+
const role = validRoles.includes(roleInput) ? roleInput : "viewer";
|
|
11943
12816
|
const password = formData.get("password")?.toString() || "";
|
|
11944
12817
|
const confirmPassword = formData.get("confirm_password")?.toString() || "";
|
|
11945
12818
|
const isActive = formData.get("is_active") === "1";
|
|
@@ -11985,7 +12858,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
11985
12858
|
dismissible: true
|
|
11986
12859
|
}));
|
|
11987
12860
|
}
|
|
11988
|
-
const passwordHash = await
|
|
12861
|
+
const passwordHash = await chunkIT2TC4ZD_cjs.AuthManager.hashPassword(password);
|
|
11989
12862
|
const userId = crypto.randomUUID();
|
|
11990
12863
|
const createStmt = db.prepare(`
|
|
11991
12864
|
INSERT INTO users (
|
|
@@ -12008,7 +12881,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
12008
12881
|
Date.now(),
|
|
12009
12882
|
Date.now()
|
|
12010
12883
|
).run();
|
|
12011
|
-
await
|
|
12884
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12012
12885
|
db,
|
|
12013
12886
|
user.userId,
|
|
12014
12887
|
"user!.create",
|
|
@@ -12046,7 +12919,7 @@ userRoutes.get("/users/:id", async (c) => {
|
|
|
12046
12919
|
if (!userRecord) {
|
|
12047
12920
|
return c.json({ error: "User not found" }, 404);
|
|
12048
12921
|
}
|
|
12049
|
-
await
|
|
12922
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12050
12923
|
db,
|
|
12051
12924
|
user.userId,
|
|
12052
12925
|
"user!.view",
|
|
@@ -12159,7 +13032,9 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
12159
13032
|
const username = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("username")?.toString());
|
|
12160
13033
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
12161
13034
|
const phone = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
12162
|
-
const
|
|
13035
|
+
const roleInput = formData.get("role")?.toString() || "viewer";
|
|
13036
|
+
const validRoles = ["admin", "editor", "author", "viewer"];
|
|
13037
|
+
const role = validRoles.includes(roleInput) ? roleInput : "viewer";
|
|
12163
13038
|
const isActive = formData.get("is_active") === "1";
|
|
12164
13039
|
const emailVerified = formData.get("email_verified") === "1";
|
|
12165
13040
|
const profileDisplayName = chunkMNWKYY5E_cjs.sanitizeInput(formData.get("profile_display_name")?.toString()) || null;
|
|
@@ -12271,7 +13146,7 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
12271
13146
|
).run();
|
|
12272
13147
|
}
|
|
12273
13148
|
}
|
|
12274
|
-
await
|
|
13149
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12275
13150
|
db,
|
|
12276
13151
|
user.userId,
|
|
12277
13152
|
"user.update",
|
|
@@ -12316,7 +13191,7 @@ userRoutes.post("/users/:id/toggle", async (c) => {
|
|
|
12316
13191
|
UPDATE users SET is_active = ?, updated_at = ? WHERE id = ?
|
|
12317
13192
|
`);
|
|
12318
13193
|
await toggleStmt.bind(active ? 1 : 0, Date.now(), userId).run();
|
|
12319
|
-
await
|
|
13194
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12320
13195
|
db,
|
|
12321
13196
|
user.userId,
|
|
12322
13197
|
active ? "user.activate" : "user.deactivate",
|
|
@@ -12357,7 +13232,7 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
12357
13232
|
DELETE FROM users WHERE id = ?
|
|
12358
13233
|
`);
|
|
12359
13234
|
await deleteStmt.bind(userId).run();
|
|
12360
|
-
await
|
|
13235
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12361
13236
|
db,
|
|
12362
13237
|
user.userId,
|
|
12363
13238
|
"user!.hard_delete",
|
|
@@ -12376,7 +13251,7 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
12376
13251
|
UPDATE users SET is_active = 0, updated_at = ? WHERE id = ?
|
|
12377
13252
|
`);
|
|
12378
13253
|
await deleteStmt.bind(Date.now(), userId).run();
|
|
12379
|
-
await
|
|
13254
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12380
13255
|
db,
|
|
12381
13256
|
user.userId,
|
|
12382
13257
|
"user!.soft_delete",
|
|
@@ -12442,7 +13317,7 @@ userRoutes.post("/invite-user", async (c) => {
|
|
|
12442
13317
|
Date.now(),
|
|
12443
13318
|
Date.now()
|
|
12444
13319
|
).run();
|
|
12445
|
-
await
|
|
13320
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12446
13321
|
db,
|
|
12447
13322
|
user.userId,
|
|
12448
13323
|
"user!.invite_sent",
|
|
@@ -12499,7 +13374,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
|
|
|
12499
13374
|
Date.now(),
|
|
12500
13375
|
userId
|
|
12501
13376
|
).run();
|
|
12502
|
-
await
|
|
13377
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12503
13378
|
db,
|
|
12504
13379
|
user.userId,
|
|
12505
13380
|
"user!.invitation_resent",
|
|
@@ -12535,7 +13410,7 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
|
|
|
12535
13410
|
}
|
|
12536
13411
|
const deleteStmt = db.prepare(`DELETE FROM users WHERE id = ?`);
|
|
12537
13412
|
await deleteStmt.bind(userId).run();
|
|
12538
|
-
await
|
|
13413
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12539
13414
|
db,
|
|
12540
13415
|
user.userId,
|
|
12541
13416
|
"user!.invitation_cancelled",
|
|
@@ -12618,7 +13493,7 @@ userRoutes.get("/activity-logs", async (c) => {
|
|
|
12618
13493
|
...log,
|
|
12619
13494
|
details: log.details ? JSON.parse(log.details) : null
|
|
12620
13495
|
}));
|
|
12621
|
-
await
|
|
13496
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12622
13497
|
db,
|
|
12623
13498
|
user.userId,
|
|
12624
13499
|
"activity.logs_viewed",
|
|
@@ -12725,7 +13600,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
|
|
|
12725
13600
|
csvRows.push(row.join(","));
|
|
12726
13601
|
}
|
|
12727
13602
|
const csvContent = csvRows.join("\n");
|
|
12728
|
-
await
|
|
13603
|
+
await chunkIT2TC4ZD_cjs.logActivity(
|
|
12729
13604
|
db,
|
|
12730
13605
|
user.userId,
|
|
12731
13606
|
"activity.logs_exported",
|
|
@@ -14064,7 +14939,7 @@ var fileValidationSchema2 = zod.z.object({
|
|
|
14064
14939
|
// 50MB max
|
|
14065
14940
|
});
|
|
14066
14941
|
var adminMediaRoutes = new hono.Hono();
|
|
14067
|
-
adminMediaRoutes.use("*",
|
|
14942
|
+
adminMediaRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
14068
14943
|
adminMediaRoutes.get("/", async (c) => {
|
|
14069
14944
|
try {
|
|
14070
14945
|
const user = c.get("user");
|
|
@@ -14650,7 +15525,7 @@ adminMediaRoutes.put("/:id", async (c) => {
|
|
|
14650
15525
|
`);
|
|
14651
15526
|
}
|
|
14652
15527
|
});
|
|
14653
|
-
adminMediaRoutes.delete("/cleanup",
|
|
15528
|
+
adminMediaRoutes.delete("/cleanup", chunkIT2TC4ZD_cjs.requireRole("admin"), async (c) => {
|
|
14654
15529
|
try {
|
|
14655
15530
|
const db = c.env.DB;
|
|
14656
15531
|
const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
|
|
@@ -16873,7 +17748,7 @@ function renderEmailSettingsContent(plugin, settings) {
|
|
|
16873
17748
|
|
|
16874
17749
|
// src/routes/admin-plugins.ts
|
|
16875
17750
|
var adminPluginRoutes = new hono.Hono();
|
|
16876
|
-
adminPluginRoutes.use("*",
|
|
17751
|
+
adminPluginRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
16877
17752
|
var AVAILABLE_PLUGINS = [
|
|
16878
17753
|
{
|
|
16879
17754
|
id: "third-party-faq",
|
|
@@ -17000,7 +17875,7 @@ adminPluginRoutes.get("/", async (c) => {
|
|
|
17000
17875
|
if (user?.role !== "admin") {
|
|
17001
17876
|
return c.text("Access denied", 403);
|
|
17002
17877
|
}
|
|
17003
|
-
const pluginService = new
|
|
17878
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
17004
17879
|
let installedPlugins = [];
|
|
17005
17880
|
let stats = { total: 0, active: 0, inactive: 0, errors: 0, uninstalled: 0 };
|
|
17006
17881
|
try {
|
|
@@ -17076,7 +17951,7 @@ adminPluginRoutes.get("/:id", async (c) => {
|
|
|
17076
17951
|
if (user?.role !== "admin") {
|
|
17077
17952
|
return c.redirect("/admin/plugins");
|
|
17078
17953
|
}
|
|
17079
|
-
const pluginService = new
|
|
17954
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
17080
17955
|
const plugin = await pluginService.getPlugin(pluginId);
|
|
17081
17956
|
if (!plugin) {
|
|
17082
17957
|
return c.text("Plugin not found", 404);
|
|
@@ -17160,7 +18035,7 @@ adminPluginRoutes.post("/:id/activate", async (c) => {
|
|
|
17160
18035
|
if (user?.role !== "admin") {
|
|
17161
18036
|
return c.json({ error: "Access denied" }, 403);
|
|
17162
18037
|
}
|
|
17163
|
-
const pluginService = new
|
|
18038
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
17164
18039
|
await pluginService.activatePlugin(pluginId);
|
|
17165
18040
|
return c.json({ success: true });
|
|
17166
18041
|
} catch (error) {
|
|
@@ -17177,7 +18052,7 @@ adminPluginRoutes.post("/:id/deactivate", async (c) => {
|
|
|
17177
18052
|
if (user?.role !== "admin") {
|
|
17178
18053
|
return c.json({ error: "Access denied" }, 403);
|
|
17179
18054
|
}
|
|
17180
|
-
const pluginService = new
|
|
18055
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
17181
18056
|
await pluginService.deactivatePlugin(pluginId);
|
|
17182
18057
|
return c.json({ success: true });
|
|
17183
18058
|
} catch (error) {
|
|
@@ -17194,7 +18069,7 @@ adminPluginRoutes.post("/install", async (c) => {
|
|
|
17194
18069
|
return c.json({ error: "Access denied" }, 403);
|
|
17195
18070
|
}
|
|
17196
18071
|
const body = await c.req.json();
|
|
17197
|
-
const pluginService = new
|
|
18072
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
17198
18073
|
if (body.name === "faq-plugin") {
|
|
17199
18074
|
const faqPlugin = await pluginService.installPlugin({
|
|
17200
18075
|
id: "third-party-faq",
|
|
@@ -17464,7 +18339,7 @@ adminPluginRoutes.post("/:id/uninstall", async (c) => {
|
|
|
17464
18339
|
if (user?.role !== "admin") {
|
|
17465
18340
|
return c.json({ error: "Access denied" }, 403);
|
|
17466
18341
|
}
|
|
17467
|
-
const pluginService = new
|
|
18342
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
17468
18343
|
await pluginService.uninstallPlugin(pluginId);
|
|
17469
18344
|
return c.json({ success: true });
|
|
17470
18345
|
} catch (error) {
|
|
@@ -17482,8 +18357,20 @@ adminPluginRoutes.post("/:id/settings", async (c) => {
|
|
|
17482
18357
|
return c.json({ error: "Access denied" }, 403);
|
|
17483
18358
|
}
|
|
17484
18359
|
const settings = await c.req.json();
|
|
17485
|
-
const pluginService = new
|
|
18360
|
+
const pluginService = new chunkIIBRG5S5_cjs.PluginService(db);
|
|
17486
18361
|
await pluginService.updatePluginSettings(pluginId, settings);
|
|
18362
|
+
if (pluginId === "core-auth") {
|
|
18363
|
+
try {
|
|
18364
|
+
const cacheKv = c.env.CACHE_KV;
|
|
18365
|
+
if (cacheKv) {
|
|
18366
|
+
await cacheKv.delete("auth:settings");
|
|
18367
|
+
await cacheKv.delete("auth:registration-enabled");
|
|
18368
|
+
console.log("[AuthSettings] Cache cleared after updating authentication settings");
|
|
18369
|
+
}
|
|
18370
|
+
} catch (cacheError) {
|
|
18371
|
+
console.error("[AuthSettings] Failed to clear cache:", cacheError);
|
|
18372
|
+
}
|
|
18373
|
+
}
|
|
17487
18374
|
return c.json({ success: true });
|
|
17488
18375
|
} catch (error) {
|
|
17489
18376
|
console.error("Error updating plugin settings:", error);
|
|
@@ -18278,7 +19165,7 @@ function renderLogConfigPage(data) {
|
|
|
18278
19165
|
|
|
18279
19166
|
// src/routes/admin-logs.ts
|
|
18280
19167
|
var adminLogsRoutes = new hono.Hono();
|
|
18281
|
-
adminLogsRoutes.use("*",
|
|
19168
|
+
adminLogsRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
18282
19169
|
adminLogsRoutes.get("/", async (c) => {
|
|
18283
19170
|
try {
|
|
18284
19171
|
const user = c.get("user");
|
|
@@ -20606,9 +21493,9 @@ function renderStorageUsage(databaseSizeBytes, mediaSizeBytes) {
|
|
|
20606
21493
|
}
|
|
20607
21494
|
|
|
20608
21495
|
// src/routes/admin-dashboard.ts
|
|
20609
|
-
var VERSION =
|
|
21496
|
+
var VERSION = chunkEKPLKUZT_cjs.getCoreVersion();
|
|
20610
21497
|
var router = new hono.Hono();
|
|
20611
|
-
router.use("*",
|
|
21498
|
+
router.use("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
20612
21499
|
router.get("/", async (c) => {
|
|
20613
21500
|
const user = c.get("user");
|
|
20614
21501
|
try {
|
|
@@ -22427,7 +23314,10 @@ function renderCollectionFormPage(data) {
|
|
|
22427
23314
|
|
|
22428
23315
|
// src/routes/admin-collections.ts
|
|
22429
23316
|
var adminCollectionsRoutes = new hono.Hono();
|
|
22430
|
-
adminCollectionsRoutes.use("*",
|
|
23317
|
+
adminCollectionsRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
23318
|
+
adminCollectionsRoutes.post("*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
|
|
23319
|
+
adminCollectionsRoutes.put("*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
|
|
23320
|
+
adminCollectionsRoutes.delete("*", chunkIT2TC4ZD_cjs.requireRole(["admin"]));
|
|
22431
23321
|
adminCollectionsRoutes.get("/", async (c) => {
|
|
22432
23322
|
try {
|
|
22433
23323
|
const user = c.get("user");
|
|
@@ -24622,7 +25512,7 @@ function renderDatabaseToolsSettings(settings) {
|
|
|
24622
25512
|
|
|
24623
25513
|
// src/routes/admin-settings.ts
|
|
24624
25514
|
var adminSettingsRoutes = new hono.Hono();
|
|
24625
|
-
adminSettingsRoutes.use("*",
|
|
25515
|
+
adminSettingsRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
24626
25516
|
function getMockSettings(user) {
|
|
24627
25517
|
return {
|
|
24628
25518
|
general: {
|
|
@@ -24790,7 +25680,7 @@ adminSettingsRoutes.get("/database-tools", (c) => {
|
|
|
24790
25680
|
adminSettingsRoutes.get("/api/migrations/status", async (c) => {
|
|
24791
25681
|
try {
|
|
24792
25682
|
const db = c.env.DB;
|
|
24793
|
-
const migrationService = new
|
|
25683
|
+
const migrationService = new chunkZMVWMJ3S_cjs.MigrationService(db);
|
|
24794
25684
|
const status = await migrationService.getMigrationStatus();
|
|
24795
25685
|
return c.json({
|
|
24796
25686
|
success: true,
|
|
@@ -24814,7 +25704,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
|
|
|
24814
25704
|
}, 403);
|
|
24815
25705
|
}
|
|
24816
25706
|
const db = c.env.DB;
|
|
24817
|
-
const migrationService = new
|
|
25707
|
+
const migrationService = new chunkZMVWMJ3S_cjs.MigrationService(db);
|
|
24818
25708
|
const result = await migrationService.runPendingMigrations();
|
|
24819
25709
|
return c.json({
|
|
24820
25710
|
success: result.success,
|
|
@@ -24832,7 +25722,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
|
|
|
24832
25722
|
adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
|
|
24833
25723
|
try {
|
|
24834
25724
|
const db = c.env.DB;
|
|
24835
|
-
const migrationService = new
|
|
25725
|
+
const migrationService = new chunkZMVWMJ3S_cjs.MigrationService(db);
|
|
24836
25726
|
const validation = await migrationService.validateSchema();
|
|
24837
25727
|
return c.json({
|
|
24838
25728
|
success: true,
|
|
@@ -26722,7 +27612,7 @@ function renderFormCreatePage(data) {
|
|
|
26722
27612
|
|
|
26723
27613
|
// src/routes/admin-forms.ts
|
|
26724
27614
|
var adminFormsRoutes = new hono.Hono();
|
|
26725
|
-
adminFormsRoutes.use("*",
|
|
27615
|
+
adminFormsRoutes.use("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
26726
27616
|
adminFormsRoutes.get("/", async (c) => {
|
|
26727
27617
|
try {
|
|
26728
27618
|
const user = c.get("user");
|
|
@@ -27873,9 +28763,9 @@ function renderAPIReferencePage(data) {
|
|
|
27873
28763
|
}
|
|
27874
28764
|
|
|
27875
28765
|
// src/routes/admin-api-reference.ts
|
|
27876
|
-
var VERSION2 =
|
|
28766
|
+
var VERSION2 = chunkEKPLKUZT_cjs.getCoreVersion();
|
|
27877
28767
|
var router2 = new hono.Hono();
|
|
27878
|
-
router2.use("*",
|
|
28768
|
+
router2.use("*", chunkIT2TC4ZD_cjs.requireAuth());
|
|
27879
28769
|
router2.get("/", async (c) => {
|
|
27880
28770
|
const user = c.get("user");
|
|
27881
28771
|
try {
|
|
@@ -27962,5 +28852,5 @@ exports.router = router;
|
|
|
27962
28852
|
exports.router2 = router2;
|
|
27963
28853
|
exports.test_cleanup_default = test_cleanup_default;
|
|
27964
28854
|
exports.userRoutes = userRoutes;
|
|
27965
|
-
//# sourceMappingURL=chunk-
|
|
27966
|
-
//# sourceMappingURL=chunk-
|
|
28855
|
+
//# sourceMappingURL=chunk-RCA6R6VE.cjs.map
|
|
28856
|
+
//# sourceMappingURL=chunk-RCA6R6VE.cjs.map
|