@sonicjs-cms/core 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-Db0AfT5F.d.cts → app-DV27cjPy.d.cts} +1 -1
- package/dist/{app-Db0AfT5F.d.ts → app-DV27cjPy.d.ts} +1 -1
- package/dist/{chunk-YIXSSJWD.cjs → chunk-63K7XXRX.cjs} +5 -5
- package/dist/{chunk-YIXSSJWD.cjs.map → chunk-63K7XXRX.cjs.map} +1 -1
- package/dist/{chunk-VNCYCH3H.js → chunk-7DL5SPPX.js} +59 -5
- package/dist/chunk-7DL5SPPX.js.map +1 -0
- package/dist/{chunk-AZLU3ROK.cjs → chunk-BZC4FYW7.cjs} +4 -4
- package/dist/chunk-BZC4FYW7.cjs.map +1 -0
- package/dist/chunk-CLIH2T74.js +403 -0
- package/dist/chunk-CLIH2T74.js.map +1 -0
- package/dist/{chunk-D2NLCPO2.js → chunk-EVZOVYLO.js} +53 -2
- package/dist/chunk-EVZOVYLO.js.map +1 -0
- package/dist/{chunk-DXM575E2.js → chunk-EYWR6UA2.js} +6 -6
- package/dist/chunk-EYWR6UA2.js.map +1 -0
- package/dist/{chunk-CPXAVWCU.js → chunk-F332TENF.js} +278 -3
- package/dist/chunk-F332TENF.js.map +1 -0
- package/dist/{chunk-FT6NBHNX.js → chunk-F6GZURXJ.js} +2536 -600
- package/dist/chunk-F6GZURXJ.js.map +1 -0
- package/dist/{chunk-2MI3LZFH.cjs → chunk-IIRVZSP2.cjs} +53 -2
- package/dist/chunk-IIRVZSP2.cjs.map +1 -0
- package/dist/{chunk-V5LBQN3I.js → chunk-KA2PDJNB.js} +4 -4
- package/dist/chunk-KA2PDJNB.js.map +1 -0
- package/dist/{chunk-AVPUX57O.js → chunk-KAOWRIFD.js} +3 -3
- package/dist/{chunk-AVPUX57O.js.map → chunk-KAOWRIFD.js.map} +1 -1
- package/dist/{chunk-ILZ3DP4I.cjs → chunk-MPT5PA6U.cjs} +24 -2
- package/dist/chunk-MPT5PA6U.cjs.map +1 -0
- package/dist/{chunk-A4SVOGG6.cjs → chunk-N7TDLOUE.cjs} +2696 -762
- package/dist/chunk-N7TDLOUE.cjs.map +1 -0
- package/dist/{chunk-7I5INVNR.cjs → chunk-T3YIKW2A.cjs} +9 -9
- package/dist/chunk-T3YIKW2A.cjs.map +1 -0
- package/dist/{chunk-DTLB6UIH.cjs → chunk-Y72M3MVX.cjs} +280 -2
- package/dist/chunk-Y72M3MVX.cjs.map +1 -0
- package/dist/{chunk-SGAG6FD3.js → chunk-YFJJU26H.js} +24 -2
- package/dist/chunk-YFJJU26H.js.map +1 -0
- package/dist/chunk-YHW27CBV.cjs +406 -0
- package/dist/chunk-YHW27CBV.cjs.map +1 -0
- package/dist/{chunk-FYEDK7K7.cjs → chunk-YMTTGHEK.cjs} +61 -4
- package/dist/chunk-YMTTGHEK.cjs.map +1 -0
- package/dist/{collection-config-FLlGtsh9.d.cts → collection-config-BF95LgQb.d.cts} +10 -2
- package/dist/{collection-config-FLlGtsh9.d.ts → collection-config-BF95LgQb.d.ts} +10 -2
- package/dist/index.cjs +2001 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +504 -9
- package/dist/index.d.ts +504 -9
- package/dist/index.js +1893 -41
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +24 -24
- package/dist/middleware.d.cts +1 -1
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +3 -3
- package/dist/migrations-QNYAWQLB.cjs +13 -0
- package/dist/{migrations-32QAYLWJ.cjs.map → migrations-QNYAWQLB.cjs.map} +1 -1
- package/dist/migrations-R6NQBKQV.js +4 -0
- package/dist/{migrations-57ZHBQ4X.js.map → migrations-R6NQBKQV.js.map} +1 -1
- package/dist/{plugin-bootstrap-CDh0JHtW.d.ts → plugin-bootstrap-CB-xaBfK.d.ts} +2 -2
- package/dist/{plugin-bootstrap-C0E3jdz-.d.cts → plugin-bootstrap-U-cw9jn3.d.cts} +2 -2
- package/dist/plugin-manager-Baa6xXqB.d.ts +328 -0
- package/dist/plugin-manager-vBal9Zip.d.cts +328 -0
- package/dist/plugins.cjs +20 -7
- package/dist/plugins.d.cts +53 -310
- package/dist/plugins.d.ts +53 -310
- package/dist/plugins.js +2 -1
- package/dist/routes.cjs +27 -26
- package/dist/routes.d.cts +1 -1
- package/dist/routes.d.ts +1 -1
- package/dist/routes.js +7 -6
- 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/templates.cjs +17 -17
- package/dist/templates.js +2 -2
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils.cjs +23 -11
- package/dist/utils.d.cts +38 -1
- package/dist/utils.d.ts +38 -1
- package/dist/utils.js +1 -1
- package/migrations/027_fix_slug_field_type.sql +18 -0
- package/migrations/028_fix_slug_field_type_in_schemas.sql +30 -0
- package/migrations/029_ai_search_plugin.sql +45 -0
- package/package.json +5 -2
- package/dist/chunk-2MI3LZFH.cjs.map +0 -1
- package/dist/chunk-7I5INVNR.cjs.map +0 -1
- package/dist/chunk-A4SVOGG6.cjs.map +0 -1
- package/dist/chunk-AZLU3ROK.cjs.map +0 -1
- package/dist/chunk-CPXAVWCU.js.map +0 -1
- package/dist/chunk-D2NLCPO2.js.map +0 -1
- package/dist/chunk-DTLB6UIH.cjs.map +0 -1
- package/dist/chunk-DXM575E2.js.map +0 -1
- package/dist/chunk-FT6NBHNX.js.map +0 -1
- package/dist/chunk-FYEDK7K7.cjs.map +0 -1
- package/dist/chunk-ILZ3DP4I.cjs.map +0 -1
- package/dist/chunk-SGAG6FD3.js.map +0 -1
- package/dist/chunk-V5LBQN3I.js.map +0 -1
- package/dist/chunk-VNCYCH3H.js.map +0 -1
- package/dist/migrations-32QAYLWJ.cjs +0 -13
- package/dist/migrations-57ZHBQ4X.js +0 -4
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunk7FOAMNTI_cjs = require('./chunk-7FOAMNTI.cjs');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
4
|
+
var chunkT3YIKW2A_cjs = require('./chunk-T3YIKW2A.cjs');
|
|
5
|
+
var chunkMPT5PA6U_cjs = require('./chunk-MPT5PA6U.cjs');
|
|
6
|
+
var chunkIIRVZSP2_cjs = require('./chunk-IIRVZSP2.cjs');
|
|
7
|
+
var chunkBZC4FYW7_cjs = require('./chunk-BZC4FYW7.cjs');
|
|
8
|
+
var chunkYHW27CBV_cjs = require('./chunk-YHW27CBV.cjs');
|
|
9
|
+
var chunkYMTTGHEK_cjs = require('./chunk-YMTTGHEK.cjs');
|
|
9
10
|
var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
|
|
10
11
|
var hono = require('hono');
|
|
11
12
|
var cors = require('hono/cors');
|
|
@@ -16,6 +17,37 @@ var html = require('hono/html');
|
|
|
16
17
|
// src/schemas/index.ts
|
|
17
18
|
var schemaDefinitions = [];
|
|
18
19
|
var apiContentCrudRoutes = new hono.Hono();
|
|
20
|
+
apiContentCrudRoutes.get("/check-slug", async (c) => {
|
|
21
|
+
try {
|
|
22
|
+
const db = c.env.DB;
|
|
23
|
+
const collectionId = c.req.query("collectionId");
|
|
24
|
+
const slug = c.req.query("slug");
|
|
25
|
+
const excludeId = c.req.query("excludeId");
|
|
26
|
+
if (!collectionId || !slug) {
|
|
27
|
+
return c.json({ error: "collectionId and slug are required" }, 400);
|
|
28
|
+
}
|
|
29
|
+
let query = "SELECT id FROM content WHERE collection_id = ? AND slug = ?";
|
|
30
|
+
const params = [collectionId, slug];
|
|
31
|
+
if (excludeId) {
|
|
32
|
+
query += " AND id != ?";
|
|
33
|
+
params.push(excludeId);
|
|
34
|
+
}
|
|
35
|
+
const existing = await db.prepare(query).bind(...params).first();
|
|
36
|
+
if (existing) {
|
|
37
|
+
return c.json({
|
|
38
|
+
available: false,
|
|
39
|
+
message: "This URL slug is already in use in this collection"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return c.json({ available: true });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error("Error checking slug:", error);
|
|
45
|
+
return c.json({
|
|
46
|
+
error: "Failed to check slug availability",
|
|
47
|
+
details: error instanceof Error ? error.message : String(error)
|
|
48
|
+
}, 500);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
19
51
|
apiContentCrudRoutes.get("/:id", async (c) => {
|
|
20
52
|
try {
|
|
21
53
|
const id = c.req.param("id");
|
|
@@ -44,7 +76,7 @@ apiContentCrudRoutes.get("/:id", async (c) => {
|
|
|
44
76
|
}, 500);
|
|
45
77
|
}
|
|
46
78
|
});
|
|
47
|
-
apiContentCrudRoutes.post("/",
|
|
79
|
+
apiContentCrudRoutes.post("/", chunkT3YIKW2A_cjs.requireAuth(), async (c) => {
|
|
48
80
|
try {
|
|
49
81
|
const db = c.env.DB;
|
|
50
82
|
const user = c.get("user");
|
|
@@ -110,7 +142,7 @@ apiContentCrudRoutes.post("/", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
|
|
|
110
142
|
}, 500);
|
|
111
143
|
}
|
|
112
144
|
});
|
|
113
|
-
apiContentCrudRoutes.put("/:id",
|
|
145
|
+
apiContentCrudRoutes.put("/:id", chunkT3YIKW2A_cjs.requireAuth(), async (c) => {
|
|
114
146
|
try {
|
|
115
147
|
const id = c.req.param("id");
|
|
116
148
|
const db = c.env.DB;
|
|
@@ -174,7 +206,7 @@ apiContentCrudRoutes.put("/:id", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
|
|
|
174
206
|
}, 500);
|
|
175
207
|
}
|
|
176
208
|
});
|
|
177
|
-
apiContentCrudRoutes.delete("/:id",
|
|
209
|
+
apiContentCrudRoutes.delete("/:id", chunkT3YIKW2A_cjs.requireAuth(), async (c) => {
|
|
178
210
|
try {
|
|
179
211
|
const id = c.req.param("id");
|
|
180
212
|
const db = c.env.DB;
|
|
@@ -210,7 +242,7 @@ apiRoutes.use("*", async (c, next) => {
|
|
|
210
242
|
c.header("X-Response-Time", `${totalTime}ms`);
|
|
211
243
|
});
|
|
212
244
|
apiRoutes.use("*", async (c, next) => {
|
|
213
|
-
const cacheEnabled = await
|
|
245
|
+
const cacheEnabled = await chunkT3YIKW2A_cjs.isPluginActive(c.env.DB, "core-cache");
|
|
214
246
|
c.set("cacheEnabled", cacheEnabled);
|
|
215
247
|
await next();
|
|
216
248
|
});
|
|
@@ -335,12 +367,12 @@ apiRoutes.get("/content", async (c) => {
|
|
|
335
367
|
});
|
|
336
368
|
}
|
|
337
369
|
}
|
|
338
|
-
const filter =
|
|
370
|
+
const filter = chunkYMTTGHEK_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
|
|
339
371
|
if (!filter.limit) {
|
|
340
372
|
filter.limit = 50;
|
|
341
373
|
}
|
|
342
374
|
filter.limit = Math.min(filter.limit, 1e3);
|
|
343
|
-
const builder3 = new
|
|
375
|
+
const builder3 = new chunkYMTTGHEK_cjs.QueryFilterBuilder();
|
|
344
376
|
const queryResult = builder3.build("content", filter);
|
|
345
377
|
if (queryResult.errors.length > 0) {
|
|
346
378
|
return c.json({
|
|
@@ -427,7 +459,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
|
|
|
427
459
|
if (!collectionResult) {
|
|
428
460
|
return c.json({ error: "Collection not found" }, 404);
|
|
429
461
|
}
|
|
430
|
-
const filter =
|
|
462
|
+
const filter = chunkYMTTGHEK_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
|
|
431
463
|
if (!filter.where) {
|
|
432
464
|
filter.where = { and: [] };
|
|
433
465
|
}
|
|
@@ -443,7 +475,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
|
|
|
443
475
|
filter.limit = 50;
|
|
444
476
|
}
|
|
445
477
|
filter.limit = Math.min(filter.limit, 1e3);
|
|
446
|
-
const builder3 = new
|
|
478
|
+
const builder3 = new chunkYMTTGHEK_cjs.QueryFilterBuilder();
|
|
447
479
|
const queryResult = builder3.build("content", filter);
|
|
448
480
|
if (queryResult.errors.length > 0) {
|
|
449
481
|
return c.json({
|
|
@@ -568,7 +600,7 @@ var fileValidationSchema = zod.z.object({
|
|
|
568
600
|
// 50MB max
|
|
569
601
|
});
|
|
570
602
|
var apiMediaRoutes = new hono.Hono();
|
|
571
|
-
apiMediaRoutes.use("*",
|
|
603
|
+
apiMediaRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
|
|
572
604
|
apiMediaRoutes.post("/upload", async (c) => {
|
|
573
605
|
try {
|
|
574
606
|
const user = c.get("user");
|
|
@@ -1312,8 +1344,8 @@ apiSystemRoutes.get("/env", (c) => {
|
|
|
1312
1344
|
});
|
|
1313
1345
|
var api_system_default = apiSystemRoutes;
|
|
1314
1346
|
var adminApiRoutes = new hono.Hono();
|
|
1315
|
-
adminApiRoutes.use("*",
|
|
1316
|
-
adminApiRoutes.use("*",
|
|
1347
|
+
adminApiRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
|
|
1348
|
+
adminApiRoutes.use("*", chunkT3YIKW2A_cjs.requireRole(["admin", "editor"]));
|
|
1317
1349
|
adminApiRoutes.get("/stats", async (c) => {
|
|
1318
1350
|
try {
|
|
1319
1351
|
const db = c.env.DB;
|
|
@@ -1553,6 +1585,107 @@ adminApiRoutes.get("/collections/:id", async (c) => {
|
|
|
1553
1585
|
return c.json({ error: "Failed to fetch collection" }, 500);
|
|
1554
1586
|
}
|
|
1555
1587
|
});
|
|
1588
|
+
adminApiRoutes.get("/references", async (c) => {
|
|
1589
|
+
try {
|
|
1590
|
+
const db = c.env.DB;
|
|
1591
|
+
const url = new URL(c.req.url);
|
|
1592
|
+
const collectionParams = url.searchParams.getAll("collection").flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
|
|
1593
|
+
const search = c.req.query("search") || "";
|
|
1594
|
+
const id = c.req.query("id") || "";
|
|
1595
|
+
const limit = Math.min(Number.parseInt(c.req.query("limit") || "20", 10) || 20, 100);
|
|
1596
|
+
if (collectionParams.length === 0) {
|
|
1597
|
+
return c.json({ error: "Collection is required" }, 400);
|
|
1598
|
+
}
|
|
1599
|
+
const placeholders = collectionParams.map(() => "?").join(", ");
|
|
1600
|
+
const collectionStmt = db.prepare(`
|
|
1601
|
+
SELECT id, name, display_name
|
|
1602
|
+
FROM collections
|
|
1603
|
+
WHERE id IN (${placeholders}) OR name IN (${placeholders})
|
|
1604
|
+
`);
|
|
1605
|
+
const collectionResults = await collectionStmt.bind(...collectionParams, ...collectionParams).all();
|
|
1606
|
+
const collections = collectionResults.results || [];
|
|
1607
|
+
if (collections.length === 0) {
|
|
1608
|
+
return c.json({ error: "Collection not found" }, 404);
|
|
1609
|
+
}
|
|
1610
|
+
const collectionById = Object.fromEntries(
|
|
1611
|
+
collections.map((entry) => [
|
|
1612
|
+
entry.id,
|
|
1613
|
+
{
|
|
1614
|
+
id: entry.id,
|
|
1615
|
+
name: entry.name,
|
|
1616
|
+
display_name: entry.display_name
|
|
1617
|
+
}
|
|
1618
|
+
])
|
|
1619
|
+
);
|
|
1620
|
+
const collectionIds = collections.map((entry) => entry.id);
|
|
1621
|
+
if (id) {
|
|
1622
|
+
const idPlaceholders = collectionIds.map(() => "?").join(", ");
|
|
1623
|
+
const itemStmt = db.prepare(`
|
|
1624
|
+
SELECT id, title, slug, collection_id
|
|
1625
|
+
FROM content
|
|
1626
|
+
WHERE id = ? AND collection_id IN (${idPlaceholders})
|
|
1627
|
+
LIMIT 1
|
|
1628
|
+
`);
|
|
1629
|
+
const item = await itemStmt.bind(id, ...collectionIds).first();
|
|
1630
|
+
if (!item) {
|
|
1631
|
+
return c.json({ error: "Reference not found" }, 404);
|
|
1632
|
+
}
|
|
1633
|
+
return c.json({
|
|
1634
|
+
data: {
|
|
1635
|
+
id: item.id,
|
|
1636
|
+
title: item.title,
|
|
1637
|
+
slug: item.slug,
|
|
1638
|
+
collection: collectionById[item.collection_id]
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
let stmt;
|
|
1643
|
+
let results;
|
|
1644
|
+
const listPlaceholders = collectionIds.map(() => "?").join(", ");
|
|
1645
|
+
const statusFilterValues = ["published"];
|
|
1646
|
+
const statusClause = ` AND status IN (${statusFilterValues.map(() => "?").join(", ")})`;
|
|
1647
|
+
if (search) {
|
|
1648
|
+
const searchParam = `%${search}%`;
|
|
1649
|
+
stmt = db.prepare(`
|
|
1650
|
+
SELECT id, title, slug, status, updated_at, collection_id
|
|
1651
|
+
FROM content
|
|
1652
|
+
WHERE collection_id IN (${listPlaceholders})
|
|
1653
|
+
AND (title LIKE ? OR slug LIKE ?)
|
|
1654
|
+
${statusClause}
|
|
1655
|
+
ORDER BY updated_at DESC
|
|
1656
|
+
LIMIT ?
|
|
1657
|
+
`);
|
|
1658
|
+
const queryResults = await stmt.bind(...collectionIds, searchParam, searchParam, ...statusFilterValues, limit).all();
|
|
1659
|
+
results = queryResults.results;
|
|
1660
|
+
} else {
|
|
1661
|
+
stmt = db.prepare(`
|
|
1662
|
+
SELECT id, title, slug, status, updated_at, collection_id
|
|
1663
|
+
FROM content
|
|
1664
|
+
WHERE collection_id IN (${listPlaceholders})
|
|
1665
|
+
${statusClause}
|
|
1666
|
+
ORDER BY updated_at DESC
|
|
1667
|
+
LIMIT ?
|
|
1668
|
+
`);
|
|
1669
|
+
const queryResults = await stmt.bind(...collectionIds, ...statusFilterValues, limit).all();
|
|
1670
|
+
results = queryResults.results;
|
|
1671
|
+
}
|
|
1672
|
+
const items = (results || []).map((row) => ({
|
|
1673
|
+
id: row.id,
|
|
1674
|
+
title: row.title,
|
|
1675
|
+
slug: row.slug,
|
|
1676
|
+
status: row.status,
|
|
1677
|
+
updated_at: row.updated_at ? Number(row.updated_at) : null,
|
|
1678
|
+
collection: collectionById[row.collection_id]
|
|
1679
|
+
}));
|
|
1680
|
+
return c.json({
|
|
1681
|
+
data: items,
|
|
1682
|
+
count: items.length
|
|
1683
|
+
});
|
|
1684
|
+
} catch (error) {
|
|
1685
|
+
console.error("Error fetching reference options:", error);
|
|
1686
|
+
return c.json({ error: "Failed to fetch references" }, 500);
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1556
1689
|
adminApiRoutes.post("/collections", async (c) => {
|
|
1557
1690
|
try {
|
|
1558
1691
|
const contentType = c.req.header("Content-Type");
|
|
@@ -1722,7 +1855,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
|
|
|
1722
1855
|
});
|
|
1723
1856
|
adminApiRoutes.get("/migrations/status", async (c) => {
|
|
1724
1857
|
try {
|
|
1725
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1858
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-QNYAWQLB.cjs');
|
|
1726
1859
|
const db = c.env.DB;
|
|
1727
1860
|
const migrationService = new MigrationService2(db);
|
|
1728
1861
|
const status = await migrationService.getMigrationStatus();
|
|
@@ -1747,7 +1880,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
1747
1880
|
error: "Unauthorized. Admin access required."
|
|
1748
1881
|
}, 403);
|
|
1749
1882
|
}
|
|
1750
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1883
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-QNYAWQLB.cjs');
|
|
1751
1884
|
const db = c.env.DB;
|
|
1752
1885
|
const migrationService = new MigrationService2(db);
|
|
1753
1886
|
const result = await migrationService.runPendingMigrations();
|
|
@@ -1766,7 +1899,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
1766
1899
|
});
|
|
1767
1900
|
adminApiRoutes.get("/migrations/validate", async (c) => {
|
|
1768
1901
|
try {
|
|
1769
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1902
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-QNYAWQLB.cjs');
|
|
1770
1903
|
const db = c.env.DB;
|
|
1771
1904
|
const migrationService = new MigrationService2(db);
|
|
1772
1905
|
const validation = await migrationService.validateSchema();
|
|
@@ -1793,7 +1926,7 @@ function renderLoginPage(data, demoLoginActive = false) {
|
|
|
1793
1926
|
<meta charset="UTF-8">
|
|
1794
1927
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1795
1928
|
<title>Login - SonicJS AI</title>
|
|
1796
|
-
<link rel="icon" type="image/
|
|
1929
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
1797
1930
|
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
|
|
1798
1931
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
1799
1932
|
<script>
|
|
@@ -1841,8 +1974,8 @@ function renderLoginPage(data, demoLoginActive = false) {
|
|
|
1841
1974
|
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
|
1842
1975
|
<div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
|
|
1843
1976
|
<!-- Alerts -->
|
|
1844
|
-
${data.error ? `<div class="mb-6">${
|
|
1845
|
-
${data.message ? `<div class="mb-6">${
|
|
1977
|
+
${data.error ? `<div class="mb-6">${chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
|
|
1978
|
+
${data.message ? `<div class="mb-6">${chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.message })}</div>` : ""}
|
|
1846
1979
|
|
|
1847
1980
|
<!-- Form Response (HTMX target) -->
|
|
1848
1981
|
<div id="form-response" class="mb-6"></div>
|
|
@@ -1970,7 +2103,7 @@ function renderRegisterPage(data) {
|
|
|
1970
2103
|
<meta charset="UTF-8">
|
|
1971
2104
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1972
2105
|
<title>Register - SonicJS AI</title>
|
|
1973
|
-
<link rel="icon" type="image/
|
|
2106
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
1974
2107
|
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
|
|
1975
2108
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
1976
2109
|
<script>
|
|
@@ -1993,42 +2126,20 @@ function renderRegisterPage(data) {
|
|
|
1993
2126
|
<div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
|
1994
2127
|
<!-- Logo Section -->
|
|
1995
2128
|
<div class="sm:mx-auto sm:w-full sm:max-w-md text-center">
|
|
1996
|
-
<div class="mx-auto w-
|
|
1997
|
-
<svg class="w-
|
|
1998
|
-
<path
|
|
1999
|
-
<path fill="#F1F2F2" d="M974.78,1398.211c-5.016,6.574-10.034,13.146-15.048,19.721c-1.828,2.398-3.657,4.796-5.487,7.194 c1.994,1.719,3.958,3.51,5.873,5.424c18.724,18.731,28.089,41.216,28.089,67.459c0,26.251-9.366,48.658-28.089,67.237 c-18.731,18.579-41.215,27.868-67.459,27.868c-9.848,0-19.156-1.308-27.923-3.923l-4.185,3.354 c-8.587,6.885-17.154,13.796-25.725,20.702c17.52,8.967,36.86,13.487,58.054,13.487c35.533,0,65.91-12.608,91.124-37.821 c25.214-25.215,37.821-55.584,37.821-91.125c0-35.534-12.607-65.911-37.821-91.126 C981.004,1403.663,977.926,1400.854,974.78,1398.211z"></path>
|
|
2000
|
-
<path fill="#F1F2F2" d="M1364.644,1439.619c-4.72,0-8.702,1.624-11.943,4.865c-3.249,3.249-4.866,7.23-4.866,11.944v138.014 l-167.651-211.003c-0.297-0.586-0.74-1.03-1.327-1.326c-4.721-4.714-10.249-7.742-16.588-9.069 c-6.346-1.326-12.608-0.732-18.801,1.77c-6.192,2.509-11.059,6.49-14.598,11.944c-3.539,5.46-5.308,11.577-5.308,18.357v208.348 c0,4.721,1.618,8.703,4.866,11.944c3.241,3.241,7.222,4.865,11.943,4.865c2.945,0,5.751-0.738,8.405-2.211 c2.654-1.472,4.713-3.463,6.193-5.971c1.473-2.503,2.212-5.378,2.212-8.627v-205.251l166.325,209.675 c2.06,2.654,4.423,4.865,7.078,6.635c5.308,3.829,11.349,5.75,18.137,5.75c5.308,0,10.464-1.182,15.482-3.538 c3.539-1.769,6.56-4.127,9.069-7.078c2.502-2.945,4.491-6.338,5.971-10.175c1.473-3.829,2.212-7.664,2.212-11.501v-141.552 c0-4.714-1.624-8.695-4.865-11.944C1373.339,1441.243,1369.359,1439.619,1364.644,1439.619z"></path>
|
|
2001
|
-
<path fill="#F1F2F2" d="M1508.406,1432.983c-2.654-1.472-5.46-2.212-8.404-2.212c-4.721,0-8.703,1.7-11.944,5.087 c-3.249,3.395-4.865,7.3-4.865,11.723v163.228c0,4.721,1.616,8.702,4.865,11.943c3.241,3.249,7.223,4.866,11.944,4.866 c2.944,0,5.751-0.732,8.404-2.212c2.655-1.472,4.714-3.539,6.193-6.194c1.473-2.654,2.213-5.453,2.213-8.404V1447.58 c0-2.945-0.74-5.75-2.213-8.405C1513.12,1436.522,1511.06,1434.462,1508.406,1432.983z"></path>
|
|
2002
|
-
<path fill="#F1F2F2" d="M1499.78,1367.957c-4.575,0-8.481,1.625-11.722,4.866c-3.249,3.249-4.865,7.23-4.865,11.943 c0,2.951,0.732,5.75,2.212,8.405c1.472,2.654,3.463,4.721,5.971,6.193c2.503,1.479,5.378,2.212,8.627,2.212 c4.423,0,8.328-1.618,11.721-4.865c3.387-3.243,5.088-7.224,5.088-11.944c0-4.713-1.701-8.694-5.088-11.943 C1508.33,1369.582,1504.349,1367.957,1499.78,1367.957z"></path>
|
|
2003
|
-
<path fill="#F1F2F2" d="M1859.627,1369.727H1747.27c-35.388,0-65.69,12.607-90.904,37.821 c-25.213,25.215-37.82,55.591-37.82,91.125c0,35.54,12.607,65.911,37.82,91.125c25.215,25.215,55.516,37.821,90.904,37.821h56.178 c4.714,0,8.695-1.618,11.944-4.866c3.241-3.241,4.865-7.222,4.865-11.943c0-4.714-1.624-8.695-4.865-11.943 c-3.249-3.243-7.23-4.866-11.944-4.866h-56.178c-26.251,0-48.659-9.359-67.237-28.09c-18.579-18.723-27.868-41.207-27.868-67.459 c0-26.243,9.29-48.659,27.868-67.237c18.579-18.579,40.987-27.868,67.237-27.868h112.357c4.714,0,8.696-1.693,11.944-5.087 c3.241-3.387,4.865-7.368,4.865-11.943c0-4.569-1.624-8.475-4.865-11.723C1868.322,1371.351,1864.341,1369.727,1859.627,1369.727z "></path>
|
|
2004
|
-
<path fill="#06b6d4" d="M2219.256,1371.054h-112.357c-4.423,0-8.336,1.624-11.723,4.865c-3.393,3.249-5.087,7.23-5.087,11.944 c0,4.721,1.694,8.702,5.087,11.943c3.387,3.249,7.3,4.866,11.723,4.866h95.547v95.105c0,26.251-9.365,48.659-28.088,67.237 c-18.731,18.579-41.215,27.868-67.459,27.868c-26.251,0-48.659-9.289-67.237-27.868c-18.579-18.579-27.868-40.987-27.868-67.237 c0-4.713-1.701-8.771-5.088-12.165c-3.393-3.387-7.374-5.087-11.943-5.087c-4.575,0-8.481,1.7-11.722,5.087 c-3.249,3.393-4.865,7.451-4.865,12.165c0,35.388,12.607,65.69,37.82,90.904c25.215,25.213,55.584,37.82,91.126,37.82 c35.532,0,65.91-12.607,91.125-37.82c25.214-25.215,37.82-55.516,37.82-90.904v-111.915c0-4.714-1.624-8.695-4.865-11.944 C2227.951,1372.678,2223.971,1371.054,2219.256,1371.054z"></path>
|
|
2005
|
-
<path fill="#06b6d4" d="M2574.24,1502.875c-14.306-14.156-31.483-21.234-51.533-21.234H2410.35 c-10.617,0-19.762-3.829-27.426-11.501c-7.672-7.664-11.501-16.954-11.501-27.868c0-10.907,3.829-20.196,11.501-27.868 c7.664-7.664,16.809-11.501,27.426-11.501h112.357c4.714,0,8.695-1.617,11.944-4.866c3.241-3.241,4.865-7.222,4.865-11.943 c0-4.714-1.624-8.695-4.865-11.944c-3.249-3.241-7.23-4.865-11.944-4.865H2410.35c-20.058,0-37.158,7.154-51.313,21.454 c-14.156,14.308-21.232,31.483-21.232,51.534c0,20.058,7.077,37.234,21.232,51.534c14.156,14.308,31.255,21.454,51.313,21.454 h112.357c7.078,0,13.637,1.77,19.684,5.308c6.042,3.539,10.838,8.336,14.377,14.377c3.538,6.047,5.307,12.607,5.307,19.685 c0,10.616-3.835,19.76-11.501,27.425c-7.672,7.673-16.961,11.502-27.868,11.502h-168.094c-4.721,0-8.703,1.7-11.944,5.087 c-3.249,3.393-4.865,7.374-4.865,11.943c0,4.576,1.616,8.481,4.865,11.723c3.241,3.249,7.223,4.866,11.944,4.866h168.094 c20.051,0,37.227-7.078,51.533-21.234c14.302-14.155,21.454-31.331,21.454-51.534 C2595.695,1534.213,2588.542,1517.03,2574.24,1502.875z"></path>
|
|
2006
|
-
<path fill="#06b6d4" d="M854.024,1585.195l20.001-16.028c16.616-13.507,33.04-27.265,50.086-40.251 c1.13-0.861,2.9-1.686,2.003-3.516c-0.843-1.716-2.481-2.302-4.484-2.123c-8.514,0.765-17.016-0.538-25.537-0.353 c-1.124,0.024-2.768,0.221-3.163-1.25c-0.371-1.369,1.088-2.063,1.919-2.894c6.26-6.242,12.574-12.43,18.816-18.691 c9.303-9.327,18.565-18.714,27.851-28.066c1.848-1.859,3.701-3.713,5.549-5.572c2.655-2.661,5.309-5.315,7.958-7.982 c0.574-0.579,1.259-1.141,1.246-1.94c-0.004-0.257-0.078-0.538-0.254-0.853c-0.556-0.981-1.441-1.1-2.469-0.957 c-0.658,0.096-1.315,0.185-1.973,0.275c-3.844,0.538-7.689,1.076-11.533,1.608c-3.641,0.505-7.281,1.02-10.922,1.529 c-4.162,0.582-8.324,1.158-12.486,1.748c-1.142,0.161-2.409,1.662-3.354,0.508c-0.419-0.508-0.431-1.028-0.251-1.531 c0.269-0.741,0.957-1.441,1.387-2.021c3.414-4.58,6.882-9.124,10.356-13.662c1.74-2.272,3.48-4.544,5.214-6.822 c4.682-6.141,9.369-12.281,14.051-18.422c0.09-0.119,0.181-0.237,0.271-0.355c6.848-8.98,13.7-17.958,20.553-26.936 c0.488-0.64,0.977-1.28,1.465-1.92c2.159-2.828,4.315-5.658,6.476-8.486c4.197-5.501,8.454-10.954,12.67-16.442 c0.263-0.347,0.538-0.718,0.717-1.106c0.269-0.586,0.299-1.196-0.335-1.776c-0.825-0.753-1.8-0.15-2.595,0.419 c-0.67,0.472-1.333,0.957-1.955,1.489c-2.206,1.889-4.401,3.797-6.595,5.698c-3.958,3.438-7.922,6.876-11.976,10.194 c-2.443,2.003-4.865,4.028-7.301,6.038c-18.689-10.581-39.53-15.906-62.549-15.906c-35.54,0-65.911,12.607-91.125,37.82 c-25.214,25.215-37.821,55.592-37.821,91.126c0,35.54,12.607,65.91,37.821,91.125c4.146,4.146,8.445,7.916,12.87,11.381 c-9.015,11.14-18.036,22.277-27.034,33.429c-1.208,1.489-3.755,3.151-2.745,4.891c0.078,0.144,0.173,0.281,0.305,0.425 c1.321,1.429,3.492-1.303,4.933-2.457c6.673-5.333,13.333-10.685,19.982-16.042c3.707-2.984,7.417-5.965,11.124-8.952 c1.474-1.188,2.951-2.373,4.425-3.561c6.41-5.164,12.816-10.333,19.238-15.481L854.024,1585.195z M797.552,1498.009 c0-26.243,9.29-48.728,27.868-67.459c18.579-18.723,40.987-28.089,67.238-28.089c12.273,0,23.712,2.075,34.34,6.171 c-3.37,2.905-6.734,5.816-10.069,8.762c-6.075,5.351-12.365,10.469-18.667,15.564c-4.179,3.378-8.371,6.744-12.514,10.164 c-7.54,6.23-15.037,12.52-22.529,18.804c-7.091,5.955-14.182,11.904-21.19,17.949c-1.136,0.974-3.055,1.907-2.135,3.94 c0.831,1.836,2.774,1.417,4.341,1.578l12.145-0.599l14.151-0.698c1.031-0.102,2.192-0.257,2.89,0.632 c0.034,0.044,0.073,0.078,0.106,0.127c1.017,1.561-0.67,2.105-1.387,2.942c-6.308,7.318-12.616,14.637-18.978,21.907 c-8.161,9.339-16.353,18.649-24.544,27.958c-2.146,2.433-4.275,4.879-6.422,7.312c-1.034,1.172-2.129,2.272-1.238,3.922 c0.933,1.728,2.685,1.752,4.323,1.602c4.134-0.367,8.263-0.489,12.396-0.492c0.242,0,0.485-0.005,0.728-0.004 c2.711,0.009,5.422,0.068,8.134,0.145c2.582,0.074,5.166,0.165,7.752,0.249c0.275,1.62-0.879,2.356-1.62,3.259 c-1.333,1.626-2.667,3.247-4,4.867c-4.315,5.252-8.62,10.514-12.928,15.772c-3.562-2.725-7.007-5.733-10.324-9.051 C806.842,1546.667,797.552,1524.26,797.552,1498.009z"></path>
|
|
2129
|
+
<div class="mx-auto flex h-12 w-12 items-center justify-center rounded-lg bg-white">
|
|
2130
|
+
<svg class="h-7 w-7 text-zinc-950" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
2131
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
2007
2132
|
</svg>
|
|
2008
2133
|
</div>
|
|
2009
|
-
<
|
|
2010
|
-
|
|
2134
|
+
<h1 class="mt-6 text-3xl font-semibold tracking-tight text-white">SonicJS AI</h1>
|
|
2135
|
+
<p class="mt-2 text-sm text-zinc-400">Create your account and get started</p>
|
|
2011
2136
|
</div>
|
|
2012
2137
|
|
|
2013
2138
|
<!-- Form Container -->
|
|
2014
2139
|
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
|
2015
2140
|
<div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
|
|
2016
|
-
<!-- Setup Banner -->
|
|
2017
|
-
${data.isSetup ? `
|
|
2018
|
-
<div class="mb-6 rounded-lg bg-blue-500/10 p-4 ring-1 ring-blue-500/20">
|
|
2019
|
-
<div class="flex items-start gap-x-3">
|
|
2020
|
-
<svg class="h-5 w-5 text-blue-400 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
2021
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
2022
|
-
</svg>
|
|
2023
|
-
<div class="flex-1">
|
|
2024
|
-
<p class="text-sm font-medium text-blue-300">First-time Setup</p>
|
|
2025
|
-
<p class="text-sm text-blue-400/80 mt-1">This account will be the administrator with full access to manage your SonicJS installation.</p>
|
|
2026
|
-
</div>
|
|
2027
|
-
</div>
|
|
2028
|
-
</div>
|
|
2029
|
-
` : ""}
|
|
2030
2141
|
<!-- Alerts -->
|
|
2031
|
-
${data.error ? `<div class="mb-6">${
|
|
2142
|
+
${data.error ? `<div class="mb-6">${chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
|
|
2032
2143
|
|
|
2033
2144
|
<!-- Form -->
|
|
2034
2145
|
<form
|
|
@@ -2141,7 +2252,6 @@ function renderRegisterPage(data) {
|
|
|
2141
2252
|
</html>
|
|
2142
2253
|
`;
|
|
2143
2254
|
}
|
|
2144
|
-
var adminExistsCache = null;
|
|
2145
2255
|
async function isRegistrationEnabled(db) {
|
|
2146
2256
|
try {
|
|
2147
2257
|
const plugin = await db.prepare("SELECT settings FROM plugins WHERE id = ?").bind("core-auth").first();
|
|
@@ -2163,21 +2273,6 @@ async function isFirstUserRegistration(db) {
|
|
|
2163
2273
|
return false;
|
|
2164
2274
|
}
|
|
2165
2275
|
}
|
|
2166
|
-
async function checkAdminUserExists(db) {
|
|
2167
|
-
if (adminExistsCache !== null) {
|
|
2168
|
-
return adminExistsCache;
|
|
2169
|
-
}
|
|
2170
|
-
try {
|
|
2171
|
-
const result = await db.prepare("SELECT id FROM users WHERE role = ?").bind("admin").first();
|
|
2172
|
-
adminExistsCache = !!result;
|
|
2173
|
-
return adminExistsCache;
|
|
2174
|
-
} catch {
|
|
2175
|
-
return false;
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
function setAdminExists() {
|
|
2179
|
-
adminExistsCache = true;
|
|
2180
|
-
}
|
|
2181
2276
|
var baseRegistrationSchema = zod.z.object({
|
|
2182
2277
|
email: zod.z.string().email("Valid email is required"),
|
|
2183
2278
|
password: zod.z.string().min(8, "Password must be at least 8 characters"),
|
|
@@ -2239,11 +2334,8 @@ authRoutes.get("/register", async (c) => {
|
|
|
2239
2334
|
}
|
|
2240
2335
|
}
|
|
2241
2336
|
const error = c.req.query("error");
|
|
2242
|
-
const isSetup = c.req.query("setup") === "true";
|
|
2243
2337
|
const pageData = {
|
|
2244
|
-
error: error || void 0
|
|
2245
|
-
isSetup: isSetup && isFirstUser
|
|
2246
|
-
// Only show setup message if truly first user
|
|
2338
|
+
error: error || void 0
|
|
2247
2339
|
};
|
|
2248
2340
|
return c.html(renderRegisterPage(pageData));
|
|
2249
2341
|
});
|
|
@@ -2289,7 +2381,7 @@ authRoutes.post(
|
|
|
2289
2381
|
if (existingUser) {
|
|
2290
2382
|
return c.json({ error: "User with this email or username already exists" }, 400);
|
|
2291
2383
|
}
|
|
2292
|
-
const passwordHash = await
|
|
2384
|
+
const passwordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(password);
|
|
2293
2385
|
const userId = crypto.randomUUID();
|
|
2294
2386
|
const now = /* @__PURE__ */ new Date();
|
|
2295
2387
|
await db.prepare(`
|
|
@@ -2309,7 +2401,7 @@ authRoutes.post(
|
|
|
2309
2401
|
now.getTime(),
|
|
2310
2402
|
now.getTime()
|
|
2311
2403
|
).run();
|
|
2312
|
-
const token = await
|
|
2404
|
+
const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer");
|
|
2313
2405
|
cookie.setCookie(c, "auth_token", token, {
|
|
2314
2406
|
httpOnly: true,
|
|
2315
2407
|
secure: true,
|
|
@@ -2362,11 +2454,11 @@ authRoutes.post("/login", async (c) => {
|
|
|
2362
2454
|
if (!user) {
|
|
2363
2455
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
2364
2456
|
}
|
|
2365
|
-
const isValidPassword = await
|
|
2457
|
+
const isValidPassword = await chunkT3YIKW2A_cjs.AuthManager.verifyPassword(password, user.password_hash);
|
|
2366
2458
|
if (!isValidPassword) {
|
|
2367
2459
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
2368
2460
|
}
|
|
2369
|
-
const token = await
|
|
2461
|
+
const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(user.id, user.email, user.role);
|
|
2370
2462
|
cookie.setCookie(c, "auth_token", token, {
|
|
2371
2463
|
httpOnly: true,
|
|
2372
2464
|
secure: true,
|
|
@@ -2415,7 +2507,7 @@ authRoutes.get("/logout", (c) => {
|
|
|
2415
2507
|
});
|
|
2416
2508
|
return c.redirect("/auth/login?message=You have been logged out successfully");
|
|
2417
2509
|
});
|
|
2418
|
-
authRoutes.get("/me",
|
|
2510
|
+
authRoutes.get("/me", chunkT3YIKW2A_cjs.requireAuth(), async (c) => {
|
|
2419
2511
|
try {
|
|
2420
2512
|
const user = c.get("user");
|
|
2421
2513
|
if (!user) {
|
|
@@ -2432,13 +2524,13 @@ authRoutes.get("/me", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
|
|
|
2432
2524
|
return c.json({ error: "Failed to get user" }, 500);
|
|
2433
2525
|
}
|
|
2434
2526
|
});
|
|
2435
|
-
authRoutes.post("/refresh",
|
|
2527
|
+
authRoutes.post("/refresh", chunkT3YIKW2A_cjs.requireAuth(), async (c) => {
|
|
2436
2528
|
try {
|
|
2437
2529
|
const user = c.get("user");
|
|
2438
2530
|
if (!user) {
|
|
2439
2531
|
return c.json({ error: "Not authenticated" }, 401);
|
|
2440
2532
|
}
|
|
2441
|
-
const token = await
|
|
2533
|
+
const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(user.userId, user.email, user.role);
|
|
2442
2534
|
cookie.setCookie(c, "auth_token", token, {
|
|
2443
2535
|
httpOnly: true,
|
|
2444
2536
|
secure: true,
|
|
@@ -2498,7 +2590,7 @@ authRoutes.post("/register/form", async (c) => {
|
|
|
2498
2590
|
</div>
|
|
2499
2591
|
`);
|
|
2500
2592
|
}
|
|
2501
|
-
const passwordHash = await
|
|
2593
|
+
const passwordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(password);
|
|
2502
2594
|
const role = isFirstUser ? "admin" : "viewer";
|
|
2503
2595
|
const userId = crypto.randomUUID();
|
|
2504
2596
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2518,10 +2610,7 @@ authRoutes.post("/register/form", async (c) => {
|
|
|
2518
2610
|
now.getTime(),
|
|
2519
2611
|
now.getTime()
|
|
2520
2612
|
).run();
|
|
2521
|
-
|
|
2522
|
-
setAdminExists();
|
|
2523
|
-
}
|
|
2524
|
-
const token = await chunk7I5INVNR_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
|
|
2613
|
+
const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
|
|
2525
2614
|
cookie.setCookie(c, "auth_token", token, {
|
|
2526
2615
|
httpOnly: true,
|
|
2527
2616
|
secure: false,
|
|
@@ -2573,7 +2662,7 @@ authRoutes.post("/login/form", async (c) => {
|
|
|
2573
2662
|
</div>
|
|
2574
2663
|
`);
|
|
2575
2664
|
}
|
|
2576
|
-
const isValidPassword = await
|
|
2665
|
+
const isValidPassword = await chunkT3YIKW2A_cjs.AuthManager.verifyPassword(password, user.password_hash);
|
|
2577
2666
|
if (!isValidPassword) {
|
|
2578
2667
|
return c.html(html.html`
|
|
2579
2668
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
@@ -2581,7 +2670,7 @@ authRoutes.post("/login/form", async (c) => {
|
|
|
2581
2670
|
</div>
|
|
2582
2671
|
`);
|
|
2583
2672
|
}
|
|
2584
|
-
const token = await
|
|
2673
|
+
const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(user.id, user.email, user.role);
|
|
2585
2674
|
cookie.setCookie(c, "auth_token", token, {
|
|
2586
2675
|
httpOnly: true,
|
|
2587
2676
|
secure: false,
|
|
@@ -2640,9 +2729,8 @@ authRoutes.post("/seed-admin", async (c) => {
|
|
|
2640
2729
|
`).run();
|
|
2641
2730
|
const existingAdmin = await db.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind("admin@sonicjs.com", "admin").first();
|
|
2642
2731
|
if (existingAdmin) {
|
|
2643
|
-
const passwordHash2 = await
|
|
2732
|
+
const passwordHash2 = await chunkT3YIKW2A_cjs.AuthManager.hashPassword("sonicjs!");
|
|
2644
2733
|
await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
|
|
2645
|
-
setAdminExists();
|
|
2646
2734
|
return c.json({
|
|
2647
2735
|
message: "Admin user already exists (password updated)",
|
|
2648
2736
|
user: {
|
|
@@ -2653,7 +2741,7 @@ authRoutes.post("/seed-admin", async (c) => {
|
|
|
2653
2741
|
}
|
|
2654
2742
|
});
|
|
2655
2743
|
}
|
|
2656
|
-
const passwordHash = await
|
|
2744
|
+
const passwordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword("sonicjs!");
|
|
2657
2745
|
const userId = "admin-user-id";
|
|
2658
2746
|
const now = Date.now();
|
|
2659
2747
|
const adminEmail = "admin@sonicjs.com".toLowerCase();
|
|
@@ -2673,7 +2761,6 @@ authRoutes.post("/seed-admin", async (c) => {
|
|
|
2673
2761
|
now,
|
|
2674
2762
|
now
|
|
2675
2763
|
).run();
|
|
2676
|
-
setAdminExists();
|
|
2677
2764
|
return c.json({
|
|
2678
2765
|
message: "Admin user created successfully",
|
|
2679
2766
|
user: {
|
|
@@ -2874,7 +2961,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
2874
2961
|
if (existingUsername) {
|
|
2875
2962
|
return c.json({ error: "Username is already taken" }, 400);
|
|
2876
2963
|
}
|
|
2877
|
-
const passwordHash = await
|
|
2964
|
+
const passwordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(password);
|
|
2878
2965
|
const updateStmt = db.prepare(`
|
|
2879
2966
|
UPDATE users SET
|
|
2880
2967
|
username = ?,
|
|
@@ -2893,7 +2980,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
2893
2980
|
Date.now(),
|
|
2894
2981
|
invitedUser.id
|
|
2895
2982
|
).run();
|
|
2896
|
-
const authToken = await
|
|
2983
|
+
const authToken = await chunkT3YIKW2A_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role);
|
|
2897
2984
|
cookie.setCookie(c, "auth_token", authToken, {
|
|
2898
2985
|
httpOnly: true,
|
|
2899
2986
|
secure: true,
|
|
@@ -3123,7 +3210,7 @@ authRoutes.post("/reset-password", async (c) => {
|
|
|
3123
3210
|
if (Date.now() > user.password_reset_expires) {
|
|
3124
3211
|
return c.json({ error: "Reset token has expired" }, 400);
|
|
3125
3212
|
}
|
|
3126
|
-
const newPasswordHash = await
|
|
3213
|
+
const newPasswordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(password);
|
|
3127
3214
|
try {
|
|
3128
3215
|
const historyStmt = db.prepare(`
|
|
3129
3216
|
INSERT INTO password_history (id, user_id, password_hash, created_at)
|
|
@@ -3381,11 +3468,168 @@ app.post("/test-cleanup/content", async (c) => {
|
|
|
3381
3468
|
var test_cleanup_default = app;
|
|
3382
3469
|
|
|
3383
3470
|
// src/templates/pages/admin-content-form.template.ts
|
|
3384
|
-
|
|
3471
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
3472
|
+
|
|
3473
|
+
// src/templates/components/drag-sortable.template.ts
|
|
3474
|
+
function getDragSortableScript() {
|
|
3475
|
+
return `
|
|
3476
|
+
<script>
|
|
3477
|
+
if (!window.__sonicDragSortableInit) {
|
|
3478
|
+
window.__sonicDragSortableInit = true;
|
|
3479
|
+
|
|
3480
|
+
window.initializeDragSortable = function(container, options) {
|
|
3481
|
+
if (!container || container.dataset.dragSortableInit === 'true') {
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
container.dataset.dragSortableInit = 'true';
|
|
3486
|
+
const itemSelector = options && options.itemSelector ? options.itemSelector : '.sortable-item';
|
|
3487
|
+
const handleSelector = options && options.handleSelector ? options.handleSelector : '[data-action="drag-handle"]';
|
|
3488
|
+
const onUpdate = options && typeof options.onUpdate === 'function' ? options.onUpdate : function() {};
|
|
3489
|
+
let activeDragItem = null;
|
|
3490
|
+
|
|
3491
|
+
const getDragAfterElement = function(list, y) {
|
|
3492
|
+
const items = Array.from(list.querySelectorAll(itemSelector + ':not(.is-dragging)'));
|
|
3493
|
+
let closest = { offset: Number.NEGATIVE_INFINITY, element: null };
|
|
3494
|
+
items.forEach(function(item) {
|
|
3495
|
+
const box = item.getBoundingClientRect();
|
|
3496
|
+
const offset = y - box.top - box.height / 2;
|
|
3497
|
+
if (offset < 0 && offset > closest.offset) {
|
|
3498
|
+
closest = { offset: offset, element: item };
|
|
3499
|
+
}
|
|
3500
|
+
});
|
|
3501
|
+
return closest.element;
|
|
3502
|
+
};
|
|
3503
|
+
|
|
3504
|
+
const activateDragItem = function(event) {
|
|
3505
|
+
const target = event.target;
|
|
3506
|
+
if (!(target instanceof Element)) return;
|
|
3507
|
+
const handle = target.closest(handleSelector);
|
|
3508
|
+
if (!handle) return;
|
|
3509
|
+
const item = handle.closest(itemSelector);
|
|
3510
|
+
if (!item) return;
|
|
3511
|
+
activeDragItem = item;
|
|
3512
|
+
};
|
|
3513
|
+
|
|
3514
|
+
const clearActiveDragItem = function() {
|
|
3515
|
+
activeDragItem = null;
|
|
3516
|
+
};
|
|
3517
|
+
|
|
3518
|
+
container.addEventListener('pointerdown', activateDragItem);
|
|
3519
|
+
container.addEventListener('mousedown', activateDragItem);
|
|
3520
|
+
container.addEventListener('pointerup', clearActiveDragItem);
|
|
3521
|
+
container.addEventListener('mouseup', clearActiveDragItem);
|
|
3522
|
+
|
|
3523
|
+
container.addEventListener('dragstart', function(event) {
|
|
3524
|
+
const target = event.target;
|
|
3525
|
+
if (!(target instanceof Element)) return;
|
|
3526
|
+
const item = target.closest(itemSelector);
|
|
3527
|
+
if (!item || item !== activeDragItem) {
|
|
3528
|
+
event.preventDefault();
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
item.classList.add('is-dragging');
|
|
3532
|
+
if (event.dataTransfer) {
|
|
3533
|
+
event.dataTransfer.setData('text/plain', '');
|
|
3534
|
+
}
|
|
3535
|
+
});
|
|
3536
|
+
|
|
3537
|
+
container.addEventListener('dragend', function(event) {
|
|
3538
|
+
const target = event.target;
|
|
3539
|
+
if (target instanceof Element) {
|
|
3540
|
+
const item = target.closest(itemSelector);
|
|
3541
|
+
if (item) {
|
|
3542
|
+
item.classList.remove('is-dragging');
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
activeDragItem = null;
|
|
3546
|
+
onUpdate();
|
|
3547
|
+
});
|
|
3548
|
+
|
|
3549
|
+
container.addEventListener('dragover', function(event) {
|
|
3550
|
+
event.preventDefault();
|
|
3551
|
+
const dragging = container.querySelector(itemSelector + '.is-dragging');
|
|
3552
|
+
if (!dragging) return;
|
|
3553
|
+
const afterElement = getDragAfterElement(container, event.clientY);
|
|
3554
|
+
if (afterElement === null) {
|
|
3555
|
+
container.appendChild(dragging);
|
|
3556
|
+
} else {
|
|
3557
|
+
container.insertBefore(dragging, afterElement);
|
|
3558
|
+
}
|
|
3559
|
+
});
|
|
3560
|
+
|
|
3561
|
+
container.addEventListener('drop', function() {
|
|
3562
|
+
onUpdate();
|
|
3563
|
+
});
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
</script>
|
|
3567
|
+
`;
|
|
3568
|
+
}
|
|
3385
3569
|
|
|
3386
3570
|
// src/templates/components/dynamic-field.template.ts
|
|
3571
|
+
function getReadFieldValueScript() {
|
|
3572
|
+
return `
|
|
3573
|
+
<script>
|
|
3574
|
+
if (!window.__sonicReadFieldValueInit) {
|
|
3575
|
+
window.__sonicReadFieldValueInit = true;
|
|
3576
|
+
|
|
3577
|
+
window.sonicReadFieldValue = function(fieldWrapper) {
|
|
3578
|
+
const fieldType = fieldWrapper.dataset.fieldType;
|
|
3579
|
+
const select = fieldWrapper.querySelector('select');
|
|
3580
|
+
const textarea = fieldWrapper.querySelector('textarea');
|
|
3581
|
+
const inputs = Array.from(fieldWrapper.querySelectorAll('input'));
|
|
3582
|
+
const checkbox = inputs.find((input) => input.type === 'checkbox');
|
|
3583
|
+
const nonHiddenInput = inputs.find((input) => input.type !== 'hidden' && input.type !== 'checkbox');
|
|
3584
|
+
const hiddenInput = inputs.find((input) => input.type === 'hidden');
|
|
3585
|
+
|
|
3586
|
+
if (fieldType === 'object' || fieldType === 'array') {
|
|
3587
|
+
if (!hiddenInput) {
|
|
3588
|
+
return fieldType === 'array' ? [] : {};
|
|
3589
|
+
}
|
|
3590
|
+
const rawValue = hiddenInput.value || '';
|
|
3591
|
+
if (!rawValue.trim()) {
|
|
3592
|
+
return fieldType === 'array' ? [] : {};
|
|
3593
|
+
}
|
|
3594
|
+
try {
|
|
3595
|
+
return JSON.parse(rawValue);
|
|
3596
|
+
} catch {
|
|
3597
|
+
return fieldType === 'array' ? [] : {};
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
if (fieldType === 'boolean' && checkbox) {
|
|
3602
|
+
return checkbox.checked;
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
if (select) {
|
|
3606
|
+
if (select.multiple) {
|
|
3607
|
+
return Array.from(select.selectedOptions).map((option) => option.value);
|
|
3608
|
+
}
|
|
3609
|
+
return select.value;
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
if (fieldType === 'quill' || fieldType === 'media') {
|
|
3613
|
+
return hiddenInput ? hiddenInput.value : '';
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
const textSource = textarea || nonHiddenInput || hiddenInput;
|
|
3617
|
+
if (!textSource) {
|
|
3618
|
+
return '';
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
if (fieldType === 'number') {
|
|
3622
|
+
return textSource.value === '' ? null : Number(textSource.value);
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
return textSource.value;
|
|
3626
|
+
};
|
|
3627
|
+
}
|
|
3628
|
+
</script>
|
|
3629
|
+
`;
|
|
3630
|
+
}
|
|
3387
3631
|
function renderDynamicField(field, options = {}) {
|
|
3388
|
-
const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {} } = options;
|
|
3632
|
+
const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {}, collectionId = "", contentId = "" } = options;
|
|
3389
3633
|
const opts = field.field_options || {};
|
|
3390
3634
|
const required = field.is_required ? "required" : "";
|
|
3391
3635
|
const baseClasses = `w-full rounded-lg px-3 py-2 text-sm text-zinc-950 dark:text-white bg-white dark:bg-zinc-800 shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow ${className}`;
|
|
@@ -3638,67 +3882,171 @@ function renderDynamicField(field, options = {}) {
|
|
|
3638
3882
|
`;
|
|
3639
3883
|
break;
|
|
3640
3884
|
case "slug":
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3885
|
+
const slugPattern = opts.pattern || "^[a-z0-9-]+$";
|
|
3886
|
+
const collectionIdValue = collectionId || opts.collectionId || "";
|
|
3887
|
+
const contentIdValue = contentId || opts.contentId || "";
|
|
3888
|
+
const isEditMode = !!value;
|
|
3644
3889
|
fieldHTML = `
|
|
3645
|
-
<
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3890
|
+
<div class="slug-field-container">
|
|
3891
|
+
<input
|
|
3892
|
+
type="text"
|
|
3893
|
+
id="${fieldId}"
|
|
3894
|
+
name="${fieldName}"
|
|
3895
|
+
value="${escapeHtml2(value)}"
|
|
3896
|
+
placeholder="${opts.placeholder || "url-friendly-slug"}"
|
|
3897
|
+
maxlength="${opts.maxLength || 100}"
|
|
3898
|
+
data-pattern="${slugPattern}"
|
|
3899
|
+
data-collection-id="${collectionIdValue}"
|
|
3900
|
+
data-content-id="${contentIdValue}"
|
|
3901
|
+
data-is-edit-mode="${isEditMode}"
|
|
3902
|
+
class="${baseClasses} ${errorClasses}"
|
|
3903
|
+
${required}
|
|
3904
|
+
${disabled ? "disabled" : ""}
|
|
3905
|
+
>
|
|
3906
|
+
<div id="${fieldId}-status" class="slug-status mt-1 text-sm min-h-[20px]"></div>
|
|
3907
|
+
<button
|
|
3908
|
+
type="button"
|
|
3909
|
+
class="regenerate-slug-btn mt-2 text-sm text-cyan-600 dark:text-cyan-400 hover:text-cyan-700 dark:hover:text-cyan-300 flex items-center gap-1 transition-colors"
|
|
3910
|
+
onclick="window.regenerateSlugFromTitle_${fieldId.replace(/-/g, "_")}()"
|
|
3911
|
+
>
|
|
3912
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
3913
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
3914
|
+
</svg>
|
|
3915
|
+
Regenerate from title
|
|
3916
|
+
</button>
|
|
3917
|
+
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">Use lowercase letters, numbers, and hyphens only</p>
|
|
3918
|
+
</div>
|
|
3919
|
+
|
|
3658
3920
|
<script>
|
|
3659
3921
|
(function() {
|
|
3660
|
-
const
|
|
3922
|
+
const slugField = document.getElementById('${fieldId}');
|
|
3923
|
+
const statusDiv = document.getElementById('${fieldId}-status');
|
|
3924
|
+
const isEditMode = slugField.dataset.isEditMode === 'true';
|
|
3661
3925
|
const pattern = new RegExp('${slugPattern}');
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
})();
|
|
3675
|
-
|
|
3676
|
-
function generateSlugFromTitle(slugFieldId) {
|
|
3677
|
-
const titleField = document.querySelector('input[name="title"]');
|
|
3678
|
-
const slugField = document.getElementById(slugFieldId);
|
|
3679
|
-
if (titleField && slugField) {
|
|
3680
|
-
const slug = titleField.value
|
|
3926
|
+
const collectionId = slugField.dataset.collectionId;
|
|
3927
|
+
const contentId = slugField.dataset.contentId;
|
|
3928
|
+
|
|
3929
|
+
let checkTimeout;
|
|
3930
|
+
let lastCheckedSlug = '';
|
|
3931
|
+
let manuallyEdited = false;
|
|
3932
|
+
|
|
3933
|
+
// Shared slug generation function
|
|
3934
|
+
function generateSlug(text) {
|
|
3935
|
+
if (!text) return '';
|
|
3936
|
+
|
|
3937
|
+
return text
|
|
3681
3938
|
.toLowerCase()
|
|
3939
|
+
.normalize('NFD')
|
|
3940
|
+
.replace(/[\\u0300-\\u036f]/g, '')
|
|
3682
3941
|
.replace(/[^a-z0-9\\s_-]/g, '')
|
|
3683
3942
|
.replace(/\\s+/g, '-')
|
|
3684
3943
|
.replace(/[-_]+/g, '-')
|
|
3685
|
-
.replace(/^[-_]
|
|
3686
|
-
|
|
3944
|
+
.replace(/^[-_]+|[-_]+$/g, '')
|
|
3945
|
+
.substring(0, 100);
|
|
3687
3946
|
}
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3947
|
+
|
|
3948
|
+
// Check if slug is available
|
|
3949
|
+
async function checkSlugAvailability(slug) {
|
|
3950
|
+
if (!slug || !collectionId) return;
|
|
3951
|
+
|
|
3952
|
+
// Don't check if it's the same as last time
|
|
3953
|
+
if (slug === lastCheckedSlug) return;
|
|
3954
|
+
lastCheckedSlug = slug;
|
|
3955
|
+
|
|
3956
|
+
try {
|
|
3957
|
+
// Show checking status
|
|
3958
|
+
statusDiv.innerHTML = '<span class="text-gray-400">\u23F3 Checking availability...</span>';
|
|
3959
|
+
|
|
3960
|
+
// Build URL
|
|
3961
|
+
let url = \`/api/content/check-slug?collectionId=\${encodeURIComponent(collectionId)}&slug=\${encodeURIComponent(slug)}\`;
|
|
3962
|
+
if (contentId) {
|
|
3963
|
+
url += \`&excludeId=\${encodeURIComponent(contentId)}\`;
|
|
3698
3964
|
}
|
|
3699
|
-
|
|
3965
|
+
|
|
3966
|
+
const response = await fetch(url);
|
|
3967
|
+
const data = await response.json();
|
|
3968
|
+
|
|
3969
|
+
if (data.available) {
|
|
3970
|
+
statusDiv.innerHTML = '<span class="text-green-500 dark:text-green-400">\u2713 Available</span>';
|
|
3971
|
+
slugField.setCustomValidity('');
|
|
3972
|
+
} else {
|
|
3973
|
+
statusDiv.innerHTML = \`<span class="text-red-500 dark:text-red-400">\u2717 \${data.message || 'Already in use'}</span>\`;
|
|
3974
|
+
slugField.setCustomValidity(data.message || 'This slug is already in use');
|
|
3975
|
+
}
|
|
3976
|
+
} catch (error) {
|
|
3977
|
+
console.error('Error checking slug:', error);
|
|
3978
|
+
statusDiv.innerHTML = '<span class="text-yellow-500 dark:text-yellow-400">\u26A0 Could not verify</span>';
|
|
3979
|
+
}
|
|
3700
3980
|
}
|
|
3701
|
-
|
|
3981
|
+
|
|
3982
|
+
// Format validation and duplicate checking
|
|
3983
|
+
slugField.addEventListener('input', function() {
|
|
3984
|
+
const value = this.value;
|
|
3985
|
+
|
|
3986
|
+
// Mark as manually edited if user types directly
|
|
3987
|
+
if (document.activeElement === this) {
|
|
3988
|
+
manuallyEdited = true;
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3991
|
+
// Clear status if empty
|
|
3992
|
+
if (!value) {
|
|
3993
|
+
statusDiv.innerHTML = '';
|
|
3994
|
+
this.setCustomValidity('');
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3998
|
+
// Pattern validation
|
|
3999
|
+
if (!pattern.test(value)) {
|
|
4000
|
+
this.setCustomValidity('Please use only lowercase letters, numbers, and hyphens.');
|
|
4001
|
+
statusDiv.innerHTML = '<span class="text-red-500 dark:text-red-400">\u2717 Invalid format</span>';
|
|
4002
|
+
return;
|
|
4003
|
+
}
|
|
4004
|
+
|
|
4005
|
+
// Debounce the availability check
|
|
4006
|
+
clearTimeout(checkTimeout);
|
|
4007
|
+
checkTimeout = setTimeout(() => {
|
|
4008
|
+
checkSlugAvailability(value);
|
|
4009
|
+
}, 500); // Wait 500ms after user stops typing
|
|
4010
|
+
});
|
|
4011
|
+
|
|
4012
|
+
// Initial check if field has value
|
|
4013
|
+
if (slugField.value) {
|
|
4014
|
+
checkSlugAvailability(slugField.value);
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
// Auto-generate only in create mode
|
|
4018
|
+
// Wait for all fields to be rendered before attaching listeners
|
|
4019
|
+
if (!isEditMode) {
|
|
4020
|
+
// Use setTimeout to ensure all fields in the form are rendered
|
|
4021
|
+
setTimeout(() => {
|
|
4022
|
+
const titleField = document.querySelector('input[name="title"]');
|
|
4023
|
+
if (titleField) {
|
|
4024
|
+
titleField.addEventListener('input', function() {
|
|
4025
|
+
if (!manuallyEdited) {
|
|
4026
|
+
const slug = generateSlug(this.value);
|
|
4027
|
+
slugField.value = slug;
|
|
4028
|
+
|
|
4029
|
+
// Trigger validation and duplicate check
|
|
4030
|
+
slugField.dispatchEvent(new Event('input', { bubbles: true }));
|
|
4031
|
+
}
|
|
4032
|
+
});
|
|
4033
|
+
}
|
|
4034
|
+
}, 0);
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
// Global function for regenerate button
|
|
4038
|
+
window.regenerateSlugFromTitle_${fieldId.replace(/-/g, "_")} = function() {
|
|
4039
|
+
const titleField = document.querySelector('input[name="title"]');
|
|
4040
|
+
if (titleField && slugField) {
|
|
4041
|
+
const slug = generateSlug(titleField.value);
|
|
4042
|
+
slugField.value = slug;
|
|
4043
|
+
manuallyEdited = false;
|
|
4044
|
+
|
|
4045
|
+
// Trigger validation and duplicate check
|
|
4046
|
+
slugField.dispatchEvent(new Event('input', { bubbles: true }));
|
|
4047
|
+
}
|
|
4048
|
+
};
|
|
4049
|
+
})();
|
|
3702
4050
|
</script>
|
|
3703
4051
|
`;
|
|
3704
4052
|
break;
|
|
@@ -3735,43 +4083,124 @@ function renderDynamicField(field, options = {}) {
|
|
|
3735
4083
|
` : ""}
|
|
3736
4084
|
`;
|
|
3737
4085
|
break;
|
|
4086
|
+
case "reference":
|
|
4087
|
+
let referenceCollections = [];
|
|
4088
|
+
if (Array.isArray(opts.collection)) {
|
|
4089
|
+
referenceCollections = opts.collection.filter(Boolean);
|
|
4090
|
+
} else if (typeof opts.collection === "string" && opts.collection) {
|
|
4091
|
+
referenceCollections = [opts.collection];
|
|
4092
|
+
}
|
|
4093
|
+
const referenceCollectionsAttr = referenceCollections.join(",");
|
|
4094
|
+
const hasReferenceCollection = referenceCollections.length > 0;
|
|
4095
|
+
const hasReferenceValue = Boolean(value);
|
|
4096
|
+
fieldHTML = `
|
|
4097
|
+
<div class="reference-field-container space-y-3" data-reference-field data-field-name="${escapeHtml2(fieldName)}" data-reference-collection="${escapeHtml2(referenceCollections[0] || "")}" data-reference-collections="${escapeHtml2(referenceCollectionsAttr)}">
|
|
4098
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml2(value)}">
|
|
4099
|
+
<div class="rounded-lg border border-zinc-200 bg-white/60 px-3 py-2 text-sm text-zinc-600 dark:border-white/10 dark:bg-white/5 dark:text-zinc-300" data-reference-display>
|
|
4100
|
+
${hasReferenceCollection ? hasReferenceValue ? "Loading selection..." : "No reference selected." : "Reference collection not configured."}
|
|
4101
|
+
</div>
|
|
4102
|
+
<div class="flex flex-wrap gap-2">
|
|
4103
|
+
<button
|
|
4104
|
+
type="button"
|
|
4105
|
+
onclick="openReferenceSelector('${fieldId}')"
|
|
4106
|
+
class="inline-flex items-center justify-center rounded-lg bg-zinc-900 px-3 py-2 text-sm font-semibold text-white hover:bg-zinc-800 dark:bg-white/10 dark:hover:bg-white/20"
|
|
4107
|
+
${hasReferenceCollection ? "" : "disabled"}
|
|
4108
|
+
>
|
|
4109
|
+
Select reference
|
|
4110
|
+
</button>
|
|
4111
|
+
<button
|
|
4112
|
+
type="button"
|
|
4113
|
+
onclick="clearReferenceField('${fieldId}')"
|
|
4114
|
+
class="inline-flex items-center justify-center rounded-lg border border-zinc-200 px-3 py-2 text-sm font-semibold text-zinc-700 hover:bg-zinc-100 dark:border-white/10 dark:text-zinc-200 dark:hover:bg-white/10"
|
|
4115
|
+
data-reference-clear
|
|
4116
|
+
${hasReferenceValue ? "" : "disabled"}
|
|
4117
|
+
>
|
|
4118
|
+
Remove
|
|
4119
|
+
</button>
|
|
4120
|
+
</div>
|
|
4121
|
+
</div>
|
|
4122
|
+
`;
|
|
4123
|
+
break;
|
|
3738
4124
|
case "media":
|
|
4125
|
+
const isMultiple = opts.multiple === true;
|
|
4126
|
+
const mediaValues = isMultiple && value ? Array.isArray(value) ? value : String(value).split(",").filter(Boolean) : [];
|
|
4127
|
+
const singleValue = !isMultiple ? value : "";
|
|
4128
|
+
const isVideoUrl = (url) => {
|
|
4129
|
+
const videoExtensions = [".mp4", ".webm", ".ogg", ".mov", ".avi"];
|
|
4130
|
+
return videoExtensions.some((ext) => url.toLowerCase().endsWith(ext));
|
|
4131
|
+
};
|
|
4132
|
+
const renderMediaPreview = (url, alt, classes) => {
|
|
4133
|
+
if (isVideoUrl(url)) {
|
|
4134
|
+
return `<video src="${url}" class="${classes}" muted></video>`;
|
|
4135
|
+
}
|
|
4136
|
+
return `<img src="${url}" alt="${alt}" class="${classes}">`;
|
|
4137
|
+
};
|
|
3739
4138
|
fieldHTML = `
|
|
3740
4139
|
<div class="media-field-container">
|
|
3741
|
-
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
4140
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${isMultiple ? mediaValues.join(",") : singleValue}" data-multiple="${isMultiple}">
|
|
4141
|
+
|
|
4142
|
+
${isMultiple ? `
|
|
4143
|
+
<div class="media-preview-grid grid grid-cols-4 gap-2 mb-2 ${mediaValues.length === 0 ? "hidden" : ""}" id="${fieldId}-preview">
|
|
4144
|
+
${mediaValues.map((url, idx) => `
|
|
4145
|
+
<div class="relative media-preview-item" data-url="${url}">
|
|
4146
|
+
${renderMediaPreview(url, `Media ${idx + 1}`, "w-full h-24 object-cover rounded-lg border border-white/20")}
|
|
4147
|
+
<button
|
|
4148
|
+
type="button"
|
|
4149
|
+
onclick="removeMediaFromMultiple('${fieldId}', '${url}')"
|
|
4150
|
+
class="absolute top-1 right-1 bg-red-600 text-white rounded-full p-1 hover:bg-red-700"
|
|
4151
|
+
${disabled ? "disabled" : ""}
|
|
4152
|
+
>
|
|
4153
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
4154
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
4155
|
+
</svg>
|
|
4156
|
+
</button>
|
|
4157
|
+
</div>
|
|
4158
|
+
`).join("")}
|
|
4159
|
+
</div>
|
|
4160
|
+
` : `
|
|
4161
|
+
<div class="media-preview ${singleValue ? "" : "hidden"}" id="${fieldId}-preview">
|
|
4162
|
+
${singleValue ? renderMediaPreview(singleValue, "Selected media", "w-32 h-32 object-cover rounded-lg border border-white/20") : ""}
|
|
4163
|
+
</div>
|
|
4164
|
+
`}
|
|
4165
|
+
|
|
3745
4166
|
<div class="media-actions mt-2 space-x-2">
|
|
3746
4167
|
<button
|
|
3747
4168
|
type="button"
|
|
3748
|
-
onclick="openMediaSelector('${fieldId}')"
|
|
4169
|
+
onclick="openMediaSelector('${fieldId}', ${isMultiple})"
|
|
3749
4170
|
class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all"
|
|
3750
4171
|
${disabled ? "disabled" : ""}
|
|
3751
4172
|
>
|
|
3752
4173
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3753
4174
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
3754
4175
|
</svg>
|
|
3755
|
-
Select Media
|
|
4176
|
+
${isMultiple ? "Select Media (Multiple)" : "Select Media"}
|
|
3756
4177
|
</button>
|
|
3757
|
-
${
|
|
4178
|
+
${(isMultiple ? mediaValues.length > 0 : singleValue) ? `
|
|
3758
4179
|
<button
|
|
3759
4180
|
type="button"
|
|
3760
4181
|
onclick="clearMediaField('${fieldId}')"
|
|
3761
4182
|
class="inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all"
|
|
3762
4183
|
${disabled ? "disabled" : ""}
|
|
3763
4184
|
>
|
|
3764
|
-
Remove
|
|
4185
|
+
${isMultiple ? "Clear All" : "Remove"}
|
|
3765
4186
|
</button>
|
|
3766
4187
|
` : ""}
|
|
3767
4188
|
</div>
|
|
3768
4189
|
</div>
|
|
3769
4190
|
`;
|
|
3770
4191
|
break;
|
|
4192
|
+
case "object":
|
|
4193
|
+
return renderStructuredObjectField(field, options2);
|
|
4194
|
+
case "array":
|
|
4195
|
+
const itemsConfig = opts.items && typeof opts.items === "object" ? opts.items : {};
|
|
4196
|
+
if (itemsConfig.blocks && typeof itemsConfig.blocks === "object") {
|
|
4197
|
+
return renderBlocksField(field, options2, baseClasses, errorClasses);
|
|
4198
|
+
}
|
|
4199
|
+
return renderStructuredArrayField(field, options2);
|
|
3771
4200
|
default:
|
|
3772
4201
|
fieldHTML = `
|
|
3773
|
-
<input
|
|
3774
|
-
type="text"
|
|
4202
|
+
<input
|
|
4203
|
+
type="text"
|
|
3775
4204
|
id="${fieldId}"
|
|
3776
4205
|
name="${fieldName}"
|
|
3777
4206
|
value="${escapeHtml2(value)}"
|
|
@@ -3821,220 +4250,767 @@ function renderFieldGroup(title, fields, collapsible = false) {
|
|
|
3821
4250
|
</div>
|
|
3822
4251
|
`;
|
|
3823
4252
|
}
|
|
3824
|
-
function
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
}
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
}
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
4253
|
+
function renderBlocksField(field, options, baseClasses, errorClasses) {
|
|
4254
|
+
const { value = [], pluginStatuses = {} } = options;
|
|
4255
|
+
const opts = field.field_options || {};
|
|
4256
|
+
const itemsConfig = opts.items && typeof opts.items === "object" ? opts.items : {};
|
|
4257
|
+
const blocks = normalizeBlockDefinitions(itemsConfig.blocks);
|
|
4258
|
+
const discriminator = typeof itemsConfig.discriminator === "string" && itemsConfig.discriminator ? itemsConfig.discriminator : "blockType";
|
|
4259
|
+
const blockValues = normalizeBlocksValue(value, discriminator);
|
|
4260
|
+
const fieldId = `field-${field.field_name}`;
|
|
4261
|
+
const fieldName = field.field_name;
|
|
4262
|
+
const emptyState = blockValues.length === 0 ? `
|
|
4263
|
+
<div class="rounded-lg border border-dashed border-zinc-200 dark:border-white/10 px-4 py-6 text-center text-sm text-zinc-500 dark:text-zinc-400" data-blocks-empty>
|
|
4264
|
+
No blocks yet. Add your first block to get started.
|
|
4265
|
+
</div>
|
|
4266
|
+
` : "";
|
|
4267
|
+
const blockOptions = blocks.map((block) => `<option value="${escapeHtml2(block.name)}">${escapeHtml2(block.label)}</option>`).join("");
|
|
4268
|
+
const blockItems = blockValues.map(
|
|
4269
|
+
(blockValue, index) => renderBlockItem(field, blockValue, blocks, discriminator, index, pluginStatuses)
|
|
4270
|
+
).join("");
|
|
4271
|
+
const templates = blocks.map((block) => renderBlockTemplate(field, block, discriminator, pluginStatuses)).join("");
|
|
4272
|
+
return `
|
|
4273
|
+
<div
|
|
4274
|
+
class="blocks-field space-y-4"
|
|
4275
|
+
data-blocks='${escapeHtml2(JSON.stringify(blocks))}'
|
|
4276
|
+
data-blocks-discriminator="${escapeHtml2(discriminator)}"
|
|
4277
|
+
data-field-name="${escapeHtml2(fieldName)}"
|
|
4278
|
+
>
|
|
4279
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml2(JSON.stringify(blockValues))}">
|
|
4280
|
+
|
|
4281
|
+
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
4282
|
+
<div class="flex-1">
|
|
4283
|
+
<select
|
|
4284
|
+
class="${baseClasses} ${errorClasses}"
|
|
4285
|
+
data-role="block-type-select"
|
|
4286
|
+
>
|
|
4287
|
+
<option value="">Choose a block...</option>
|
|
4288
|
+
${blockOptions}
|
|
4289
|
+
</select>
|
|
4290
|
+
</div>
|
|
4291
|
+
<button
|
|
4292
|
+
type="button"
|
|
4293
|
+
data-action="add-block"
|
|
4294
|
+
class="inline-flex items-center justify-center rounded-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white hover:bg-zinc-800 dark:bg-white/10 dark:hover:bg-white/20"
|
|
4295
|
+
>
|
|
4296
|
+
Add Block
|
|
4297
|
+
</button>
|
|
4298
|
+
</div>
|
|
4299
|
+
|
|
4300
|
+
<div class="space-y-4" data-blocks-list>
|
|
4301
|
+
${blockItems || emptyState}
|
|
4302
|
+
</div>
|
|
4303
|
+
|
|
4304
|
+
${templates}
|
|
4305
|
+
</div>
|
|
4306
|
+
${getDragSortableScript()}
|
|
4307
|
+
${getBlocksFieldScript()}
|
|
4308
|
+
`;
|
|
4309
|
+
}
|
|
4310
|
+
function renderStructuredObjectField(field, options, baseClasses, errorClasses) {
|
|
4311
|
+
const { value = {}, pluginStatuses = {} } = options;
|
|
4312
|
+
const opts = field.field_options || {};
|
|
4313
|
+
const properties = opts.properties && typeof opts.properties === "object" ? opts.properties : {};
|
|
4314
|
+
const fieldId = `field-${field.field_name}`;
|
|
4315
|
+
const fieldName = field.field_name;
|
|
4316
|
+
const objectValue = normalizeStructuredObjectValue(value);
|
|
4317
|
+
const subfields = Object.entries(properties).map(
|
|
4318
|
+
([propertyName, propertyConfig]) => renderStructuredSubfield(
|
|
4319
|
+
field,
|
|
4320
|
+
propertyName,
|
|
4321
|
+
propertyConfig,
|
|
4322
|
+
objectValue,
|
|
4323
|
+
pluginStatuses,
|
|
4324
|
+
field.field_name
|
|
4325
|
+
)
|
|
4326
|
+
).join("");
|
|
4327
|
+
return `
|
|
4328
|
+
<div class="space-y-4" data-structured-object data-field-name="${escapeHtml2(fieldName)}">
|
|
4329
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml2(JSON.stringify(objectValue))}">
|
|
4330
|
+
<div class="space-y-4" data-structured-object-fields>
|
|
4331
|
+
${subfields}
|
|
4332
|
+
</div>
|
|
4333
|
+
</div>
|
|
4334
|
+
${getStructuredFieldScript()}
|
|
4335
|
+
`;
|
|
4336
|
+
}
|
|
4337
|
+
function renderStructuredArrayField(field, options, baseClasses, errorClasses) {
|
|
4338
|
+
const { value = [], pluginStatuses = {} } = options;
|
|
4339
|
+
const opts = field.field_options || {};
|
|
4340
|
+
const itemsConfig = opts.items && typeof opts.items === "object" ? opts.items : {};
|
|
4341
|
+
const fieldId = `field-${field.field_name}`;
|
|
4342
|
+
const fieldName = field.field_name;
|
|
4343
|
+
const arrayValue = normalizeStructuredArrayValue(value);
|
|
4344
|
+
const items = arrayValue.map(
|
|
4345
|
+
(itemValue, index) => renderStructuredArrayItem(field, itemsConfig, String(index), itemValue, pluginStatuses)
|
|
4346
|
+
).join("");
|
|
4347
|
+
const emptyState = arrayValue.length === 0 ? `
|
|
4348
|
+
<div class="rounded-lg border border-dashed border-zinc-200 dark:border-white/10 px-4 py-6 text-center text-sm text-zinc-500 dark:text-zinc-400" data-structured-empty>
|
|
4349
|
+
No items yet. Add the first item to get started.
|
|
4350
|
+
</div>
|
|
4351
|
+
` : "";
|
|
4352
|
+
return `
|
|
4353
|
+
<div class="space-y-4" data-structured-array data-field-name="${escapeHtml2(fieldName)}">
|
|
4354
|
+
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${escapeHtml2(JSON.stringify(arrayValue))}">
|
|
4355
|
+
|
|
4356
|
+
<div class="flex items-center justify-between gap-3">
|
|
4357
|
+
<div class="text-sm text-zinc-500 dark:text-zinc-400">
|
|
4358
|
+
${escapeHtml2(opts.itemLabel || "Items")}
|
|
4359
|
+
</div>
|
|
4360
|
+
<button
|
|
4361
|
+
type="button"
|
|
4362
|
+
data-action="add-item"
|
|
4363
|
+
class="inline-flex items-center justify-center rounded-lg bg-zinc-900 px-3 py-2 text-sm font-semibold text-white hover:bg-zinc-800 dark:bg-white/10 dark:hover:bg-white/20"
|
|
4364
|
+
>
|
|
4365
|
+
Add item
|
|
4366
|
+
</button>
|
|
4367
|
+
</div>
|
|
4368
|
+
|
|
4369
|
+
<div class="space-y-4" data-structured-array-list>
|
|
4370
|
+
${items || emptyState}
|
|
4371
|
+
</div>
|
|
4372
|
+
|
|
4373
|
+
<template data-structured-array-template>
|
|
4374
|
+
${renderStructuredArrayItem(field, itemsConfig, "__INDEX__", {}, pluginStatuses)}
|
|
4375
|
+
</template>
|
|
4376
|
+
</div>
|
|
4377
|
+
${getDragSortableScript()}
|
|
4378
|
+
${getStructuredFieldScript()}
|
|
4379
|
+
`;
|
|
4380
|
+
}
|
|
4381
|
+
function renderStructuredArrayItem(field, itemConfig, index, itemValue, pluginStatuses) {
|
|
4382
|
+
const itemFields = renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses);
|
|
4383
|
+
return `
|
|
4384
|
+
<div class="structured-array-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-white/5 p-4 shadow-sm" data-array-index="${escapeHtml2(index)}" draggable="true">
|
|
4385
|
+
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
4386
|
+
<div class="flex items-center gap-3">
|
|
4387
|
+
<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">
|
|
4388
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
4389
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
|
|
4390
|
+
</svg>
|
|
4391
|
+
</div>
|
|
4392
|
+
<div class="text-sm font-semibold text-zinc-900 dark:text-white">
|
|
4393
|
+
Item <span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-array-order-label></span>
|
|
4394
|
+
</div>
|
|
4395
|
+
</div>
|
|
4396
|
+
<div class="flex flex-wrap gap-2 text-xs">
|
|
4397
|
+
<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">
|
|
4398
|
+
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
4399
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
4400
|
+
</svg>
|
|
4401
|
+
</button>
|
|
4402
|
+
<button type="button" data-action="move-down" 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 down" title="Move down">
|
|
4403
|
+
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
4404
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18l4-4m-4 4l-4-4m4 4V6"/>
|
|
4405
|
+
</svg>
|
|
4406
|
+
</button>
|
|
4407
|
+
<button type="button" data-action="remove-item" class="inline-flex items-center gap-x-1 px-2.5 py-1.5 text-xs font-medium text-pink-700 dark:text-pink-300 hover:bg-pink-50 dark:hover:bg-pink-900/20 rounded-lg transition-colors">
|
|
4408
|
+
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
4409
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 0 00-7.5 0"/>
|
|
4410
|
+
</svg>
|
|
4411
|
+
Delete item
|
|
4412
|
+
</button>
|
|
4413
|
+
</div>
|
|
4414
|
+
</div>
|
|
4415
|
+
<div class="mt-4 space-y-4" data-array-item-fields>
|
|
4416
|
+
${itemFields}
|
|
4417
|
+
</div>
|
|
4418
|
+
</div>
|
|
4419
|
+
`;
|
|
4420
|
+
}
|
|
4421
|
+
function renderStructuredItemFields(field, itemConfig, index, itemValue, pluginStatuses) {
|
|
4422
|
+
const itemType = itemConfig?.type || "string";
|
|
4423
|
+
if (itemType === "object" && itemConfig?.properties && typeof itemConfig.properties === "object") {
|
|
4424
|
+
const fieldPrefix = `array-${field.field_name}-${index}`;
|
|
4425
|
+
return Object.entries(itemConfig.properties).map(
|
|
4426
|
+
([propertyName, propertyConfig]) => renderStructuredSubfield(
|
|
4427
|
+
field,
|
|
4428
|
+
propertyName,
|
|
4429
|
+
propertyConfig,
|
|
4430
|
+
itemValue || {},
|
|
4431
|
+
pluginStatuses,
|
|
4432
|
+
fieldPrefix
|
|
4433
|
+
)
|
|
4434
|
+
).join("");
|
|
3986
4435
|
}
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
};
|
|
3996
|
-
|
|
3997
|
-
|
|
4436
|
+
const normalizedField = normalizeBlockField(itemConfig, "Item");
|
|
4437
|
+
const fieldValue = itemValue ?? normalizedField.defaultValue ?? "";
|
|
4438
|
+
const fieldDefinition = {
|
|
4439
|
+
id: `array-${field.field_name}-${index}-value`,
|
|
4440
|
+
field_name: `array-${field.field_name}-${index}-value`,
|
|
4441
|
+
field_type: normalizedField.type,
|
|
4442
|
+
field_label: normalizedField.label,
|
|
4443
|
+
field_options: normalizedField.options,
|
|
4444
|
+
is_required: normalizedField.required};
|
|
4445
|
+
return `
|
|
4446
|
+
<div class="structured-subfield" data-structured-field="__value" data-field-type="${escapeHtml2(normalizedField.type)}">
|
|
4447
|
+
${renderDynamicField(fieldDefinition, { value: fieldValue, pluginStatuses })}
|
|
4448
|
+
</div>
|
|
4449
|
+
`;
|
|
4450
|
+
}
|
|
4451
|
+
function renderStructuredSubfield(field, propertyName, propertyConfig, objectValue, pluginStatuses, fieldPrefix) {
|
|
4452
|
+
const normalizedField = normalizeBlockField(propertyConfig, propertyName);
|
|
4453
|
+
const fieldValue = objectValue?.[propertyName] ?? normalizedField.defaultValue ?? "";
|
|
4454
|
+
const fieldDefinition = {
|
|
4455
|
+
field_name: `${fieldPrefix}__${propertyName}`,
|
|
4456
|
+
field_type: normalizedField.type,
|
|
4457
|
+
field_label: normalizedField.label,
|
|
4458
|
+
field_options: normalizedField.options,
|
|
4459
|
+
is_required: normalizedField.required};
|
|
4460
|
+
return `
|
|
4461
|
+
<div class="structured-subfield" data-structured-field="${escapeHtml2(propertyName)}" data-field-type="${escapeHtml2(normalizedField.type)}">
|
|
4462
|
+
${renderDynamicField(fieldDefinition, { value: fieldValue, pluginStatuses })}
|
|
4463
|
+
</div>
|
|
4464
|
+
`;
|
|
4465
|
+
}
|
|
4466
|
+
function normalizeStructuredObjectValue(value) {
|
|
4467
|
+
if (!value) return {};
|
|
4468
|
+
if (typeof value === "string") {
|
|
4469
|
+
try {
|
|
4470
|
+
const parsed = JSON.parse(value);
|
|
4471
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4472
|
+
} catch {
|
|
4473
|
+
return {};
|
|
4474
|
+
}
|
|
3998
4475
|
}
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4476
|
+
if (typeof value === "object" && !Array.isArray(value)) return value;
|
|
4477
|
+
return {};
|
|
4478
|
+
}
|
|
4479
|
+
function normalizeStructuredArrayValue(value) {
|
|
4480
|
+
if (!value) return [];
|
|
4481
|
+
if (Array.isArray(value)) return value;
|
|
4482
|
+
if (typeof value === "string") {
|
|
4483
|
+
try {
|
|
4484
|
+
const parsed = JSON.parse(value);
|
|
4485
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
4486
|
+
} catch {
|
|
4487
|
+
return [];
|
|
4488
|
+
}
|
|
4005
4489
|
}
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
}
|
|
4015
|
-
|
|
4016
|
-
|
|
4490
|
+
return [];
|
|
4491
|
+
}
|
|
4492
|
+
function normalizeBlockDefinitions(rawBlocks) {
|
|
4493
|
+
if (!rawBlocks || typeof rawBlocks !== "object") return [];
|
|
4494
|
+
return Object.entries(rawBlocks).filter(([name, block]) => typeof name === "string" && block && typeof block === "object").map(([name, block]) => ({
|
|
4495
|
+
name,
|
|
4496
|
+
label: block.label || name,
|
|
4497
|
+
description: block.description,
|
|
4498
|
+
properties: block.properties && typeof block.properties === "object" ? block.properties : {}
|
|
4499
|
+
}));
|
|
4500
|
+
}
|
|
4501
|
+
function normalizeBlocksValue(value, discriminator) {
|
|
4502
|
+
const normalizeItem = (item) => {
|
|
4503
|
+
if (!item || typeof item !== "object") return null;
|
|
4504
|
+
if (item[discriminator]) return item;
|
|
4505
|
+
if (item.blockType && item.data && typeof item.data === "object") {
|
|
4506
|
+
return { [discriminator]: item.blockType, ...item.data };
|
|
4507
|
+
}
|
|
4508
|
+
return item;
|
|
4509
|
+
};
|
|
4510
|
+
const fromArray = (items) => items.map(normalizeItem).filter((item) => item && typeof item === "object");
|
|
4511
|
+
if (Array.isArray(value)) return fromArray(value);
|
|
4512
|
+
if (typeof value === "string" && value.trim()) {
|
|
4513
|
+
try {
|
|
4514
|
+
const parsed = JSON.parse(value);
|
|
4515
|
+
return Array.isArray(parsed) ? fromArray(parsed) : [];
|
|
4516
|
+
} catch {
|
|
4517
|
+
return [];
|
|
4518
|
+
}
|
|
4017
4519
|
}
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4520
|
+
return [];
|
|
4521
|
+
}
|
|
4522
|
+
function renderBlockTemplate(field, block, discriminator, pluginStatuses) {
|
|
4523
|
+
return `
|
|
4524
|
+
<template data-block-template="${escapeHtml2(block.name)}">
|
|
4525
|
+
${renderBlockCard(field, block, discriminator, "__INDEX__", {}, pluginStatuses)}
|
|
4526
|
+
</template>
|
|
4527
|
+
`;
|
|
4528
|
+
}
|
|
4529
|
+
function renderBlockItem(field, blockValue, blocks, discriminator, index, pluginStatuses) {
|
|
4530
|
+
const blockType = blockValue?.[discriminator] || blockValue?.blockType;
|
|
4531
|
+
const blockDefinition = blocks.find((block) => block.name === blockType);
|
|
4532
|
+
if (!blockDefinition) {
|
|
4533
|
+
return `
|
|
4534
|
+
<div class="rounded-lg border border-amber-200 bg-amber-50/50 px-4 py-3 text-sm text-amber-700 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200" data-block-raw="${escapeHtml2(JSON.stringify(blockValue || {}))}">
|
|
4535
|
+
Unknown block type: <strong>${escapeHtml2(String(blockType || "unknown"))}</strong>. This block will be preserved as-is.
|
|
4536
|
+
</div>
|
|
4537
|
+
`;
|
|
4024
4538
|
}
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4539
|
+
const data = blockValue && typeof blockValue === "object" ? Object.fromEntries(Object.entries(blockValue).filter(([key]) => key !== discriminator)) : {};
|
|
4540
|
+
return renderBlockCard(field, blockDefinition, discriminator, String(index), data, pluginStatuses);
|
|
4541
|
+
}
|
|
4542
|
+
function renderBlockCard(field, block, discriminator, index, data, pluginStatuses) {
|
|
4543
|
+
const blockFields = Object.entries(block.properties).map(([fieldName, fieldConfig]) => {
|
|
4544
|
+
if (fieldConfig?.type === "array" && fieldConfig?.items?.blocks) {
|
|
4545
|
+
return `
|
|
4546
|
+
<div class="rounded-lg border border-dashed border-amber-200 bg-amber-50/50 px-4 py-3 text-xs text-amber-700 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200">
|
|
4547
|
+
Nested blocks are not supported yet for "${escapeHtml2(fieldName)}".
|
|
4548
|
+
</div>
|
|
4549
|
+
`;
|
|
4031
4550
|
}
|
|
4032
|
-
|
|
4551
|
+
const normalizedField = normalizeBlockField(fieldConfig, fieldName);
|
|
4552
|
+
const fieldValue = data?.[fieldName] ?? normalizedField.defaultValue ?? "";
|
|
4553
|
+
const fieldDefinition = {
|
|
4554
|
+
id: `block-${field.field_name}-${index}-${fieldName}`,
|
|
4555
|
+
field_name: `block-${field.field_name}-${index}-${fieldName}`,
|
|
4556
|
+
field_type: normalizedField.type,
|
|
4557
|
+
field_label: normalizedField.label,
|
|
4558
|
+
field_options: normalizedField.options,
|
|
4559
|
+
is_required: normalizedField.required};
|
|
4560
|
+
return `
|
|
4561
|
+
<div class="blocks-subfield" data-block-field="${escapeHtml2(fieldName)}" data-field-type="${escapeHtml2(normalizedField.type)}">
|
|
4562
|
+
${renderDynamicField(fieldDefinition, { value: fieldValue, pluginStatuses })}
|
|
4563
|
+
</div>
|
|
4564
|
+
`;
|
|
4565
|
+
}).join("");
|
|
4566
|
+
return `
|
|
4567
|
+
<div class="blocks-item rounded-lg border border-zinc-200 dark:border-white/10 bg-white/60 dark:bg-white/5 p-4 shadow-sm" data-block-type="${escapeHtml2(block.name)}" data-block-discriminator="${escapeHtml2(discriminator)}" draggable="true">
|
|
4568
|
+
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
4569
|
+
<div class="flex items-start gap-3">
|
|
4570
|
+
<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">
|
|
4571
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
4572
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M4 8h16M4 16h16"/>
|
|
4573
|
+
</svg>
|
|
4574
|
+
</div>
|
|
4575
|
+
<div>
|
|
4576
|
+
<div class="text-sm font-semibold text-zinc-900 dark:text-white">
|
|
4577
|
+
${escapeHtml2(block.label)}
|
|
4578
|
+
<span class="ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400" data-block-order-label></span>
|
|
4579
|
+
</div>
|
|
4580
|
+
${block.description ? `<p class="text-xs text-zinc-500 dark:text-zinc-400">${escapeHtml2(block.description)}</p>` : ""}
|
|
4581
|
+
</div>
|
|
4582
|
+
</div>
|
|
4583
|
+
<div class="flex flex-wrap gap-2 text-xs">
|
|
4584
|
+
<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">
|
|
4585
|
+
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
4586
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6l-4 4m4-4l4 4m-4-4v12"/>
|
|
4587
|
+
</svg>
|
|
4588
|
+
</button>
|
|
4589
|
+
<button type="button" data-action="move-down" 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 down" title="Move down">
|
|
4590
|
+
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="4">
|
|
4591
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18l4-4m-4 4l-4-4m4 4V6"/>
|
|
4592
|
+
</svg>
|
|
4593
|
+
</button>
|
|
4594
|
+
<button type="button" data-action="remove-block" class="inline-flex items-center gap-x-1 px-2.5 py-1.5 text-xs font-medium text-pink-700 dark:text-pink-300 hover:bg-pink-50 dark:hover:bg-pink-900/20 rounded-lg transition-colors">
|
|
4595
|
+
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
4596
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/>
|
|
4597
|
+
</svg>
|
|
4598
|
+
Delete block
|
|
4599
|
+
</button>
|
|
4600
|
+
</div>
|
|
4601
|
+
</div>
|
|
4602
|
+
<div class="mt-4 space-y-4">
|
|
4603
|
+
${blockFields}
|
|
4604
|
+
</div>
|
|
4605
|
+
</div>
|
|
4606
|
+
`;
|
|
4607
|
+
}
|
|
4608
|
+
function normalizeBlockField(fieldConfig, fieldName) {
|
|
4609
|
+
const type = fieldConfig?.type || "text";
|
|
4610
|
+
const label = fieldConfig?.title || fieldName;
|
|
4611
|
+
const required = fieldConfig?.required === true;
|
|
4612
|
+
const options = { ...fieldConfig };
|
|
4613
|
+
if (type === "select" && Array.isArray(fieldConfig?.enum)) {
|
|
4614
|
+
options.options = fieldConfig.enum.map((value, index) => ({
|
|
4615
|
+
value,
|
|
4616
|
+
label: fieldConfig.enumLabels?.[index] || value
|
|
4617
|
+
}));
|
|
4033
4618
|
}
|
|
4034
|
-
|
|
4619
|
+
return {
|
|
4620
|
+
type,
|
|
4621
|
+
label,
|
|
4622
|
+
required,
|
|
4623
|
+
defaultValue: fieldConfig?.default,
|
|
4624
|
+
options
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4627
|
+
function getStructuredFieldScript() {
|
|
4628
|
+
return `
|
|
4629
|
+
${getReadFieldValueScript()}
|
|
4630
|
+
<script>
|
|
4631
|
+
if (!window.__sonicStructuredFieldInit) {
|
|
4632
|
+
window.__sonicStructuredFieldInit = true;
|
|
4633
|
+
|
|
4634
|
+
function initializeStructuredFields() {
|
|
4635
|
+
const readFieldValue = window.sonicReadFieldValue;
|
|
4636
|
+
|
|
4637
|
+
const readStructuredValue = (container) => {
|
|
4638
|
+
const fields = Array.from(container.querySelectorAll('.structured-subfield'));
|
|
4639
|
+
if (fields.length === 1 && fields[0].dataset.structuredField === '__value') {
|
|
4640
|
+
return readFieldValue(fields[0]);
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
return fields.reduce((acc, fieldWrapper) => {
|
|
4644
|
+
const fieldName = fieldWrapper.dataset.structuredField;
|
|
4645
|
+
if (!fieldName || fieldName === '__value') return acc;
|
|
4646
|
+
acc[fieldName] = readFieldValue(fieldWrapper);
|
|
4647
|
+
return acc;
|
|
4648
|
+
}, {});
|
|
4649
|
+
};
|
|
4650
|
+
|
|
4651
|
+
document.querySelectorAll('[data-structured-object]').forEach((container) => {
|
|
4652
|
+
if (container.dataset.structuredInitialized === 'true') {
|
|
4653
|
+
return;
|
|
4654
|
+
}
|
|
4655
|
+
container.dataset.structuredInitialized = 'true';
|
|
4656
|
+
const hiddenInput = container.querySelector('input[type="hidden"]');
|
|
4657
|
+
|
|
4658
|
+
const updateHiddenInput = () => {
|
|
4659
|
+
if (!hiddenInput) return;
|
|
4660
|
+
const value = readStructuredValue(container);
|
|
4661
|
+
hiddenInput.value = JSON.stringify(value);
|
|
4662
|
+
};
|
|
4663
|
+
|
|
4664
|
+
container.addEventListener('input', updateHiddenInput);
|
|
4665
|
+
container.addEventListener('change', updateHiddenInput);
|
|
4666
|
+
updateHiddenInput();
|
|
4667
|
+
});
|
|
4668
|
+
|
|
4669
|
+
document.querySelectorAll('[data-structured-array]').forEach((container) => {
|
|
4670
|
+
if (container.dataset.structuredInitialized === 'true') {
|
|
4671
|
+
return;
|
|
4672
|
+
}
|
|
4673
|
+
container.dataset.structuredInitialized = 'true';
|
|
4674
|
+
const list = container.querySelector('[data-structured-array-list]');
|
|
4675
|
+
const hiddenInput = container.querySelector('input[type="hidden"]');
|
|
4676
|
+
const template = container.querySelector('template[data-structured-array-template]');
|
|
4677
|
+
|
|
4678
|
+
const updateOrderLabels = () => {
|
|
4679
|
+
const items = Array.from(container.querySelectorAll('.structured-array-item'));
|
|
4680
|
+
items.forEach((item, index) => {
|
|
4681
|
+
const label = item.querySelector('[data-array-order-label]');
|
|
4682
|
+
if (label) {
|
|
4683
|
+
label.textContent = '#'+ (index + 1);
|
|
4684
|
+
}
|
|
4685
|
+
|
|
4686
|
+
const moveUpButton = item.querySelector('[data-action="move-up"]');
|
|
4687
|
+
if (moveUpButton instanceof HTMLButtonElement) {
|
|
4688
|
+
moveUpButton.disabled = index === 0;
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
const moveDownButton = item.querySelector('[data-action="move-down"]');
|
|
4692
|
+
if (moveDownButton instanceof HTMLButtonElement) {
|
|
4693
|
+
moveDownButton.disabled = index === items.length - 1;
|
|
4694
|
+
}
|
|
4695
|
+
});
|
|
4696
|
+
};
|
|
4697
|
+
|
|
4698
|
+
const updateHiddenInput = () => {
|
|
4699
|
+
if (!hiddenInput || !list) return;
|
|
4700
|
+
const items = Array.from(list.querySelectorAll('.structured-array-item'));
|
|
4701
|
+
const values = items.map((item) => readStructuredValue(item));
|
|
4702
|
+
hiddenInput.value = JSON.stringify(values);
|
|
4703
|
+
|
|
4704
|
+
const emptyState = list.querySelector('[data-structured-empty]');
|
|
4705
|
+
if (emptyState) {
|
|
4706
|
+
emptyState.style.display = values.length === 0 ? 'block' : 'none';
|
|
4707
|
+
}
|
|
4708
|
+
updateOrderLabels();
|
|
4709
|
+
};
|
|
4710
|
+
|
|
4711
|
+
if (typeof window.initializeDragSortable === 'function' && list) {
|
|
4712
|
+
window.initializeDragSortable(list, {
|
|
4713
|
+
itemSelector: '.structured-array-item',
|
|
4714
|
+
handleSelector: '[data-action="drag-handle"]',
|
|
4715
|
+
onUpdate: updateHiddenInput
|
|
4716
|
+
});
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
container.addEventListener('click', (event) => {
|
|
4720
|
+
const target = event.target;
|
|
4721
|
+
if (!(target instanceof Element)) return;
|
|
4722
|
+
const actionButton = target.closest('[data-action]');
|
|
4723
|
+
if (!actionButton || actionButton.hasAttribute('disabled')) return;
|
|
4724
|
+
|
|
4725
|
+
const action = actionButton.getAttribute('data-action');
|
|
4726
|
+
|
|
4727
|
+
if (action === 'add-item') {
|
|
4728
|
+
if (!list || !template) return;
|
|
4729
|
+
const nextIndex = list.querySelectorAll('.structured-array-item').length;
|
|
4730
|
+
const html = template.innerHTML.replace(/__INDEX__/g, String(nextIndex));
|
|
4731
|
+
list.insertAdjacentHTML('beforeend', html);
|
|
4732
|
+
if (typeof initializeTinyMCE === 'function') {
|
|
4733
|
+
initializeTinyMCE();
|
|
4734
|
+
}
|
|
4735
|
+
if (typeof window.initializeQuillEditors === 'function') {
|
|
4736
|
+
window.initializeQuillEditors();
|
|
4737
|
+
}
|
|
4738
|
+
if (typeof initializeMDXEditor === 'function') {
|
|
4739
|
+
initializeMDXEditor();
|
|
4740
|
+
}
|
|
4741
|
+
updateHiddenInput();
|
|
4742
|
+
return;
|
|
4743
|
+
}
|
|
4744
|
+
|
|
4745
|
+
const item = actionButton.closest('.structured-array-item');
|
|
4746
|
+
if (!item || !list) return;
|
|
4747
|
+
|
|
4748
|
+
if (action === 'remove-item') {
|
|
4749
|
+
item.remove();
|
|
4750
|
+
updateHiddenInput();
|
|
4751
|
+
return;
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4754
|
+
if (action === 'move-up') {
|
|
4755
|
+
const previous = item.previousElementSibling;
|
|
4756
|
+
if (previous) {
|
|
4757
|
+
list.insertBefore(item, previous);
|
|
4758
|
+
updateHiddenInput();
|
|
4759
|
+
}
|
|
4760
|
+
return;
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4763
|
+
if (action === 'move-down') {
|
|
4764
|
+
const next = item.nextElementSibling;
|
|
4765
|
+
if (next) {
|
|
4766
|
+
list.insertBefore(next, item);
|
|
4767
|
+
updateHiddenInput();
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
});
|
|
4771
|
+
|
|
4772
|
+
container.addEventListener('input', (event) => {
|
|
4773
|
+
const target = event.target;
|
|
4774
|
+
if (!(target instanceof Element)) return;
|
|
4775
|
+
if (target.closest('[data-structured-array-list]')) {
|
|
4776
|
+
updateHiddenInput();
|
|
4777
|
+
}
|
|
4778
|
+
});
|
|
4779
|
+
|
|
4780
|
+
container.addEventListener('change', (event) => {
|
|
4781
|
+
const target = event.target;
|
|
4782
|
+
if (!(target instanceof Element)) return;
|
|
4783
|
+
if (target.closest('[data-structured-array-list]')) {
|
|
4784
|
+
updateHiddenInput();
|
|
4785
|
+
}
|
|
4786
|
+
});
|
|
4787
|
+
|
|
4788
|
+
updateHiddenInput();
|
|
4789
|
+
});
|
|
4790
|
+
}
|
|
4791
|
+
|
|
4792
|
+
window.initializeStructuredFields = initializeStructuredFields;
|
|
4793
|
+
|
|
4794
|
+
if (document.readyState === 'loading') {
|
|
4795
|
+
document.addEventListener('DOMContentLoaded', initializeStructuredFields);
|
|
4796
|
+
} else {
|
|
4797
|
+
initializeStructuredFields();
|
|
4798
|
+
}
|
|
4799
|
+
|
|
4800
|
+
document.addEventListener('htmx:afterSwap', function() {
|
|
4801
|
+
setTimeout(initializeStructuredFields, 50);
|
|
4802
|
+
});
|
|
4803
|
+
} else if (typeof window.initializeStructuredFields === 'function') {
|
|
4804
|
+
window.initializeStructuredFields();
|
|
4805
|
+
}
|
|
4806
|
+
</script>
|
|
4807
|
+
`;
|
|
4808
|
+
}
|
|
4809
|
+
function getBlocksFieldScript() {
|
|
4810
|
+
return `
|
|
4811
|
+
${getReadFieldValueScript()}
|
|
4812
|
+
<script>
|
|
4813
|
+
if (!window.__sonicBlocksFieldInit) {
|
|
4814
|
+
window.__sonicBlocksFieldInit = true;
|
|
4815
|
+
|
|
4816
|
+
function initializeBlocksFields() {
|
|
4817
|
+
document.querySelectorAll('.blocks-field').forEach((container) => {
|
|
4818
|
+
if (container.dataset.blocksInitialized === 'true') {
|
|
4819
|
+
return;
|
|
4820
|
+
}
|
|
4821
|
+
|
|
4822
|
+
container.dataset.blocksInitialized = 'true';
|
|
4823
|
+
const list = container.querySelector('[data-blocks-list]');
|
|
4824
|
+
const hiddenInput = container.querySelector('input[type="hidden"]');
|
|
4825
|
+
const typeSelect = container.querySelector('[data-role="block-type-select"]');
|
|
4826
|
+
const discriminator = container.dataset.blocksDiscriminator || 'blockType';
|
|
4827
|
+
|
|
4828
|
+
const updateOrderLabels = () => {
|
|
4829
|
+
const items = Array.from(container.querySelectorAll('.blocks-item'));
|
|
4830
|
+
items.forEach((item, index) => {
|
|
4831
|
+
const label = item.querySelector('[data-block-order-label]');
|
|
4832
|
+
if (label) {
|
|
4833
|
+
label.textContent = '#'+ (index + 1);
|
|
4834
|
+
}
|
|
4835
|
+
|
|
4836
|
+
const moveUpButton = item.querySelector('[data-action="move-up"]');
|
|
4837
|
+
if (moveUpButton instanceof HTMLButtonElement) {
|
|
4838
|
+
moveUpButton.disabled = index === 0;
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4841
|
+
const moveDownButton = item.querySelector('[data-action="move-down"]');
|
|
4842
|
+
if (moveDownButton instanceof HTMLButtonElement) {
|
|
4843
|
+
moveDownButton.disabled = index === items.length - 1;
|
|
4844
|
+
}
|
|
4845
|
+
});
|
|
4846
|
+
};
|
|
4847
|
+
|
|
4848
|
+
const readFieldValue = window.sonicReadFieldValue;
|
|
4849
|
+
|
|
4850
|
+
const readBlockItem = (item) => {
|
|
4851
|
+
if (item.dataset.blockRaw) {
|
|
4852
|
+
try {
|
|
4853
|
+
return JSON.parse(item.dataset.blockRaw);
|
|
4854
|
+
} catch (error) {
|
|
4855
|
+
return {};
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
|
|
4859
|
+
const blockType = item.dataset.blockType;
|
|
4860
|
+
const data = {};
|
|
4861
|
+
|
|
4862
|
+
item.querySelectorAll('.blocks-subfield').forEach((fieldWrapper) => {
|
|
4863
|
+
const fieldName = fieldWrapper.dataset.blockField;
|
|
4864
|
+
if (!fieldName) {
|
|
4865
|
+
return;
|
|
4866
|
+
}
|
|
4867
|
+
data[fieldName] = readFieldValue(fieldWrapper);
|
|
4868
|
+
});
|
|
4869
|
+
|
|
4870
|
+
return { [discriminator]: blockType, ...data };
|
|
4871
|
+
};
|
|
4872
|
+
|
|
4873
|
+
const updateHiddenInput = () => {
|
|
4874
|
+
if (!hiddenInput || !list) return;
|
|
4875
|
+
const items = Array.from(list.querySelectorAll('.blocks-item, [data-block-raw]'));
|
|
4876
|
+
const blocksData = items.map((item) => readBlockItem(item));
|
|
4877
|
+
hiddenInput.value = JSON.stringify(blocksData);
|
|
4878
|
+
|
|
4879
|
+
const emptyState = list.querySelector('[data-blocks-empty]');
|
|
4880
|
+
if (emptyState) {
|
|
4881
|
+
emptyState.style.display = blocksData.length === 0 ? 'block' : 'none';
|
|
4882
|
+
}
|
|
4883
|
+
updateOrderLabels();
|
|
4884
|
+
};
|
|
4885
|
+
|
|
4886
|
+
const initializeEditors = () => {
|
|
4887
|
+
if (typeof initializeTinyMCE === 'function') {
|
|
4888
|
+
initializeTinyMCE();
|
|
4889
|
+
}
|
|
4890
|
+
if (typeof window.initializeQuillEditors === 'function') {
|
|
4891
|
+
window.initializeQuillEditors();
|
|
4892
|
+
}
|
|
4893
|
+
if (typeof initializeMDXEditor === 'function') {
|
|
4894
|
+
initializeMDXEditor();
|
|
4895
|
+
}
|
|
4896
|
+
};
|
|
4897
|
+
|
|
4898
|
+
if (typeof window.initializeDragSortable === 'function' && list) {
|
|
4899
|
+
window.initializeDragSortable(list, {
|
|
4900
|
+
itemSelector: '.blocks-item',
|
|
4901
|
+
handleSelector: '[data-action="drag-handle"]',
|
|
4902
|
+
onUpdate: updateHiddenInput
|
|
4903
|
+
});
|
|
4904
|
+
}
|
|
4905
|
+
|
|
4906
|
+
container.addEventListener('click', (event) => {
|
|
4907
|
+
const target = event.target;
|
|
4908
|
+
if (!(target instanceof Element)) return;
|
|
4909
|
+
const actionButton = target.closest('[data-action]');
|
|
4910
|
+
if (!actionButton) return;
|
|
4911
|
+
|
|
4912
|
+
if (actionButton.hasAttribute('disabled')) {
|
|
4913
|
+
return;
|
|
4914
|
+
}
|
|
4915
|
+
|
|
4916
|
+
const action = actionButton.getAttribute('data-action');
|
|
4917
|
+
if (action === 'add-block') {
|
|
4918
|
+
const blockType = typeSelect ? typeSelect.value : '';
|
|
4919
|
+
if (!blockType || !list) return;
|
|
4920
|
+
const template = container.querySelector('template[data-block-template="' + blockType + '"]');
|
|
4921
|
+
if (!template) return;
|
|
4922
|
+
|
|
4923
|
+
const nextIndex = list.querySelectorAll('.blocks-item').length;
|
|
4924
|
+
const html = template.innerHTML.replace(/__INDEX__/g, String(nextIndex));
|
|
4925
|
+
list.insertAdjacentHTML('beforeend', html);
|
|
4926
|
+
if (typeSelect) {
|
|
4927
|
+
typeSelect.value = '';
|
|
4928
|
+
}
|
|
4929
|
+
initializeEditors();
|
|
4930
|
+
if (typeof window.initializeStructuredFields === 'function') {
|
|
4931
|
+
window.initializeStructuredFields();
|
|
4932
|
+
}
|
|
4933
|
+
updateHiddenInput();
|
|
4934
|
+
return;
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4937
|
+
const item = actionButton.closest('.blocks-item');
|
|
4938
|
+
if (!item || !list) return;
|
|
4939
|
+
|
|
4940
|
+
if (action === 'remove-block') {
|
|
4941
|
+
item.remove();
|
|
4942
|
+
updateHiddenInput();
|
|
4943
|
+
return;
|
|
4944
|
+
}
|
|
4945
|
+
|
|
4946
|
+
if (action === 'move-up') {
|
|
4947
|
+
const previous = item.previousElementSibling;
|
|
4948
|
+
if (previous) {
|
|
4949
|
+
list.insertBefore(item, previous);
|
|
4950
|
+
updateHiddenInput();
|
|
4951
|
+
}
|
|
4952
|
+
return;
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4955
|
+
if (action === 'move-down') {
|
|
4956
|
+
const next = item.nextElementSibling;
|
|
4957
|
+
if (next) {
|
|
4958
|
+
list.insertBefore(next, item);
|
|
4959
|
+
updateHiddenInput();
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
});
|
|
4963
|
+
|
|
4964
|
+
container.addEventListener('input', (event) => {
|
|
4965
|
+
const target = event.target;
|
|
4966
|
+
if (!(target instanceof Element)) return;
|
|
4967
|
+
if (target.closest('[data-blocks-list]')) {
|
|
4968
|
+
updateHiddenInput();
|
|
4969
|
+
}
|
|
4970
|
+
});
|
|
4971
|
+
|
|
4972
|
+
container.addEventListener('change', (event) => {
|
|
4973
|
+
const target = event.target;
|
|
4974
|
+
if (!(target instanceof Element)) return;
|
|
4975
|
+
if (target.closest('[data-blocks-list]')) {
|
|
4976
|
+
updateHiddenInput();
|
|
4977
|
+
}
|
|
4978
|
+
});
|
|
4979
|
+
|
|
4980
|
+
updateHiddenInput();
|
|
4981
|
+
});
|
|
4982
|
+
}
|
|
4983
|
+
|
|
4984
|
+
window.initializeBlocksFields = initializeBlocksFields;
|
|
4985
|
+
|
|
4986
|
+
if (document.readyState === 'loading') {
|
|
4987
|
+
document.addEventListener('DOMContentLoaded', initializeBlocksFields);
|
|
4988
|
+
} else {
|
|
4989
|
+
initializeBlocksFields();
|
|
4990
|
+
}
|
|
4991
|
+
|
|
4992
|
+
document.addEventListener('htmx:afterSwap', function() {
|
|
4993
|
+
setTimeout(initializeBlocksFields, 50);
|
|
4994
|
+
});
|
|
4995
|
+
} else if (typeof window.initializeBlocksFields === 'function') {
|
|
4996
|
+
window.initializeBlocksFields();
|
|
4997
|
+
}
|
|
4998
|
+
</script>
|
|
4999
|
+
`;
|
|
5000
|
+
}
|
|
5001
|
+
function escapeHtml2(text) {
|
|
5002
|
+
if (typeof text !== "string") return String(text || "");
|
|
5003
|
+
return text.replace(/[&<>"']/g, (char) => ({
|
|
5004
|
+
"&": "&",
|
|
5005
|
+
"<": "<",
|
|
5006
|
+
">": ">",
|
|
5007
|
+
'"': """,
|
|
5008
|
+
"'": "'"
|
|
5009
|
+
})[char] || char);
|
|
5010
|
+
}
|
|
4035
5011
|
|
|
4036
5012
|
// src/plugins/available/tinymce-plugin/index.ts
|
|
4037
|
-
var builder = PluginBuilder.create({
|
|
5013
|
+
var builder = chunkYHW27CBV_cjs.PluginBuilder.create({
|
|
4038
5014
|
name: "tinymce-plugin",
|
|
4039
5015
|
version: "1.0.0",
|
|
4040
5016
|
description: "Powerful WYSIWYG rich text editor for content creation"
|
|
@@ -4317,7 +5293,7 @@ function getQuillCDN(version = "2.0.2") {
|
|
|
4317
5293
|
`;
|
|
4318
5294
|
}
|
|
4319
5295
|
function createQuillEditorPlugin() {
|
|
4320
|
-
const builder3 = PluginBuilder.create({
|
|
5296
|
+
const builder3 = chunkYHW27CBV_cjs.PluginBuilder.create({
|
|
4321
5297
|
name: "quill-editor",
|
|
4322
5298
|
version: "1.0.0",
|
|
4323
5299
|
description: "Quill rich text editor integration for SonicJS"
|
|
@@ -4343,7 +5319,7 @@ function createQuillEditorPlugin() {
|
|
|
4343
5319
|
createQuillEditorPlugin();
|
|
4344
5320
|
|
|
4345
5321
|
// src/plugins/available/easy-mdx/index.ts
|
|
4346
|
-
var builder2 = PluginBuilder.create({
|
|
5322
|
+
var builder2 = chunkYHW27CBV_cjs.PluginBuilder.create({
|
|
4347
5323
|
name: "easy-mdx",
|
|
4348
5324
|
version: "1.0.0",
|
|
4349
5325
|
description: "Lightweight markdown editor with live preview"
|
|
@@ -4575,17 +5551,24 @@ function renderContentFormPage(data) {
|
|
|
4575
5551
|
const coreFieldsHTML = coreFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
|
|
4576
5552
|
value: getFieldValue(field.field_name),
|
|
4577
5553
|
errors: data.validationErrors?.[field.field_name] || [],
|
|
4578
|
-
pluginStatuses
|
|
5554
|
+
pluginStatuses,
|
|
5555
|
+
collectionId: data.collection.id,
|
|
5556
|
+
contentId: data.id
|
|
5557
|
+
// Pass content ID when editing
|
|
4579
5558
|
}));
|
|
4580
5559
|
const contentFieldsHTML = contentFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
|
|
4581
5560
|
value: getFieldValue(field.field_name),
|
|
4582
5561
|
errors: data.validationErrors?.[field.field_name] || [],
|
|
4583
|
-
pluginStatuses
|
|
5562
|
+
pluginStatuses,
|
|
5563
|
+
collectionId: data.collection.id,
|
|
5564
|
+
contentId: data.id
|
|
4584
5565
|
}));
|
|
4585
5566
|
const metaFieldsHTML = metaFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
|
|
4586
5567
|
value: getFieldValue(field.field_name),
|
|
4587
5568
|
errors: data.validationErrors?.[field.field_name] || [],
|
|
4588
|
-
pluginStatuses
|
|
5569
|
+
pluginStatuses,
|
|
5570
|
+
collectionId: data.collection.id,
|
|
5571
|
+
contentId: data.id
|
|
4589
5572
|
}));
|
|
4590
5573
|
const pageContent = `
|
|
4591
5574
|
<div class="space-y-6">
|
|
@@ -4627,8 +5610,8 @@ function renderContentFormPage(data) {
|
|
|
4627
5610
|
<!-- Form Content -->
|
|
4628
5611
|
<div class="px-6 py-6">
|
|
4629
5612
|
<div id="form-messages">
|
|
4630
|
-
${data.error ?
|
|
4631
|
-
${data.success ?
|
|
5613
|
+
${data.error ? chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
|
|
5614
|
+
${data.success ? chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
|
|
4632
5615
|
</div>
|
|
4633
5616
|
|
|
4634
5617
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
@@ -4863,7 +5846,7 @@ function renderContentFormPage(data) {
|
|
|
4863
5846
|
</div>
|
|
4864
5847
|
|
|
4865
5848
|
<!-- Confirmation Dialogs -->
|
|
4866
|
-
${
|
|
5849
|
+
${chunkBZC4FYW7_cjs.renderConfirmationDialog({
|
|
4867
5850
|
id: "duplicate-content-confirm",
|
|
4868
5851
|
title: "Duplicate Content",
|
|
4869
5852
|
message: "Create a copy of this content?",
|
|
@@ -4874,7 +5857,7 @@ function renderContentFormPage(data) {
|
|
|
4874
5857
|
onConfirm: "performDuplicateContent()"
|
|
4875
5858
|
})}
|
|
4876
5859
|
|
|
4877
|
-
${
|
|
5860
|
+
${chunkBZC4FYW7_cjs.renderConfirmationDialog({
|
|
4878
5861
|
id: "delete-content-confirm",
|
|
4879
5862
|
title: "Delete Content",
|
|
4880
5863
|
message: "Are you sure you want to delete this content? This action cannot be undone.",
|
|
@@ -4885,7 +5868,7 @@ function renderContentFormPage(data) {
|
|
|
4885
5868
|
onConfirm: `performDeleteContent('${data.id}')`
|
|
4886
5869
|
})}
|
|
4887
5870
|
|
|
4888
|
-
${
|
|
5871
|
+
${chunkBZC4FYW7_cjs.getConfirmationDialogScript()}
|
|
4889
5872
|
|
|
4890
5873
|
${data.tinymceEnabled ? getTinyMCEScript(data.tinymceSettings?.apiKey) : "<!-- TinyMCE plugin not active -->"}
|
|
4891
5874
|
|
|
@@ -4971,71 +5954,371 @@ function renderContentFormPage(data) {
|
|
|
4971
5954
|
}
|
|
4972
5955
|
}
|
|
4973
5956
|
|
|
4974
|
-
// Close modal
|
|
4975
|
-
closeMediaSelector();
|
|
4976
|
-
}
|
|
5957
|
+
// Close modal
|
|
5958
|
+
closeMediaSelector();
|
|
5959
|
+
}
|
|
5960
|
+
|
|
5961
|
+
function clearMediaField(fieldId) {
|
|
5962
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
5963
|
+
const preview = document.getElementById(fieldId + '-preview');
|
|
5964
|
+
|
|
5965
|
+
if (hiddenInput) {
|
|
5966
|
+
hiddenInput.value = '';
|
|
5967
|
+
}
|
|
5968
|
+
|
|
5969
|
+
if (preview) {
|
|
5970
|
+
// Clear all children if it's a grid, or hide it
|
|
5971
|
+
if (preview.classList.contains('media-preview-grid')) {
|
|
5972
|
+
preview.innerHTML = '';
|
|
5973
|
+
}
|
|
5974
|
+
preview.classList.add('hidden');
|
|
5975
|
+
}
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5978
|
+
// Global function to remove a single media from multiple selection
|
|
5979
|
+
window.removeMediaFromMultiple = function(fieldId, urlToRemove) {
|
|
5980
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
5981
|
+
if (!hiddenInput) return;
|
|
5982
|
+
|
|
5983
|
+
const values = hiddenInput.value.split(',').filter(url => url !== urlToRemove);
|
|
5984
|
+
hiddenInput.value = values.join(',');
|
|
5985
|
+
|
|
5986
|
+
// Remove preview item
|
|
5987
|
+
const previewItem = document.querySelector(\`[data-url="\${urlToRemove}"]\`);
|
|
5988
|
+
if (previewItem) {
|
|
5989
|
+
previewItem.remove();
|
|
5990
|
+
}
|
|
5991
|
+
|
|
5992
|
+
// Hide preview grid if empty
|
|
5993
|
+
if (values.length === 0) {
|
|
5994
|
+
const preview = document.getElementById(fieldId + '-preview');
|
|
5995
|
+
if (preview) {
|
|
5996
|
+
preview.classList.add('hidden');
|
|
5997
|
+
}
|
|
5998
|
+
}
|
|
5999
|
+
};
|
|
6000
|
+
|
|
6001
|
+
// Global function called by media selector buttons
|
|
6002
|
+
window.selectMediaFile = function(mediaId, mediaUrl, filename) {
|
|
6003
|
+
if (!currentMediaFieldId) {
|
|
6004
|
+
console.error('No field ID set for media selection');
|
|
6005
|
+
return;
|
|
6006
|
+
}
|
|
6007
|
+
|
|
6008
|
+
const fieldId = currentMediaFieldId;
|
|
6009
|
+
|
|
6010
|
+
// Set the hidden input value to the media URL (not ID)
|
|
6011
|
+
const hiddenInput = document.getElementById(fieldId);
|
|
6012
|
+
if (hiddenInput) {
|
|
6013
|
+
hiddenInput.value = mediaUrl;
|
|
6014
|
+
}
|
|
6015
|
+
|
|
6016
|
+
// Update the preview
|
|
6017
|
+
const preview = document.getElementById(fieldId + '-preview');
|
|
6018
|
+
if (preview) {
|
|
6019
|
+
preview.innerHTML = \`<img src="\${mediaUrl}" alt="\${filename}" class="w-32 h-32 object-cover rounded-lg border border-white/20">\`;
|
|
6020
|
+
preview.classList.remove('hidden');
|
|
6021
|
+
}
|
|
6022
|
+
|
|
6023
|
+
// Show the remove button by finding the media actions container and updating it
|
|
6024
|
+
const mediaField = hiddenInput?.closest('.media-field-container');
|
|
6025
|
+
if (mediaField) {
|
|
6026
|
+
const actionsDiv = mediaField.querySelector('.media-actions');
|
|
6027
|
+
if (actionsDiv && !actionsDiv.querySelector('button:has-text("Remove")')) {
|
|
6028
|
+
const removeBtn = document.createElement('button');
|
|
6029
|
+
removeBtn.type = 'button';
|
|
6030
|
+
removeBtn.onclick = () => clearMediaField(fieldId);
|
|
6031
|
+
removeBtn.className = 'inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all';
|
|
6032
|
+
removeBtn.textContent = 'Remove';
|
|
6033
|
+
actionsDiv.appendChild(removeBtn);
|
|
6034
|
+
}
|
|
6035
|
+
}
|
|
6036
|
+
|
|
6037
|
+
// DON'T close the modal - let user click OK button
|
|
6038
|
+
// Visual feedback: highlight the selected item
|
|
6039
|
+
document.querySelectorAll('#media-selector-grid [data-media-id]').forEach(el => {
|
|
6040
|
+
el.classList.remove('ring-2', 'ring-lime-500', 'dark:ring-lime-400');
|
|
6041
|
+
});
|
|
6042
|
+
const selectedItem = document.querySelector(\`#media-selector-grid [data-media-id="\${mediaId}"]\`);
|
|
6043
|
+
if (selectedItem) {
|
|
6044
|
+
selectedItem.classList.add('ring-2', 'ring-lime-500', 'dark:ring-lime-400');
|
|
6045
|
+
}
|
|
6046
|
+
};
|
|
6047
|
+
|
|
6048
|
+
function setMediaField(fieldId, mediaUrl) {
|
|
6049
|
+
document.getElementById(fieldId).value = mediaUrl;
|
|
6050
|
+
const preview = document.getElementById(fieldId + '-preview');
|
|
6051
|
+
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">\`;
|
|
6052
|
+
preview.classList.remove('hidden');
|
|
6053
|
+
|
|
6054
|
+
// Close modal
|
|
6055
|
+
document.querySelector('.fixed.inset-0')?.remove();
|
|
6056
|
+
}
|
|
6057
|
+
|
|
6058
|
+
// Reference field functions
|
|
6059
|
+
let currentReferenceFieldId = null;
|
|
6060
|
+
let referenceSearchTimeout = null;
|
|
6061
|
+
|
|
6062
|
+
function getReferenceContainer(fieldId) {
|
|
6063
|
+
const input = document.getElementById(fieldId);
|
|
6064
|
+
return input ? input.closest('[data-reference-field]') : null;
|
|
6065
|
+
}
|
|
6066
|
+
|
|
6067
|
+
function getReferenceCollections(container) {
|
|
6068
|
+
if (!container) return [];
|
|
6069
|
+
const rawCollections = container.dataset.referenceCollections || '';
|
|
6070
|
+
const collections = rawCollections
|
|
6071
|
+
.split(',')
|
|
6072
|
+
.map((value) => value.trim())
|
|
6073
|
+
.filter(Boolean);
|
|
6074
|
+
if (collections.length > 0) {
|
|
6075
|
+
return collections;
|
|
6076
|
+
}
|
|
6077
|
+
const singleCollection = container.dataset.referenceCollection;
|
|
6078
|
+
return singleCollection ? [singleCollection] : [];
|
|
6079
|
+
}
|
|
6080
|
+
|
|
6081
|
+
async function fetchReferenceItems(collections, search = '', limit = 20) {
|
|
6082
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
6083
|
+
collections.forEach((collection) => params.append('collection', collection));
|
|
6084
|
+
if (search) {
|
|
6085
|
+
params.set('search', search);
|
|
6086
|
+
}
|
|
6087
|
+
const response = await fetch('/admin/api/references?' + params.toString());
|
|
6088
|
+
if (!response.ok) {
|
|
6089
|
+
throw new Error('Failed to load references');
|
|
6090
|
+
}
|
|
6091
|
+
const data = await response.json();
|
|
6092
|
+
return data?.data || [];
|
|
6093
|
+
}
|
|
6094
|
+
|
|
6095
|
+
async function fetchReferenceById(collections, id) {
|
|
6096
|
+
if (!id) return null;
|
|
6097
|
+
const params = new URLSearchParams({ id });
|
|
6098
|
+
collections.forEach((collection) => params.append('collection', collection));
|
|
6099
|
+
const response = await fetch('/admin/api/references?' + params.toString());
|
|
6100
|
+
if (!response.ok) {
|
|
6101
|
+
return null;
|
|
6102
|
+
}
|
|
6103
|
+
const data = await response.json();
|
|
6104
|
+
return data?.data || null;
|
|
6105
|
+
}
|
|
6106
|
+
|
|
6107
|
+
function renderReferenceDisplay(container, item, fallbackMessage = 'No reference selected.') {
|
|
6108
|
+
const display = container.querySelector('[data-reference-display]');
|
|
6109
|
+
const removeButton = container.querySelector('[data-reference-clear]');
|
|
6110
|
+
if (!display) return;
|
|
6111
|
+
|
|
6112
|
+
display.innerHTML = '';
|
|
6113
|
+
|
|
6114
|
+
if (!item) {
|
|
6115
|
+
display.textContent = fallbackMessage;
|
|
6116
|
+
if (removeButton) {
|
|
6117
|
+
removeButton.disabled = true;
|
|
6118
|
+
}
|
|
6119
|
+
return;
|
|
6120
|
+
}
|
|
6121
|
+
|
|
6122
|
+
const title = item.title || item.slug || item.id || 'Untitled';
|
|
6123
|
+
const titleEl = document.createElement('div');
|
|
6124
|
+
titleEl.className = 'font-medium text-zinc-900 dark:text-white';
|
|
6125
|
+
titleEl.textContent = title;
|
|
6126
|
+
|
|
6127
|
+
display.appendChild(titleEl);
|
|
6128
|
+
|
|
6129
|
+
const metaRow = document.createElement('div');
|
|
6130
|
+
metaRow.className = 'mt-1 flex flex-wrap items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400';
|
|
6131
|
+
|
|
6132
|
+
if (item.collection?.display_name || item.collection?.name) {
|
|
6133
|
+
const collectionLabel = document.createElement('span');
|
|
6134
|
+
collectionLabel.className = 'inline-flex items-center rounded-full bg-zinc-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-zinc-600 dark:bg-white/10 dark:text-zinc-200';
|
|
6135
|
+
collectionLabel.textContent = item.collection.display_name || item.collection.name;
|
|
6136
|
+
metaRow.appendChild(collectionLabel);
|
|
6137
|
+
}
|
|
6138
|
+
|
|
6139
|
+
if (item.slug) {
|
|
6140
|
+
const slugEl = document.createElement('span');
|
|
6141
|
+
slugEl.textContent = item.slug;
|
|
6142
|
+
metaRow.appendChild(slugEl);
|
|
6143
|
+
}
|
|
6144
|
+
|
|
6145
|
+
if (metaRow.childElementCount > 0) {
|
|
6146
|
+
display.appendChild(metaRow);
|
|
6147
|
+
}
|
|
6148
|
+
|
|
6149
|
+
if (removeButton) {
|
|
6150
|
+
removeButton.disabled = false;
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
|
|
6154
|
+
function updateReferenceField(fieldId, item) {
|
|
6155
|
+
const input = document.getElementById(fieldId);
|
|
6156
|
+
const container = getReferenceContainer(fieldId);
|
|
6157
|
+
if (!input || !container) return;
|
|
6158
|
+
|
|
6159
|
+
input.value = item?.id || '';
|
|
6160
|
+
renderReferenceDisplay(container, item, 'No reference selected.');
|
|
6161
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
6162
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
6163
|
+
}
|
|
6164
|
+
|
|
6165
|
+
function clearReferenceField(fieldId) {
|
|
6166
|
+
updateReferenceField(fieldId, null);
|
|
6167
|
+
}
|
|
6168
|
+
|
|
6169
|
+
function closeReferenceSelector() {
|
|
6170
|
+
const modal = document.getElementById('reference-selector-modal');
|
|
6171
|
+
if (modal) {
|
|
6172
|
+
modal.remove();
|
|
6173
|
+
}
|
|
6174
|
+
currentReferenceFieldId = null;
|
|
6175
|
+
}
|
|
6176
|
+
|
|
6177
|
+
function openReferenceSelector(fieldId) {
|
|
6178
|
+
const container = getReferenceContainer(fieldId);
|
|
6179
|
+
const collections = getReferenceCollections(container);
|
|
6180
|
+
if (!container || collections.length === 0) {
|
|
6181
|
+
console.error('Reference collection is missing for field', fieldId);
|
|
6182
|
+
return;
|
|
6183
|
+
}
|
|
6184
|
+
|
|
6185
|
+
currentReferenceFieldId = fieldId;
|
|
6186
|
+
|
|
6187
|
+
const modal = document.createElement('div');
|
|
6188
|
+
modal.className = 'fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50';
|
|
6189
|
+
modal.id = 'reference-selector-modal';
|
|
6190
|
+
modal.innerHTML = \`
|
|
6191
|
+
<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-3xl max-h-[90vh] overflow-y-auto">
|
|
6192
|
+
<div class="flex items-center justify-between gap-3">
|
|
6193
|
+
<h3 class="text-lg font-semibold text-zinc-950 dark:text-white">Select Reference</h3>
|
|
6194
|
+
<button
|
|
6195
|
+
type="button"
|
|
6196
|
+
onclick="closeReferenceSelector()"
|
|
6197
|
+
class="rounded-md text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"
|
|
6198
|
+
aria-label="Close"
|
|
6199
|
+
>
|
|
6200
|
+
\u2715
|
|
6201
|
+
</button>
|
|
6202
|
+
</div>
|
|
6203
|
+
<div class="mt-4">
|
|
6204
|
+
<input
|
|
6205
|
+
type="search"
|
|
6206
|
+
id="reference-search-input"
|
|
6207
|
+
placeholder="Search by title or slug..."
|
|
6208
|
+
class="w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 dark:border-white/10 dark:bg-zinc-900 dark:text-white"
|
|
6209
|
+
>
|
|
6210
|
+
</div>
|
|
6211
|
+
<div id="reference-results" class="mt-4 space-y-2"></div>
|
|
6212
|
+
<div class="mt-4 flex justify-end">
|
|
6213
|
+
<button
|
|
6214
|
+
type="button"
|
|
6215
|
+
onclick="closeReferenceSelector()"
|
|
6216
|
+
class="rounded-lg bg-zinc-950 dark:bg-white px-4 py-2 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors"
|
|
6217
|
+
>
|
|
6218
|
+
Close
|
|
6219
|
+
</button>
|
|
6220
|
+
</div>
|
|
6221
|
+
</div>
|
|
6222
|
+
\`;
|
|
6223
|
+
|
|
6224
|
+
document.body.appendChild(modal);
|
|
6225
|
+
|
|
6226
|
+
const resultsContainer = modal.querySelector('#reference-results');
|
|
6227
|
+
const searchInput = modal.querySelector('#reference-search-input');
|
|
6228
|
+
|
|
6229
|
+
const renderResults = (items) => {
|
|
6230
|
+
resultsContainer.innerHTML = '';
|
|
6231
|
+
if (!items || items.length === 0) {
|
|
6232
|
+
resultsContainer.innerHTML = '<div class="rounded-lg border border-dashed border-zinc-200 p-4 text-sm text-zinc-500 dark:border-white/10 dark:text-zinc-400">No items found.</div>';
|
|
6233
|
+
return;
|
|
6234
|
+
}
|
|
6235
|
+
|
|
6236
|
+
const selectedId = document.getElementById(fieldId)?.value;
|
|
6237
|
+
|
|
6238
|
+
items.forEach((item) => {
|
|
6239
|
+
const button = document.createElement('button');
|
|
6240
|
+
button.type = 'button';
|
|
6241
|
+
button.className = 'w-full text-left rounded-lg border border-zinc-200 px-4 py-3 text-sm text-zinc-700 hover:bg-zinc-50 dark:border-white/10 dark:text-zinc-200 dark:hover:bg-white/5';
|
|
6242
|
+
if (item.id === selectedId) {
|
|
6243
|
+
button.classList.add('ring-2', 'ring-cyan-500', 'dark:ring-cyan-400');
|
|
6244
|
+
}
|
|
6245
|
+
|
|
6246
|
+
const title = item.title || item.slug || item.id || 'Untitled';
|
|
6247
|
+
const titleEl = document.createElement('div');
|
|
6248
|
+
titleEl.className = 'font-medium text-zinc-900 dark:text-white';
|
|
6249
|
+
titleEl.textContent = title;
|
|
4977
6250
|
|
|
4978
|
-
|
|
4979
|
-
document.getElementById(fieldId).value = '';
|
|
4980
|
-
document.getElementById(fieldId + '-preview').classList.add('hidden');
|
|
4981
|
-
}
|
|
6251
|
+
button.appendChild(titleEl);
|
|
4982
6252
|
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
if (!currentMediaFieldId) {
|
|
4986
|
-
console.error('No field ID set for media selection');
|
|
4987
|
-
return;
|
|
4988
|
-
}
|
|
6253
|
+
const metaRow = document.createElement('div');
|
|
6254
|
+
metaRow.className = 'mt-1 flex flex-wrap items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400';
|
|
4989
6255
|
|
|
4990
|
-
|
|
6256
|
+
if (item.collection?.display_name || item.collection?.name) {
|
|
6257
|
+
const collectionLabel = document.createElement('span');
|
|
6258
|
+
collectionLabel.className = 'inline-flex items-center rounded-full bg-zinc-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-zinc-600 dark:bg-white/10 dark:text-zinc-200';
|
|
6259
|
+
collectionLabel.textContent = item.collection.display_name || item.collection.name;
|
|
6260
|
+
metaRow.appendChild(collectionLabel);
|
|
6261
|
+
}
|
|
4991
6262
|
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
6263
|
+
if (item.slug) {
|
|
6264
|
+
const slugEl = document.createElement('span');
|
|
6265
|
+
slugEl.textContent = item.slug;
|
|
6266
|
+
metaRow.appendChild(slugEl);
|
|
6267
|
+
}
|
|
4997
6268
|
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
preview.innerHTML = \`<img src="\${mediaUrl}" alt="\${filename}" class="w-32 h-32 object-cover rounded-lg border border-white/20">\`;
|
|
5002
|
-
preview.classList.remove('hidden');
|
|
5003
|
-
}
|
|
6269
|
+
if (metaRow.childElementCount > 0) {
|
|
6270
|
+
button.appendChild(metaRow);
|
|
6271
|
+
}
|
|
5004
6272
|
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
6273
|
+
button.addEventListener('click', () => {
|
|
6274
|
+
updateReferenceField(fieldId, item);
|
|
6275
|
+
closeReferenceSelector();
|
|
6276
|
+
});
|
|
6277
|
+
|
|
6278
|
+
resultsContainer.appendChild(button);
|
|
6279
|
+
});
|
|
6280
|
+
};
|
|
6281
|
+
|
|
6282
|
+
const loadResults = async (searchValue = '') => {
|
|
6283
|
+
try {
|
|
6284
|
+
const items = await fetchReferenceItems(collections, searchValue);
|
|
6285
|
+
renderResults(items);
|
|
6286
|
+
} catch (error) {
|
|
6287
|
+
resultsContainer.innerHTML = '<div class="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-700 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-200">Failed to load references.</div>';
|
|
5016
6288
|
}
|
|
5017
|
-
}
|
|
6289
|
+
};
|
|
5018
6290
|
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
6291
|
+
loadResults();
|
|
6292
|
+
|
|
6293
|
+
searchInput.addEventListener('input', () => {
|
|
6294
|
+
if (referenceSearchTimeout) {
|
|
6295
|
+
clearTimeout(referenceSearchTimeout);
|
|
6296
|
+
}
|
|
6297
|
+
referenceSearchTimeout = setTimeout(() => {
|
|
6298
|
+
loadResults(searchInput.value.trim());
|
|
6299
|
+
}, 250);
|
|
5023
6300
|
});
|
|
5024
|
-
|
|
5025
|
-
if (selectedItem) {
|
|
5026
|
-
selectedItem.classList.add('ring-2', 'ring-lime-500', 'dark:ring-lime-400');
|
|
5027
|
-
}
|
|
5028
|
-
};
|
|
6301
|
+
}
|
|
5029
6302
|
|
|
5030
|
-
|
|
5031
|
-
document.
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
6303
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
6304
|
+
document.querySelectorAll('[data-reference-field]').forEach(async (container) => {
|
|
6305
|
+
const input = container.querySelector('input[type="hidden"]');
|
|
6306
|
+
const collections = getReferenceCollections(container);
|
|
6307
|
+
if (!input || collections.length === 0) return;
|
|
5035
6308
|
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
6309
|
+
if (!input.value) {
|
|
6310
|
+
renderReferenceDisplay(container, null, 'No reference selected.');
|
|
6311
|
+
return;
|
|
6312
|
+
}
|
|
6313
|
+
|
|
6314
|
+
const item = await fetchReferenceById(collections, input.value);
|
|
6315
|
+
if (item) {
|
|
6316
|
+
renderReferenceDisplay(container, item);
|
|
6317
|
+
} else {
|
|
6318
|
+
renderReferenceDisplay(container, null, 'Reference not found.');
|
|
6319
|
+
}
|
|
6320
|
+
});
|
|
6321
|
+
});
|
|
5039
6322
|
|
|
5040
6323
|
// Custom select options
|
|
5041
6324
|
function addCustomOption(input, selectId) {
|
|
@@ -5190,11 +6473,11 @@ function renderContentFormPage(data) {
|
|
|
5190
6473
|
content: pageContent,
|
|
5191
6474
|
version: data.version
|
|
5192
6475
|
};
|
|
5193
|
-
return
|
|
6476
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
5194
6477
|
}
|
|
5195
6478
|
|
|
5196
6479
|
// src/templates/pages/admin-content-list.template.ts
|
|
5197
|
-
|
|
6480
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
5198
6481
|
function renderContentListPage(data) {
|
|
5199
6482
|
const urlParams = new URLSearchParams();
|
|
5200
6483
|
if (data.modelName && data.modelName !== "all") urlParams.set("model", data.modelName);
|
|
@@ -5202,7 +6485,7 @@ function renderContentListPage(data) {
|
|
|
5202
6485
|
if (data.search) urlParams.set("search", data.search);
|
|
5203
6486
|
if (data.page && data.page !== 1) urlParams.set("page", data.page.toString());
|
|
5204
6487
|
const currentParams = urlParams.toString();
|
|
5205
|
-
|
|
6488
|
+
data.modelName !== "all" || data.status !== "all" || !!data.search;
|
|
5206
6489
|
const filterBarData = {
|
|
5207
6490
|
filters: [
|
|
5208
6491
|
{
|
|
@@ -5232,6 +6515,11 @@ function renderContentListPage(data) {
|
|
|
5232
6515
|
}
|
|
5233
6516
|
],
|
|
5234
6517
|
actions: [
|
|
6518
|
+
{
|
|
6519
|
+
label: "Advanced Search",
|
|
6520
|
+
className: "btn-primary",
|
|
6521
|
+
onclick: "openAdvancedSearch()"
|
|
6522
|
+
},
|
|
5235
6523
|
{
|
|
5236
6524
|
label: "Refresh",
|
|
5237
6525
|
className: "btn-secondary",
|
|
@@ -5383,12 +6671,57 @@ function renderContentListPage(data) {
|
|
|
5383
6671
|
<div class="relative bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 rounded-xl">
|
|
5384
6672
|
<div class="px-6 py-5">
|
|
5385
6673
|
<div class="flex items-center justify-between">
|
|
5386
|
-
<div class="flex items-center space-x-4">
|
|
5387
|
-
<!--
|
|
6674
|
+
<div class="flex items-center space-x-4 flex-1">
|
|
6675
|
+
<!-- Model Filter -->
|
|
6676
|
+
<div>
|
|
6677
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Model</label>
|
|
6678
|
+
<div class="grid grid-cols-1">
|
|
6679
|
+
<select
|
|
6680
|
+
name="model"
|
|
6681
|
+
onchange="updateContentFilters('model', this.value)"
|
|
6682
|
+
class="col-start-1 row-start-1 w-full appearance-none rounded-lg bg-white/5 dark:bg-white/5 py-2 pl-3 pr-8 text-sm text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-cyan-500/30 dark:outline-cyan-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-cyan-500 dark:focus-visible:outline-cyan-400 min-w-40"
|
|
6683
|
+
>
|
|
6684
|
+
<option value="all" ${data.modelName === "all" ? "selected" : ""}>All Models</option>
|
|
6685
|
+
${data.models.map((model) => `
|
|
6686
|
+
<option value="${model.name}" ${data.modelName === model.name ? "selected" : ""}>
|
|
6687
|
+
${model.displayName}
|
|
6688
|
+
</option>
|
|
6689
|
+
`).join("")}
|
|
6690
|
+
</select>
|
|
6691
|
+
<svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-cyan-600 dark:text-cyan-400 sm:size-4">
|
|
6692
|
+
<path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
|
|
6693
|
+
</svg>
|
|
6694
|
+
</div>
|
|
6695
|
+
</div>
|
|
6696
|
+
|
|
6697
|
+
<!-- Status Filter -->
|
|
5388
6698
|
<div>
|
|
6699
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Status</label>
|
|
6700
|
+
<div class="grid grid-cols-1">
|
|
6701
|
+
<select
|
|
6702
|
+
name="status"
|
|
6703
|
+
onchange="updateContentFilters('status', this.value)"
|
|
6704
|
+
class="col-start-1 row-start-1 w-full appearance-none rounded-lg bg-white/5 dark:bg-white/5 py-2 pl-3 pr-8 text-sm text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-cyan-500/30 dark:outline-cyan-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-cyan-500 dark:focus-visible:outline-cyan-400 min-w-40"
|
|
6705
|
+
>
|
|
6706
|
+
<option value="all" ${data.status === "all" ? "selected" : ""}>All Status</option>
|
|
6707
|
+
<option value="draft" ${data.status === "draft" ? "selected" : ""}>Draft</option>
|
|
6708
|
+
<option value="review" ${data.status === "review" ? "selected" : ""}>Under Review</option>
|
|
6709
|
+
<option value="scheduled" ${data.status === "scheduled" ? "selected" : ""}>Scheduled</option>
|
|
6710
|
+
<option value="published" ${data.status === "published" ? "selected" : ""}>Published</option>
|
|
6711
|
+
<option value="archived" ${data.status === "archived" ? "selected" : ""}>Archived</option>
|
|
6712
|
+
<option value="deleted" ${data.status === "deleted" ? "selected" : ""}>Deleted</option>
|
|
6713
|
+
</select>
|
|
6714
|
+
<svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-cyan-600 dark:text-cyan-400 sm:size-4">
|
|
6715
|
+
<path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
|
|
6716
|
+
</svg>
|
|
6717
|
+
</div>
|
|
6718
|
+
</div>
|
|
6719
|
+
|
|
6720
|
+
<!-- Search Input -->
|
|
6721
|
+
<div class="flex-1 max-w-md">
|
|
5389
6722
|
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search</label>
|
|
5390
6723
|
<form onsubmit="performContentSearch(event)" class="flex items-center space-x-2">
|
|
5391
|
-
<div class="relative group">
|
|
6724
|
+
<div class="relative group flex-1">
|
|
5392
6725
|
<input
|
|
5393
6726
|
type="text"
|
|
5394
6727
|
name="search"
|
|
@@ -5396,7 +6729,7 @@ function renderContentListPage(data) {
|
|
|
5396
6729
|
value="${data.search || ""}"
|
|
5397
6730
|
oninput="toggleContentClearButton()"
|
|
5398
6731
|
placeholder="Search content..."
|
|
5399
|
-
class="rounded-full bg-white/90 dark:bg-zinc-800/90 backdrop-blur-sm px-4 py-2.5 pl-11 pr-10 text-sm
|
|
6732
|
+
class="w-full rounded-full bg-white/90 dark:bg-zinc-800/90 backdrop-blur-sm px-4 py-2.5 pl-11 pr-10 text-sm text-zinc-950 dark:text-white border-2 border-cyan-200/50 dark:border-cyan-700/50 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:border-cyan-500 dark:focus:border-cyan-400 focus:bg-white dark:focus:bg-zinc-800 focus:shadow-lg focus:shadow-cyan-500/20 dark:focus:shadow-cyan-400/20 transition-all duration-300"
|
|
5400
6733
|
>
|
|
5401
6734
|
<div class="absolute left-3.5 top-2.5 flex items-center justify-center w-5 h-5 rounded-full bg-gradient-to-br from-cyan-400 to-blue-500 dark:from-cyan-300 dark:to-blue-400 opacity-90 group-focus-within:opacity-100 transition-opacity">
|
|
5402
6735
|
<svg class="h-3 w-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
|
@@ -5468,57 +6801,6 @@ function renderContentListPage(data) {
|
|
|
5468
6801
|
}
|
|
5469
6802
|
</script>
|
|
5470
6803
|
</div>
|
|
5471
|
-
|
|
5472
|
-
${filterBarData.filters.map((filter) => {
|
|
5473
|
-
const selectedOption = filter.options.find((opt) => opt.selected);
|
|
5474
|
-
const selectedColor = selectedOption?.color || "cyan";
|
|
5475
|
-
const colorMap = {
|
|
5476
|
-
"cyan": "bg-cyan-400 dark:bg-cyan-400",
|
|
5477
|
-
"lime": "bg-lime-400 dark:bg-lime-400",
|
|
5478
|
-
"pink": "bg-pink-400 dark:bg-pink-400",
|
|
5479
|
-
"purple": "bg-purple-400 dark:bg-purple-400",
|
|
5480
|
-
"amber": "bg-amber-400 dark:bg-amber-400",
|
|
5481
|
-
"zinc": "bg-zinc-400 dark:bg-zinc-400"
|
|
5482
|
-
};
|
|
5483
|
-
return `
|
|
5484
|
-
<div>
|
|
5485
|
-
<label class="block text-sm/6 font-medium text-zinc-950 dark:text-white">${filter.label}</label>
|
|
5486
|
-
<div class="mt-2 grid grid-cols-1">
|
|
5487
|
-
<div class="col-start-1 row-start-1 flex items-center gap-3 pl-3 pr-8 pointer-events-none">
|
|
5488
|
-
${filter.name === "status" ? `<span class="inline-block size-2 shrink-0 rounded-full border border-transparent ${colorMap[selectedColor]}"></span>` : ""}
|
|
5489
|
-
</div>
|
|
5490
|
-
<select
|
|
5491
|
-
name="${filter.name}"
|
|
5492
|
-
onchange="updateContentFilters('${filter.name}', this.value)"
|
|
5493
|
-
class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white/5 dark:bg-white/5 py-1.5 ${filter.name === "status" ? "pl-8" : "pl-3"} pr-8 text-base text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-cyan-500/30 dark:outline-cyan-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-cyan-500 dark:focus-visible:outline-cyan-400 sm:text-sm/6 min-w-48"
|
|
5494
|
-
>
|
|
5495
|
-
${filter.options.map((opt) => `
|
|
5496
|
-
<option value="${opt.value}" ${opt.selected ? "selected" : ""}>${opt.label}</option>
|
|
5497
|
-
`).join("")}
|
|
5498
|
-
</select>
|
|
5499
|
-
<svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-cyan-600 dark:text-cyan-400 sm:size-4">
|
|
5500
|
-
<path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
|
|
5501
|
-
</svg>
|
|
5502
|
-
</div>
|
|
5503
|
-
</div>
|
|
5504
|
-
`;
|
|
5505
|
-
}).join("")}
|
|
5506
|
-
|
|
5507
|
-
<!-- Clear Filters Button -->
|
|
5508
|
-
${hasActiveFilters ? `
|
|
5509
|
-
<div>
|
|
5510
|
-
<label class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2"> </label>
|
|
5511
|
-
<button
|
|
5512
|
-
onclick="clearAllFilters()"
|
|
5513
|
-
class="inline-flex items-center gap-x-1.5 px-3 py-2 bg-pink-50 dark:bg-pink-500/10 text-pink-700 dark:text-pink-300 text-sm font-medium rounded-md ring-1 ring-inset ring-pink-600/20 dark:ring-pink-500/20 hover:bg-pink-100 dark:hover:bg-pink-500/20 transition-colors"
|
|
5514
|
-
>
|
|
5515
|
-
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
5516
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
|
5517
|
-
</svg>
|
|
5518
|
-
Clear Filters
|
|
5519
|
-
</button>
|
|
5520
|
-
</div>
|
|
5521
|
-
` : ""}
|
|
5522
6804
|
</div>
|
|
5523
6805
|
<div class="flex items-center gap-x-3">
|
|
5524
6806
|
<span class="text-sm/6 font-medium text-zinc-700 dark:text-zinc-300 px-3 py-1.5 rounded-full bg-white/60 dark:bg-zinc-800/60 backdrop-blur-sm">${data.totalItems} ${data.totalItems === 1 ? "item" : "items"}</span>
|
|
@@ -5599,8 +6881,8 @@ function renderContentListPage(data) {
|
|
|
5599
6881
|
|
|
5600
6882
|
<!-- Content List -->
|
|
5601
6883
|
<div id="content-list">
|
|
5602
|
-
${
|
|
5603
|
-
${
|
|
6884
|
+
${chunkBZC4FYW7_cjs.renderTable(tableData)}
|
|
6885
|
+
${chunkBZC4FYW7_cjs.renderPagination(paginationData)}
|
|
5604
6886
|
</div>
|
|
5605
6887
|
|
|
5606
6888
|
</div>
|
|
@@ -5758,70 +7040,356 @@ function renderContentListPage(data) {
|
|
|
5758
7040
|
function executeBulkAction() {
|
|
5759
7041
|
if (!currentBulkAction || currentSelectedIds.length === 0) return;
|
|
5760
7042
|
|
|
5761
|
-
// Close dropdown
|
|
5762
|
-
const menu = document.getElementById('bulk-actions-menu');
|
|
5763
|
-
menu.classList.add('hidden');
|
|
7043
|
+
// Close dropdown
|
|
7044
|
+
const menu = document.getElementById('bulk-actions-menu');
|
|
7045
|
+
menu.classList.add('hidden');
|
|
7046
|
+
|
|
7047
|
+
fetch('/admin/content/bulk-action', {
|
|
7048
|
+
method: 'POST',
|
|
7049
|
+
headers: {
|
|
7050
|
+
'Content-Type': 'application/json'
|
|
7051
|
+
},
|
|
7052
|
+
body: JSON.stringify({
|
|
7053
|
+
action: currentBulkAction,
|
|
7054
|
+
ids: currentSelectedIds
|
|
7055
|
+
})
|
|
7056
|
+
})
|
|
7057
|
+
.then(res => res.json())
|
|
7058
|
+
.then(data => {
|
|
7059
|
+
if (data.success) {
|
|
7060
|
+
location.reload();
|
|
7061
|
+
} else {
|
|
7062
|
+
alert('Error: ' + (data.error || 'Unknown error'));
|
|
7063
|
+
}
|
|
7064
|
+
})
|
|
7065
|
+
.catch(err => {
|
|
7066
|
+
console.error('Bulk action error:', err);
|
|
7067
|
+
alert('Failed to perform bulk action');
|
|
7068
|
+
})
|
|
7069
|
+
.finally(() => {
|
|
7070
|
+
// Clear context
|
|
7071
|
+
currentBulkAction = null;
|
|
7072
|
+
currentSelectedIds = [];
|
|
7073
|
+
});
|
|
7074
|
+
}
|
|
7075
|
+
|
|
7076
|
+
// Helper to get action text for display
|
|
7077
|
+
function getActionText(action) {
|
|
7078
|
+
const actionCount = currentSelectedIds.length;
|
|
7079
|
+
switch(action) {
|
|
7080
|
+
case 'publish':
|
|
7081
|
+
return \`publish \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
|
|
7082
|
+
case 'draft':
|
|
7083
|
+
return \`move \${actionCount} item\${actionCount > 1 ? 's' : ''} to draft\`;
|
|
7084
|
+
case 'delete':
|
|
7085
|
+
return \`delete \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
|
|
7086
|
+
default:
|
|
7087
|
+
return \`perform action on \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
|
|
7091
|
+
</script>
|
|
7092
|
+
|
|
7093
|
+
<!-- Confirmation Dialog for Bulk Actions -->
|
|
7094
|
+
${chunkBZC4FYW7_cjs.renderConfirmationDialog({
|
|
7095
|
+
id: "bulk-action-confirm",
|
|
7096
|
+
title: "Confirm Bulk Action",
|
|
7097
|
+
message: "Are you sure you want to perform this action? This operation will affect multiple items.",
|
|
7098
|
+
confirmText: "Confirm",
|
|
7099
|
+
cancelText: "Cancel",
|
|
7100
|
+
confirmClass: "bg-blue-500 hover:bg-blue-400",
|
|
7101
|
+
iconColor: "blue",
|
|
7102
|
+
onConfirm: "executeBulkAction()"
|
|
7103
|
+
})}
|
|
7104
|
+
|
|
7105
|
+
<!-- Confirmation Dialog Script -->
|
|
7106
|
+
${chunkBZC4FYW7_cjs.getConfirmationDialogScript()}
|
|
7107
|
+
|
|
7108
|
+
<!-- Advanced Search Modal -->
|
|
7109
|
+
<div id="advancedSearchModal" class="hidden fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
|
7110
|
+
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
7111
|
+
<!-- Background overlay -->
|
|
7112
|
+
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" onclick="closeAdvancedSearch()"></div>
|
|
7113
|
+
|
|
7114
|
+
<!-- Modal panel -->
|
|
7115
|
+
<div class="inline-block align-bottom bg-white dark:bg-zinc-900 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
|
|
7116
|
+
<div class="bg-white dark:bg-zinc-900 px-4 pt-5 pb-4 sm:p-6">
|
|
7117
|
+
<!-- Header -->
|
|
7118
|
+
<div class="flex items-center justify-between mb-4">
|
|
7119
|
+
<h3 class="text-lg font-semibold text-zinc-950 dark:text-white" id="modal-title">
|
|
7120
|
+
\u{1F50D} Advanced Search
|
|
7121
|
+
</h3>
|
|
7122
|
+
<button onclick="closeAdvancedSearch()" class="text-zinc-400 hover:text-zinc-500 dark:hover:text-zinc-300">
|
|
7123
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
7124
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
7125
|
+
</svg>
|
|
7126
|
+
</button>
|
|
7127
|
+
</div>
|
|
7128
|
+
|
|
7129
|
+
<!-- Search Form -->
|
|
7130
|
+
<form id="advancedSearchForm" class="space-y-4">
|
|
7131
|
+
<!-- Search Input -->
|
|
7132
|
+
<div>
|
|
7133
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search Query</label>
|
|
7134
|
+
<div class="relative">
|
|
7135
|
+
<input
|
|
7136
|
+
type="text"
|
|
7137
|
+
id="searchQuery"
|
|
7138
|
+
name="query"
|
|
7139
|
+
placeholder="Enter your search query..."
|
|
7140
|
+
class="w-full rounded-lg bg-white dark:bg-white/5 px-4 py-3 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 focus:ring-2 focus:ring-indigo-500"
|
|
7141
|
+
autocomplete="off"
|
|
7142
|
+
/>
|
|
7143
|
+
<div id="searchSuggestions" class="hidden absolute z-10 w-full mt-1 bg-white dark:bg-zinc-800 rounded-lg shadow-lg border border-zinc-200 dark:border-zinc-700 max-h-60 overflow-y-auto"></div>
|
|
7144
|
+
</div>
|
|
7145
|
+
</div>
|
|
7146
|
+
|
|
7147
|
+
<!-- Mode Toggle -->
|
|
7148
|
+
<div>
|
|
7149
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search Mode</label>
|
|
7150
|
+
<div class="flex gap-4">
|
|
7151
|
+
<label class="flex items-center">
|
|
7152
|
+
<input type="radio" name="mode" value="ai" checked class="mr-2">
|
|
7153
|
+
<span class="text-sm text-zinc-950 dark:text-white">\u{1F916} AI Search (Semantic)</span>
|
|
7154
|
+
</label>
|
|
7155
|
+
<label class="flex items-center">
|
|
7156
|
+
<input type="radio" name="mode" value="keyword" class="mr-2">
|
|
7157
|
+
<span class="text-sm text-zinc-950 dark:text-white">\u{1F524} Keyword Search</span>
|
|
7158
|
+
</label>
|
|
7159
|
+
</div>
|
|
7160
|
+
</div>
|
|
7161
|
+
|
|
7162
|
+
<!-- Filters -->
|
|
7163
|
+
<div class="border-t border-zinc-200 dark:border-zinc-800 pt-4">
|
|
7164
|
+
<h4 class="text-sm font-semibold text-zinc-950 dark:text-white mb-3">Filters</h4>
|
|
7165
|
+
|
|
7166
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
7167
|
+
<!-- Collection Filter -->
|
|
7168
|
+
<div>
|
|
7169
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Collections</label>
|
|
7170
|
+
<select
|
|
7171
|
+
id="filterCollections"
|
|
7172
|
+
name="collections"
|
|
7173
|
+
multiple
|
|
7174
|
+
class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10"
|
|
7175
|
+
size="4"
|
|
7176
|
+
>
|
|
7177
|
+
<option value="">All Collections</option>
|
|
7178
|
+
${data.models.map(
|
|
7179
|
+
(model) => `
|
|
7180
|
+
<option value="${model.name}">${model.displayName}</option>
|
|
7181
|
+
`
|
|
7182
|
+
).join("")}
|
|
7183
|
+
</select>
|
|
7184
|
+
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">Hold Ctrl/Cmd to select multiple</p>
|
|
7185
|
+
</div>
|
|
7186
|
+
|
|
7187
|
+
<!-- Status Filter -->
|
|
7188
|
+
<div>
|
|
7189
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Status</label>
|
|
7190
|
+
<select
|
|
7191
|
+
id="filterStatus"
|
|
7192
|
+
name="status"
|
|
7193
|
+
multiple
|
|
7194
|
+
class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10"
|
|
7195
|
+
size="4"
|
|
7196
|
+
>
|
|
7197
|
+
<option value="published">Published</option>
|
|
7198
|
+
<option value="draft">Draft</option>
|
|
7199
|
+
<option value="review">Under Review</option>
|
|
7200
|
+
<option value="scheduled">Scheduled</option>
|
|
7201
|
+
<option value="archived">Archived</option>
|
|
7202
|
+
</select>
|
|
7203
|
+
</div>
|
|
7204
|
+
</div>
|
|
7205
|
+
</div>
|
|
7206
|
+
|
|
7207
|
+
<!-- Actions -->
|
|
7208
|
+
<div class="flex items-center justify-end gap-3 pt-4 border-t border-zinc-200 dark:border-zinc-800">
|
|
7209
|
+
<button
|
|
7210
|
+
type="button"
|
|
7211
|
+
onclick="closeAdvancedSearch()"
|
|
7212
|
+
class="inline-flex items-center justify-center 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"
|
|
7213
|
+
>
|
|
7214
|
+
Cancel
|
|
7215
|
+
</button>
|
|
7216
|
+
<button
|
|
7217
|
+
type="submit"
|
|
7218
|
+
class="inline-flex items-center justify-center rounded-lg bg-indigo-600 text-white px-6 py-2.5 text-sm font-semibold hover:bg-indigo-500 shadow-sm"
|
|
7219
|
+
>
|
|
7220
|
+
Search
|
|
7221
|
+
</button>
|
|
7222
|
+
</div>
|
|
7223
|
+
</form>
|
|
7224
|
+
</div>
|
|
7225
|
+
|
|
7226
|
+
<!-- Results Area -->
|
|
7227
|
+
<div id="searchResults" class="hidden px-4 pb-4 sm:px-6">
|
|
7228
|
+
<div class="border-t border-zinc-200 dark:border-zinc-800 pt-4">
|
|
7229
|
+
<div id="searchResultsContent" class="space-y-3"></div>
|
|
7230
|
+
<div id="searchResultsPagination" class="mt-4 flex items-center justify-between"></div>
|
|
7231
|
+
</div>
|
|
7232
|
+
</div>
|
|
7233
|
+
</div>
|
|
7234
|
+
</div>
|
|
7235
|
+
</div>
|
|
7236
|
+
|
|
7237
|
+
<script>
|
|
7238
|
+
// Open modal
|
|
7239
|
+
function openAdvancedSearch() {
|
|
7240
|
+
document.getElementById('advancedSearchModal').classList.remove('hidden');
|
|
7241
|
+
document.getElementById('searchQuery').focus();
|
|
7242
|
+
}
|
|
7243
|
+
|
|
7244
|
+
// Close modal
|
|
7245
|
+
function closeAdvancedSearch() {
|
|
7246
|
+
document.getElementById('advancedSearchModal').classList.add('hidden');
|
|
7247
|
+
document.getElementById('searchResults').classList.add('hidden');
|
|
7248
|
+
}
|
|
7249
|
+
|
|
7250
|
+
// Autocomplete
|
|
7251
|
+
let autocompleteTimeout;
|
|
7252
|
+
const searchQueryInput = document.getElementById('searchQuery');
|
|
7253
|
+
if (searchQueryInput) {
|
|
7254
|
+
searchQueryInput.addEventListener('input', (e) => {
|
|
7255
|
+
const query = e.target.value.trim();
|
|
7256
|
+
const suggestionsDiv = document.getElementById('searchSuggestions');
|
|
7257
|
+
|
|
7258
|
+
clearTimeout(autocompleteTimeout);
|
|
7259
|
+
|
|
7260
|
+
if (query.length < 2) {
|
|
7261
|
+
suggestionsDiv.classList.add('hidden');
|
|
7262
|
+
return;
|
|
7263
|
+
}
|
|
7264
|
+
|
|
7265
|
+
autocompleteTimeout = setTimeout(async () => {
|
|
7266
|
+
try {
|
|
7267
|
+
const res = await fetch(\`/api/search/suggest?q=\${encodeURIComponent(query)}\`);
|
|
7268
|
+
const { data } = await res.json();
|
|
7269
|
+
|
|
7270
|
+
if (data && data.length > 0) {
|
|
7271
|
+
suggestionsDiv.innerHTML = data.map(s => \`
|
|
7272
|
+
<div class="px-4 py-2 hover:bg-zinc-100 dark:hover:bg-zinc-700 cursor-pointer" onclick="selectSuggestion('\${s.replace(/'/g, "\\'")}')">\${s}</div>
|
|
7273
|
+
\`).join('');
|
|
7274
|
+
suggestionsDiv.classList.remove('hidden');
|
|
7275
|
+
} else {
|
|
7276
|
+
suggestionsDiv.classList.add('hidden');
|
|
7277
|
+
}
|
|
7278
|
+
} catch (error) {
|
|
7279
|
+
console.error('Autocomplete error:', error);
|
|
7280
|
+
}
|
|
7281
|
+
}, 300);
|
|
7282
|
+
});
|
|
7283
|
+
}
|
|
7284
|
+
|
|
7285
|
+
function selectSuggestion(suggestion) {
|
|
7286
|
+
document.getElementById('searchQuery').value = suggestion;
|
|
7287
|
+
document.getElementById('searchSuggestions').classList.add('hidden');
|
|
7288
|
+
}
|
|
7289
|
+
|
|
7290
|
+
// Hide suggestions when clicking outside
|
|
7291
|
+
document.addEventListener('click', (e) => {
|
|
7292
|
+
const suggestionsDiv = document.getElementById('searchSuggestions');
|
|
7293
|
+
if (!e.target.closest('#searchQuery') && !e.target.closest('#searchSuggestions')) {
|
|
7294
|
+
suggestionsDiv.classList.add('hidden');
|
|
7295
|
+
}
|
|
7296
|
+
});
|
|
7297
|
+
|
|
7298
|
+
// Form submission
|
|
7299
|
+
const advancedSearchForm = document.getElementById('advancedSearchForm');
|
|
7300
|
+
if (advancedSearchForm) {
|
|
7301
|
+
advancedSearchForm.addEventListener('submit', async (e) => {
|
|
7302
|
+
e.preventDefault();
|
|
7303
|
+
|
|
7304
|
+
const formData = new FormData(e.target);
|
|
7305
|
+
const query = formData.get('query');
|
|
7306
|
+
const mode = formData.get('mode') || 'ai';
|
|
7307
|
+
|
|
7308
|
+
// Build filters
|
|
7309
|
+
const filters = {};
|
|
7310
|
+
|
|
7311
|
+
const collections = Array.from(formData.getAll('collections')).filter(c => c !== '');
|
|
7312
|
+
if (collections.length > 0) {
|
|
7313
|
+
// Need to convert collection names to IDs - for now, pass names
|
|
7314
|
+
filters.collections = collections;
|
|
7315
|
+
}
|
|
7316
|
+
|
|
7317
|
+
const status = Array.from(formData.getAll('status'));
|
|
7318
|
+
if (status.length > 0) {
|
|
7319
|
+
filters.status = status;
|
|
7320
|
+
}
|
|
7321
|
+
|
|
7322
|
+
const dateStart = formData.get('date_start');
|
|
7323
|
+
const dateEnd = formData.get('date_end');
|
|
7324
|
+
if (dateStart || dateEnd) {
|
|
7325
|
+
filters.dateRange = {
|
|
7326
|
+
start: dateStart ? new Date(dateStart) : null,
|
|
7327
|
+
end: dateEnd ? new Date(dateEnd) : null,
|
|
7328
|
+
field: 'created_at'
|
|
7329
|
+
};
|
|
7330
|
+
}
|
|
7331
|
+
|
|
7332
|
+
// Execute search
|
|
7333
|
+
try {
|
|
7334
|
+
const res = await fetch('/api/search', {
|
|
7335
|
+
method: 'POST',
|
|
7336
|
+
headers: {'Content-Type': 'application/json'},
|
|
7337
|
+
body: JSON.stringify({
|
|
7338
|
+
query,
|
|
7339
|
+
mode,
|
|
7340
|
+
filters,
|
|
7341
|
+
limit: 20
|
|
7342
|
+
})
|
|
7343
|
+
});
|
|
5764
7344
|
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
})
|
|
5774
|
-
})
|
|
5775
|
-
.then(res => res.json())
|
|
5776
|
-
.then(data => {
|
|
5777
|
-
if (data.success) {
|
|
5778
|
-
location.reload();
|
|
5779
|
-
} else {
|
|
5780
|
-
alert('Error: ' + (data.error || 'Unknown error'));
|
|
7345
|
+
const { data } = await res.json();
|
|
7346
|
+
|
|
7347
|
+
if (data && data.results) {
|
|
7348
|
+
displaySearchResults(data);
|
|
7349
|
+
}
|
|
7350
|
+
} catch (error) {
|
|
7351
|
+
console.error('Search error:', error);
|
|
7352
|
+
alert('Search failed. Please try again.');
|
|
5781
7353
|
}
|
|
5782
|
-
})
|
|
5783
|
-
.catch(err => {
|
|
5784
|
-
console.error('Bulk action error:', err);
|
|
5785
|
-
alert('Failed to perform bulk action');
|
|
5786
|
-
})
|
|
5787
|
-
.finally(() => {
|
|
5788
|
-
// Clear context
|
|
5789
|
-
currentBulkAction = null;
|
|
5790
|
-
currentSelectedIds = [];
|
|
5791
7354
|
});
|
|
5792
7355
|
}
|
|
5793
7356
|
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
const
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
7357
|
+
function displaySearchResults(searchData) {
|
|
7358
|
+
const resultsDiv = document.getElementById('searchResultsContent');
|
|
7359
|
+
const resultsSection = document.getElementById('searchResults');
|
|
7360
|
+
|
|
7361
|
+
if (searchData.results.length === 0) {
|
|
7362
|
+
resultsDiv.innerHTML = '<p class="text-sm text-zinc-500 dark:text-zinc-400">No results found.</p>';
|
|
7363
|
+
} else {
|
|
7364
|
+
resultsDiv.innerHTML = searchData.results.map(result => \`
|
|
7365
|
+
<div class="p-4 rounded-lg border border-zinc-200 dark:border-zinc-800 hover:bg-zinc-50 dark:hover:bg-zinc-800">
|
|
7366
|
+
<div class="flex items-start justify-between">
|
|
7367
|
+
<div class="flex-1">
|
|
7368
|
+
<h4 class="text-sm font-semibold text-zinc-950 dark:text-white mb-1">
|
|
7369
|
+
<a href="/admin/content/\${result.id}/edit" class="hover:text-indigo-600 dark:hover:text-indigo-400">\${result.title || 'Untitled'}</a>
|
|
7370
|
+
</h4>
|
|
7371
|
+
<p class="text-xs text-zinc-500 dark:text-zinc-400 mb-2">
|
|
7372
|
+
\${result.collection_name} \u2022 \${new Date(result.created_at).toLocaleDateString()}
|
|
7373
|
+
\${result.relevance_score ? \` \u2022 Relevance: \${(result.relevance_score * 100).toFixed(0)}%\` : ''}
|
|
7374
|
+
</p>
|
|
7375
|
+
\${result.snippet ? \`<p class="text-sm text-zinc-600 dark:text-zinc-400">\${result.snippet}</p>\` : ''}
|
|
7376
|
+
</div>
|
|
7377
|
+
<div class="ml-4">
|
|
7378
|
+
<span class="px-2 py-1 text-xs rounded-full \${result.status === 'published' ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300' : 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300'}">\${result.status}</span>
|
|
7379
|
+
</div>
|
|
7380
|
+
</div>
|
|
7381
|
+
</div>
|
|
7382
|
+
\`).join('');
|
|
5806
7383
|
}
|
|
7384
|
+
|
|
7385
|
+
resultsSection.classList.remove('hidden');
|
|
7386
|
+
resultsSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
5807
7387
|
}
|
|
5808
7388
|
|
|
7389
|
+
// Make functions globally available
|
|
7390
|
+
window.openAdvancedSearch = openAdvancedSearch;
|
|
7391
|
+
window.closeAdvancedSearch = closeAdvancedSearch;
|
|
5809
7392
|
</script>
|
|
5810
|
-
|
|
5811
|
-
<!-- Confirmation Dialog for Bulk Actions -->
|
|
5812
|
-
${chunkAZLU3ROK_cjs.renderConfirmationDialog({
|
|
5813
|
-
id: "bulk-action-confirm",
|
|
5814
|
-
title: "Confirm Bulk Action",
|
|
5815
|
-
message: "Are you sure you want to perform this action? This operation will affect multiple items.",
|
|
5816
|
-
confirmText: "Confirm",
|
|
5817
|
-
cancelText: "Cancel",
|
|
5818
|
-
confirmClass: "bg-blue-500 hover:bg-blue-400",
|
|
5819
|
-
iconColor: "blue",
|
|
5820
|
-
onConfirm: "executeBulkAction()"
|
|
5821
|
-
})}
|
|
5822
|
-
|
|
5823
|
-
<!-- Confirmation Dialog Script -->
|
|
5824
|
-
${chunkAZLU3ROK_cjs.getConfirmationDialogScript()}
|
|
5825
7393
|
`;
|
|
5826
7394
|
const layoutData = {
|
|
5827
7395
|
title: "Content Management",
|
|
@@ -5831,7 +7399,7 @@ function renderContentListPage(data) {
|
|
|
5831
7399
|
version: data.version,
|
|
5832
7400
|
content: pageContent
|
|
5833
7401
|
};
|
|
5834
|
-
return
|
|
7402
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
5835
7403
|
}
|
|
5836
7404
|
|
|
5837
7405
|
// src/templates/components/version-history.template.ts
|
|
@@ -6025,7 +7593,123 @@ async function isPluginActive2(db, pluginId) {
|
|
|
6025
7593
|
|
|
6026
7594
|
// src/routes/admin-content.ts
|
|
6027
7595
|
var adminContentRoutes = new hono.Hono();
|
|
6028
|
-
|
|
7596
|
+
function parseFieldValue(field, formData, options = {}) {
|
|
7597
|
+
const { skipValidation = false } = options;
|
|
7598
|
+
const value = formData.get(field.field_name);
|
|
7599
|
+
const errors = [];
|
|
7600
|
+
const blocksConfig = chunkYMTTGHEK_cjs.getBlocksFieldConfig(field.field_options);
|
|
7601
|
+
if (blocksConfig) {
|
|
7602
|
+
const parsed = chunkYMTTGHEK_cjs.parseBlocksValue(value, blocksConfig);
|
|
7603
|
+
if (!skipValidation && field.is_required && parsed.value.length === 0) {
|
|
7604
|
+
parsed.errors.push(`${field.field_label} is required`);
|
|
7605
|
+
}
|
|
7606
|
+
return { value: parsed.value, errors: parsed.errors };
|
|
7607
|
+
}
|
|
7608
|
+
if (!skipValidation && field.is_required && (!value || value.toString().trim() === "")) {
|
|
7609
|
+
return { value: null, errors: [`${field.field_label} is required`] };
|
|
7610
|
+
}
|
|
7611
|
+
switch (field.field_type) {
|
|
7612
|
+
case "number":
|
|
7613
|
+
if (value && isNaN(Number(value))) {
|
|
7614
|
+
if (!skipValidation) {
|
|
7615
|
+
errors.push(`${field.field_label} must be a valid number`);
|
|
7616
|
+
}
|
|
7617
|
+
return { value: null, errors };
|
|
7618
|
+
}
|
|
7619
|
+
return { value: value ? Number(value) : null, errors: [] };
|
|
7620
|
+
case "boolean":
|
|
7621
|
+
const submitted = formData.get(`${field.field_name}_submitted`);
|
|
7622
|
+
return { value: submitted ? value === "true" : false, errors: [] };
|
|
7623
|
+
case "select":
|
|
7624
|
+
if (field.field_options?.multiple) {
|
|
7625
|
+
return { value: formData.getAll(`${field.field_name}[]`), errors: [] };
|
|
7626
|
+
}
|
|
7627
|
+
return { value, errors: [] };
|
|
7628
|
+
case "array": {
|
|
7629
|
+
if (!value || value.toString().trim() === "") {
|
|
7630
|
+
if (!skipValidation && field.is_required) {
|
|
7631
|
+
errors.push(`${field.field_label} is required`);
|
|
7632
|
+
}
|
|
7633
|
+
return { value: [], errors };
|
|
7634
|
+
}
|
|
7635
|
+
try {
|
|
7636
|
+
const parsed = JSON.parse(value.toString());
|
|
7637
|
+
if (!Array.isArray(parsed)) {
|
|
7638
|
+
if (!skipValidation) {
|
|
7639
|
+
errors.push(`${field.field_label} must be a JSON array`);
|
|
7640
|
+
}
|
|
7641
|
+
return { value: [], errors };
|
|
7642
|
+
}
|
|
7643
|
+
if (!skipValidation && field.is_required && parsed.length === 0) {
|
|
7644
|
+
errors.push(`${field.field_label} is required`);
|
|
7645
|
+
}
|
|
7646
|
+
return { value: parsed, errors };
|
|
7647
|
+
} catch {
|
|
7648
|
+
if (!skipValidation) {
|
|
7649
|
+
errors.push(`${field.field_label} must be valid JSON`);
|
|
7650
|
+
}
|
|
7651
|
+
return { value: [], errors };
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7654
|
+
case "object": {
|
|
7655
|
+
if (!value || value.toString().trim() === "") {
|
|
7656
|
+
if (!skipValidation && field.is_required) {
|
|
7657
|
+
errors.push(`${field.field_label} is required`);
|
|
7658
|
+
}
|
|
7659
|
+
return { value: {}, errors };
|
|
7660
|
+
}
|
|
7661
|
+
try {
|
|
7662
|
+
const parsed = JSON.parse(value.toString());
|
|
7663
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
7664
|
+
if (!skipValidation) {
|
|
7665
|
+
errors.push(`${field.field_label} must be a JSON object`);
|
|
7666
|
+
}
|
|
7667
|
+
return { value: {}, errors };
|
|
7668
|
+
}
|
|
7669
|
+
if (!skipValidation && field.is_required && Object.keys(parsed).length === 0) {
|
|
7670
|
+
errors.push(`${field.field_label} is required`);
|
|
7671
|
+
}
|
|
7672
|
+
return { value: parsed, errors };
|
|
7673
|
+
} catch {
|
|
7674
|
+
if (!skipValidation) {
|
|
7675
|
+
errors.push(`${field.field_label} must be valid JSON`);
|
|
7676
|
+
}
|
|
7677
|
+
return { value: {}, errors };
|
|
7678
|
+
}
|
|
7679
|
+
}
|
|
7680
|
+
case "json": {
|
|
7681
|
+
if (!value || value.toString().trim() === "") {
|
|
7682
|
+
if (!skipValidation && field.is_required) {
|
|
7683
|
+
errors.push(`${field.field_label} is required`);
|
|
7684
|
+
}
|
|
7685
|
+
return { value: null, errors };
|
|
7686
|
+
}
|
|
7687
|
+
try {
|
|
7688
|
+
return { value: JSON.parse(value.toString()), errors: [] };
|
|
7689
|
+
} catch {
|
|
7690
|
+
if (!skipValidation) {
|
|
7691
|
+
errors.push(`${field.field_label} must be valid JSON`);
|
|
7692
|
+
}
|
|
7693
|
+
return { value: null, errors };
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
default:
|
|
7697
|
+
return { value, errors: [] };
|
|
7698
|
+
}
|
|
7699
|
+
}
|
|
7700
|
+
function extractFieldData(fields, formData, options = {}) {
|
|
7701
|
+
const data = {};
|
|
7702
|
+
const errors = {};
|
|
7703
|
+
for (const field of fields) {
|
|
7704
|
+
const result = parseFieldValue(field, formData, options);
|
|
7705
|
+
data[field.field_name] = result.value;
|
|
7706
|
+
if (result.errors.length > 0) {
|
|
7707
|
+
errors[field.field_name] = result.errors;
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7710
|
+
return { data, errors };
|
|
7711
|
+
}
|
|
7712
|
+
adminContentRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
|
|
6029
7713
|
async function getCollectionFields(db, collectionId) {
|
|
6030
7714
|
const cache = chunk7FOAMNTI_cjs.getCacheService(chunk7FOAMNTI_cjs.CACHE_CONFIGS.collection);
|
|
6031
7715
|
return cache.getOrSet(
|
|
@@ -6307,21 +7991,21 @@ adminContentRoutes.get("/new", async (c) => {
|
|
|
6307
7991
|
const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
|
|
6308
7992
|
let tinymceSettings;
|
|
6309
7993
|
if (tinymceEnabled) {
|
|
6310
|
-
const pluginService = new
|
|
7994
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6311
7995
|
const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
|
|
6312
7996
|
tinymceSettings = tinymcePlugin2?.settings;
|
|
6313
7997
|
}
|
|
6314
7998
|
const quillEnabled = await isPluginActive2(db, "quill-editor");
|
|
6315
7999
|
let quillSettings;
|
|
6316
8000
|
if (quillEnabled) {
|
|
6317
|
-
const pluginService = new
|
|
8001
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6318
8002
|
const quillPlugin = await pluginService.getPlugin("quill-editor");
|
|
6319
8003
|
quillSettings = quillPlugin?.settings;
|
|
6320
8004
|
}
|
|
6321
8005
|
const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
|
|
6322
8006
|
let mdxeditorSettings;
|
|
6323
8007
|
if (mdxeditorEnabled) {
|
|
6324
|
-
const pluginService = new
|
|
8008
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6325
8009
|
const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
|
|
6326
8010
|
mdxeditorSettings = mdxeditorPlugin?.settings;
|
|
6327
8011
|
}
|
|
@@ -6412,21 +8096,21 @@ adminContentRoutes.get("/:id/edit", async (c) => {
|
|
|
6412
8096
|
const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
|
|
6413
8097
|
let tinymceSettings;
|
|
6414
8098
|
if (tinymceEnabled) {
|
|
6415
|
-
const pluginService = new
|
|
8099
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6416
8100
|
const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
|
|
6417
8101
|
tinymceSettings = tinymcePlugin2?.settings;
|
|
6418
8102
|
}
|
|
6419
8103
|
const quillEnabled = await isPluginActive2(db, "quill-editor");
|
|
6420
8104
|
let quillSettings;
|
|
6421
8105
|
if (quillEnabled) {
|
|
6422
|
-
const pluginService = new
|
|
8106
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6423
8107
|
const quillPlugin = await pluginService.getPlugin("quill-editor");
|
|
6424
8108
|
quillSettings = quillPlugin?.settings;
|
|
6425
8109
|
}
|
|
6426
8110
|
const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
|
|
6427
8111
|
let mdxeditorSettings;
|
|
6428
8112
|
if (mdxeditorEnabled) {
|
|
6429
|
-
const pluginService = new
|
|
8113
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6430
8114
|
const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
|
|
6431
8115
|
mdxeditorSettings = mdxeditorPlugin?.settings;
|
|
6432
8116
|
}
|
|
@@ -6498,36 +8182,7 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
6498
8182
|
`);
|
|
6499
8183
|
}
|
|
6500
8184
|
const fields = await getCollectionFields(db, collectionId);
|
|
6501
|
-
const data =
|
|
6502
|
-
const errors = {};
|
|
6503
|
-
for (const field of fields) {
|
|
6504
|
-
const value = formData.get(field.field_name);
|
|
6505
|
-
if (field.is_required && (!value || value.toString().trim() === "")) {
|
|
6506
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6507
|
-
continue;
|
|
6508
|
-
}
|
|
6509
|
-
switch (field.field_type) {
|
|
6510
|
-
case "number":
|
|
6511
|
-
if (value && isNaN(Number(value))) {
|
|
6512
|
-
errors[field.field_name] = [`${field.field_label} must be a valid number`];
|
|
6513
|
-
} else {
|
|
6514
|
-
data[field.field_name] = value ? Number(value) : null;
|
|
6515
|
-
}
|
|
6516
|
-
break;
|
|
6517
|
-
case "boolean":
|
|
6518
|
-
data[field.field_name] = formData.get(`${field.field_name}_submitted`) ? value === "true" : false;
|
|
6519
|
-
break;
|
|
6520
|
-
case "select":
|
|
6521
|
-
if (field.field_options?.multiple) {
|
|
6522
|
-
data[field.field_name] = formData.getAll(`${field.field_name}[]`);
|
|
6523
|
-
} else {
|
|
6524
|
-
data[field.field_name] = value;
|
|
6525
|
-
}
|
|
6526
|
-
break;
|
|
6527
|
-
default:
|
|
6528
|
-
data[field.field_name] = value;
|
|
6529
|
-
}
|
|
6530
|
-
}
|
|
8185
|
+
const { data, errors } = extractFieldData(fields, formData);
|
|
6531
8186
|
if (Object.keys(errors).length > 0) {
|
|
6532
8187
|
const formDataWithErrors = {
|
|
6533
8188
|
collection,
|
|
@@ -6644,36 +8299,7 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
6644
8299
|
`);
|
|
6645
8300
|
}
|
|
6646
8301
|
const fields = await getCollectionFields(db, existingContent.collection_id);
|
|
6647
|
-
const data =
|
|
6648
|
-
const errors = {};
|
|
6649
|
-
for (const field of fields) {
|
|
6650
|
-
const value = formData.get(field.field_name);
|
|
6651
|
-
if (field.is_required && (!value || value.toString().trim() === "")) {
|
|
6652
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6653
|
-
continue;
|
|
6654
|
-
}
|
|
6655
|
-
switch (field.field_type) {
|
|
6656
|
-
case "number":
|
|
6657
|
-
if (value && isNaN(Number(value))) {
|
|
6658
|
-
errors[field.field_name] = [`${field.field_label} must be a valid number`];
|
|
6659
|
-
} else {
|
|
6660
|
-
data[field.field_name] = value ? Number(value) : null;
|
|
6661
|
-
}
|
|
6662
|
-
break;
|
|
6663
|
-
case "boolean":
|
|
6664
|
-
data[field.field_name] = formData.get(`${field.field_name}_submitted`) ? value === "true" : false;
|
|
6665
|
-
break;
|
|
6666
|
-
case "select":
|
|
6667
|
-
if (field.field_options?.multiple) {
|
|
6668
|
-
data[field.field_name] = formData.getAll(`${field.field_name}[]`);
|
|
6669
|
-
} else {
|
|
6670
|
-
data[field.field_name] = value;
|
|
6671
|
-
}
|
|
6672
|
-
break;
|
|
6673
|
-
default:
|
|
6674
|
-
data[field.field_name] = value;
|
|
6675
|
-
}
|
|
6676
|
-
}
|
|
8302
|
+
const { data, errors } = extractFieldData(fields, formData);
|
|
6677
8303
|
if (Object.keys(errors).length > 0) {
|
|
6678
8304
|
const formDataWithErrors = {
|
|
6679
8305
|
id,
|
|
@@ -6786,27 +8412,7 @@ adminContentRoutes.post("/preview", async (c) => {
|
|
|
6786
8412
|
return c.html("<p>Collection not found</p>");
|
|
6787
8413
|
}
|
|
6788
8414
|
const fields = await getCollectionFields(db, collectionId);
|
|
6789
|
-
const data = {};
|
|
6790
|
-
for (const field of fields) {
|
|
6791
|
-
const value = formData.get(field.field_name);
|
|
6792
|
-
switch (field.field_type) {
|
|
6793
|
-
case "number":
|
|
6794
|
-
data[field.field_name] = value ? Number(value) : null;
|
|
6795
|
-
break;
|
|
6796
|
-
case "boolean":
|
|
6797
|
-
data[field.field_name] = value === "true";
|
|
6798
|
-
break;
|
|
6799
|
-
case "select":
|
|
6800
|
-
if (field.field_options?.multiple) {
|
|
6801
|
-
data[field.field_name] = formData.getAll(`${field.field_name}[]`);
|
|
6802
|
-
} else {
|
|
6803
|
-
data[field.field_name] = value;
|
|
6804
|
-
}
|
|
6805
|
-
break;
|
|
6806
|
-
default:
|
|
6807
|
-
data[field.field_name] = value;
|
|
6808
|
-
}
|
|
6809
|
-
}
|
|
8415
|
+
const { data } = extractFieldData(fields, formData, { skipValidation: true });
|
|
6810
8416
|
const previewHTML = `
|
|
6811
8417
|
<!DOCTYPE html>
|
|
6812
8418
|
<html lang="en">
|
|
@@ -7234,7 +8840,7 @@ ${JSON.stringify(data, null, 2)}
|
|
|
7234
8840
|
var admin_content_default = adminContentRoutes;
|
|
7235
8841
|
|
|
7236
8842
|
// src/templates/pages/admin-profile.template.ts
|
|
7237
|
-
|
|
8843
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
7238
8844
|
function renderAvatarImage(avatarUrl, firstName, lastName) {
|
|
7239
8845
|
return `<div id="avatar-image-container" class="w-24 h-24 rounded-full mx-auto mb-4 overflow-hidden bg-gradient-to-br from-cyan-400 to-purple-400 flex items-center justify-center ring-4 ring-zinc-950/5 dark:ring-white/10">
|
|
7240
8846
|
${avatarUrl ? `<img src="${avatarUrl}" alt="Profile picture" class="w-full h-full object-cover">` : `<span class="text-2xl font-bold text-white">${firstName.charAt(0)}${lastName.charAt(0)}</span>`}
|
|
@@ -7254,8 +8860,8 @@ function renderProfilePage(data) {
|
|
|
7254
8860
|
</div>
|
|
7255
8861
|
|
|
7256
8862
|
<!-- Alert Messages -->
|
|
7257
|
-
${data.error ?
|
|
7258
|
-
${data.success ?
|
|
8863
|
+
${data.error ? chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
|
|
8864
|
+
${data.success ? chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
|
|
7259
8865
|
|
|
7260
8866
|
<!-- Profile Form -->
|
|
7261
8867
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
@@ -7642,7 +9248,7 @@ function renderProfilePage(data) {
|
|
|
7642
9248
|
version: data.version,
|
|
7643
9249
|
content: pageContent
|
|
7644
9250
|
};
|
|
7645
|
-
return
|
|
9251
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
7646
9252
|
}
|
|
7647
9253
|
|
|
7648
9254
|
// src/templates/components/alert.template.ts
|
|
@@ -7925,7 +9531,7 @@ function renderActivityLogsPage(data) {
|
|
|
7925
9531
|
user: data.user,
|
|
7926
9532
|
content: pageContent
|
|
7927
9533
|
};
|
|
7928
|
-
return
|
|
9534
|
+
return chunkBZC4FYW7_cjs.renderAdminLayout(layoutData);
|
|
7929
9535
|
}
|
|
7930
9536
|
function getActionBadgeClass(action) {
|
|
7931
9537
|
if (action.includes("login") || action.includes("logout")) {
|
|
@@ -7945,7 +9551,7 @@ function formatAction(action) {
|
|
|
7945
9551
|
}
|
|
7946
9552
|
|
|
7947
9553
|
// src/templates/pages/admin-user-edit.template.ts
|
|
7948
|
-
|
|
9554
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
7949
9555
|
|
|
7950
9556
|
// src/templates/components/confirmation-dialog.template.ts
|
|
7951
9557
|
function renderConfirmationDialog2(options) {
|
|
@@ -8066,8 +9672,8 @@ function renderUserEditPage(data) {
|
|
|
8066
9672
|
|
|
8067
9673
|
<!-- Alert Messages -->
|
|
8068
9674
|
<div id="form-messages">
|
|
8069
|
-
${data.error ?
|
|
8070
|
-
${data.success ?
|
|
9675
|
+
${data.error ? chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
|
|
9676
|
+
${data.success ? chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
|
|
8071
9677
|
</div>
|
|
8072
9678
|
|
|
8073
9679
|
<!-- User Edit Form -->
|
|
@@ -8086,7 +9692,7 @@ function renderUserEditPage(data) {
|
|
|
8086
9692
|
<input
|
|
8087
9693
|
type="text"
|
|
8088
9694
|
name="first_name"
|
|
8089
|
-
value="${
|
|
9695
|
+
value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.firstName || "")}"
|
|
8090
9696
|
required
|
|
8091
9697
|
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
8092
9698
|
/>
|
|
@@ -8097,7 +9703,7 @@ function renderUserEditPage(data) {
|
|
|
8097
9703
|
<input
|
|
8098
9704
|
type="text"
|
|
8099
9705
|
name="last_name"
|
|
8100
|
-
value="${
|
|
9706
|
+
value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.lastName || "")}"
|
|
8101
9707
|
required
|
|
8102
9708
|
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
8103
9709
|
/>
|
|
@@ -8108,7 +9714,7 @@ function renderUserEditPage(data) {
|
|
|
8108
9714
|
<input
|
|
8109
9715
|
type="text"
|
|
8110
9716
|
name="username"
|
|
8111
|
-
value="${
|
|
9717
|
+
value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.username || "")}"
|
|
8112
9718
|
required
|
|
8113
9719
|
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
8114
9720
|
/>
|
|
@@ -8119,7 +9725,7 @@ function renderUserEditPage(data) {
|
|
|
8119
9725
|
<input
|
|
8120
9726
|
type="email"
|
|
8121
9727
|
name="email"
|
|
8122
|
-
value="${
|
|
9728
|
+
value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.email || "")}"
|
|
8123
9729
|
required
|
|
8124
9730
|
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
8125
9731
|
/>
|
|
@@ -8130,7 +9736,7 @@ function renderUserEditPage(data) {
|
|
|
8130
9736
|
<input
|
|
8131
9737
|
type="tel"
|
|
8132
9738
|
name="phone"
|
|
8133
|
-
value="${
|
|
9739
|
+
value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.phone || "")}"
|
|
8134
9740
|
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
8135
9741
|
/>
|
|
8136
9742
|
</div>
|
|
@@ -8144,7 +9750,7 @@ function renderUserEditPage(data) {
|
|
|
8144
9750
|
class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white/5 dark:bg-white/5 py-1.5 pl-3 pr-8 text-base text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-zinc-500/30 dark:outline-zinc-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-zinc-500 dark:focus-visible:outline-zinc-400 sm:text-sm/6"
|
|
8145
9751
|
>
|
|
8146
9752
|
${data.roles.map((role) => `
|
|
8147
|
-
<option value="${
|
|
9753
|
+
<option value="${chunkYMTTGHEK_cjs.escapeHtml(role.value)}" ${data.userToEdit.role === role.value ? "selected" : ""}>${chunkYMTTGHEK_cjs.escapeHtml(role.label)}</option>
|
|
8148
9754
|
`).join("")}
|
|
8149
9755
|
</select>
|
|
8150
9756
|
<svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-zinc-600 dark:text-zinc-400 sm:size-4">
|
|
@@ -8153,14 +9759,87 @@ function renderUserEditPage(data) {
|
|
|
8153
9759
|
</div>
|
|
8154
9760
|
</div>
|
|
8155
9761
|
</div>
|
|
9762
|
+
</div>
|
|
9763
|
+
|
|
9764
|
+
<!-- Profile Information -->
|
|
9765
|
+
<div class="mb-8">
|
|
9766
|
+
<h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Profile Information</h3>
|
|
9767
|
+
<p class="text-sm text-zinc-500 dark:text-zinc-400 mb-4">Extended profile data for this user</p>
|
|
9768
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
9769
|
+
<div>
|
|
9770
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Display Name</label>
|
|
9771
|
+
<input
|
|
9772
|
+
type="text"
|
|
9773
|
+
name="profile_display_name"
|
|
9774
|
+
value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.displayName || "")}"
|
|
9775
|
+
placeholder="Public display name"
|
|
9776
|
+
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
9777
|
+
/>
|
|
9778
|
+
</div>
|
|
9779
|
+
|
|
9780
|
+
<div>
|
|
9781
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Company</label>
|
|
9782
|
+
<input
|
|
9783
|
+
type="text"
|
|
9784
|
+
name="profile_company"
|
|
9785
|
+
value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.company || "")}"
|
|
9786
|
+
placeholder="Company or organization"
|
|
9787
|
+
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
9788
|
+
/>
|
|
9789
|
+
</div>
|
|
9790
|
+
|
|
9791
|
+
<div>
|
|
9792
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Job Title</label>
|
|
9793
|
+
<input
|
|
9794
|
+
type="text"
|
|
9795
|
+
name="profile_job_title"
|
|
9796
|
+
value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.jobTitle || "")}"
|
|
9797
|
+
placeholder="Job title or role"
|
|
9798
|
+
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
9799
|
+
/>
|
|
9800
|
+
</div>
|
|
9801
|
+
|
|
9802
|
+
<div>
|
|
9803
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Website</label>
|
|
9804
|
+
<input
|
|
9805
|
+
type="url"
|
|
9806
|
+
name="profile_website"
|
|
9807
|
+
value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.website || "")}"
|
|
9808
|
+
placeholder="https://example.com"
|
|
9809
|
+
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
9810
|
+
/>
|
|
9811
|
+
</div>
|
|
9812
|
+
|
|
9813
|
+
<div>
|
|
9814
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Location</label>
|
|
9815
|
+
<input
|
|
9816
|
+
type="text"
|
|
9817
|
+
name="profile_location"
|
|
9818
|
+
value="${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.location || "")}"
|
|
9819
|
+
placeholder="City, Country"
|
|
9820
|
+
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
9821
|
+
/>
|
|
9822
|
+
</div>
|
|
9823
|
+
|
|
9824
|
+
<div>
|
|
9825
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Date of Birth</label>
|
|
9826
|
+
<input
|
|
9827
|
+
type="date"
|
|
9828
|
+
name="profile_date_of_birth"
|
|
9829
|
+
value="${data.userToEdit.profile?.dateOfBirth ? new Date(data.userToEdit.profile.dateOfBirth).toISOString().split("T")[0] : ""}"
|
|
9830
|
+
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
9831
|
+
/>
|
|
9832
|
+
</div>
|
|
9833
|
+
</div>
|
|
8156
9834
|
|
|
8157
9835
|
<div class="mt-6">
|
|
8158
9836
|
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Bio</label>
|
|
8159
9837
|
<textarea
|
|
8160
|
-
name="
|
|
9838
|
+
name="profile_bio"
|
|
8161
9839
|
rows="3"
|
|
9840
|
+
placeholder="Short bio or description"
|
|
8162
9841
|
class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
|
|
8163
|
-
>${
|
|
9842
|
+
>${chunkYMTTGHEK_cjs.escapeHtml(data.userToEdit.profile?.bio || "")}</textarea>
|
|
8164
9843
|
</div>
|
|
8165
9844
|
</div>
|
|
8166
9845
|
|
|
@@ -8360,11 +10039,11 @@ function renderUserEditPage(data) {
|
|
|
8360
10039
|
user: data.user,
|
|
8361
10040
|
content: pageContent
|
|
8362
10041
|
};
|
|
8363
|
-
return
|
|
10042
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
8364
10043
|
}
|
|
8365
10044
|
|
|
8366
10045
|
// src/templates/pages/admin-user-new.template.ts
|
|
8367
|
-
|
|
10046
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
8368
10047
|
function renderUserNewPage(data) {
|
|
8369
10048
|
const pageContent = `
|
|
8370
10049
|
<div>
|
|
@@ -8403,8 +10082,8 @@ function renderUserNewPage(data) {
|
|
|
8403
10082
|
|
|
8404
10083
|
<!-- Alert Messages -->
|
|
8405
10084
|
<div id="form-messages">
|
|
8406
|
-
${data.error ?
|
|
8407
|
-
${data.success ?
|
|
10085
|
+
${data.error ? chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
|
|
10086
|
+
${data.success ? chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
|
|
8408
10087
|
</div>
|
|
8409
10088
|
|
|
8410
10089
|
<!-- User New Form -->
|
|
@@ -8648,11 +10327,11 @@ function renderUserNewPage(data) {
|
|
|
8648
10327
|
user: data.user,
|
|
8649
10328
|
content: pageContent
|
|
8650
10329
|
};
|
|
8651
|
-
return
|
|
10330
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
8652
10331
|
}
|
|
8653
10332
|
|
|
8654
10333
|
// src/templates/pages/admin-users-list.template.ts
|
|
8655
|
-
|
|
10334
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
8656
10335
|
function renderUsersListPage(data) {
|
|
8657
10336
|
const columns = [
|
|
8658
10337
|
{
|
|
@@ -8803,8 +10482,8 @@ function renderUsersListPage(data) {
|
|
|
8803
10482
|
</div>
|
|
8804
10483
|
|
|
8805
10484
|
<!-- Alert Messages -->
|
|
8806
|
-
${data.error ?
|
|
8807
|
-
${data.success ?
|
|
10485
|
+
${data.error ? chunkBZC4FYW7_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
|
|
10486
|
+
${data.success ? chunkBZC4FYW7_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
|
|
8808
10487
|
|
|
8809
10488
|
<!-- Stats -->
|
|
8810
10489
|
<div class="mb-6">
|
|
@@ -8981,10 +10660,10 @@ function renderUsersListPage(data) {
|
|
|
8981
10660
|
</div>
|
|
8982
10661
|
|
|
8983
10662
|
<!-- Users Table -->
|
|
8984
|
-
${
|
|
10663
|
+
${chunkBZC4FYW7_cjs.renderTable(tableData)}
|
|
8985
10664
|
|
|
8986
10665
|
<!-- Pagination -->
|
|
8987
|
-
${data.pagination ?
|
|
10666
|
+
${data.pagination ? chunkBZC4FYW7_cjs.renderPagination(data.pagination) : ""}
|
|
8988
10667
|
</div>
|
|
8989
10668
|
|
|
8990
10669
|
<script>
|
|
@@ -9055,12 +10734,12 @@ function renderUsersListPage(data) {
|
|
|
9055
10734
|
version: data.version,
|
|
9056
10735
|
content: pageContent
|
|
9057
10736
|
};
|
|
9058
|
-
return
|
|
10737
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
9059
10738
|
}
|
|
9060
10739
|
|
|
9061
10740
|
// src/routes/admin-users.ts
|
|
9062
10741
|
var userRoutes = new hono.Hono();
|
|
9063
|
-
userRoutes.use("*",
|
|
10742
|
+
userRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
|
|
9064
10743
|
userRoutes.get("/", (c) => {
|
|
9065
10744
|
return c.redirect("/admin/dashboard");
|
|
9066
10745
|
});
|
|
@@ -9159,12 +10838,12 @@ userRoutes.put("/profile", async (c) => {
|
|
|
9159
10838
|
const db = c.env.DB;
|
|
9160
10839
|
try {
|
|
9161
10840
|
const formData = await c.req.formData();
|
|
9162
|
-
const firstName =
|
|
9163
|
-
const lastName =
|
|
9164
|
-
const username =
|
|
10841
|
+
const firstName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("first_name")?.toString());
|
|
10842
|
+
const lastName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("last_name")?.toString());
|
|
10843
|
+
const username = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("username")?.toString());
|
|
9165
10844
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
9166
|
-
const phone =
|
|
9167
|
-
const bio =
|
|
10845
|
+
const phone = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
10846
|
+
const bio = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
9168
10847
|
const timezone = formData.get("timezone")?.toString() || "UTC";
|
|
9169
10848
|
const language = formData.get("language")?.toString() || "en";
|
|
9170
10849
|
const emailNotifications = formData.get("email_notifications") === "1";
|
|
@@ -9215,7 +10894,7 @@ userRoutes.put("/profile", async (c) => {
|
|
|
9215
10894
|
Date.now(),
|
|
9216
10895
|
user.userId
|
|
9217
10896
|
).run();
|
|
9218
|
-
await
|
|
10897
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9219
10898
|
db,
|
|
9220
10899
|
user.userId,
|
|
9221
10900
|
"profile.update",
|
|
@@ -9278,7 +10957,7 @@ userRoutes.post("/profile/avatar", async (c) => {
|
|
|
9278
10957
|
SELECT first_name, last_name FROM users WHERE id = ?
|
|
9279
10958
|
`);
|
|
9280
10959
|
const userData = await userStmt.bind(user.userId).first();
|
|
9281
|
-
await
|
|
10960
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9282
10961
|
db,
|
|
9283
10962
|
user.userId,
|
|
9284
10963
|
"profile.avatar_update",
|
|
@@ -9349,7 +11028,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
9349
11028
|
dismissible: true
|
|
9350
11029
|
}));
|
|
9351
11030
|
}
|
|
9352
|
-
const validPassword = await
|
|
11031
|
+
const validPassword = await chunkT3YIKW2A_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
|
|
9353
11032
|
if (!validPassword) {
|
|
9354
11033
|
return c.html(renderAlert2({
|
|
9355
11034
|
type: "error",
|
|
@@ -9357,7 +11036,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
9357
11036
|
dismissible: true
|
|
9358
11037
|
}));
|
|
9359
11038
|
}
|
|
9360
|
-
const newPasswordHash = await
|
|
11039
|
+
const newPasswordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(newPassword);
|
|
9361
11040
|
const historyStmt = db.prepare(`
|
|
9362
11041
|
INSERT INTO password_history (id, user_id, password_hash, created_at)
|
|
9363
11042
|
VALUES (?, ?, ?, ?)
|
|
@@ -9373,7 +11052,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
9373
11052
|
WHERE id = ?
|
|
9374
11053
|
`);
|
|
9375
11054
|
await updateStmt.bind(newPasswordHash, Date.now(), user.userId).run();
|
|
9376
|
-
await
|
|
11055
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9377
11056
|
db,
|
|
9378
11057
|
user.userId,
|
|
9379
11058
|
"profile.password_change",
|
|
@@ -9440,7 +11119,7 @@ userRoutes.get("/users", async (c) => {
|
|
|
9440
11119
|
`);
|
|
9441
11120
|
const countResult = await countStmt.bind(...params).first();
|
|
9442
11121
|
const totalUsers = countResult?.total || 0;
|
|
9443
|
-
await
|
|
11122
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9444
11123
|
db,
|
|
9445
11124
|
user.userId,
|
|
9446
11125
|
"users.list_view",
|
|
@@ -9542,12 +11221,12 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
9542
11221
|
const user = c.get("user");
|
|
9543
11222
|
try {
|
|
9544
11223
|
const formData = await c.req.formData();
|
|
9545
|
-
const firstName =
|
|
9546
|
-
const lastName =
|
|
9547
|
-
const username =
|
|
11224
|
+
const firstName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("first_name")?.toString());
|
|
11225
|
+
const lastName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("last_name")?.toString());
|
|
11226
|
+
const username = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("username")?.toString());
|
|
9548
11227
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
9549
|
-
const phone =
|
|
9550
|
-
const bio =
|
|
11228
|
+
const phone = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
11229
|
+
const bio = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
9551
11230
|
const role = formData.get("role")?.toString() || "viewer";
|
|
9552
11231
|
const password = formData.get("password")?.toString() || "";
|
|
9553
11232
|
const confirmPassword = formData.get("confirm_password")?.toString() || "";
|
|
@@ -9594,7 +11273,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
9594
11273
|
dismissible: true
|
|
9595
11274
|
}));
|
|
9596
11275
|
}
|
|
9597
|
-
const passwordHash = await
|
|
11276
|
+
const passwordHash = await chunkT3YIKW2A_cjs.AuthManager.hashPassword(password);
|
|
9598
11277
|
const userId = crypto.randomUUID();
|
|
9599
11278
|
const createStmt = db.prepare(`
|
|
9600
11279
|
INSERT INTO users (
|
|
@@ -9617,7 +11296,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
9617
11296
|
Date.now(),
|
|
9618
11297
|
Date.now()
|
|
9619
11298
|
).run();
|
|
9620
|
-
await
|
|
11299
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9621
11300
|
db,
|
|
9622
11301
|
user.userId,
|
|
9623
11302
|
"user!.create",
|
|
@@ -9655,7 +11334,7 @@ userRoutes.get("/users/:id", async (c) => {
|
|
|
9655
11334
|
if (!userRecord) {
|
|
9656
11335
|
return c.json({ error: "User not found" }, 404);
|
|
9657
11336
|
}
|
|
9658
|
-
await
|
|
11337
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9659
11338
|
db,
|
|
9660
11339
|
user.userId,
|
|
9661
11340
|
"user!.view",
|
|
@@ -9694,7 +11373,7 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
9694
11373
|
const userId = c.req.param("id");
|
|
9695
11374
|
try {
|
|
9696
11375
|
const userStmt = db.prepare(`
|
|
9697
|
-
SELECT id, email, username, first_name, last_name, phone,
|
|
11376
|
+
SELECT id, email, username, first_name, last_name, phone, avatar_url,
|
|
9698
11377
|
role, is_active, email_verified, two_factor_enabled, created_at, last_login_at
|
|
9699
11378
|
FROM users
|
|
9700
11379
|
WHERE id = ?
|
|
@@ -9707,6 +11386,21 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
9707
11386
|
dismissible: true
|
|
9708
11387
|
}), 404);
|
|
9709
11388
|
}
|
|
11389
|
+
const profileStmt = db.prepare(`
|
|
11390
|
+
SELECT display_name, bio, company, job_title, website, location, date_of_birth
|
|
11391
|
+
FROM user_profiles
|
|
11392
|
+
WHERE user_id = ?
|
|
11393
|
+
`);
|
|
11394
|
+
const profileData = await profileStmt.bind(userId).first();
|
|
11395
|
+
const profile = profileData ? {
|
|
11396
|
+
displayName: profileData.display_name,
|
|
11397
|
+
bio: profileData.bio,
|
|
11398
|
+
company: profileData.company,
|
|
11399
|
+
jobTitle: profileData.job_title,
|
|
11400
|
+
website: profileData.website,
|
|
11401
|
+
location: profileData.location,
|
|
11402
|
+
dateOfBirth: profileData.date_of_birth
|
|
11403
|
+
} : void 0;
|
|
9710
11404
|
const editData = {
|
|
9711
11405
|
id: userToEdit.id,
|
|
9712
11406
|
email: userToEdit.email,
|
|
@@ -9714,14 +11408,14 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
9714
11408
|
firstName: userToEdit.first_name || "",
|
|
9715
11409
|
lastName: userToEdit.last_name || "",
|
|
9716
11410
|
phone: userToEdit.phone,
|
|
9717
|
-
bio: userToEdit.bio,
|
|
9718
11411
|
avatarUrl: userToEdit.avatar_url,
|
|
9719
11412
|
role: userToEdit.role,
|
|
9720
11413
|
isActive: Boolean(userToEdit.is_active),
|
|
9721
11414
|
emailVerified: Boolean(userToEdit.email_verified),
|
|
9722
11415
|
twoFactorEnabled: Boolean(userToEdit.two_factor_enabled),
|
|
9723
11416
|
createdAt: userToEdit.created_at,
|
|
9724
|
-
lastLoginAt: userToEdit.last_login_at
|
|
11417
|
+
lastLoginAt: userToEdit.last_login_at,
|
|
11418
|
+
profile
|
|
9725
11419
|
};
|
|
9726
11420
|
const pageData = {
|
|
9727
11421
|
userToEdit: editData,
|
|
@@ -9737,7 +11431,7 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
9737
11431
|
console.error("User edit page error:", error);
|
|
9738
11432
|
return c.html(renderAlert2({
|
|
9739
11433
|
type: "error",
|
|
9740
|
-
message: "Failed to load user
|
|
11434
|
+
message: "Failed to load user. Please try again.",
|
|
9741
11435
|
dismissible: true
|
|
9742
11436
|
}), 500);
|
|
9743
11437
|
}
|
|
@@ -9748,15 +11442,22 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9748
11442
|
const userId = c.req.param("id");
|
|
9749
11443
|
try {
|
|
9750
11444
|
const formData = await c.req.formData();
|
|
9751
|
-
const firstName =
|
|
9752
|
-
const lastName =
|
|
9753
|
-
const username =
|
|
11445
|
+
const firstName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("first_name")?.toString());
|
|
11446
|
+
const lastName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("last_name")?.toString());
|
|
11447
|
+
const username = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("username")?.toString());
|
|
9754
11448
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
9755
|
-
const phone =
|
|
9756
|
-
const bio = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
11449
|
+
const phone = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
9757
11450
|
const role = formData.get("role")?.toString() || "viewer";
|
|
9758
11451
|
const isActive = formData.get("is_active") === "1";
|
|
9759
11452
|
const emailVerified = formData.get("email_verified") === "1";
|
|
11453
|
+
const profileDisplayName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("profile_display_name")?.toString()) || null;
|
|
11454
|
+
const profileBio = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("profile_bio")?.toString()) || null;
|
|
11455
|
+
const profileCompany = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("profile_company")?.toString()) || null;
|
|
11456
|
+
const profileJobTitle = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("profile_job_title")?.toString()) || null;
|
|
11457
|
+
const profileWebsite = formData.get("profile_website")?.toString()?.trim() || null;
|
|
11458
|
+
const profileLocation = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("profile_location")?.toString()) || null;
|
|
11459
|
+
const profileDateOfBirthStr = formData.get("profile_date_of_birth")?.toString()?.trim() || null;
|
|
11460
|
+
const profileDateOfBirth = profileDateOfBirthStr ? new Date(profileDateOfBirthStr).getTime() : null;
|
|
9760
11461
|
if (!firstName || !lastName || !username || !email) {
|
|
9761
11462
|
return c.html(renderAlert2({
|
|
9762
11463
|
type: "error",
|
|
@@ -9772,6 +11473,17 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9772
11473
|
dismissible: true
|
|
9773
11474
|
}));
|
|
9774
11475
|
}
|
|
11476
|
+
if (profileWebsite) {
|
|
11477
|
+
try {
|
|
11478
|
+
new URL(profileWebsite);
|
|
11479
|
+
} catch {
|
|
11480
|
+
return c.html(renderAlert2({
|
|
11481
|
+
type: "error",
|
|
11482
|
+
message: "Please enter a valid website URL.",
|
|
11483
|
+
dismissible: true
|
|
11484
|
+
}));
|
|
11485
|
+
}
|
|
11486
|
+
}
|
|
9775
11487
|
const checkStmt = db.prepare(`
|
|
9776
11488
|
SELECT id FROM users
|
|
9777
11489
|
WHERE (username = ? OR email = ?) AND id != ?
|
|
@@ -9780,14 +11492,14 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9780
11492
|
if (existingUser) {
|
|
9781
11493
|
return c.html(renderAlert2({
|
|
9782
11494
|
type: "error",
|
|
9783
|
-
message: "Username or email is already taken by another user
|
|
11495
|
+
message: "Username or email is already taken by another user.",
|
|
9784
11496
|
dismissible: true
|
|
9785
11497
|
}));
|
|
9786
11498
|
}
|
|
9787
11499
|
const updateStmt = db.prepare(`
|
|
9788
11500
|
UPDATE users SET
|
|
9789
11501
|
first_name = ?, last_name = ?, username = ?, email = ?,
|
|
9790
|
-
phone = ?,
|
|
11502
|
+
phone = ?, role = ?, is_active = ?, email_verified = ?,
|
|
9791
11503
|
updated_at = ?
|
|
9792
11504
|
WHERE id = ?
|
|
9793
11505
|
`);
|
|
@@ -9797,20 +11509,63 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9797
11509
|
username,
|
|
9798
11510
|
email,
|
|
9799
11511
|
phone,
|
|
9800
|
-
bio,
|
|
9801
11512
|
role,
|
|
9802
11513
|
isActive ? 1 : 0,
|
|
9803
11514
|
emailVerified ? 1 : 0,
|
|
9804
11515
|
Date.now(),
|
|
9805
11516
|
userId
|
|
9806
11517
|
).run();
|
|
9807
|
-
|
|
11518
|
+
const hasProfileData = profileDisplayName || profileBio || profileCompany || profileJobTitle || profileWebsite || profileLocation || profileDateOfBirth;
|
|
11519
|
+
if (hasProfileData) {
|
|
11520
|
+
const now = Date.now();
|
|
11521
|
+
const profileCheckStmt = db.prepare(`SELECT id FROM user_profiles WHERE user_id = ?`);
|
|
11522
|
+
const existingProfile = await profileCheckStmt.bind(userId).first();
|
|
11523
|
+
if (existingProfile) {
|
|
11524
|
+
const updateProfileStmt = db.prepare(`
|
|
11525
|
+
UPDATE user_profiles SET
|
|
11526
|
+
display_name = ?, bio = ?, company = ?, job_title = ?,
|
|
11527
|
+
website = ?, location = ?, date_of_birth = ?, updated_at = ?
|
|
11528
|
+
WHERE user_id = ?
|
|
11529
|
+
`);
|
|
11530
|
+
await updateProfileStmt.bind(
|
|
11531
|
+
profileDisplayName,
|
|
11532
|
+
profileBio,
|
|
11533
|
+
profileCompany,
|
|
11534
|
+
profileJobTitle,
|
|
11535
|
+
profileWebsite,
|
|
11536
|
+
profileLocation,
|
|
11537
|
+
profileDateOfBirth,
|
|
11538
|
+
now,
|
|
11539
|
+
userId
|
|
11540
|
+
).run();
|
|
11541
|
+
} else {
|
|
11542
|
+
const profileId = `profile_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
11543
|
+
const insertProfileStmt = db.prepare(`
|
|
11544
|
+
INSERT INTO user_profiles (id, user_id, display_name, bio, company, job_title, website, location, date_of_birth, created_at, updated_at)
|
|
11545
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
11546
|
+
`);
|
|
11547
|
+
await insertProfileStmt.bind(
|
|
11548
|
+
profileId,
|
|
11549
|
+
userId,
|
|
11550
|
+
profileDisplayName,
|
|
11551
|
+
profileBio,
|
|
11552
|
+
profileCompany,
|
|
11553
|
+
profileJobTitle,
|
|
11554
|
+
profileWebsite,
|
|
11555
|
+
profileLocation,
|
|
11556
|
+
profileDateOfBirth,
|
|
11557
|
+
now,
|
|
11558
|
+
now
|
|
11559
|
+
).run();
|
|
11560
|
+
}
|
|
11561
|
+
}
|
|
11562
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9808
11563
|
db,
|
|
9809
11564
|
user.userId,
|
|
9810
|
-
"user
|
|
11565
|
+
"user.update",
|
|
9811
11566
|
"users",
|
|
9812
11567
|
userId,
|
|
9813
|
-
{ fields: ["first_name", "last_name", "username", "email", "phone", "
|
|
11568
|
+
{ fields: ["first_name", "last_name", "username", "email", "phone", "role", "is_active", "email_verified", "profile"] },
|
|
9814
11569
|
c.req.header("x-forwarded-for") || c.req.header("cf-connecting-ip"),
|
|
9815
11570
|
c.req.header("user-agent")
|
|
9816
11571
|
);
|
|
@@ -9823,7 +11578,7 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9823
11578
|
console.error("User update error:", error);
|
|
9824
11579
|
return c.html(renderAlert2({
|
|
9825
11580
|
type: "error",
|
|
9826
|
-
message: "Failed to update user
|
|
11581
|
+
message: "Failed to update user. Please try again.",
|
|
9827
11582
|
dismissible: true
|
|
9828
11583
|
}));
|
|
9829
11584
|
}
|
|
@@ -9849,7 +11604,7 @@ userRoutes.post("/users/:id/toggle", async (c) => {
|
|
|
9849
11604
|
UPDATE users SET is_active = ?, updated_at = ? WHERE id = ?
|
|
9850
11605
|
`);
|
|
9851
11606
|
await toggleStmt.bind(active ? 1 : 0, Date.now(), userId).run();
|
|
9852
|
-
await
|
|
11607
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9853
11608
|
db,
|
|
9854
11609
|
user.userId,
|
|
9855
11610
|
active ? "user.activate" : "user.deactivate",
|
|
@@ -9890,7 +11645,7 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
9890
11645
|
DELETE FROM users WHERE id = ?
|
|
9891
11646
|
`);
|
|
9892
11647
|
await deleteStmt.bind(userId).run();
|
|
9893
|
-
await
|
|
11648
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9894
11649
|
db,
|
|
9895
11650
|
user.userId,
|
|
9896
11651
|
"user!.hard_delete",
|
|
@@ -9909,7 +11664,7 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
9909
11664
|
UPDATE users SET is_active = 0, updated_at = ? WHERE id = ?
|
|
9910
11665
|
`);
|
|
9911
11666
|
await deleteStmt.bind(Date.now(), userId).run();
|
|
9912
|
-
await
|
|
11667
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9913
11668
|
db,
|
|
9914
11669
|
user.userId,
|
|
9915
11670
|
"user!.soft_delete",
|
|
@@ -9936,8 +11691,8 @@ userRoutes.post("/invite-user", async (c) => {
|
|
|
9936
11691
|
const formData = await c.req.formData();
|
|
9937
11692
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
9938
11693
|
const role = formData.get("role")?.toString()?.trim() || "viewer";
|
|
9939
|
-
const firstName =
|
|
9940
|
-
const lastName =
|
|
11694
|
+
const firstName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("first_name")?.toString());
|
|
11695
|
+
const lastName = chunkYMTTGHEK_cjs.sanitizeInput(formData.get("last_name")?.toString());
|
|
9941
11696
|
if (!email || !firstName || !lastName) {
|
|
9942
11697
|
return c.json({ error: "Email, first name, and last name are required" }, 400);
|
|
9943
11698
|
}
|
|
@@ -9975,7 +11730,7 @@ userRoutes.post("/invite-user", async (c) => {
|
|
|
9975
11730
|
Date.now(),
|
|
9976
11731
|
Date.now()
|
|
9977
11732
|
).run();
|
|
9978
|
-
await
|
|
11733
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
9979
11734
|
db,
|
|
9980
11735
|
user.userId,
|
|
9981
11736
|
"user!.invite_sent",
|
|
@@ -10032,7 +11787,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
|
|
|
10032
11787
|
Date.now(),
|
|
10033
11788
|
userId
|
|
10034
11789
|
).run();
|
|
10035
|
-
await
|
|
11790
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
10036
11791
|
db,
|
|
10037
11792
|
user.userId,
|
|
10038
11793
|
"user!.invitation_resent",
|
|
@@ -10068,7 +11823,7 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
|
|
|
10068
11823
|
}
|
|
10069
11824
|
const deleteStmt = db.prepare(`DELETE FROM users WHERE id = ?`);
|
|
10070
11825
|
await deleteStmt.bind(userId).run();
|
|
10071
|
-
await
|
|
11826
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
10072
11827
|
db,
|
|
10073
11828
|
user.userId,
|
|
10074
11829
|
"user!.invitation_cancelled",
|
|
@@ -10151,7 +11906,7 @@ userRoutes.get("/activity-logs", async (c) => {
|
|
|
10151
11906
|
...log,
|
|
10152
11907
|
details: log.details ? JSON.parse(log.details) : null
|
|
10153
11908
|
}));
|
|
10154
|
-
await
|
|
11909
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
10155
11910
|
db,
|
|
10156
11911
|
user.userId,
|
|
10157
11912
|
"activity.logs_viewed",
|
|
@@ -10258,7 +12013,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
|
|
|
10258
12013
|
csvRows.push(row.join(","));
|
|
10259
12014
|
}
|
|
10260
12015
|
const csvContent = csvRows.join("\n");
|
|
10261
|
-
await
|
|
12016
|
+
await chunkT3YIKW2A_cjs.logActivity(
|
|
10262
12017
|
db,
|
|
10263
12018
|
user.userId,
|
|
10264
12019
|
"activity.logs_exported",
|
|
@@ -10476,7 +12231,7 @@ function getFileIcon(mimeType) {
|
|
|
10476
12231
|
}
|
|
10477
12232
|
|
|
10478
12233
|
// src/templates/pages/admin-media-library.template.ts
|
|
10479
|
-
|
|
12234
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
10480
12235
|
function renderMediaLibraryPage(data) {
|
|
10481
12236
|
const pageContent = `
|
|
10482
12237
|
<div>
|
|
@@ -11411,7 +13166,7 @@ function renderMediaLibraryPage(data) {
|
|
|
11411
13166
|
version: data.version,
|
|
11412
13167
|
content: pageContent
|
|
11413
13168
|
};
|
|
11414
|
-
return
|
|
13169
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
11415
13170
|
}
|
|
11416
13171
|
|
|
11417
13172
|
// src/templates/components/media-file-details.template.ts
|
|
@@ -11597,7 +13352,7 @@ var fileValidationSchema2 = zod.z.object({
|
|
|
11597
13352
|
// 50MB max
|
|
11598
13353
|
});
|
|
11599
13354
|
var adminMediaRoutes = new hono.Hono();
|
|
11600
|
-
adminMediaRoutes.use("*",
|
|
13355
|
+
adminMediaRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
|
|
11601
13356
|
adminMediaRoutes.get("/", async (c) => {
|
|
11602
13357
|
try {
|
|
11603
13358
|
const user = c.get("user");
|
|
@@ -12183,7 +13938,7 @@ adminMediaRoutes.put("/:id", async (c) => {
|
|
|
12183
13938
|
`);
|
|
12184
13939
|
}
|
|
12185
13940
|
});
|
|
12186
|
-
adminMediaRoutes.delete("/cleanup",
|
|
13941
|
+
adminMediaRoutes.delete("/cleanup", chunkT3YIKW2A_cjs.requireRole("admin"), async (c) => {
|
|
12187
13942
|
try {
|
|
12188
13943
|
const db = c.env.DB;
|
|
12189
13944
|
const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
|
|
@@ -12433,7 +14188,7 @@ function formatFileSize(bytes) {
|
|
|
12433
14188
|
}
|
|
12434
14189
|
|
|
12435
14190
|
// src/templates/pages/admin-plugins-list.template.ts
|
|
12436
|
-
|
|
14191
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
12437
14192
|
function renderPluginsListPage(data) {
|
|
12438
14193
|
const categories = [
|
|
12439
14194
|
{ value: "content", label: "Content Management" },
|
|
@@ -12913,7 +14668,7 @@ function renderPluginsListPage(data) {
|
|
|
12913
14668
|
version: data.version,
|
|
12914
14669
|
content: pageContent
|
|
12915
14670
|
};
|
|
12916
|
-
return
|
|
14671
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
12917
14672
|
}
|
|
12918
14673
|
function renderPluginCard(plugin) {
|
|
12919
14674
|
const statusColors = {
|
|
@@ -13317,6 +15072,9 @@ function renderAuthSettingsForm(settings) {
|
|
|
13317
15072
|
}
|
|
13318
15073
|
|
|
13319
15074
|
// src/templates/pages/admin-plugin-settings.template.ts
|
|
15075
|
+
function escapeHtmlAttr(value) {
|
|
15076
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
15077
|
+
}
|
|
13320
15078
|
function renderPluginSettingsPage(data) {
|
|
13321
15079
|
const { plugin, activity = [], user } = data;
|
|
13322
15080
|
const pageContent = `
|
|
@@ -13565,7 +15323,7 @@ function renderPluginSettingsPage(data) {
|
|
|
13565
15323
|
user,
|
|
13566
15324
|
content: pageContent
|
|
13567
15325
|
};
|
|
13568
|
-
return
|
|
15326
|
+
return chunkBZC4FYW7_cjs.renderAdminLayout(layoutData);
|
|
13569
15327
|
}
|
|
13570
15328
|
function renderStatusBadge(status) {
|
|
13571
15329
|
const statusColors = {
|
|
@@ -13594,6 +15352,7 @@ function renderSettingsTab(plugin) {
|
|
|
13594
15352
|
const settings = plugin.settings || {};
|
|
13595
15353
|
const isSeedDataPlugin = plugin.id === "seed-data" || plugin.name === "seed-data";
|
|
13596
15354
|
const isAuthPlugin = plugin.id === "core-auth" || plugin.name === "core-auth";
|
|
15355
|
+
const isTurnstilePlugin = plugin.id === "turnstile" || plugin.name === "turnstile";
|
|
13597
15356
|
return `
|
|
13598
15357
|
${isSeedDataPlugin ? `
|
|
13599
15358
|
<div class="backdrop-blur-md bg-black/20 rounded-xl border border-white/10 shadow-xl p-6 mb-6">
|
|
@@ -13620,12 +15379,15 @@ function renderSettingsTab(plugin) {
|
|
|
13620
15379
|
${isAuthPlugin ? `
|
|
13621
15380
|
<h2 class="text-xl font-semibold text-white mb-4">Authentication Settings</h2>
|
|
13622
15381
|
<p class="text-gray-400 mb-6">Configure user registration fields and validation rules.</p>
|
|
15382
|
+
` : isTurnstilePlugin ? `
|
|
15383
|
+
<h2 class="text-xl font-semibold text-white mb-4">Cloudflare Turnstile Settings</h2>
|
|
15384
|
+
<p class="text-gray-400 mb-6">Configure CAPTCHA-free bot protection for your forms.</p>
|
|
13623
15385
|
` : `
|
|
13624
15386
|
<h2 class="text-xl font-semibold text-white mb-4">Plugin Settings</h2>
|
|
13625
15387
|
`}
|
|
13626
15388
|
|
|
13627
15389
|
<form id="settings-form" class="space-y-6">
|
|
13628
|
-
${isAuthPlugin && Object.keys(settings).length > 0 ? renderAuthSettingsForm(settings) : Object.keys(settings).length > 0 ? renderSettingsFields(settings) : renderNoSettings(plugin)}
|
|
15390
|
+
${isAuthPlugin && Object.keys(settings).length > 0 ? renderAuthSettingsForm(settings) : isTurnstilePlugin && Object.keys(settings).length > 0 ? renderTurnstileSettingsForm(settings) : Object.keys(settings).length > 0 ? renderSettingsFields(settings) : renderNoSettings(plugin)}
|
|
13629
15391
|
|
|
13630
15392
|
${Object.keys(settings).length > 0 ? `
|
|
13631
15393
|
<div class="flex items-center justify-end pt-6 border-t border-white/10">
|
|
@@ -13689,6 +15451,80 @@ function renderSettingsFields(settings) {
|
|
|
13689
15451
|
}
|
|
13690
15452
|
}).join("");
|
|
13691
15453
|
}
|
|
15454
|
+
function renderTurnstileSettingsForm(settings) {
|
|
15455
|
+
const inputClass = "backdrop-blur-sm bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white placeholder-gray-300 focus:border-blue-400 focus:outline-none transition-colors w-full";
|
|
15456
|
+
const selectClass = "backdrop-blur-sm bg-zinc-800 border border-white/20 rounded-lg px-3 py-2 text-white focus:border-blue-400 focus:outline-none transition-colors w-full [&>option]:bg-zinc-800 [&>option]:text-white";
|
|
15457
|
+
return `
|
|
15458
|
+
<!-- Enable Toggle -->
|
|
15459
|
+
<div class="flex items-center justify-between">
|
|
15460
|
+
<div>
|
|
15461
|
+
<label for="setting_enabled" class="text-sm font-medium text-gray-300">Enable Turnstile</label>
|
|
15462
|
+
<p class="text-xs text-gray-400">Enable or disable Turnstile verification globally</p>
|
|
15463
|
+
</div>
|
|
15464
|
+
<label class="relative inline-flex items-center cursor-pointer">
|
|
15465
|
+
<input type="checkbox" name="setting_enabled" id="setting_enabled" ${settings.enabled ? "checked" : ""} class="sr-only peer">
|
|
15466
|
+
<div class="w-11 h-6 bg-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
|
|
15467
|
+
</label>
|
|
15468
|
+
</div>
|
|
15469
|
+
|
|
15470
|
+
<!-- Site Key -->
|
|
15471
|
+
<div>
|
|
15472
|
+
<label for="setting_siteKey" class="block text-sm font-medium text-gray-300 mb-2">Site Key</label>
|
|
15473
|
+
<input type="text" name="setting_siteKey" id="setting_siteKey" value="${escapeHtmlAttr(settings.siteKey || "")}" placeholder="0x4AAAAAAAA..." class="${inputClass}">
|
|
15474
|
+
<p class="text-xs text-gray-400 mt-1">Your Cloudflare Turnstile site key (public)</p>
|
|
15475
|
+
</div>
|
|
15476
|
+
|
|
15477
|
+
<!-- Secret Key -->
|
|
15478
|
+
<div>
|
|
15479
|
+
<label for="setting_secretKey" class="block text-sm font-medium text-gray-300 mb-2">Secret Key</label>
|
|
15480
|
+
<input type="password" name="setting_secretKey" id="setting_secretKey" value="${escapeHtmlAttr(settings.secretKey || "")}" placeholder="0x4AAAAAAAA..." class="${inputClass}">
|
|
15481
|
+
<p class="text-xs text-gray-400 mt-1">Your Cloudflare Turnstile secret key (private)</p>
|
|
15482
|
+
</div>
|
|
15483
|
+
|
|
15484
|
+
<!-- Theme -->
|
|
15485
|
+
<div>
|
|
15486
|
+
<label for="setting_theme" class="block text-sm font-medium text-gray-300 mb-2">Widget Theme</label>
|
|
15487
|
+
<select name="setting_theme" id="setting_theme" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
|
|
15488
|
+
<option value="auto" ${settings.theme === "auto" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Auto (matches page theme)</option>
|
|
15489
|
+
<option value="light" ${settings.theme === "light" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Light</option>
|
|
15490
|
+
<option value="dark" ${settings.theme === "dark" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Dark</option>
|
|
15491
|
+
</select>
|
|
15492
|
+
<p class="text-xs text-gray-400 mt-1">Visual appearance of the Turnstile widget</p>
|
|
15493
|
+
</div>
|
|
15494
|
+
|
|
15495
|
+
<!-- Size -->
|
|
15496
|
+
<div>
|
|
15497
|
+
<label for="setting_size" class="block text-sm font-medium text-gray-300 mb-2">Widget Size</label>
|
|
15498
|
+
<select name="setting_size" id="setting_size" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
|
|
15499
|
+
<option value="normal" ${settings.size === "normal" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Normal (300x65px)</option>
|
|
15500
|
+
<option value="compact" ${settings.size === "compact" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Compact (130x120px)</option>
|
|
15501
|
+
</select>
|
|
15502
|
+
<p class="text-xs text-gray-400 mt-1">Size of the Turnstile challenge widget</p>
|
|
15503
|
+
</div>
|
|
15504
|
+
|
|
15505
|
+
<!-- Widget Mode -->
|
|
15506
|
+
<div>
|
|
15507
|
+
<label for="setting_mode" class="block text-sm font-medium text-gray-300 mb-2">Widget Mode</label>
|
|
15508
|
+
<select name="setting_mode" id="setting_mode" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
|
|
15509
|
+
<option value="managed" ${!settings.mode || settings.mode === "managed" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Managed (Recommended) - Adaptive challenge</option>
|
|
15510
|
+
<option value="non-interactive" ${settings.mode === "non-interactive" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Non-Interactive - Always visible, minimal friction</option>
|
|
15511
|
+
<option value="invisible" ${settings.mode === "invisible" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Invisible - No visible widget</option>
|
|
15512
|
+
</select>
|
|
15513
|
+
<p class="text-xs text-gray-400 mt-1"><strong>Managed:</strong> Shows challenge only when needed. <strong>Non-Interactive:</strong> Always shows but doesn't require interaction. <strong>Invisible:</strong> Runs in background without UI.</p>
|
|
15514
|
+
</div>
|
|
15515
|
+
|
|
15516
|
+
<!-- Appearance (Pre-clearance) -->
|
|
15517
|
+
<div>
|
|
15518
|
+
<label for="setting_appearance" class="block text-sm font-medium text-gray-300 mb-2">Pre-clearance / Appearance</label>
|
|
15519
|
+
<select name="setting_appearance" id="setting_appearance" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
|
|
15520
|
+
<option value="always" ${!settings.appearance || settings.appearance === "always" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Always - Pre-clearance enabled (verifies immediately)</option>
|
|
15521
|
+
<option value="execute" ${settings.appearance === "execute" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Execute - Challenge on form submit</option>
|
|
15522
|
+
<option value="interaction-only" ${settings.appearance === "interaction-only" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Interaction Only - Only after user interaction</option>
|
|
15523
|
+
</select>
|
|
15524
|
+
<p class="text-xs text-gray-400 mt-1">Controls when Turnstile verification occurs. <strong>Always:</strong> Verifies immediately (pre-clearance). <strong>Execute:</strong> Verifies on form submit. <strong>Interaction Only:</strong> Only after user interaction.</p>
|
|
15525
|
+
</div>
|
|
15526
|
+
`;
|
|
15527
|
+
}
|
|
13692
15528
|
function renderNoSettings(plugin) {
|
|
13693
15529
|
if (plugin.id === "seed-data" || plugin.name === "seed-data") {
|
|
13694
15530
|
return `
|
|
@@ -13828,7 +15664,7 @@ function formatTimestamp(timestamp) {
|
|
|
13828
15664
|
|
|
13829
15665
|
// src/routes/admin-plugins.ts
|
|
13830
15666
|
var adminPluginRoutes = new hono.Hono();
|
|
13831
|
-
adminPluginRoutes.use("*",
|
|
15667
|
+
adminPluginRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
|
|
13832
15668
|
var AVAILABLE_PLUGINS = [
|
|
13833
15669
|
{
|
|
13834
15670
|
id: "third-party-faq",
|
|
@@ -13920,6 +15756,32 @@ var AVAILABLE_PLUGINS = [
|
|
|
13920
15756
|
permissions: [],
|
|
13921
15757
|
dependencies: [],
|
|
13922
15758
|
is_core: false
|
|
15759
|
+
},
|
|
15760
|
+
{
|
|
15761
|
+
id: "turnstile",
|
|
15762
|
+
name: "turnstile-plugin",
|
|
15763
|
+
display_name: "Cloudflare Turnstile",
|
|
15764
|
+
description: "CAPTCHA-free bot protection for forms using Cloudflare Turnstile. Provides seamless spam prevention with configurable modes, themes, and pre-clearance options.",
|
|
15765
|
+
version: "1.0.0",
|
|
15766
|
+
author: "SonicJS Team",
|
|
15767
|
+
category: "security",
|
|
15768
|
+
icon: "\u{1F6E1}\uFE0F",
|
|
15769
|
+
permissions: [],
|
|
15770
|
+
dependencies: [],
|
|
15771
|
+
is_core: true
|
|
15772
|
+
},
|
|
15773
|
+
{
|
|
15774
|
+
id: "ai-search",
|
|
15775
|
+
name: "ai-search-plugin",
|
|
15776
|
+
display_name: "AI Search",
|
|
15777
|
+
description: "Advanced search with Cloudflare AI Search. Full-text search, semantic search, and advanced filtering across all content collections.",
|
|
15778
|
+
version: "1.0.0",
|
|
15779
|
+
author: "SonicJS Team",
|
|
15780
|
+
category: "search",
|
|
15781
|
+
icon: "\u{1F50D}",
|
|
15782
|
+
permissions: [],
|
|
15783
|
+
dependencies: [],
|
|
15784
|
+
is_core: true
|
|
13923
15785
|
}
|
|
13924
15786
|
];
|
|
13925
15787
|
adminPluginRoutes.get("/", async (c) => {
|
|
@@ -13929,7 +15791,7 @@ adminPluginRoutes.get("/", async (c) => {
|
|
|
13929
15791
|
if (user?.role !== "admin") {
|
|
13930
15792
|
return c.text("Access denied", 403);
|
|
13931
15793
|
}
|
|
13932
|
-
const pluginService = new
|
|
15794
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
13933
15795
|
let installedPlugins = [];
|
|
13934
15796
|
let stats = { total: 0, active: 0, inactive: 0, errors: 0, uninstalled: 0 };
|
|
13935
15797
|
try {
|
|
@@ -13998,10 +15860,13 @@ adminPluginRoutes.get("/:id", async (c) => {
|
|
|
13998
15860
|
const user = c.get("user");
|
|
13999
15861
|
const db = c.env.DB;
|
|
14000
15862
|
const pluginId = c.req.param("id");
|
|
15863
|
+
if (pluginId === "ai-search") {
|
|
15864
|
+
return c.text("", 404);
|
|
15865
|
+
}
|
|
14001
15866
|
if (user?.role !== "admin") {
|
|
14002
15867
|
return c.redirect("/admin/plugins");
|
|
14003
15868
|
}
|
|
14004
|
-
const pluginService = new
|
|
15869
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14005
15870
|
const plugin = await pluginService.getPlugin(pluginId);
|
|
14006
15871
|
if (!plugin) {
|
|
14007
15872
|
return c.text("Plugin not found", 404);
|
|
@@ -14055,7 +15920,7 @@ adminPluginRoutes.post("/:id/activate", async (c) => {
|
|
|
14055
15920
|
if (user?.role !== "admin") {
|
|
14056
15921
|
return c.json({ error: "Access denied" }, 403);
|
|
14057
15922
|
}
|
|
14058
|
-
const pluginService = new
|
|
15923
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14059
15924
|
await pluginService.activatePlugin(pluginId);
|
|
14060
15925
|
return c.json({ success: true });
|
|
14061
15926
|
} catch (error) {
|
|
@@ -14072,7 +15937,7 @@ adminPluginRoutes.post("/:id/deactivate", async (c) => {
|
|
|
14072
15937
|
if (user?.role !== "admin") {
|
|
14073
15938
|
return c.json({ error: "Access denied" }, 403);
|
|
14074
15939
|
}
|
|
14075
|
-
const pluginService = new
|
|
15940
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14076
15941
|
await pluginService.deactivatePlugin(pluginId);
|
|
14077
15942
|
return c.json({ success: true });
|
|
14078
15943
|
} catch (error) {
|
|
@@ -14089,7 +15954,7 @@ adminPluginRoutes.post("/install", async (c) => {
|
|
|
14089
15954
|
return c.json({ error: "Access denied" }, 403);
|
|
14090
15955
|
}
|
|
14091
15956
|
const body = await c.req.json();
|
|
14092
|
-
const pluginService = new
|
|
15957
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14093
15958
|
if (body.name === "faq-plugin") {
|
|
14094
15959
|
const faqPlugin = await pluginService.installPlugin({
|
|
14095
15960
|
id: "third-party-faq",
|
|
@@ -14290,6 +16155,60 @@ adminPluginRoutes.post("/install", async (c) => {
|
|
|
14290
16155
|
});
|
|
14291
16156
|
return c.json({ success: true, plugin: easyMdxPlugin2 });
|
|
14292
16157
|
}
|
|
16158
|
+
if (body.name === "ai-search-plugin" || body.name === "ai-search") {
|
|
16159
|
+
const defaultSettings = {
|
|
16160
|
+
enabled: true,
|
|
16161
|
+
ai_mode_enabled: true,
|
|
16162
|
+
selected_collections: [],
|
|
16163
|
+
dismissed_collections: [],
|
|
16164
|
+
autocomplete_enabled: true,
|
|
16165
|
+
cache_duration: 1,
|
|
16166
|
+
results_limit: 20,
|
|
16167
|
+
index_media: false
|
|
16168
|
+
};
|
|
16169
|
+
const aiSearchPlugin = await pluginService.installPlugin({
|
|
16170
|
+
id: "ai-search",
|
|
16171
|
+
name: "ai-search-plugin",
|
|
16172
|
+
display_name: "AI Search",
|
|
16173
|
+
description: "Advanced search with Cloudflare AI Search. Full-text search, semantic search, and advanced filtering across all content collections.",
|
|
16174
|
+
version: "1.0.0",
|
|
16175
|
+
author: "SonicJS Team",
|
|
16176
|
+
category: "search",
|
|
16177
|
+
icon: "\u{1F50D}",
|
|
16178
|
+
permissions: [],
|
|
16179
|
+
dependencies: [],
|
|
16180
|
+
is_core: true,
|
|
16181
|
+
settings: defaultSettings
|
|
16182
|
+
});
|
|
16183
|
+
return c.json({ success: true, plugin: aiSearchPlugin });
|
|
16184
|
+
}
|
|
16185
|
+
if (body.name === "turnstile-plugin") {
|
|
16186
|
+
const turnstilePlugin = await pluginService.installPlugin({
|
|
16187
|
+
id: "turnstile",
|
|
16188
|
+
name: "turnstile-plugin",
|
|
16189
|
+
display_name: "Cloudflare Turnstile",
|
|
16190
|
+
description: "CAPTCHA-free bot protection for forms using Cloudflare Turnstile. Provides seamless spam prevention with configurable modes, themes, and pre-clearance options.",
|
|
16191
|
+
version: "1.0.0",
|
|
16192
|
+
author: "SonicJS Team",
|
|
16193
|
+
category: "security",
|
|
16194
|
+
icon: "\u{1F6E1}\uFE0F",
|
|
16195
|
+
permissions: [],
|
|
16196
|
+
dependencies: [],
|
|
16197
|
+
is_core: true,
|
|
16198
|
+
settings: {
|
|
16199
|
+
siteKey: "",
|
|
16200
|
+
secretKey: "",
|
|
16201
|
+
theme: "auto",
|
|
16202
|
+
size: "normal",
|
|
16203
|
+
mode: "managed",
|
|
16204
|
+
appearance: "always",
|
|
16205
|
+
preClearanceEnabled: false,
|
|
16206
|
+
preClearanceLevel: "managed",
|
|
16207
|
+
enabled: false
|
|
16208
|
+
}
|
|
16209
|
+
});
|
|
16210
|
+
return c.json({ success: true, plugin: turnstilePlugin });
|
|
16211
|
+
}
|
|
14293
16212
|
return c.json({ error: "Plugin not found in registry" }, 404);
|
|
14294
16213
|
} catch (error) {
|
|
14295
16214
|
console.error("Error installing plugin:", error);
|
|
@@ -14305,7 +16224,7 @@ adminPluginRoutes.post("/:id/uninstall", async (c) => {
|
|
|
14305
16224
|
if (user?.role !== "admin") {
|
|
14306
16225
|
return c.json({ error: "Access denied" }, 403);
|
|
14307
16226
|
}
|
|
14308
|
-
const pluginService = new
|
|
16227
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14309
16228
|
await pluginService.uninstallPlugin(pluginId);
|
|
14310
16229
|
return c.json({ success: true });
|
|
14311
16230
|
} catch (error) {
|
|
@@ -14323,7 +16242,7 @@ adminPluginRoutes.post("/:id/settings", async (c) => {
|
|
|
14323
16242
|
return c.json({ error: "Access denied" }, 403);
|
|
14324
16243
|
}
|
|
14325
16244
|
const settings = await c.req.json();
|
|
14326
|
-
const pluginService = new
|
|
16245
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14327
16246
|
await pluginService.updatePluginSettings(pluginId, settings);
|
|
14328
16247
|
return c.json({ success: true });
|
|
14329
16248
|
} catch (error) {
|
|
@@ -14344,7 +16263,7 @@ function formatLastUpdated(timestamp) {
|
|
|
14344
16263
|
}
|
|
14345
16264
|
|
|
14346
16265
|
// src/templates/pages/admin-logs-list.template.ts
|
|
14347
|
-
|
|
16266
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
14348
16267
|
function renderLogsListPage(data) {
|
|
14349
16268
|
const { logs, pagination, filters, user } = data;
|
|
14350
16269
|
const content = `
|
|
@@ -14655,7 +16574,7 @@ function renderLogsListPage(data) {
|
|
|
14655
16574
|
user,
|
|
14656
16575
|
content
|
|
14657
16576
|
};
|
|
14658
|
-
return
|
|
16577
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
14659
16578
|
}
|
|
14660
16579
|
function renderLogDetailsPage(data) {
|
|
14661
16580
|
const { log, user } = data;
|
|
@@ -14867,7 +16786,7 @@ function renderLogDetailsPage(data) {
|
|
|
14867
16786
|
</div>
|
|
14868
16787
|
</div>
|
|
14869
16788
|
`;
|
|
14870
|
-
return
|
|
16789
|
+
return chunkBZC4FYW7_cjs.adminLayoutV2({
|
|
14871
16790
|
title: `Log Details - ${log.id}`,
|
|
14872
16791
|
user,
|
|
14873
16792
|
content
|
|
@@ -15110,7 +17029,7 @@ function renderLogConfigPage(data) {
|
|
|
15110
17029
|
|
|
15111
17030
|
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
|
|
15112
17031
|
`;
|
|
15113
|
-
return
|
|
17032
|
+
return chunkBZC4FYW7_cjs.adminLayoutV2({
|
|
15114
17033
|
title: "Log Configuration",
|
|
15115
17034
|
user,
|
|
15116
17035
|
content
|
|
@@ -15119,7 +17038,7 @@ function renderLogConfigPage(data) {
|
|
|
15119
17038
|
|
|
15120
17039
|
// src/routes/admin-logs.ts
|
|
15121
17040
|
var adminLogsRoutes = new hono.Hono();
|
|
15122
|
-
adminLogsRoutes.use("*",
|
|
17041
|
+
adminLogsRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
|
|
15123
17042
|
adminLogsRoutes.get("/", async (c) => {
|
|
15124
17043
|
try {
|
|
15125
17044
|
const user = c.get("user");
|
|
@@ -15491,7 +17410,7 @@ adminDesignRoutes.get("/", (c) => {
|
|
|
15491
17410
|
role: user.role
|
|
15492
17411
|
} : void 0
|
|
15493
17412
|
};
|
|
15494
|
-
return c.html(
|
|
17413
|
+
return c.html(chunkBZC4FYW7_cjs.renderDesignPage(pageData));
|
|
15495
17414
|
});
|
|
15496
17415
|
var adminCheckboxRoutes = new hono.Hono();
|
|
15497
17416
|
adminCheckboxRoutes.get("/", (c) => {
|
|
@@ -15503,7 +17422,7 @@ adminCheckboxRoutes.get("/", (c) => {
|
|
|
15503
17422
|
role: user.role
|
|
15504
17423
|
} : void 0
|
|
15505
17424
|
};
|
|
15506
|
-
return c.html(
|
|
17425
|
+
return c.html(chunkBZC4FYW7_cjs.renderCheckboxPage(pageData));
|
|
15507
17426
|
});
|
|
15508
17427
|
|
|
15509
17428
|
// src/templates/pages/admin-testimonials-form.template.ts
|
|
@@ -15531,7 +17450,7 @@ function renderTestimonialsForm(data) {
|
|
|
15531
17450
|
</div>
|
|
15532
17451
|
</div>
|
|
15533
17452
|
|
|
15534
|
-
${message ?
|
|
17453
|
+
${message ? chunkBZC4FYW7_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
|
|
15535
17454
|
|
|
15536
17455
|
<!-- Form -->
|
|
15537
17456
|
<div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
|
|
@@ -15760,7 +17679,7 @@ function renderTestimonialsForm(data) {
|
|
|
15760
17679
|
user: data.user,
|
|
15761
17680
|
content: pageContent
|
|
15762
17681
|
};
|
|
15763
|
-
return
|
|
17682
|
+
return chunkBZC4FYW7_cjs.renderAdminLayout(layoutData);
|
|
15764
17683
|
}
|
|
15765
17684
|
function escapeHtml4(unsafe) {
|
|
15766
17685
|
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -15786,7 +17705,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
|
|
|
15786
17705
|
const offset = (currentPage - 1) * limit;
|
|
15787
17706
|
const db = c.env?.DB;
|
|
15788
17707
|
if (!db) {
|
|
15789
|
-
return c.html(
|
|
17708
|
+
return c.html(chunkBZC4FYW7_cjs.renderTestimonialsList({
|
|
15790
17709
|
testimonials: [],
|
|
15791
17710
|
totalCount: 0,
|
|
15792
17711
|
currentPage: 1,
|
|
@@ -15826,7 +17745,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
|
|
|
15826
17745
|
`;
|
|
15827
17746
|
const { results: testimonials } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
|
|
15828
17747
|
const totalPages = Math.ceil(totalCount / limit);
|
|
15829
|
-
return c.html(
|
|
17748
|
+
return c.html(chunkBZC4FYW7_cjs.renderTestimonialsList({
|
|
15830
17749
|
testimonials: testimonials || [],
|
|
15831
17750
|
totalCount,
|
|
15832
17751
|
currentPage,
|
|
@@ -15840,7 +17759,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
|
|
|
15840
17759
|
} catch (error) {
|
|
15841
17760
|
console.error("Error fetching testimonials:", error);
|
|
15842
17761
|
const user = c.get("user");
|
|
15843
|
-
return c.html(
|
|
17762
|
+
return c.html(chunkBZC4FYW7_cjs.renderTestimonialsList({
|
|
15844
17763
|
testimonials: [],
|
|
15845
17764
|
totalCount: 0,
|
|
15846
17765
|
currentPage: 1,
|
|
@@ -16159,7 +18078,7 @@ function renderCodeExamplesForm(data) {
|
|
|
16159
18078
|
</div>
|
|
16160
18079
|
</div>
|
|
16161
18080
|
|
|
16162
|
-
${message ?
|
|
18081
|
+
${message ? chunkBZC4FYW7_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
|
|
16163
18082
|
|
|
16164
18083
|
<!-- Form -->
|
|
16165
18084
|
<div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
|
|
@@ -16429,7 +18348,7 @@ function renderCodeExamplesForm(data) {
|
|
|
16429
18348
|
user: data.user,
|
|
16430
18349
|
content: pageContent
|
|
16431
18350
|
};
|
|
16432
|
-
return
|
|
18351
|
+
return chunkBZC4FYW7_cjs.renderAdminLayout(layoutData);
|
|
16433
18352
|
}
|
|
16434
18353
|
function escapeHtml5(unsafe) {
|
|
16435
18354
|
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -16456,7 +18375,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
|
|
|
16456
18375
|
const offset = (currentPage - 1) * limit;
|
|
16457
18376
|
const db = c.env?.DB;
|
|
16458
18377
|
if (!db) {
|
|
16459
|
-
return c.html(
|
|
18378
|
+
return c.html(chunkBZC4FYW7_cjs.renderCodeExamplesList({
|
|
16460
18379
|
codeExamples: [],
|
|
16461
18380
|
totalCount: 0,
|
|
16462
18381
|
currentPage: 1,
|
|
@@ -16496,7 +18415,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
|
|
|
16496
18415
|
`;
|
|
16497
18416
|
const { results: codeExamples } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
|
|
16498
18417
|
const totalPages = Math.ceil(totalCount / limit);
|
|
16499
|
-
return c.html(
|
|
18418
|
+
return c.html(chunkBZC4FYW7_cjs.renderCodeExamplesList({
|
|
16500
18419
|
codeExamples: codeExamples || [],
|
|
16501
18420
|
totalCount,
|
|
16502
18421
|
currentPage,
|
|
@@ -16510,7 +18429,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
|
|
|
16510
18429
|
} catch (error) {
|
|
16511
18430
|
console.error("Error fetching code examples:", error);
|
|
16512
18431
|
const user = c.get("user");
|
|
16513
|
-
return c.html(
|
|
18432
|
+
return c.html(chunkBZC4FYW7_cjs.renderCodeExamplesList({
|
|
16514
18433
|
codeExamples: [],
|
|
16515
18434
|
totalCount: 0,
|
|
16516
18435
|
currentPage: 1,
|
|
@@ -16899,7 +18818,7 @@ function renderDashboardPage(data) {
|
|
|
16899
18818
|
version: data.version,
|
|
16900
18819
|
content: pageContent
|
|
16901
18820
|
};
|
|
16902
|
-
return
|
|
18821
|
+
return chunkBZC4FYW7_cjs.renderAdminLayout(layoutData);
|
|
16903
18822
|
}
|
|
16904
18823
|
function renderStatsCards(stats) {
|
|
16905
18824
|
const cards = [
|
|
@@ -17447,9 +19366,9 @@ function renderStorageUsage(databaseSizeBytes, mediaSizeBytes) {
|
|
|
17447
19366
|
}
|
|
17448
19367
|
|
|
17449
19368
|
// src/routes/admin-dashboard.ts
|
|
17450
|
-
var VERSION =
|
|
19369
|
+
var VERSION = chunkYMTTGHEK_cjs.getCoreVersion();
|
|
17451
19370
|
var router = new hono.Hono();
|
|
17452
|
-
router.use("*",
|
|
19371
|
+
router.use("*", chunkT3YIKW2A_cjs.requireAuth());
|
|
17453
19372
|
router.get("/", async (c) => {
|
|
17454
19373
|
const user = c.get("user");
|
|
17455
19374
|
try {
|
|
@@ -17674,7 +19593,7 @@ router.get("/system-status", async (c) => {
|
|
|
17674
19593
|
});
|
|
17675
19594
|
|
|
17676
19595
|
// src/templates/pages/admin-collections-list.template.ts
|
|
17677
|
-
|
|
19596
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
17678
19597
|
|
|
17679
19598
|
// src/templates/components/table.template.ts
|
|
17680
19599
|
function renderTable2(data) {
|
|
@@ -18148,11 +20067,11 @@ function renderCollectionsListPage(data) {
|
|
|
18148
20067
|
version: data.version,
|
|
18149
20068
|
content: pageContent
|
|
18150
20069
|
};
|
|
18151
|
-
return
|
|
20070
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
18152
20071
|
}
|
|
18153
20072
|
|
|
18154
20073
|
// src/templates/pages/admin-collections-form.template.ts
|
|
18155
|
-
|
|
20074
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
18156
20075
|
function getFieldTypeBadge(fieldType) {
|
|
18157
20076
|
const typeLabels = {
|
|
18158
20077
|
"text": "Text",
|
|
@@ -18413,7 +20332,7 @@ function renderCollectionFormPage(data) {
|
|
|
18413
20332
|
}
|
|
18414
20333
|
</style>
|
|
18415
20334
|
|
|
18416
|
-
${
|
|
20335
|
+
${chunkBZC4FYW7_cjs.renderForm(formData)}
|
|
18417
20336
|
|
|
18418
20337
|
${isEdit && data.managed ? `
|
|
18419
20338
|
<!-- Read-Only Fields Display for Managed Collections -->
|
|
@@ -19202,12 +21121,12 @@ function renderCollectionFormPage(data) {
|
|
|
19202
21121
|
version: data.version,
|
|
19203
21122
|
content: pageContent
|
|
19204
21123
|
};
|
|
19205
|
-
return
|
|
21124
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
19206
21125
|
}
|
|
19207
21126
|
|
|
19208
21127
|
// src/routes/admin-collections.ts
|
|
19209
21128
|
var adminCollectionsRoutes = new hono.Hono();
|
|
19210
|
-
adminCollectionsRoutes.use("*",
|
|
21129
|
+
adminCollectionsRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
|
|
19211
21130
|
adminCollectionsRoutes.get("/", async (c) => {
|
|
19212
21131
|
try {
|
|
19213
21132
|
const user = c.get("user");
|
|
@@ -19463,16 +21382,30 @@ adminCollectionsRoutes.get("/:id", async (c) => {
|
|
|
19463
21382
|
const schema = typeof collection.schema === "string" ? JSON.parse(collection.schema) : collection.schema;
|
|
19464
21383
|
if (schema && schema.properties) {
|
|
19465
21384
|
let fieldOrder = 0;
|
|
19466
|
-
fields = Object.entries(schema.properties).map(([fieldName, fieldConfig]) =>
|
|
19467
|
-
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
|
|
19475
|
-
|
|
21385
|
+
fields = Object.entries(schema.properties).map(([fieldName, fieldConfig]) => {
|
|
21386
|
+
let fieldType = fieldConfig.type || "string";
|
|
21387
|
+
if (fieldConfig.enum) {
|
|
21388
|
+
fieldType = "select";
|
|
21389
|
+
} else if (fieldConfig.format === "richtext") {
|
|
21390
|
+
fieldType = "richtext";
|
|
21391
|
+
} else if (fieldConfig.format === "media") {
|
|
21392
|
+
fieldType = "media";
|
|
21393
|
+
} else if (fieldConfig.format === "date-time") {
|
|
21394
|
+
fieldType = "date";
|
|
21395
|
+
} else if (fieldConfig.type === "slug" || fieldConfig.format === "slug") {
|
|
21396
|
+
fieldType = "slug";
|
|
21397
|
+
}
|
|
21398
|
+
return {
|
|
21399
|
+
id: `schema-${fieldName}`,
|
|
21400
|
+
field_name: fieldName,
|
|
21401
|
+
field_type: fieldType,
|
|
21402
|
+
field_label: fieldConfig.title || fieldName,
|
|
21403
|
+
field_options: fieldConfig,
|
|
21404
|
+
field_order: fieldOrder++,
|
|
21405
|
+
is_required: fieldConfig.required === true || schema.required && schema.required.includes(fieldName),
|
|
21406
|
+
is_searchable: fieldConfig.searchable === true || false
|
|
21407
|
+
};
|
|
21408
|
+
});
|
|
19476
21409
|
}
|
|
19477
21410
|
} catch (e) {
|
|
19478
21411
|
console.error("Error parsing collection schema:", e);
|
|
@@ -19687,6 +21620,9 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
|
|
|
19687
21620
|
fieldConfig.enum = parsedOptions.options || [];
|
|
19688
21621
|
} else if (fieldType === "media") {
|
|
19689
21622
|
fieldConfig.format = "media";
|
|
21623
|
+
} else if (fieldType === "slug") {
|
|
21624
|
+
fieldConfig.type = "slug";
|
|
21625
|
+
fieldConfig.format = "slug";
|
|
19690
21626
|
} else if (fieldType === "quill") {
|
|
19691
21627
|
fieldConfig.type = "quill";
|
|
19692
21628
|
} else if (fieldType === "mdxeditor") {
|
|
@@ -19906,7 +21842,7 @@ adminCollectionsRoutes.post("/:collectionId/fields/reorder", async (c) => {
|
|
|
19906
21842
|
});
|
|
19907
21843
|
|
|
19908
21844
|
// src/templates/pages/admin-settings.template.ts
|
|
19909
|
-
|
|
21845
|
+
chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
|
|
19910
21846
|
function renderSettingsPage(data) {
|
|
19911
21847
|
const activeTab = data.activeTab || "general";
|
|
19912
21848
|
const pageContent = `
|
|
@@ -20288,7 +22224,7 @@ function renderSettingsPage(data) {
|
|
|
20288
22224
|
version: data.version,
|
|
20289
22225
|
content: pageContent
|
|
20290
22226
|
};
|
|
20291
|
-
return
|
|
22227
|
+
return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
20292
22228
|
}
|
|
20293
22229
|
function renderTabButton(tabId, label, iconPath, activeTab) {
|
|
20294
22230
|
const isActive = activeTab === tabId;
|
|
@@ -21370,7 +23306,7 @@ function renderDatabaseToolsSettings(settings) {
|
|
|
21370
23306
|
|
|
21371
23307
|
// src/routes/admin-settings.ts
|
|
21372
23308
|
var adminSettingsRoutes = new hono.Hono();
|
|
21373
|
-
adminSettingsRoutes.use("*",
|
|
23309
|
+
adminSettingsRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
|
|
21374
23310
|
function getMockSettings(user) {
|
|
21375
23311
|
return {
|
|
21376
23312
|
general: {
|
|
@@ -21538,7 +23474,7 @@ adminSettingsRoutes.get("/database-tools", (c) => {
|
|
|
21538
23474
|
adminSettingsRoutes.get("/api/migrations/status", async (c) => {
|
|
21539
23475
|
try {
|
|
21540
23476
|
const db = c.env.DB;
|
|
21541
|
-
const migrationService = new
|
|
23477
|
+
const migrationService = new chunkIIRVZSP2_cjs.MigrationService(db);
|
|
21542
23478
|
const status = await migrationService.getMigrationStatus();
|
|
21543
23479
|
return c.json({
|
|
21544
23480
|
success: true,
|
|
@@ -21562,7 +23498,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
|
|
|
21562
23498
|
}, 403);
|
|
21563
23499
|
}
|
|
21564
23500
|
const db = c.env.DB;
|
|
21565
|
-
const migrationService = new
|
|
23501
|
+
const migrationService = new chunkIIRVZSP2_cjs.MigrationService(db);
|
|
21566
23502
|
const result = await migrationService.runPendingMigrations();
|
|
21567
23503
|
return c.json({
|
|
21568
23504
|
success: result.success,
|
|
@@ -21580,7 +23516,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
|
|
|
21580
23516
|
adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
|
|
21581
23517
|
try {
|
|
21582
23518
|
const db = c.env.DB;
|
|
21583
|
-
const migrationService = new
|
|
23519
|
+
const migrationService = new chunkIIRVZSP2_cjs.MigrationService(db);
|
|
21584
23520
|
const validation = await migrationService.validateSchema();
|
|
21585
23521
|
return c.json({
|
|
21586
23522
|
success: true,
|
|
@@ -21804,7 +23740,6 @@ var ROUTES_INFO = {
|
|
|
21804
23740
|
reference: "https://github.com/sonicjs/sonicjs"
|
|
21805
23741
|
};
|
|
21806
23742
|
|
|
21807
|
-
exports.PluginBuilder = PluginBuilder;
|
|
21808
23743
|
exports.ROUTES_INFO = ROUTES_INFO;
|
|
21809
23744
|
exports.adminCheckboxRoutes = adminCheckboxRoutes;
|
|
21810
23745
|
exports.adminCollectionsRoutes = adminCollectionsRoutes;
|
|
@@ -21822,9 +23757,8 @@ exports.api_default = api_default;
|
|
|
21822
23757
|
exports.api_media_default = api_media_default;
|
|
21823
23758
|
exports.api_system_default = api_system_default;
|
|
21824
23759
|
exports.auth_default = auth_default;
|
|
21825
|
-
exports.checkAdminUserExists = checkAdminUserExists;
|
|
21826
23760
|
exports.router = router;
|
|
21827
23761
|
exports.test_cleanup_default = test_cleanup_default;
|
|
21828
23762
|
exports.userRoutes = userRoutes;
|
|
21829
|
-
//# sourceMappingURL=chunk-
|
|
21830
|
-
//# sourceMappingURL=chunk-
|
|
23763
|
+
//# sourceMappingURL=chunk-N7TDLOUE.cjs.map
|
|
23764
|
+
//# sourceMappingURL=chunk-N7TDLOUE.cjs.map
|