@sonicjs-cms/core 2.5.0 → 2.7.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-AYPF6C4D.cjs} +5 -5
- package/dist/{chunk-YIXSSJWD.cjs.map → chunk-AYPF6C4D.cjs.map} +1 -1
- package/dist/chunk-CLIH2T74.js +403 -0
- package/dist/chunk-CLIH2T74.js.map +1 -0
- package/dist/{chunk-BHNDALCA.js → chunk-DNHJS6RN.js} +6 -4
- package/dist/chunk-DNHJS6RN.js.map +1 -0
- package/dist/{chunk-YYV3XQOQ.cjs → chunk-E2BXLXPW.cjs} +7 -7
- package/dist/{chunk-YYV3XQOQ.cjs.map → chunk-E2BXLXPW.cjs.map} +1 -1
- package/dist/{chunk-AZLU3ROK.cjs → chunk-EHSZ6TAN.cjs} +11 -4
- package/dist/chunk-EHSZ6TAN.cjs.map +1 -0
- package/dist/{chunk-3YUHXWSG.js → chunk-F332TENF.js} +3 -3
- package/dist/{chunk-3YUHXWSG.js.map → chunk-F332TENF.js.map} +1 -1
- package/dist/{chunk-V5LBQN3I.js → chunk-GRN3GHUG.js} +11 -4
- package/dist/chunk-GRN3GHUG.js.map +1 -0
- package/dist/{chunk-UAQL2VWX.cjs → chunk-J7F3NPAP.cjs} +2436 -707
- package/dist/chunk-J7F3NPAP.cjs.map +1 -0
- package/dist/{chunk-VEL7QRYI.js → chunk-L2IDZI7F.js} +9 -2
- package/dist/chunk-L2IDZI7F.js.map +1 -0
- package/dist/{chunk-ILZ3DP4I.cjs → chunk-MPT5PA6U.cjs} +24 -2
- package/dist/chunk-MPT5PA6U.cjs.map +1 -0
- package/dist/{chunk-ZWV3EBZ7.cjs → chunk-MYB5RY7H.cjs} +6 -4
- package/dist/chunk-MYB5RY7H.cjs.map +1 -0
- package/dist/{chunk-OJZ45OJD.js → chunk-UISZ2MBW.js} +2272 -544
- package/dist/chunk-UISZ2MBW.js.map +1 -0
- package/dist/{chunk-AVPUX57O.js → chunk-V3KVSEG6.js} +3 -3
- package/dist/{chunk-AVPUX57O.js.map → chunk-V3KVSEG6.js.map} +1 -1
- package/dist/{chunk-TJTWRO4G.js → chunk-Y3EWJQ4D.js} +4 -4
- package/dist/{chunk-TJTWRO4G.js.map → chunk-Y3EWJQ4D.js.map} +1 -1
- package/dist/{chunk-LWG2MWDA.cjs → chunk-Y72M3MVX.cjs} +4 -4
- package/dist/{chunk-LWG2MWDA.cjs.map → chunk-Y72M3MVX.cjs.map} +1 -1
- 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-I4V3VZWF.cjs → chunk-YRFAQ6MI.cjs} +9 -2
- package/dist/chunk-YRFAQ6MI.cjs.map +1 -0
- package/dist/{collection-config-B6gMPunn.d.cts → collection-config-BF95LgQb.d.cts} +1 -1
- package/dist/{collection-config-B6gMPunn.d.ts → collection-config-BF95LgQb.d.ts} +1 -1
- package/dist/index.cjs +4098 -424
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +503 -8
- package/dist/index.d.ts +503 -8
- package/dist/index.js +4008 -341
- 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-LEMFV2ND.cjs +13 -0
- package/dist/{migrations-NIEUFG44.cjs.map → migrations-LEMFV2ND.cjs.map} +1 -1
- package/dist/migrations-RKQES6XY.js +4 -0
- package/dist/{migrations-TGZKJKV4.js.map → migrations-RKQES6XY.js.map} +1 -1
- package/dist/{plugin-bootstrap-dYhD9fQR.d.ts → plugin-bootstrap-CB-xaBfK.d.ts} +2 -2
- package/dist/{plugin-bootstrap-SHsdjE6X.d.cts → plugin-bootstrap-U-cw9jn3.d.cts} +2 -2
- package/dist/plugins.cjs +11 -11
- package/dist/plugins.js +2 -2
- package/dist/routes.cjs +27 -27
- package/dist/routes.d.cts +1 -1
- package/dist/routes.d.ts +1 -1
- package/dist/routes.js +7 -7
- 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 +14 -14
- package/dist/utils.d.cts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/migrations/029_ai_search_plugin.sql +45 -0
- package/package.json +4 -2
- package/dist/chunk-AI2JJIJX.cjs +0 -211
- package/dist/chunk-AI2JJIJX.cjs.map +0 -1
- package/dist/chunk-AZLU3ROK.cjs.map +0 -1
- package/dist/chunk-BHNDALCA.js.map +0 -1
- package/dist/chunk-I4V3VZWF.cjs.map +0 -1
- package/dist/chunk-ILZ3DP4I.cjs.map +0 -1
- package/dist/chunk-OJZ45OJD.js.map +0 -1
- package/dist/chunk-QDBNW7KQ.js +0 -209
- package/dist/chunk-QDBNW7KQ.js.map +0 -1
- package/dist/chunk-SGAG6FD3.js.map +0 -1
- package/dist/chunk-UAQL2VWX.cjs.map +0 -1
- package/dist/chunk-V5LBQN3I.js.map +0 -1
- package/dist/chunk-VEL7QRYI.js.map +0 -1
- package/dist/chunk-ZWV3EBZ7.cjs.map +0 -1
- package/dist/migrations-NIEUFG44.cjs +0 -13
- package/dist/migrations-TGZKJKV4.js +0 -4
|
@@ -1,12 +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
|
|
9
|
-
var
|
|
4
|
+
var chunkE2BXLXPW_cjs = require('./chunk-E2BXLXPW.cjs');
|
|
5
|
+
var chunkMPT5PA6U_cjs = require('./chunk-MPT5PA6U.cjs');
|
|
6
|
+
var chunkYRFAQ6MI_cjs = require('./chunk-YRFAQ6MI.cjs');
|
|
7
|
+
var chunkEHSZ6TAN_cjs = require('./chunk-EHSZ6TAN.cjs');
|
|
8
|
+
var chunkYHW27CBV_cjs = require('./chunk-YHW27CBV.cjs');
|
|
9
|
+
var chunkMYB5RY7H_cjs = require('./chunk-MYB5RY7H.cjs');
|
|
10
10
|
var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
|
|
11
11
|
var hono = require('hono');
|
|
12
12
|
var cors = require('hono/cors');
|
|
@@ -76,7 +76,7 @@ apiContentCrudRoutes.get("/:id", async (c) => {
|
|
|
76
76
|
}, 500);
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
|
-
apiContentCrudRoutes.post("/",
|
|
79
|
+
apiContentCrudRoutes.post("/", chunkE2BXLXPW_cjs.requireAuth(), async (c) => {
|
|
80
80
|
try {
|
|
81
81
|
const db = c.env.DB;
|
|
82
82
|
const user = c.get("user");
|
|
@@ -142,7 +142,7 @@ apiContentCrudRoutes.post("/", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
|
|
|
142
142
|
}, 500);
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
|
-
apiContentCrudRoutes.put("/:id",
|
|
145
|
+
apiContentCrudRoutes.put("/:id", chunkE2BXLXPW_cjs.requireAuth(), async (c) => {
|
|
146
146
|
try {
|
|
147
147
|
const id = c.req.param("id");
|
|
148
148
|
const db = c.env.DB;
|
|
@@ -206,7 +206,7 @@ apiContentCrudRoutes.put("/:id", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
|
|
|
206
206
|
}, 500);
|
|
207
207
|
}
|
|
208
208
|
});
|
|
209
|
-
apiContentCrudRoutes.delete("/:id",
|
|
209
|
+
apiContentCrudRoutes.delete("/:id", chunkE2BXLXPW_cjs.requireAuth(), async (c) => {
|
|
210
210
|
try {
|
|
211
211
|
const id = c.req.param("id");
|
|
212
212
|
const db = c.env.DB;
|
|
@@ -242,7 +242,7 @@ apiRoutes.use("*", async (c, next) => {
|
|
|
242
242
|
c.header("X-Response-Time", `${totalTime}ms`);
|
|
243
243
|
});
|
|
244
244
|
apiRoutes.use("*", async (c, next) => {
|
|
245
|
-
const cacheEnabled = await
|
|
245
|
+
const cacheEnabled = await chunkE2BXLXPW_cjs.isPluginActive(c.env.DB, "core-cache");
|
|
246
246
|
c.set("cacheEnabled", cacheEnabled);
|
|
247
247
|
await next();
|
|
248
248
|
});
|
|
@@ -367,12 +367,12 @@ apiRoutes.get("/content", async (c) => {
|
|
|
367
367
|
});
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
|
-
const filter =
|
|
370
|
+
const filter = chunkMYB5RY7H_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
|
|
371
371
|
if (!filter.limit) {
|
|
372
372
|
filter.limit = 50;
|
|
373
373
|
}
|
|
374
374
|
filter.limit = Math.min(filter.limit, 1e3);
|
|
375
|
-
const builder3 = new
|
|
375
|
+
const builder3 = new chunkMYB5RY7H_cjs.QueryFilterBuilder();
|
|
376
376
|
const queryResult = builder3.build("content", filter);
|
|
377
377
|
if (queryResult.errors.length > 0) {
|
|
378
378
|
return c.json({
|
|
@@ -459,7 +459,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
|
|
|
459
459
|
if (!collectionResult) {
|
|
460
460
|
return c.json({ error: "Collection not found" }, 404);
|
|
461
461
|
}
|
|
462
|
-
const filter =
|
|
462
|
+
const filter = chunkMYB5RY7H_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
|
|
463
463
|
if (!filter.where) {
|
|
464
464
|
filter.where = { and: [] };
|
|
465
465
|
}
|
|
@@ -475,7 +475,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
|
|
|
475
475
|
filter.limit = 50;
|
|
476
476
|
}
|
|
477
477
|
filter.limit = Math.min(filter.limit, 1e3);
|
|
478
|
-
const builder3 = new
|
|
478
|
+
const builder3 = new chunkMYB5RY7H_cjs.QueryFilterBuilder();
|
|
479
479
|
const queryResult = builder3.build("content", filter);
|
|
480
480
|
if (queryResult.errors.length > 0) {
|
|
481
481
|
return c.json({
|
|
@@ -600,7 +600,7 @@ var fileValidationSchema = zod.z.object({
|
|
|
600
600
|
// 50MB max
|
|
601
601
|
});
|
|
602
602
|
var apiMediaRoutes = new hono.Hono();
|
|
603
|
-
apiMediaRoutes.use("*",
|
|
603
|
+
apiMediaRoutes.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
604
604
|
apiMediaRoutes.post("/upload", async (c) => {
|
|
605
605
|
try {
|
|
606
606
|
const user = c.get("user");
|
|
@@ -1344,8 +1344,8 @@ apiSystemRoutes.get("/env", (c) => {
|
|
|
1344
1344
|
});
|
|
1345
1345
|
var api_system_default = apiSystemRoutes;
|
|
1346
1346
|
var adminApiRoutes = new hono.Hono();
|
|
1347
|
-
adminApiRoutes.use("*",
|
|
1348
|
-
adminApiRoutes.use("*",
|
|
1347
|
+
adminApiRoutes.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
1348
|
+
adminApiRoutes.use("*", chunkE2BXLXPW_cjs.requireRole(["admin", "editor"]));
|
|
1349
1349
|
adminApiRoutes.get("/stats", async (c) => {
|
|
1350
1350
|
try {
|
|
1351
1351
|
const db = c.env.DB;
|
|
@@ -1585,6 +1585,107 @@ adminApiRoutes.get("/collections/:id", async (c) => {
|
|
|
1585
1585
|
return c.json({ error: "Failed to fetch collection" }, 500);
|
|
1586
1586
|
}
|
|
1587
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
|
+
});
|
|
1588
1689
|
adminApiRoutes.post("/collections", async (c) => {
|
|
1589
1690
|
try {
|
|
1590
1691
|
const contentType = c.req.header("Content-Type");
|
|
@@ -1754,7 +1855,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
|
|
|
1754
1855
|
});
|
|
1755
1856
|
adminApiRoutes.get("/migrations/status", async (c) => {
|
|
1756
1857
|
try {
|
|
1757
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1858
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-LEMFV2ND.cjs');
|
|
1758
1859
|
const db = c.env.DB;
|
|
1759
1860
|
const migrationService = new MigrationService2(db);
|
|
1760
1861
|
const status = await migrationService.getMigrationStatus();
|
|
@@ -1779,7 +1880,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
1779
1880
|
error: "Unauthorized. Admin access required."
|
|
1780
1881
|
}, 403);
|
|
1781
1882
|
}
|
|
1782
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1883
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-LEMFV2ND.cjs');
|
|
1783
1884
|
const db = c.env.DB;
|
|
1784
1885
|
const migrationService = new MigrationService2(db);
|
|
1785
1886
|
const result = await migrationService.runPendingMigrations();
|
|
@@ -1798,7 +1899,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
1798
1899
|
});
|
|
1799
1900
|
adminApiRoutes.get("/migrations/validate", async (c) => {
|
|
1800
1901
|
try {
|
|
1801
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1902
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-LEMFV2ND.cjs');
|
|
1802
1903
|
const db = c.env.DB;
|
|
1803
1904
|
const migrationService = new MigrationService2(db);
|
|
1804
1905
|
const validation = await migrationService.validateSchema();
|
|
@@ -1825,7 +1926,7 @@ function renderLoginPage(data, demoLoginActive = false) {
|
|
|
1825
1926
|
<meta charset="UTF-8">
|
|
1826
1927
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1827
1928
|
<title>Login - SonicJS AI</title>
|
|
1828
|
-
<link rel="icon" type="image/
|
|
1929
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
1829
1930
|
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
|
|
1830
1931
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
1831
1932
|
<script>
|
|
@@ -1873,8 +1974,8 @@ function renderLoginPage(data, demoLoginActive = false) {
|
|
|
1873
1974
|
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
|
1874
1975
|
<div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
|
|
1875
1976
|
<!-- Alerts -->
|
|
1876
|
-
${data.error ? `<div class="mb-6">${
|
|
1877
|
-
${data.message ? `<div class="mb-6">${
|
|
1977
|
+
${data.error ? `<div class="mb-6">${chunkEHSZ6TAN_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
|
|
1978
|
+
${data.message ? `<div class="mb-6">${chunkEHSZ6TAN_cjs.renderAlert({ type: "success", message: data.message })}</div>` : ""}
|
|
1878
1979
|
|
|
1879
1980
|
<!-- Form Response (HTMX target) -->
|
|
1880
1981
|
<div id="form-response" class="mb-6"></div>
|
|
@@ -2002,7 +2103,7 @@ function renderRegisterPage(data) {
|
|
|
2002
2103
|
<meta charset="UTF-8">
|
|
2003
2104
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2004
2105
|
<title>Register - SonicJS AI</title>
|
|
2005
|
-
<link rel="icon" type="image/
|
|
2106
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
2006
2107
|
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
|
|
2007
2108
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
2008
2109
|
<script>
|
|
@@ -2025,42 +2126,20 @@ function renderRegisterPage(data) {
|
|
|
2025
2126
|
<div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
|
2026
2127
|
<!-- Logo Section -->
|
|
2027
2128
|
<div class="sm:mx-auto sm:w-full sm:max-w-md text-center">
|
|
2028
|
-
<div class="mx-auto w-
|
|
2029
|
-
<svg class="w-
|
|
2030
|
-
<path
|
|
2031
|
-
<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>
|
|
2032
|
-
<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>
|
|
2033
|
-
<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>
|
|
2034
|
-
<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>
|
|
2035
|
-
<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>
|
|
2036
|
-
<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>
|
|
2037
|
-
<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>
|
|
2038
|
-
<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"/>
|
|
2039
2132
|
</svg>
|
|
2040
2133
|
</div>
|
|
2041
|
-
<
|
|
2042
|
-
|
|
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>
|
|
2043
2136
|
</div>
|
|
2044
2137
|
|
|
2045
2138
|
<!-- Form Container -->
|
|
2046
2139
|
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
|
2047
2140
|
<div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
|
|
2048
|
-
<!-- Setup Banner -->
|
|
2049
|
-
${data.isSetup ? `
|
|
2050
|
-
<div class="mb-6 rounded-lg bg-blue-500/10 p-4 ring-1 ring-blue-500/20">
|
|
2051
|
-
<div class="flex items-start gap-x-3">
|
|
2052
|
-
<svg class="h-5 w-5 text-blue-400 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
2053
|
-
<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"/>
|
|
2054
|
-
</svg>
|
|
2055
|
-
<div class="flex-1">
|
|
2056
|
-
<p class="text-sm font-medium text-blue-300">First-time Setup</p>
|
|
2057
|
-
<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>
|
|
2058
|
-
</div>
|
|
2059
|
-
</div>
|
|
2060
|
-
</div>
|
|
2061
|
-
` : ""}
|
|
2062
2141
|
<!-- Alerts -->
|
|
2063
|
-
${data.error ? `<div class="mb-6">${
|
|
2142
|
+
${data.error ? `<div class="mb-6">${chunkEHSZ6TAN_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
|
|
2064
2143
|
|
|
2065
2144
|
<!-- Form -->
|
|
2066
2145
|
<form
|
|
@@ -2173,7 +2252,6 @@ function renderRegisterPage(data) {
|
|
|
2173
2252
|
</html>
|
|
2174
2253
|
`;
|
|
2175
2254
|
}
|
|
2176
|
-
var adminExistsCache = null;
|
|
2177
2255
|
async function isRegistrationEnabled(db) {
|
|
2178
2256
|
try {
|
|
2179
2257
|
const plugin = await db.prepare("SELECT settings FROM plugins WHERE id = ?").bind("core-auth").first();
|
|
@@ -2195,21 +2273,6 @@ async function isFirstUserRegistration(db) {
|
|
|
2195
2273
|
return false;
|
|
2196
2274
|
}
|
|
2197
2275
|
}
|
|
2198
|
-
async function checkAdminUserExists(db) {
|
|
2199
|
-
if (adminExistsCache !== null) {
|
|
2200
|
-
return adminExistsCache;
|
|
2201
|
-
}
|
|
2202
|
-
try {
|
|
2203
|
-
const result = await db.prepare("SELECT id FROM users WHERE role = ?").bind("admin").first();
|
|
2204
|
-
adminExistsCache = !!result;
|
|
2205
|
-
return adminExistsCache;
|
|
2206
|
-
} catch {
|
|
2207
|
-
return false;
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
function setAdminExists() {
|
|
2211
|
-
adminExistsCache = true;
|
|
2212
|
-
}
|
|
2213
2276
|
var baseRegistrationSchema = zod.z.object({
|
|
2214
2277
|
email: zod.z.string().email("Valid email is required"),
|
|
2215
2278
|
password: zod.z.string().min(8, "Password must be at least 8 characters"),
|
|
@@ -2271,11 +2334,8 @@ authRoutes.get("/register", async (c) => {
|
|
|
2271
2334
|
}
|
|
2272
2335
|
}
|
|
2273
2336
|
const error = c.req.query("error");
|
|
2274
|
-
const isSetup = c.req.query("setup") === "true";
|
|
2275
2337
|
const pageData = {
|
|
2276
|
-
error: error || void 0
|
|
2277
|
-
isSetup: isSetup && isFirstUser
|
|
2278
|
-
// Only show setup message if truly first user
|
|
2338
|
+
error: error || void 0
|
|
2279
2339
|
};
|
|
2280
2340
|
return c.html(renderRegisterPage(pageData));
|
|
2281
2341
|
});
|
|
@@ -2321,7 +2381,7 @@ authRoutes.post(
|
|
|
2321
2381
|
if (existingUser) {
|
|
2322
2382
|
return c.json({ error: "User with this email or username already exists" }, 400);
|
|
2323
2383
|
}
|
|
2324
|
-
const passwordHash = await
|
|
2384
|
+
const passwordHash = await chunkE2BXLXPW_cjs.AuthManager.hashPassword(password);
|
|
2325
2385
|
const userId = crypto.randomUUID();
|
|
2326
2386
|
const now = /* @__PURE__ */ new Date();
|
|
2327
2387
|
await db.prepare(`
|
|
@@ -2341,7 +2401,7 @@ authRoutes.post(
|
|
|
2341
2401
|
now.getTime(),
|
|
2342
2402
|
now.getTime()
|
|
2343
2403
|
).run();
|
|
2344
|
-
const token = await
|
|
2404
|
+
const token = await chunkE2BXLXPW_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer");
|
|
2345
2405
|
cookie.setCookie(c, "auth_token", token, {
|
|
2346
2406
|
httpOnly: true,
|
|
2347
2407
|
secure: true,
|
|
@@ -2394,11 +2454,11 @@ authRoutes.post("/login", async (c) => {
|
|
|
2394
2454
|
if (!user) {
|
|
2395
2455
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
2396
2456
|
}
|
|
2397
|
-
const isValidPassword = await
|
|
2457
|
+
const isValidPassword = await chunkE2BXLXPW_cjs.AuthManager.verifyPassword(password, user.password_hash);
|
|
2398
2458
|
if (!isValidPassword) {
|
|
2399
2459
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
2400
2460
|
}
|
|
2401
|
-
const token = await
|
|
2461
|
+
const token = await chunkE2BXLXPW_cjs.AuthManager.generateToken(user.id, user.email, user.role);
|
|
2402
2462
|
cookie.setCookie(c, "auth_token", token, {
|
|
2403
2463
|
httpOnly: true,
|
|
2404
2464
|
secure: true,
|
|
@@ -2447,7 +2507,7 @@ authRoutes.get("/logout", (c) => {
|
|
|
2447
2507
|
});
|
|
2448
2508
|
return c.redirect("/auth/login?message=You have been logged out successfully");
|
|
2449
2509
|
});
|
|
2450
|
-
authRoutes.get("/me",
|
|
2510
|
+
authRoutes.get("/me", chunkE2BXLXPW_cjs.requireAuth(), async (c) => {
|
|
2451
2511
|
try {
|
|
2452
2512
|
const user = c.get("user");
|
|
2453
2513
|
if (!user) {
|
|
@@ -2464,13 +2524,13 @@ authRoutes.get("/me", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
|
|
|
2464
2524
|
return c.json({ error: "Failed to get user" }, 500);
|
|
2465
2525
|
}
|
|
2466
2526
|
});
|
|
2467
|
-
authRoutes.post("/refresh",
|
|
2527
|
+
authRoutes.post("/refresh", chunkE2BXLXPW_cjs.requireAuth(), async (c) => {
|
|
2468
2528
|
try {
|
|
2469
2529
|
const user = c.get("user");
|
|
2470
2530
|
if (!user) {
|
|
2471
2531
|
return c.json({ error: "Not authenticated" }, 401);
|
|
2472
2532
|
}
|
|
2473
|
-
const token = await
|
|
2533
|
+
const token = await chunkE2BXLXPW_cjs.AuthManager.generateToken(user.userId, user.email, user.role);
|
|
2474
2534
|
cookie.setCookie(c, "auth_token", token, {
|
|
2475
2535
|
httpOnly: true,
|
|
2476
2536
|
secure: true,
|
|
@@ -2530,7 +2590,7 @@ authRoutes.post("/register/form", async (c) => {
|
|
|
2530
2590
|
</div>
|
|
2531
2591
|
`);
|
|
2532
2592
|
}
|
|
2533
|
-
const passwordHash = await
|
|
2593
|
+
const passwordHash = await chunkE2BXLXPW_cjs.AuthManager.hashPassword(password);
|
|
2534
2594
|
const role = isFirstUser ? "admin" : "viewer";
|
|
2535
2595
|
const userId = crypto.randomUUID();
|
|
2536
2596
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2550,10 +2610,7 @@ authRoutes.post("/register/form", async (c) => {
|
|
|
2550
2610
|
now.getTime(),
|
|
2551
2611
|
now.getTime()
|
|
2552
2612
|
).run();
|
|
2553
|
-
|
|
2554
|
-
setAdminExists();
|
|
2555
|
-
}
|
|
2556
|
-
const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
|
|
2613
|
+
const token = await chunkE2BXLXPW_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
|
|
2557
2614
|
cookie.setCookie(c, "auth_token", token, {
|
|
2558
2615
|
httpOnly: true,
|
|
2559
2616
|
secure: false,
|
|
@@ -2605,7 +2662,7 @@ authRoutes.post("/login/form", async (c) => {
|
|
|
2605
2662
|
</div>
|
|
2606
2663
|
`);
|
|
2607
2664
|
}
|
|
2608
|
-
const isValidPassword = await
|
|
2665
|
+
const isValidPassword = await chunkE2BXLXPW_cjs.AuthManager.verifyPassword(password, user.password_hash);
|
|
2609
2666
|
if (!isValidPassword) {
|
|
2610
2667
|
return c.html(html.html`
|
|
2611
2668
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
@@ -2613,7 +2670,7 @@ authRoutes.post("/login/form", async (c) => {
|
|
|
2613
2670
|
</div>
|
|
2614
2671
|
`);
|
|
2615
2672
|
}
|
|
2616
|
-
const token = await
|
|
2673
|
+
const token = await chunkE2BXLXPW_cjs.AuthManager.generateToken(user.id, user.email, user.role);
|
|
2617
2674
|
cookie.setCookie(c, "auth_token", token, {
|
|
2618
2675
|
httpOnly: true,
|
|
2619
2676
|
secure: false,
|
|
@@ -2672,9 +2729,8 @@ authRoutes.post("/seed-admin", async (c) => {
|
|
|
2672
2729
|
`).run();
|
|
2673
2730
|
const existingAdmin = await db.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind("admin@sonicjs.com", "admin").first();
|
|
2674
2731
|
if (existingAdmin) {
|
|
2675
|
-
const passwordHash2 = await
|
|
2732
|
+
const passwordHash2 = await chunkE2BXLXPW_cjs.AuthManager.hashPassword("sonicjs!");
|
|
2676
2733
|
await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
|
|
2677
|
-
setAdminExists();
|
|
2678
2734
|
return c.json({
|
|
2679
2735
|
message: "Admin user already exists (password updated)",
|
|
2680
2736
|
user: {
|
|
@@ -2685,7 +2741,7 @@ authRoutes.post("/seed-admin", async (c) => {
|
|
|
2685
2741
|
}
|
|
2686
2742
|
});
|
|
2687
2743
|
}
|
|
2688
|
-
const passwordHash = await
|
|
2744
|
+
const passwordHash = await chunkE2BXLXPW_cjs.AuthManager.hashPassword("sonicjs!");
|
|
2689
2745
|
const userId = "admin-user-id";
|
|
2690
2746
|
const now = Date.now();
|
|
2691
2747
|
const adminEmail = "admin@sonicjs.com".toLowerCase();
|
|
@@ -2705,7 +2761,6 @@ authRoutes.post("/seed-admin", async (c) => {
|
|
|
2705
2761
|
now,
|
|
2706
2762
|
now
|
|
2707
2763
|
).run();
|
|
2708
|
-
setAdminExists();
|
|
2709
2764
|
return c.json({
|
|
2710
2765
|
message: "Admin user created successfully",
|
|
2711
2766
|
user: {
|
|
@@ -2906,7 +2961,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
2906
2961
|
if (existingUsername) {
|
|
2907
2962
|
return c.json({ error: "Username is already taken" }, 400);
|
|
2908
2963
|
}
|
|
2909
|
-
const passwordHash = await
|
|
2964
|
+
const passwordHash = await chunkE2BXLXPW_cjs.AuthManager.hashPassword(password);
|
|
2910
2965
|
const updateStmt = db.prepare(`
|
|
2911
2966
|
UPDATE users SET
|
|
2912
2967
|
username = ?,
|
|
@@ -2925,7 +2980,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
2925
2980
|
Date.now(),
|
|
2926
2981
|
invitedUser.id
|
|
2927
2982
|
).run();
|
|
2928
|
-
const authToken = await
|
|
2983
|
+
const authToken = await chunkE2BXLXPW_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role);
|
|
2929
2984
|
cookie.setCookie(c, "auth_token", authToken, {
|
|
2930
2985
|
httpOnly: true,
|
|
2931
2986
|
secure: true,
|
|
@@ -3155,7 +3210,7 @@ authRoutes.post("/reset-password", async (c) => {
|
|
|
3155
3210
|
if (Date.now() > user.password_reset_expires) {
|
|
3156
3211
|
return c.json({ error: "Reset token has expired" }, 400);
|
|
3157
3212
|
}
|
|
3158
|
-
const newPasswordHash = await
|
|
3213
|
+
const newPasswordHash = await chunkE2BXLXPW_cjs.AuthManager.hashPassword(password);
|
|
3159
3214
|
try {
|
|
3160
3215
|
const historyStmt = db.prepare(`
|
|
3161
3216
|
INSERT INTO password_history (id, user_id, password_hash, created_at)
|
|
@@ -3413,9 +3468,166 @@ app.post("/test-cleanup/content", async (c) => {
|
|
|
3413
3468
|
var test_cleanup_default = app;
|
|
3414
3469
|
|
|
3415
3470
|
// src/templates/pages/admin-content-form.template.ts
|
|
3416
|
-
|
|
3471
|
+
chunkEHSZ6TAN_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
|
+
}
|
|
3417
3569
|
|
|
3418
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
|
+
}
|
|
3419
3631
|
function renderDynamicField(field, options = {}) {
|
|
3420
3632
|
const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {}, collectionId = "", contentId = "" } = options;
|
|
3421
3633
|
const opts = field.field_options || {};
|
|
@@ -3839,11 +4051,11 @@ function renderDynamicField(field, options = {}) {
|
|
|
3839
4051
|
`;
|
|
3840
4052
|
break;
|
|
3841
4053
|
case "select":
|
|
3842
|
-
const
|
|
4054
|
+
const selectOptions = opts.options || [];
|
|
3843
4055
|
const multiple = opts.multiple ? "multiple" : "";
|
|
3844
4056
|
const selectedValues = Array.isArray(value) ? value : [value];
|
|
3845
4057
|
fieldHTML = `
|
|
3846
|
-
<select
|
|
4058
|
+
<select
|
|
3847
4059
|
id="${fieldId}"
|
|
3848
4060
|
name="${fieldName}${opts.multiple ? "[]" : ""}"
|
|
3849
4061
|
class="${baseClasses} ${errorClasses}"
|
|
@@ -3852,7 +4064,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
3852
4064
|
${disabled ? "disabled" : ""}
|
|
3853
4065
|
>
|
|
3854
4066
|
${!required && !opts.multiple ? '<option value="">Choose an option...</option>' : ""}
|
|
3855
|
-
${
|
|
4067
|
+
${selectOptions.map((option) => {
|
|
3856
4068
|
const optionValue = typeof option === "string" ? option : option.value;
|
|
3857
4069
|
const optionLabel = typeof option === "string" ? option : option.label;
|
|
3858
4070
|
const selected = selectedValues.includes(optionValue) ? "selected" : "";
|
|
@@ -3871,43 +4083,124 @@ function renderDynamicField(field, options = {}) {
|
|
|
3871
4083
|
` : ""}
|
|
3872
4084
|
`;
|
|
3873
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;
|
|
3874
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
|
+
};
|
|
3875
4138
|
fieldHTML = `
|
|
3876
4139
|
<div class="media-field-container">
|
|
3877
|
-
<input type="hidden" id="${fieldId}" name="${fieldName}" value="${
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
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
|
+
|
|
3881
4166
|
<div class="media-actions mt-2 space-x-2">
|
|
3882
4167
|
<button
|
|
3883
4168
|
type="button"
|
|
3884
|
-
onclick="openMediaSelector('${fieldId}')"
|
|
4169
|
+
onclick="openMediaSelector('${fieldId}', ${isMultiple})"
|
|
3885
4170
|
class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all"
|
|
3886
4171
|
${disabled ? "disabled" : ""}
|
|
3887
4172
|
>
|
|
3888
4173
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3889
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>
|
|
3890
4175
|
</svg>
|
|
3891
|
-
Select Media
|
|
4176
|
+
${isMultiple ? "Select Media (Multiple)" : "Select Media"}
|
|
3892
4177
|
</button>
|
|
3893
|
-
${
|
|
4178
|
+
${(isMultiple ? mediaValues.length > 0 : singleValue) ? `
|
|
3894
4179
|
<button
|
|
3895
4180
|
type="button"
|
|
3896
4181
|
onclick="clearMediaField('${fieldId}')"
|
|
3897
4182
|
class="inline-flex items-center px-4 py-2 bg-red-600 text-white rounded-xl hover:bg-red-700 transition-all"
|
|
3898
4183
|
${disabled ? "disabled" : ""}
|
|
3899
4184
|
>
|
|
3900
|
-
Remove
|
|
4185
|
+
${isMultiple ? "Clear All" : "Remove"}
|
|
3901
4186
|
</button>
|
|
3902
4187
|
` : ""}
|
|
3903
4188
|
</div>
|
|
3904
4189
|
</div>
|
|
3905
4190
|
`;
|
|
3906
4191
|
break;
|
|
4192
|
+
case "object":
|
|
4193
|
+
return renderStructuredObjectField(field, options);
|
|
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, options, baseClasses, errorClasses);
|
|
4198
|
+
}
|
|
4199
|
+
return renderStructuredArrayField(field, options);
|
|
3907
4200
|
default:
|
|
3908
4201
|
fieldHTML = `
|
|
3909
|
-
<input
|
|
3910
|
-
type="text"
|
|
4202
|
+
<input
|
|
4203
|
+
type="text"
|
|
3911
4204
|
id="${fieldId}"
|
|
3912
4205
|
name="${fieldName}"
|
|
3913
4206
|
value="${escapeHtml2(value)}"
|
|
@@ -3957,77 +4250,825 @@ function renderFieldGroup(title, fields, collapsible = false) {
|
|
|
3957
4250
|
</div>
|
|
3958
4251
|
`;
|
|
3959
4252
|
}
|
|
3960
|
-
function
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
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))}">
|
|
3970
4280
|
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
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
|
+
`;
|
|
3996
4309
|
}
|
|
3997
|
-
function
|
|
3998
|
-
const
|
|
3999
|
-
const
|
|
4000
|
-
const
|
|
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("");
|
|
4001
4327
|
return `
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
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))}">
|
|
4011
4355
|
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
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>
|
|
4016
4368
|
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
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("");
|
|
4435
|
+
}
|
|
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
|
+
}
|
|
4475
|
+
}
|
|
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
|
+
}
|
|
4489
|
+
}
|
|
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
|
+
}
|
|
4519
|
+
}
|
|
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
|
+
`;
|
|
4538
|
+
}
|
|
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
|
+
`;
|
|
4550
|
+
}
|
|
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
|
+
}));
|
|
4618
|
+
}
|
|
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
|
+
}
|
|
5011
|
+
|
|
5012
|
+
// src/plugins/available/tinymce-plugin/index.ts
|
|
5013
|
+
var builder = chunkYHW27CBV_cjs.PluginBuilder.create({
|
|
5014
|
+
name: "tinymce-plugin",
|
|
5015
|
+
version: "1.0.0",
|
|
5016
|
+
description: "Powerful WYSIWYG rich text editor for content creation"
|
|
5017
|
+
});
|
|
5018
|
+
builder.metadata({
|
|
5019
|
+
author: {
|
|
5020
|
+
name: "SonicJS Team",
|
|
5021
|
+
email: "team@sonicjs.com"
|
|
5022
|
+
},
|
|
5023
|
+
license: "MIT",
|
|
5024
|
+
compatibility: "^2.0.0"
|
|
5025
|
+
});
|
|
5026
|
+
builder.lifecycle({
|
|
5027
|
+
activate: async () => {
|
|
5028
|
+
console.info("\u2705 TinyMCE plugin activated");
|
|
5029
|
+
},
|
|
5030
|
+
deactivate: async () => {
|
|
5031
|
+
console.info("\u274C TinyMCE plugin deactivated");
|
|
5032
|
+
}
|
|
5033
|
+
});
|
|
5034
|
+
builder.build();
|
|
5035
|
+
function getTinyMCEScript(apiKey = "no-api-key") {
|
|
5036
|
+
return `<script src="https://cdn.tiny.cloud/1/${apiKey}/tinymce/6/tinymce.min.js" referrerpolicy="origin"></script>`;
|
|
5037
|
+
}
|
|
5038
|
+
function getTinyMCEInitScript(config) {
|
|
5039
|
+
const skin = config?.skin || "oxide-dark";
|
|
5040
|
+
const contentCss = skin.includes("dark") ? "dark" : "default";
|
|
5041
|
+
const defaultHeight = config?.defaultHeight || 300;
|
|
5042
|
+
return `
|
|
5043
|
+
// Initialize TinyMCE for all richtext fields
|
|
5044
|
+
function initializeTinyMCE() {
|
|
5045
|
+
if (typeof tinymce !== 'undefined') {
|
|
5046
|
+
// Find all textareas that need TinyMCE
|
|
5047
|
+
document.querySelectorAll('.richtext-container textarea').forEach((textarea) => {
|
|
5048
|
+
// Skip if already initialized
|
|
5049
|
+
if (tinymce.get(textarea.id)) {
|
|
5050
|
+
return;
|
|
5051
|
+
}
|
|
5052
|
+
|
|
5053
|
+
// Get configuration from data attributes
|
|
5054
|
+
const container = textarea.closest('.richtext-container');
|
|
5055
|
+
const height = container?.dataset.height || ${defaultHeight};
|
|
5056
|
+
const toolbar = container?.dataset.toolbar || 'full';
|
|
5057
|
+
|
|
5058
|
+
tinymce.init({
|
|
5059
|
+
selector: '#' + textarea.id,
|
|
5060
|
+
skin: '${skin}',
|
|
5061
|
+
content_css: '${contentCss}',
|
|
5062
|
+
height: parseInt(height),
|
|
5063
|
+
menubar: false,
|
|
5064
|
+
plugins: [
|
|
5065
|
+
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
|
|
5066
|
+
'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
|
|
5067
|
+
'insertdatetime', 'media', 'table', 'help', 'wordcount'
|
|
5068
|
+
],
|
|
5069
|
+
toolbar: toolbar === 'simple'
|
|
5070
|
+
? 'bold italic underline | bullist numlist | link'
|
|
5071
|
+
: toolbar === 'minimal'
|
|
4031
5072
|
? 'bold italic | link'
|
|
4032
5073
|
: 'undo redo | blocks | bold italic forecolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help',
|
|
4033
5074
|
content_style: 'body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; font-size: 14px }'
|
|
@@ -4252,7 +5293,7 @@ function getQuillCDN(version = "2.0.2") {
|
|
|
4252
5293
|
`;
|
|
4253
5294
|
}
|
|
4254
5295
|
function createQuillEditorPlugin() {
|
|
4255
|
-
const builder3 =
|
|
5296
|
+
const builder3 = chunkYHW27CBV_cjs.PluginBuilder.create({
|
|
4256
5297
|
name: "quill-editor",
|
|
4257
5298
|
version: "1.0.0",
|
|
4258
5299
|
description: "Quill rich text editor integration for SonicJS"
|
|
@@ -4278,7 +5319,7 @@ function createQuillEditorPlugin() {
|
|
|
4278
5319
|
createQuillEditorPlugin();
|
|
4279
5320
|
|
|
4280
5321
|
// src/plugins/available/easy-mdx/index.ts
|
|
4281
|
-
var builder2 =
|
|
5322
|
+
var builder2 = chunkYHW27CBV_cjs.PluginBuilder.create({
|
|
4282
5323
|
name: "easy-mdx",
|
|
4283
5324
|
version: "1.0.0",
|
|
4284
5325
|
description: "Lightweight markdown editor with live preview"
|
|
@@ -4569,8 +5610,8 @@ function renderContentFormPage(data) {
|
|
|
4569
5610
|
<!-- Form Content -->
|
|
4570
5611
|
<div class="px-6 py-6">
|
|
4571
5612
|
<div id="form-messages">
|
|
4572
|
-
${data.error ?
|
|
4573
|
-
${data.success ?
|
|
5613
|
+
${data.error ? chunkEHSZ6TAN_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
|
|
5614
|
+
${data.success ? chunkEHSZ6TAN_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
|
|
4574
5615
|
</div>
|
|
4575
5616
|
|
|
4576
5617
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
@@ -4805,7 +5846,7 @@ function renderContentFormPage(data) {
|
|
|
4805
5846
|
</div>
|
|
4806
5847
|
|
|
4807
5848
|
<!-- Confirmation Dialogs -->
|
|
4808
|
-
${
|
|
5849
|
+
${chunkEHSZ6TAN_cjs.renderConfirmationDialog({
|
|
4809
5850
|
id: "duplicate-content-confirm",
|
|
4810
5851
|
title: "Duplicate Content",
|
|
4811
5852
|
message: "Create a copy of this content?",
|
|
@@ -4816,7 +5857,7 @@ function renderContentFormPage(data) {
|
|
|
4816
5857
|
onConfirm: "performDuplicateContent()"
|
|
4817
5858
|
})}
|
|
4818
5859
|
|
|
4819
|
-
${
|
|
5860
|
+
${chunkEHSZ6TAN_cjs.renderConfirmationDialog({
|
|
4820
5861
|
id: "delete-content-confirm",
|
|
4821
5862
|
title: "Delete Content",
|
|
4822
5863
|
message: "Are you sure you want to delete this content? This action cannot be undone.",
|
|
@@ -4827,7 +5868,7 @@ function renderContentFormPage(data) {
|
|
|
4827
5868
|
onConfirm: `performDeleteContent('${data.id}')`
|
|
4828
5869
|
})}
|
|
4829
5870
|
|
|
4830
|
-
${
|
|
5871
|
+
${chunkEHSZ6TAN_cjs.getConfirmationDialogScript()}
|
|
4831
5872
|
|
|
4832
5873
|
${data.tinymceEnabled ? getTinyMCEScript(data.tinymceSettings?.apiKey) : "<!-- TinyMCE plugin not active -->"}
|
|
4833
5874
|
|
|
@@ -4905,79 +5946,379 @@ function renderContentFormPage(data) {
|
|
|
4905
5946
|
hiddenInput.value = originalValue;
|
|
4906
5947
|
}
|
|
4907
5948
|
|
|
4908
|
-
// If original value was empty, hide the preview and show select button
|
|
4909
|
-
if (!originalValue) {
|
|
4910
|
-
const preview = document.getElementById(fieldId + '-preview');
|
|
4911
|
-
if (preview) {
|
|
4912
|
-
preview.classList.add('hidden');
|
|
5949
|
+
// If original value was empty, hide the preview and show select button
|
|
5950
|
+
if (!originalValue) {
|
|
5951
|
+
const preview = document.getElementById(fieldId + '-preview');
|
|
5952
|
+
if (preview) {
|
|
5953
|
+
preview.classList.add('hidden');
|
|
5954
|
+
}
|
|
5955
|
+
}
|
|
5956
|
+
|
|
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;
|
|
4913
6234
|
}
|
|
4914
|
-
}
|
|
4915
6235
|
|
|
4916
|
-
|
|
4917
|
-
closeMediaSelector();
|
|
4918
|
-
}
|
|
6236
|
+
const selectedId = document.getElementById(fieldId)?.value;
|
|
4919
6237
|
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
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
|
+
}
|
|
4924
6245
|
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
return;
|
|
4930
|
-
}
|
|
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;
|
|
4931
6250
|
|
|
4932
|
-
|
|
6251
|
+
button.appendChild(titleEl);
|
|
4933
6252
|
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
if (hiddenInput) {
|
|
4937
|
-
hiddenInput.value = mediaUrl;
|
|
4938
|
-
}
|
|
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';
|
|
4939
6255
|
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
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
|
+
}
|
|
4946
6262
|
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
6263
|
+
if (item.slug) {
|
|
6264
|
+
const slugEl = document.createElement('span');
|
|
6265
|
+
slugEl.textContent = item.slug;
|
|
6266
|
+
metaRow.appendChild(slugEl);
|
|
6267
|
+
}
|
|
6268
|
+
|
|
6269
|
+
if (metaRow.childElementCount > 0) {
|
|
6270
|
+
button.appendChild(metaRow);
|
|
6271
|
+
}
|
|
6272
|
+
|
|
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>';
|
|
4958
6288
|
}
|
|
4959
|
-
}
|
|
6289
|
+
};
|
|
4960
6290
|
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
6291
|
+
loadResults();
|
|
6292
|
+
|
|
6293
|
+
searchInput.addEventListener('input', () => {
|
|
6294
|
+
if (referenceSearchTimeout) {
|
|
6295
|
+
clearTimeout(referenceSearchTimeout);
|
|
6296
|
+
}
|
|
6297
|
+
referenceSearchTimeout = setTimeout(() => {
|
|
6298
|
+
loadResults(searchInput.value.trim());
|
|
6299
|
+
}, 250);
|
|
4965
6300
|
});
|
|
4966
|
-
|
|
4967
|
-
if (selectedItem) {
|
|
4968
|
-
selectedItem.classList.add('ring-2', 'ring-lime-500', 'dark:ring-lime-400');
|
|
4969
|
-
}
|
|
4970
|
-
};
|
|
6301
|
+
}
|
|
4971
6302
|
|
|
4972
|
-
|
|
4973
|
-
document.
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
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;
|
|
4977
6308
|
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
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
|
+
});
|
|
4981
6322
|
|
|
4982
6323
|
// Custom select options
|
|
4983
6324
|
function addCustomOption(input, selectId) {
|
|
@@ -5132,11 +6473,11 @@ function renderContentFormPage(data) {
|
|
|
5132
6473
|
content: pageContent,
|
|
5133
6474
|
version: data.version
|
|
5134
6475
|
};
|
|
5135
|
-
return
|
|
6476
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
5136
6477
|
}
|
|
5137
6478
|
|
|
5138
6479
|
// src/templates/pages/admin-content-list.template.ts
|
|
5139
|
-
|
|
6480
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
5140
6481
|
function renderContentListPage(data) {
|
|
5141
6482
|
const urlParams = new URLSearchParams();
|
|
5142
6483
|
if (data.modelName && data.modelName !== "all") urlParams.set("model", data.modelName);
|
|
@@ -5144,7 +6485,7 @@ function renderContentListPage(data) {
|
|
|
5144
6485
|
if (data.search) urlParams.set("search", data.search);
|
|
5145
6486
|
if (data.page && data.page !== 1) urlParams.set("page", data.page.toString());
|
|
5146
6487
|
const currentParams = urlParams.toString();
|
|
5147
|
-
|
|
6488
|
+
data.modelName !== "all" || data.status !== "all" || !!data.search;
|
|
5148
6489
|
const filterBarData = {
|
|
5149
6490
|
filters: [
|
|
5150
6491
|
{
|
|
@@ -5174,6 +6515,11 @@ function renderContentListPage(data) {
|
|
|
5174
6515
|
}
|
|
5175
6516
|
],
|
|
5176
6517
|
actions: [
|
|
6518
|
+
{
|
|
6519
|
+
label: "Advanced Search",
|
|
6520
|
+
className: "btn-primary",
|
|
6521
|
+
onclick: "openAdvancedSearch()"
|
|
6522
|
+
},
|
|
5177
6523
|
{
|
|
5178
6524
|
label: "Refresh",
|
|
5179
6525
|
className: "btn-secondary",
|
|
@@ -5325,12 +6671,57 @@ function renderContentListPage(data) {
|
|
|
5325
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">
|
|
5326
6672
|
<div class="px-6 py-5">
|
|
5327
6673
|
<div class="flex items-center justify-between">
|
|
5328
|
-
<div class="flex items-center space-x-4">
|
|
5329
|
-
<!--
|
|
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 -->
|
|
5330
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">
|
|
5331
6722
|
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search</label>
|
|
5332
6723
|
<form onsubmit="performContentSearch(event)" class="flex items-center space-x-2">
|
|
5333
|
-
<div class="relative group">
|
|
6724
|
+
<div class="relative group flex-1">
|
|
5334
6725
|
<input
|
|
5335
6726
|
type="text"
|
|
5336
6727
|
name="search"
|
|
@@ -5338,7 +6729,7 @@ function renderContentListPage(data) {
|
|
|
5338
6729
|
value="${data.search || ""}"
|
|
5339
6730
|
oninput="toggleContentClearButton()"
|
|
5340
6731
|
placeholder="Search content..."
|
|
5341
|
-
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"
|
|
5342
6733
|
>
|
|
5343
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">
|
|
5344
6735
|
<svg class="h-3 w-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
|
@@ -5410,57 +6801,6 @@ function renderContentListPage(data) {
|
|
|
5410
6801
|
}
|
|
5411
6802
|
</script>
|
|
5412
6803
|
</div>
|
|
5413
|
-
|
|
5414
|
-
${filterBarData.filters.map((filter) => {
|
|
5415
|
-
const selectedOption = filter.options.find((opt) => opt.selected);
|
|
5416
|
-
const selectedColor = selectedOption?.color || "cyan";
|
|
5417
|
-
const colorMap = {
|
|
5418
|
-
"cyan": "bg-cyan-400 dark:bg-cyan-400",
|
|
5419
|
-
"lime": "bg-lime-400 dark:bg-lime-400",
|
|
5420
|
-
"pink": "bg-pink-400 dark:bg-pink-400",
|
|
5421
|
-
"purple": "bg-purple-400 dark:bg-purple-400",
|
|
5422
|
-
"amber": "bg-amber-400 dark:bg-amber-400",
|
|
5423
|
-
"zinc": "bg-zinc-400 dark:bg-zinc-400"
|
|
5424
|
-
};
|
|
5425
|
-
return `
|
|
5426
|
-
<div>
|
|
5427
|
-
<label class="block text-sm/6 font-medium text-zinc-950 dark:text-white">${filter.label}</label>
|
|
5428
|
-
<div class="mt-2 grid grid-cols-1">
|
|
5429
|
-
<div class="col-start-1 row-start-1 flex items-center gap-3 pl-3 pr-8 pointer-events-none">
|
|
5430
|
-
${filter.name === "status" ? `<span class="inline-block size-2 shrink-0 rounded-full border border-transparent ${colorMap[selectedColor]}"></span>` : ""}
|
|
5431
|
-
</div>
|
|
5432
|
-
<select
|
|
5433
|
-
name="${filter.name}"
|
|
5434
|
-
onchange="updateContentFilters('${filter.name}', this.value)"
|
|
5435
|
-
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"
|
|
5436
|
-
>
|
|
5437
|
-
${filter.options.map((opt) => `
|
|
5438
|
-
<option value="${opt.value}" ${opt.selected ? "selected" : ""}>${opt.label}</option>
|
|
5439
|
-
`).join("")}
|
|
5440
|
-
</select>
|
|
5441
|
-
<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">
|
|
5442
|
-
<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" />
|
|
5443
|
-
</svg>
|
|
5444
|
-
</div>
|
|
5445
|
-
</div>
|
|
5446
|
-
`;
|
|
5447
|
-
}).join("")}
|
|
5448
|
-
|
|
5449
|
-
<!-- Clear Filters Button -->
|
|
5450
|
-
${hasActiveFilters ? `
|
|
5451
|
-
<div>
|
|
5452
|
-
<label class="block text-sm/6 font-medium text-zinc-950 dark:text-white mb-2"> </label>
|
|
5453
|
-
<button
|
|
5454
|
-
onclick="clearAllFilters()"
|
|
5455
|
-
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"
|
|
5456
|
-
>
|
|
5457
|
-
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
|
|
5458
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
|
5459
|
-
</svg>
|
|
5460
|
-
Clear Filters
|
|
5461
|
-
</button>
|
|
5462
|
-
</div>
|
|
5463
|
-
` : ""}
|
|
5464
6804
|
</div>
|
|
5465
6805
|
<div class="flex items-center gap-x-3">
|
|
5466
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>
|
|
@@ -5541,8 +6881,8 @@ function renderContentListPage(data) {
|
|
|
5541
6881
|
|
|
5542
6882
|
<!-- Content List -->
|
|
5543
6883
|
<div id="content-list">
|
|
5544
|
-
${
|
|
5545
|
-
${
|
|
6884
|
+
${chunkEHSZ6TAN_cjs.renderTable(tableData)}
|
|
6885
|
+
${chunkEHSZ6TAN_cjs.renderPagination(paginationData)}
|
|
5546
6886
|
</div>
|
|
5547
6887
|
|
|
5548
6888
|
</div>
|
|
@@ -5630,8 +6970,9 @@ function renderContentListPage(data) {
|
|
|
5630
6970
|
});
|
|
5631
6971
|
|
|
5632
6972
|
// Store current bulk action context
|
|
5633
|
-
let
|
|
5634
|
-
|
|
6973
|
+
// Using var instead of let to avoid redeclaration errors when HTMX re-executes script tags
|
|
6974
|
+
var currentBulkAction = null;
|
|
6975
|
+
var currentSelectedIds = [];
|
|
5635
6976
|
|
|
5636
6977
|
// Perform bulk action
|
|
5637
6978
|
function performBulkAction(action) {
|
|
@@ -5721,49 +7062,336 @@ function renderContentListPage(data) {
|
|
|
5721
7062
|
} else {
|
|
5722
7063
|
alert('Error: ' + (data.error || 'Unknown error'));
|
|
5723
7064
|
}
|
|
5724
|
-
})
|
|
5725
|
-
.catch(err => {
|
|
5726
|
-
console.error('Bulk action error:', err);
|
|
5727
|
-
alert('Failed to perform bulk action');
|
|
5728
|
-
})
|
|
5729
|
-
.finally(() => {
|
|
5730
|
-
// Clear context
|
|
5731
|
-
currentBulkAction = null;
|
|
5732
|
-
currentSelectedIds = [];
|
|
7065
|
+
})
|
|
7066
|
+
.catch(err => {
|
|
7067
|
+
console.error('Bulk action error:', err);
|
|
7068
|
+
alert('Failed to perform bulk action');
|
|
7069
|
+
})
|
|
7070
|
+
.finally(() => {
|
|
7071
|
+
// Clear context
|
|
7072
|
+
currentBulkAction = null;
|
|
7073
|
+
currentSelectedIds = [];
|
|
7074
|
+
});
|
|
7075
|
+
}
|
|
7076
|
+
|
|
7077
|
+
// Helper to get action text for display
|
|
7078
|
+
function getActionText(action) {
|
|
7079
|
+
const actionCount = currentSelectedIds.length;
|
|
7080
|
+
switch(action) {
|
|
7081
|
+
case 'publish':
|
|
7082
|
+
return \`publish \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
|
|
7083
|
+
case 'draft':
|
|
7084
|
+
return \`move \${actionCount} item\${actionCount > 1 ? 's' : ''} to draft\`;
|
|
7085
|
+
case 'delete':
|
|
7086
|
+
return \`delete \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
|
|
7087
|
+
default:
|
|
7088
|
+
return \`perform action on \${actionCount} item\${actionCount > 1 ? 's' : ''}\`;
|
|
7089
|
+
}
|
|
7090
|
+
}
|
|
7091
|
+
|
|
7092
|
+
</script>
|
|
7093
|
+
|
|
7094
|
+
<!-- Confirmation Dialog for Bulk Actions -->
|
|
7095
|
+
${chunkEHSZ6TAN_cjs.renderConfirmationDialog({
|
|
7096
|
+
id: "bulk-action-confirm",
|
|
7097
|
+
title: "Confirm Bulk Action",
|
|
7098
|
+
message: "Are you sure you want to perform this action? This operation will affect multiple items.",
|
|
7099
|
+
confirmText: "Confirm",
|
|
7100
|
+
cancelText: "Cancel",
|
|
7101
|
+
confirmClass: "bg-blue-500 hover:bg-blue-400",
|
|
7102
|
+
iconColor: "blue",
|
|
7103
|
+
onConfirm: "executeBulkAction()"
|
|
7104
|
+
})}
|
|
7105
|
+
|
|
7106
|
+
<!-- Confirmation Dialog Script -->
|
|
7107
|
+
${chunkEHSZ6TAN_cjs.getConfirmationDialogScript()}
|
|
7108
|
+
|
|
7109
|
+
<!-- Advanced Search Modal -->
|
|
7110
|
+
<div id="advancedSearchModal" class="hidden fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
|
7111
|
+
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
7112
|
+
<!-- Background overlay -->
|
|
7113
|
+
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" onclick="closeAdvancedSearch()"></div>
|
|
7114
|
+
|
|
7115
|
+
<!-- Modal panel -->
|
|
7116
|
+
<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">
|
|
7117
|
+
<div class="bg-white dark:bg-zinc-900 px-4 pt-5 pb-4 sm:p-6">
|
|
7118
|
+
<!-- Header -->
|
|
7119
|
+
<div class="flex items-center justify-between mb-4">
|
|
7120
|
+
<h3 class="text-lg font-semibold text-zinc-950 dark:text-white" id="modal-title">
|
|
7121
|
+
\u{1F50D} Advanced Search
|
|
7122
|
+
</h3>
|
|
7123
|
+
<button onclick="closeAdvancedSearch()" class="text-zinc-400 hover:text-zinc-500 dark:hover:text-zinc-300">
|
|
7124
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
7125
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
7126
|
+
</svg>
|
|
7127
|
+
</button>
|
|
7128
|
+
</div>
|
|
7129
|
+
|
|
7130
|
+
<!-- Search Form -->
|
|
7131
|
+
<form id="advancedSearchForm" class="space-y-4">
|
|
7132
|
+
<!-- Search Input -->
|
|
7133
|
+
<div>
|
|
7134
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search Query</label>
|
|
7135
|
+
<div class="relative">
|
|
7136
|
+
<input
|
|
7137
|
+
type="text"
|
|
7138
|
+
id="searchQuery"
|
|
7139
|
+
name="query"
|
|
7140
|
+
placeholder="Enter your search query..."
|
|
7141
|
+
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"
|
|
7142
|
+
autocomplete="off"
|
|
7143
|
+
/>
|
|
7144
|
+
<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>
|
|
7145
|
+
</div>
|
|
7146
|
+
</div>
|
|
7147
|
+
|
|
7148
|
+
<!-- Mode Toggle -->
|
|
7149
|
+
<div>
|
|
7150
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search Mode</label>
|
|
7151
|
+
<div class="flex gap-4">
|
|
7152
|
+
<label class="flex items-center">
|
|
7153
|
+
<input type="radio" name="mode" value="ai" checked class="mr-2">
|
|
7154
|
+
<span class="text-sm text-zinc-950 dark:text-white">\u{1F916} AI Search (Semantic)</span>
|
|
7155
|
+
</label>
|
|
7156
|
+
<label class="flex items-center">
|
|
7157
|
+
<input type="radio" name="mode" value="keyword" class="mr-2">
|
|
7158
|
+
<span class="text-sm text-zinc-950 dark:text-white">\u{1F524} Keyword Search</span>
|
|
7159
|
+
</label>
|
|
7160
|
+
</div>
|
|
7161
|
+
</div>
|
|
7162
|
+
|
|
7163
|
+
<!-- Filters -->
|
|
7164
|
+
<div class="border-t border-zinc-200 dark:border-zinc-800 pt-4">
|
|
7165
|
+
<h4 class="text-sm font-semibold text-zinc-950 dark:text-white mb-3">Filters</h4>
|
|
7166
|
+
|
|
7167
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
7168
|
+
<!-- Collection Filter -->
|
|
7169
|
+
<div>
|
|
7170
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Collections</label>
|
|
7171
|
+
<select
|
|
7172
|
+
id="filterCollections"
|
|
7173
|
+
name="collections"
|
|
7174
|
+
multiple
|
|
7175
|
+
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"
|
|
7176
|
+
size="4"
|
|
7177
|
+
>
|
|
7178
|
+
<option value="">All Collections</option>
|
|
7179
|
+
${data.models.map(
|
|
7180
|
+
(model) => `
|
|
7181
|
+
<option value="${model.name}">${model.displayName}</option>
|
|
7182
|
+
`
|
|
7183
|
+
).join("")}
|
|
7184
|
+
</select>
|
|
7185
|
+
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">Hold Ctrl/Cmd to select multiple</p>
|
|
7186
|
+
</div>
|
|
7187
|
+
|
|
7188
|
+
<!-- Status Filter -->
|
|
7189
|
+
<div>
|
|
7190
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Status</label>
|
|
7191
|
+
<select
|
|
7192
|
+
id="filterStatus"
|
|
7193
|
+
name="status"
|
|
7194
|
+
multiple
|
|
7195
|
+
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"
|
|
7196
|
+
size="4"
|
|
7197
|
+
>
|
|
7198
|
+
<option value="published">Published</option>
|
|
7199
|
+
<option value="draft">Draft</option>
|
|
7200
|
+
<option value="review">Under Review</option>
|
|
7201
|
+
<option value="scheduled">Scheduled</option>
|
|
7202
|
+
<option value="archived">Archived</option>
|
|
7203
|
+
</select>
|
|
7204
|
+
</div>
|
|
7205
|
+
</div>
|
|
7206
|
+
</div>
|
|
7207
|
+
|
|
7208
|
+
<!-- Actions -->
|
|
7209
|
+
<div class="flex items-center justify-end gap-3 pt-4 border-t border-zinc-200 dark:border-zinc-800">
|
|
7210
|
+
<button
|
|
7211
|
+
type="button"
|
|
7212
|
+
onclick="closeAdvancedSearch()"
|
|
7213
|
+
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"
|
|
7214
|
+
>
|
|
7215
|
+
Cancel
|
|
7216
|
+
</button>
|
|
7217
|
+
<button
|
|
7218
|
+
type="submit"
|
|
7219
|
+
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"
|
|
7220
|
+
>
|
|
7221
|
+
Search
|
|
7222
|
+
</button>
|
|
7223
|
+
</div>
|
|
7224
|
+
</form>
|
|
7225
|
+
</div>
|
|
7226
|
+
|
|
7227
|
+
<!-- Results Area -->
|
|
7228
|
+
<div id="searchResults" class="hidden px-4 pb-4 sm:px-6">
|
|
7229
|
+
<div class="border-t border-zinc-200 dark:border-zinc-800 pt-4">
|
|
7230
|
+
<div id="searchResultsContent" class="space-y-3"></div>
|
|
7231
|
+
<div id="searchResultsPagination" class="mt-4 flex items-center justify-between"></div>
|
|
7232
|
+
</div>
|
|
7233
|
+
</div>
|
|
7234
|
+
</div>
|
|
7235
|
+
</div>
|
|
7236
|
+
</div>
|
|
7237
|
+
|
|
7238
|
+
<script>
|
|
7239
|
+
// Open modal
|
|
7240
|
+
function openAdvancedSearch() {
|
|
7241
|
+
document.getElementById('advancedSearchModal').classList.remove('hidden');
|
|
7242
|
+
document.getElementById('searchQuery').focus();
|
|
7243
|
+
}
|
|
7244
|
+
|
|
7245
|
+
// Close modal
|
|
7246
|
+
function closeAdvancedSearch() {
|
|
7247
|
+
document.getElementById('advancedSearchModal').classList.add('hidden');
|
|
7248
|
+
document.getElementById('searchResults').classList.add('hidden');
|
|
7249
|
+
}
|
|
7250
|
+
|
|
7251
|
+
// Autocomplete
|
|
7252
|
+
// Using var instead of let to avoid redeclaration errors when HTMX re-executes script tags
|
|
7253
|
+
var autocompleteTimeout;
|
|
7254
|
+
var searchQueryInput = document.getElementById('searchQuery');
|
|
7255
|
+
if (searchQueryInput) {
|
|
7256
|
+
searchQueryInput.addEventListener('input', (e) => {
|
|
7257
|
+
const query = e.target.value.trim();
|
|
7258
|
+
const suggestionsDiv = document.getElementById('searchSuggestions');
|
|
7259
|
+
|
|
7260
|
+
clearTimeout(autocompleteTimeout);
|
|
7261
|
+
|
|
7262
|
+
if (query.length < 2) {
|
|
7263
|
+
suggestionsDiv.classList.add('hidden');
|
|
7264
|
+
return;
|
|
7265
|
+
}
|
|
7266
|
+
|
|
7267
|
+
autocompleteTimeout = setTimeout(async () => {
|
|
7268
|
+
try {
|
|
7269
|
+
const res = await fetch(\`/api/search/suggest?q=\${encodeURIComponent(query)}\`);
|
|
7270
|
+
const { data } = await res.json();
|
|
7271
|
+
|
|
7272
|
+
if (data && data.length > 0) {
|
|
7273
|
+
suggestionsDiv.innerHTML = data.map(s => \`
|
|
7274
|
+
<div class="px-4 py-2 hover:bg-zinc-100 dark:hover:bg-zinc-700 cursor-pointer" onclick="selectSuggestion('\${s.replace(/'/g, "\\'")}')">\${s}</div>
|
|
7275
|
+
\`).join('');
|
|
7276
|
+
suggestionsDiv.classList.remove('hidden');
|
|
7277
|
+
} else {
|
|
7278
|
+
suggestionsDiv.classList.add('hidden');
|
|
7279
|
+
}
|
|
7280
|
+
} catch (error) {
|
|
7281
|
+
console.error('Autocomplete error:', error);
|
|
7282
|
+
}
|
|
7283
|
+
}, 300);
|
|
7284
|
+
});
|
|
7285
|
+
}
|
|
7286
|
+
|
|
7287
|
+
function selectSuggestion(suggestion) {
|
|
7288
|
+
document.getElementById('searchQuery').value = suggestion;
|
|
7289
|
+
document.getElementById('searchSuggestions').classList.add('hidden');
|
|
7290
|
+
}
|
|
7291
|
+
|
|
7292
|
+
// Hide suggestions when clicking outside
|
|
7293
|
+
document.addEventListener('click', (e) => {
|
|
7294
|
+
const suggestionsDiv = document.getElementById('searchSuggestions');
|
|
7295
|
+
if (!e.target.closest('#searchQuery') && !e.target.closest('#searchSuggestions')) {
|
|
7296
|
+
suggestionsDiv.classList.add('hidden');
|
|
7297
|
+
}
|
|
7298
|
+
});
|
|
7299
|
+
|
|
7300
|
+
// Form submission
|
|
7301
|
+
var advancedSearchForm = document.getElementById('advancedSearchForm');
|
|
7302
|
+
if (advancedSearchForm) {
|
|
7303
|
+
advancedSearchForm.addEventListener('submit', async (e) => {
|
|
7304
|
+
e.preventDefault();
|
|
7305
|
+
|
|
7306
|
+
const formData = new FormData(e.target);
|
|
7307
|
+
const query = formData.get('query');
|
|
7308
|
+
const mode = formData.get('mode') || 'ai';
|
|
7309
|
+
|
|
7310
|
+
// Build filters
|
|
7311
|
+
const filters = {};
|
|
7312
|
+
|
|
7313
|
+
const collections = Array.from(formData.getAll('collections')).filter(c => c !== '');
|
|
7314
|
+
if (collections.length > 0) {
|
|
7315
|
+
// Need to convert collection names to IDs - for now, pass names
|
|
7316
|
+
filters.collections = collections;
|
|
7317
|
+
}
|
|
7318
|
+
|
|
7319
|
+
const status = Array.from(formData.getAll('status'));
|
|
7320
|
+
if (status.length > 0) {
|
|
7321
|
+
filters.status = status;
|
|
7322
|
+
}
|
|
7323
|
+
|
|
7324
|
+
const dateStart = formData.get('date_start');
|
|
7325
|
+
const dateEnd = formData.get('date_end');
|
|
7326
|
+
if (dateStart || dateEnd) {
|
|
7327
|
+
filters.dateRange = {
|
|
7328
|
+
start: dateStart ? new Date(dateStart) : null,
|
|
7329
|
+
end: dateEnd ? new Date(dateEnd) : null,
|
|
7330
|
+
field: 'created_at'
|
|
7331
|
+
};
|
|
7332
|
+
}
|
|
7333
|
+
|
|
7334
|
+
// Execute search
|
|
7335
|
+
try {
|
|
7336
|
+
const res = await fetch('/api/search', {
|
|
7337
|
+
method: 'POST',
|
|
7338
|
+
headers: {'Content-Type': 'application/json'},
|
|
7339
|
+
body: JSON.stringify({
|
|
7340
|
+
query,
|
|
7341
|
+
mode,
|
|
7342
|
+
filters,
|
|
7343
|
+
limit: 20
|
|
7344
|
+
})
|
|
7345
|
+
});
|
|
7346
|
+
|
|
7347
|
+
const { data } = await res.json();
|
|
7348
|
+
|
|
7349
|
+
if (data && data.results) {
|
|
7350
|
+
displaySearchResults(data);
|
|
7351
|
+
}
|
|
7352
|
+
} catch (error) {
|
|
7353
|
+
console.error('Search error:', error);
|
|
7354
|
+
alert('Search failed. Please try again.');
|
|
7355
|
+
}
|
|
5733
7356
|
});
|
|
5734
7357
|
}
|
|
5735
7358
|
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
const
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
7359
|
+
function displaySearchResults(searchData) {
|
|
7360
|
+
const resultsDiv = document.getElementById('searchResultsContent');
|
|
7361
|
+
const resultsSection = document.getElementById('searchResults');
|
|
7362
|
+
|
|
7363
|
+
if (searchData.results.length === 0) {
|
|
7364
|
+
resultsDiv.innerHTML = '<p class="text-sm text-zinc-500 dark:text-zinc-400">No results found.</p>';
|
|
7365
|
+
} else {
|
|
7366
|
+
resultsDiv.innerHTML = searchData.results.map(result => \`
|
|
7367
|
+
<div class="p-4 rounded-lg border border-zinc-200 dark:border-zinc-800 hover:bg-zinc-50 dark:hover:bg-zinc-800">
|
|
7368
|
+
<div class="flex items-start justify-between">
|
|
7369
|
+
<div class="flex-1">
|
|
7370
|
+
<h4 class="text-sm font-semibold text-zinc-950 dark:text-white mb-1">
|
|
7371
|
+
<a href="/admin/content/\${result.id}/edit" class="hover:text-indigo-600 dark:hover:text-indigo-400">\${result.title || 'Untitled'}</a>
|
|
7372
|
+
</h4>
|
|
7373
|
+
<p class="text-xs text-zinc-500 dark:text-zinc-400 mb-2">
|
|
7374
|
+
\${result.collection_name} \u2022 \${new Date(result.created_at).toLocaleDateString()}
|
|
7375
|
+
\${result.relevance_score ? \` \u2022 Relevance: \${(result.relevance_score * 100).toFixed(0)}%\` : ''}
|
|
7376
|
+
</p>
|
|
7377
|
+
\${result.snippet ? \`<p class="text-sm text-zinc-600 dark:text-zinc-400">\${result.snippet}</p>\` : ''}
|
|
7378
|
+
</div>
|
|
7379
|
+
<div class="ml-4">
|
|
7380
|
+
<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>
|
|
7381
|
+
</div>
|
|
7382
|
+
</div>
|
|
7383
|
+
</div>
|
|
7384
|
+
\`).join('');
|
|
5748
7385
|
}
|
|
7386
|
+
|
|
7387
|
+
resultsSection.classList.remove('hidden');
|
|
7388
|
+
resultsSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
5749
7389
|
}
|
|
5750
7390
|
|
|
7391
|
+
// Make functions globally available
|
|
7392
|
+
window.openAdvancedSearch = openAdvancedSearch;
|
|
7393
|
+
window.closeAdvancedSearch = closeAdvancedSearch;
|
|
5751
7394
|
</script>
|
|
5752
|
-
|
|
5753
|
-
<!-- Confirmation Dialog for Bulk Actions -->
|
|
5754
|
-
${chunkAZLU3ROK_cjs.renderConfirmationDialog({
|
|
5755
|
-
id: "bulk-action-confirm",
|
|
5756
|
-
title: "Confirm Bulk Action",
|
|
5757
|
-
message: "Are you sure you want to perform this action? This operation will affect multiple items.",
|
|
5758
|
-
confirmText: "Confirm",
|
|
5759
|
-
cancelText: "Cancel",
|
|
5760
|
-
confirmClass: "bg-blue-500 hover:bg-blue-400",
|
|
5761
|
-
iconColor: "blue",
|
|
5762
|
-
onConfirm: "executeBulkAction()"
|
|
5763
|
-
})}
|
|
5764
|
-
|
|
5765
|
-
<!-- Confirmation Dialog Script -->
|
|
5766
|
-
${chunkAZLU3ROK_cjs.getConfirmationDialogScript()}
|
|
5767
7395
|
`;
|
|
5768
7396
|
const layoutData = {
|
|
5769
7397
|
title: "Content Management",
|
|
@@ -5773,7 +7401,7 @@ function renderContentListPage(data) {
|
|
|
5773
7401
|
version: data.version,
|
|
5774
7402
|
content: pageContent
|
|
5775
7403
|
};
|
|
5776
|
-
return
|
|
7404
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
5777
7405
|
}
|
|
5778
7406
|
|
|
5779
7407
|
// src/templates/components/version-history.template.ts
|
|
@@ -5967,7 +7595,123 @@ async function isPluginActive2(db, pluginId) {
|
|
|
5967
7595
|
|
|
5968
7596
|
// src/routes/admin-content.ts
|
|
5969
7597
|
var adminContentRoutes = new hono.Hono();
|
|
5970
|
-
|
|
7598
|
+
function parseFieldValue(field, formData, options = {}) {
|
|
7599
|
+
const { skipValidation = false } = options;
|
|
7600
|
+
const value = formData.get(field.field_name);
|
|
7601
|
+
const errors = [];
|
|
7602
|
+
const blocksConfig = chunkMYB5RY7H_cjs.getBlocksFieldConfig(field.field_options);
|
|
7603
|
+
if (blocksConfig) {
|
|
7604
|
+
const parsed = chunkMYB5RY7H_cjs.parseBlocksValue(value, blocksConfig);
|
|
7605
|
+
if (!skipValidation && field.is_required && parsed.value.length === 0) {
|
|
7606
|
+
parsed.errors.push(`${field.field_label} is required`);
|
|
7607
|
+
}
|
|
7608
|
+
return { value: parsed.value, errors: parsed.errors };
|
|
7609
|
+
}
|
|
7610
|
+
if (!skipValidation && field.is_required && (!value || value.toString().trim() === "")) {
|
|
7611
|
+
return { value: null, errors: [`${field.field_label} is required`] };
|
|
7612
|
+
}
|
|
7613
|
+
switch (field.field_type) {
|
|
7614
|
+
case "number":
|
|
7615
|
+
if (value && isNaN(Number(value))) {
|
|
7616
|
+
if (!skipValidation) {
|
|
7617
|
+
errors.push(`${field.field_label} must be a valid number`);
|
|
7618
|
+
}
|
|
7619
|
+
return { value: null, errors };
|
|
7620
|
+
}
|
|
7621
|
+
return { value: value ? Number(value) : null, errors: [] };
|
|
7622
|
+
case "boolean":
|
|
7623
|
+
const submitted = formData.get(`${field.field_name}_submitted`);
|
|
7624
|
+
return { value: submitted ? value === "true" : false, errors: [] };
|
|
7625
|
+
case "select":
|
|
7626
|
+
if (field.field_options?.multiple) {
|
|
7627
|
+
return { value: formData.getAll(`${field.field_name}[]`), errors: [] };
|
|
7628
|
+
}
|
|
7629
|
+
return { value, errors: [] };
|
|
7630
|
+
case "array": {
|
|
7631
|
+
if (!value || value.toString().trim() === "") {
|
|
7632
|
+
if (!skipValidation && field.is_required) {
|
|
7633
|
+
errors.push(`${field.field_label} is required`);
|
|
7634
|
+
}
|
|
7635
|
+
return { value: [], errors };
|
|
7636
|
+
}
|
|
7637
|
+
try {
|
|
7638
|
+
const parsed = JSON.parse(value.toString());
|
|
7639
|
+
if (!Array.isArray(parsed)) {
|
|
7640
|
+
if (!skipValidation) {
|
|
7641
|
+
errors.push(`${field.field_label} must be a JSON array`);
|
|
7642
|
+
}
|
|
7643
|
+
return { value: [], errors };
|
|
7644
|
+
}
|
|
7645
|
+
if (!skipValidation && field.is_required && parsed.length === 0) {
|
|
7646
|
+
errors.push(`${field.field_label} is required`);
|
|
7647
|
+
}
|
|
7648
|
+
return { value: parsed, errors };
|
|
7649
|
+
} catch {
|
|
7650
|
+
if (!skipValidation) {
|
|
7651
|
+
errors.push(`${field.field_label} must be valid JSON`);
|
|
7652
|
+
}
|
|
7653
|
+
return { value: [], errors };
|
|
7654
|
+
}
|
|
7655
|
+
}
|
|
7656
|
+
case "object": {
|
|
7657
|
+
if (!value || value.toString().trim() === "") {
|
|
7658
|
+
if (!skipValidation && field.is_required) {
|
|
7659
|
+
errors.push(`${field.field_label} is required`);
|
|
7660
|
+
}
|
|
7661
|
+
return { value: {}, errors };
|
|
7662
|
+
}
|
|
7663
|
+
try {
|
|
7664
|
+
const parsed = JSON.parse(value.toString());
|
|
7665
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
7666
|
+
if (!skipValidation) {
|
|
7667
|
+
errors.push(`${field.field_label} must be a JSON object`);
|
|
7668
|
+
}
|
|
7669
|
+
return { value: {}, errors };
|
|
7670
|
+
}
|
|
7671
|
+
if (!skipValidation && field.is_required && Object.keys(parsed).length === 0) {
|
|
7672
|
+
errors.push(`${field.field_label} is required`);
|
|
7673
|
+
}
|
|
7674
|
+
return { value: parsed, errors };
|
|
7675
|
+
} catch {
|
|
7676
|
+
if (!skipValidation) {
|
|
7677
|
+
errors.push(`${field.field_label} must be valid JSON`);
|
|
7678
|
+
}
|
|
7679
|
+
return { value: {}, errors };
|
|
7680
|
+
}
|
|
7681
|
+
}
|
|
7682
|
+
case "json": {
|
|
7683
|
+
if (!value || value.toString().trim() === "") {
|
|
7684
|
+
if (!skipValidation && field.is_required) {
|
|
7685
|
+
errors.push(`${field.field_label} is required`);
|
|
7686
|
+
}
|
|
7687
|
+
return { value: null, errors };
|
|
7688
|
+
}
|
|
7689
|
+
try {
|
|
7690
|
+
return { value: JSON.parse(value.toString()), errors: [] };
|
|
7691
|
+
} catch {
|
|
7692
|
+
if (!skipValidation) {
|
|
7693
|
+
errors.push(`${field.field_label} must be valid JSON`);
|
|
7694
|
+
}
|
|
7695
|
+
return { value: null, errors };
|
|
7696
|
+
}
|
|
7697
|
+
}
|
|
7698
|
+
default:
|
|
7699
|
+
return { value, errors: [] };
|
|
7700
|
+
}
|
|
7701
|
+
}
|
|
7702
|
+
function extractFieldData(fields, formData, options = {}) {
|
|
7703
|
+
const data = {};
|
|
7704
|
+
const errors = {};
|
|
7705
|
+
for (const field of fields) {
|
|
7706
|
+
const result = parseFieldValue(field, formData, options);
|
|
7707
|
+
data[field.field_name] = result.value;
|
|
7708
|
+
if (result.errors.length > 0) {
|
|
7709
|
+
errors[field.field_name] = result.errors;
|
|
7710
|
+
}
|
|
7711
|
+
}
|
|
7712
|
+
return { data, errors };
|
|
7713
|
+
}
|
|
7714
|
+
adminContentRoutes.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
5971
7715
|
async function getCollectionFields(db, collectionId) {
|
|
5972
7716
|
const cache = chunk7FOAMNTI_cjs.getCacheService(chunk7FOAMNTI_cjs.CACHE_CONFIGS.collection);
|
|
5973
7717
|
return cache.getOrSet(
|
|
@@ -6249,21 +7993,21 @@ adminContentRoutes.get("/new", async (c) => {
|
|
|
6249
7993
|
const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
|
|
6250
7994
|
let tinymceSettings;
|
|
6251
7995
|
if (tinymceEnabled) {
|
|
6252
|
-
const pluginService = new
|
|
7996
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6253
7997
|
const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
|
|
6254
7998
|
tinymceSettings = tinymcePlugin2?.settings;
|
|
6255
7999
|
}
|
|
6256
8000
|
const quillEnabled = await isPluginActive2(db, "quill-editor");
|
|
6257
8001
|
let quillSettings;
|
|
6258
8002
|
if (quillEnabled) {
|
|
6259
|
-
const pluginService = new
|
|
8003
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6260
8004
|
const quillPlugin = await pluginService.getPlugin("quill-editor");
|
|
6261
8005
|
quillSettings = quillPlugin?.settings;
|
|
6262
8006
|
}
|
|
6263
8007
|
const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
|
|
6264
8008
|
let mdxeditorSettings;
|
|
6265
8009
|
if (mdxeditorEnabled) {
|
|
6266
|
-
const pluginService = new
|
|
8010
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6267
8011
|
const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
|
|
6268
8012
|
mdxeditorSettings = mdxeditorPlugin?.settings;
|
|
6269
8013
|
}
|
|
@@ -6354,21 +8098,21 @@ adminContentRoutes.get("/:id/edit", async (c) => {
|
|
|
6354
8098
|
const tinymceEnabled = await isPluginActive2(db, "tinymce-plugin");
|
|
6355
8099
|
let tinymceSettings;
|
|
6356
8100
|
if (tinymceEnabled) {
|
|
6357
|
-
const pluginService = new
|
|
8101
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6358
8102
|
const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
|
|
6359
8103
|
tinymceSettings = tinymcePlugin2?.settings;
|
|
6360
8104
|
}
|
|
6361
8105
|
const quillEnabled = await isPluginActive2(db, "quill-editor");
|
|
6362
8106
|
let quillSettings;
|
|
6363
8107
|
if (quillEnabled) {
|
|
6364
|
-
const pluginService = new
|
|
8108
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6365
8109
|
const quillPlugin = await pluginService.getPlugin("quill-editor");
|
|
6366
8110
|
quillSettings = quillPlugin?.settings;
|
|
6367
8111
|
}
|
|
6368
8112
|
const mdxeditorEnabled = await isPluginActive2(db, "easy-mdx");
|
|
6369
8113
|
let mdxeditorSettings;
|
|
6370
8114
|
if (mdxeditorEnabled) {
|
|
6371
|
-
const pluginService = new
|
|
8115
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
6372
8116
|
const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
|
|
6373
8117
|
mdxeditorSettings = mdxeditorPlugin?.settings;
|
|
6374
8118
|
}
|
|
@@ -6440,109 +8184,7 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
6440
8184
|
`);
|
|
6441
8185
|
}
|
|
6442
8186
|
const fields = await getCollectionFields(db, collectionId);
|
|
6443
|
-
const data =
|
|
6444
|
-
const errors = {};
|
|
6445
|
-
for (const field of fields) {
|
|
6446
|
-
const value = formData.get(field.field_name);
|
|
6447
|
-
const blocksConfig = chunkZWV3EBZ7_cjs.getBlocksFieldConfig(field.field_options);
|
|
6448
|
-
if (blocksConfig) {
|
|
6449
|
-
const parsed = chunkZWV3EBZ7_cjs.parseBlocksValue(value, blocksConfig);
|
|
6450
|
-
if (field.is_required && parsed.value.length === 0) {
|
|
6451
|
-
parsed.errors.push(`${field.field_label} is required`);
|
|
6452
|
-
}
|
|
6453
|
-
if (parsed.errors.length > 0) {
|
|
6454
|
-
errors[field.field_name] = parsed.errors;
|
|
6455
|
-
}
|
|
6456
|
-
data[field.field_name] = parsed.value;
|
|
6457
|
-
continue;
|
|
6458
|
-
}
|
|
6459
|
-
if (field.is_required && (!value || value.toString().trim() === "")) {
|
|
6460
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6461
|
-
continue;
|
|
6462
|
-
}
|
|
6463
|
-
switch (field.field_type) {
|
|
6464
|
-
case "number":
|
|
6465
|
-
if (value && isNaN(Number(value))) {
|
|
6466
|
-
errors[field.field_name] = [`${field.field_label} must be a valid number`];
|
|
6467
|
-
} else {
|
|
6468
|
-
data[field.field_name] = value ? Number(value) : null;
|
|
6469
|
-
}
|
|
6470
|
-
break;
|
|
6471
|
-
case "boolean":
|
|
6472
|
-
data[field.field_name] = formData.get(`${field.field_name}_submitted`) ? value === "true" : false;
|
|
6473
|
-
break;
|
|
6474
|
-
case "select":
|
|
6475
|
-
if (field.field_options?.multiple) {
|
|
6476
|
-
data[field.field_name] = formData.getAll(`${field.field_name}[]`);
|
|
6477
|
-
} else {
|
|
6478
|
-
data[field.field_name] = value;
|
|
6479
|
-
}
|
|
6480
|
-
break;
|
|
6481
|
-
case "array": {
|
|
6482
|
-
if (!value || value.toString().trim() === "") {
|
|
6483
|
-
data[field.field_name] = [];
|
|
6484
|
-
if (field.is_required) {
|
|
6485
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6486
|
-
}
|
|
6487
|
-
break;
|
|
6488
|
-
}
|
|
6489
|
-
try {
|
|
6490
|
-
const parsed = JSON.parse(value.toString());
|
|
6491
|
-
if (!Array.isArray(parsed)) {
|
|
6492
|
-
errors[field.field_name] = [`${field.field_label} must be a JSON array`];
|
|
6493
|
-
} else {
|
|
6494
|
-
if (field.is_required && parsed.length === 0) {
|
|
6495
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6496
|
-
}
|
|
6497
|
-
data[field.field_name] = parsed;
|
|
6498
|
-
}
|
|
6499
|
-
} catch {
|
|
6500
|
-
errors[field.field_name] = [`${field.field_label} must be valid JSON`];
|
|
6501
|
-
}
|
|
6502
|
-
break;
|
|
6503
|
-
}
|
|
6504
|
-
case "object": {
|
|
6505
|
-
if (!value || value.toString().trim() === "") {
|
|
6506
|
-
data[field.field_name] = {};
|
|
6507
|
-
if (field.is_required) {
|
|
6508
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6509
|
-
}
|
|
6510
|
-
break;
|
|
6511
|
-
}
|
|
6512
|
-
try {
|
|
6513
|
-
const parsed = JSON.parse(value.toString());
|
|
6514
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
6515
|
-
errors[field.field_name] = [`${field.field_label} must be a JSON object`];
|
|
6516
|
-
} else {
|
|
6517
|
-
if (field.is_required && Object.keys(parsed).length === 0) {
|
|
6518
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6519
|
-
}
|
|
6520
|
-
data[field.field_name] = parsed;
|
|
6521
|
-
}
|
|
6522
|
-
} catch {
|
|
6523
|
-
errors[field.field_name] = [`${field.field_label} must be valid JSON`];
|
|
6524
|
-
}
|
|
6525
|
-
break;
|
|
6526
|
-
}
|
|
6527
|
-
case "json": {
|
|
6528
|
-
if (!value || value.toString().trim() === "") {
|
|
6529
|
-
data[field.field_name] = null;
|
|
6530
|
-
if (field.is_required) {
|
|
6531
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6532
|
-
}
|
|
6533
|
-
break;
|
|
6534
|
-
}
|
|
6535
|
-
try {
|
|
6536
|
-
data[field.field_name] = JSON.parse(value.toString());
|
|
6537
|
-
} catch {
|
|
6538
|
-
errors[field.field_name] = [`${field.field_label} must be valid JSON`];
|
|
6539
|
-
}
|
|
6540
|
-
break;
|
|
6541
|
-
}
|
|
6542
|
-
default:
|
|
6543
|
-
data[field.field_name] = value;
|
|
6544
|
-
}
|
|
6545
|
-
}
|
|
8187
|
+
const { data, errors } = extractFieldData(fields, formData);
|
|
6546
8188
|
if (Object.keys(errors).length > 0) {
|
|
6547
8189
|
const formDataWithErrors = {
|
|
6548
8190
|
collection,
|
|
@@ -6659,109 +8301,7 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
6659
8301
|
`);
|
|
6660
8302
|
}
|
|
6661
8303
|
const fields = await getCollectionFields(db, existingContent.collection_id);
|
|
6662
|
-
const data =
|
|
6663
|
-
const errors = {};
|
|
6664
|
-
for (const field of fields) {
|
|
6665
|
-
const value = formData.get(field.field_name);
|
|
6666
|
-
const blocksConfig = chunkZWV3EBZ7_cjs.getBlocksFieldConfig(field.field_options);
|
|
6667
|
-
if (blocksConfig) {
|
|
6668
|
-
const parsed = chunkZWV3EBZ7_cjs.parseBlocksValue(value, blocksConfig);
|
|
6669
|
-
if (field.is_required && parsed.value.length === 0) {
|
|
6670
|
-
parsed.errors.push(`${field.field_label} is required`);
|
|
6671
|
-
}
|
|
6672
|
-
if (parsed.errors.length > 0) {
|
|
6673
|
-
errors[field.field_name] = parsed.errors;
|
|
6674
|
-
}
|
|
6675
|
-
data[field.field_name] = parsed.value;
|
|
6676
|
-
continue;
|
|
6677
|
-
}
|
|
6678
|
-
if (field.is_required && (!value || value.toString().trim() === "")) {
|
|
6679
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6680
|
-
continue;
|
|
6681
|
-
}
|
|
6682
|
-
switch (field.field_type) {
|
|
6683
|
-
case "number":
|
|
6684
|
-
if (value && isNaN(Number(value))) {
|
|
6685
|
-
errors[field.field_name] = [`${field.field_label} must be a valid number`];
|
|
6686
|
-
} else {
|
|
6687
|
-
data[field.field_name] = value ? Number(value) : null;
|
|
6688
|
-
}
|
|
6689
|
-
break;
|
|
6690
|
-
case "boolean":
|
|
6691
|
-
data[field.field_name] = formData.get(`${field.field_name}_submitted`) ? value === "true" : false;
|
|
6692
|
-
break;
|
|
6693
|
-
case "select":
|
|
6694
|
-
if (field.field_options?.multiple) {
|
|
6695
|
-
data[field.field_name] = formData.getAll(`${field.field_name}[]`);
|
|
6696
|
-
} else {
|
|
6697
|
-
data[field.field_name] = value;
|
|
6698
|
-
}
|
|
6699
|
-
break;
|
|
6700
|
-
case "array": {
|
|
6701
|
-
if (!value || value.toString().trim() === "") {
|
|
6702
|
-
data[field.field_name] = [];
|
|
6703
|
-
if (field.is_required) {
|
|
6704
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6705
|
-
}
|
|
6706
|
-
break;
|
|
6707
|
-
}
|
|
6708
|
-
try {
|
|
6709
|
-
const parsed = JSON.parse(value.toString());
|
|
6710
|
-
if (!Array.isArray(parsed)) {
|
|
6711
|
-
errors[field.field_name] = [`${field.field_label} must be a JSON array`];
|
|
6712
|
-
} else {
|
|
6713
|
-
if (field.is_required && parsed.length === 0) {
|
|
6714
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6715
|
-
}
|
|
6716
|
-
data[field.field_name] = parsed;
|
|
6717
|
-
}
|
|
6718
|
-
} catch {
|
|
6719
|
-
errors[field.field_name] = [`${field.field_label} must be valid JSON`];
|
|
6720
|
-
}
|
|
6721
|
-
break;
|
|
6722
|
-
}
|
|
6723
|
-
case "object": {
|
|
6724
|
-
if (!value || value.toString().trim() === "") {
|
|
6725
|
-
data[field.field_name] = {};
|
|
6726
|
-
if (field.is_required) {
|
|
6727
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6728
|
-
}
|
|
6729
|
-
break;
|
|
6730
|
-
}
|
|
6731
|
-
try {
|
|
6732
|
-
const parsed = JSON.parse(value.toString());
|
|
6733
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
6734
|
-
errors[field.field_name] = [`${field.field_label} must be a JSON object`];
|
|
6735
|
-
} else {
|
|
6736
|
-
if (field.is_required && Object.keys(parsed).length === 0) {
|
|
6737
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6738
|
-
}
|
|
6739
|
-
data[field.field_name] = parsed;
|
|
6740
|
-
}
|
|
6741
|
-
} catch {
|
|
6742
|
-
errors[field.field_name] = [`${field.field_label} must be valid JSON`];
|
|
6743
|
-
}
|
|
6744
|
-
break;
|
|
6745
|
-
}
|
|
6746
|
-
case "json": {
|
|
6747
|
-
if (!value || value.toString().trim() === "") {
|
|
6748
|
-
data[field.field_name] = null;
|
|
6749
|
-
if (field.is_required) {
|
|
6750
|
-
errors[field.field_name] = [`${field.field_label} is required`];
|
|
6751
|
-
}
|
|
6752
|
-
break;
|
|
6753
|
-
}
|
|
6754
|
-
try {
|
|
6755
|
-
data[field.field_name] = JSON.parse(value.toString());
|
|
6756
|
-
} catch {
|
|
6757
|
-
errors[field.field_name] = [`${field.field_label} must be valid JSON`];
|
|
6758
|
-
}
|
|
6759
|
-
break;
|
|
6760
|
-
}
|
|
6761
|
-
default:
|
|
6762
|
-
data[field.field_name] = value;
|
|
6763
|
-
}
|
|
6764
|
-
}
|
|
8304
|
+
const { data, errors } = extractFieldData(fields, formData);
|
|
6765
8305
|
if (Object.keys(errors).length > 0) {
|
|
6766
8306
|
const formDataWithErrors = {
|
|
6767
8307
|
id,
|
|
@@ -6874,33 +8414,7 @@ adminContentRoutes.post("/preview", async (c) => {
|
|
|
6874
8414
|
return c.html("<p>Collection not found</p>");
|
|
6875
8415
|
}
|
|
6876
8416
|
const fields = await getCollectionFields(db, collectionId);
|
|
6877
|
-
const data = {};
|
|
6878
|
-
for (const field of fields) {
|
|
6879
|
-
const value = formData.get(field.field_name);
|
|
6880
|
-
const blocksConfig = chunkZWV3EBZ7_cjs.getBlocksFieldConfig(field.field_options);
|
|
6881
|
-
if (blocksConfig) {
|
|
6882
|
-
const parsed = chunkZWV3EBZ7_cjs.parseBlocksValue(value, blocksConfig);
|
|
6883
|
-
data[field.field_name] = parsed.value;
|
|
6884
|
-
continue;
|
|
6885
|
-
}
|
|
6886
|
-
switch (field.field_type) {
|
|
6887
|
-
case "number":
|
|
6888
|
-
data[field.field_name] = value ? Number(value) : null;
|
|
6889
|
-
break;
|
|
6890
|
-
case "boolean":
|
|
6891
|
-
data[field.field_name] = value === "true";
|
|
6892
|
-
break;
|
|
6893
|
-
case "select":
|
|
6894
|
-
if (field.field_options?.multiple) {
|
|
6895
|
-
data[field.field_name] = formData.getAll(`${field.field_name}[]`);
|
|
6896
|
-
} else {
|
|
6897
|
-
data[field.field_name] = value;
|
|
6898
|
-
}
|
|
6899
|
-
break;
|
|
6900
|
-
default:
|
|
6901
|
-
data[field.field_name] = value;
|
|
6902
|
-
}
|
|
6903
|
-
}
|
|
8417
|
+
const { data } = extractFieldData(fields, formData, { skipValidation: true });
|
|
6904
8418
|
const previewHTML = `
|
|
6905
8419
|
<!DOCTYPE html>
|
|
6906
8420
|
<html lang="en">
|
|
@@ -7328,7 +8842,7 @@ ${JSON.stringify(data, null, 2)}
|
|
|
7328
8842
|
var admin_content_default = adminContentRoutes;
|
|
7329
8843
|
|
|
7330
8844
|
// src/templates/pages/admin-profile.template.ts
|
|
7331
|
-
|
|
8845
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
7332
8846
|
function renderAvatarImage(avatarUrl, firstName, lastName) {
|
|
7333
8847
|
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">
|
|
7334
8848
|
${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>`}
|
|
@@ -7348,8 +8862,8 @@ function renderProfilePage(data) {
|
|
|
7348
8862
|
</div>
|
|
7349
8863
|
|
|
7350
8864
|
<!-- Alert Messages -->
|
|
7351
|
-
${data.error ?
|
|
7352
|
-
${data.success ?
|
|
8865
|
+
${data.error ? chunkEHSZ6TAN_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
|
|
8866
|
+
${data.success ? chunkEHSZ6TAN_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
|
|
7353
8867
|
|
|
7354
8868
|
<!-- Profile Form -->
|
|
7355
8869
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
@@ -7736,7 +9250,7 @@ function renderProfilePage(data) {
|
|
|
7736
9250
|
version: data.version,
|
|
7737
9251
|
content: pageContent
|
|
7738
9252
|
};
|
|
7739
|
-
return
|
|
9253
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
7740
9254
|
}
|
|
7741
9255
|
|
|
7742
9256
|
// src/templates/components/alert.template.ts
|
|
@@ -8019,7 +9533,7 @@ function renderActivityLogsPage(data) {
|
|
|
8019
9533
|
user: data.user,
|
|
8020
9534
|
content: pageContent
|
|
8021
9535
|
};
|
|
8022
|
-
return
|
|
9536
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayout(layoutData);
|
|
8023
9537
|
}
|
|
8024
9538
|
function getActionBadgeClass(action) {
|
|
8025
9539
|
if (action.includes("login") || action.includes("logout")) {
|
|
@@ -8039,7 +9553,7 @@ function formatAction(action) {
|
|
|
8039
9553
|
}
|
|
8040
9554
|
|
|
8041
9555
|
// src/templates/pages/admin-user-edit.template.ts
|
|
8042
|
-
|
|
9556
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
8043
9557
|
|
|
8044
9558
|
// src/templates/components/confirmation-dialog.template.ts
|
|
8045
9559
|
function renderConfirmationDialog2(options) {
|
|
@@ -8160,8 +9674,8 @@ function renderUserEditPage(data) {
|
|
|
8160
9674
|
|
|
8161
9675
|
<!-- Alert Messages -->
|
|
8162
9676
|
<div id="form-messages">
|
|
8163
|
-
${data.error ?
|
|
8164
|
-
${data.success ?
|
|
9677
|
+
${data.error ? chunkEHSZ6TAN_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
|
|
9678
|
+
${data.success ? chunkEHSZ6TAN_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
|
|
8165
9679
|
</div>
|
|
8166
9680
|
|
|
8167
9681
|
<!-- User Edit Form -->
|
|
@@ -8180,7 +9694,7 @@ function renderUserEditPage(data) {
|
|
|
8180
9694
|
<input
|
|
8181
9695
|
type="text"
|
|
8182
9696
|
name="first_name"
|
|
8183
|
-
value="${
|
|
9697
|
+
value="${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.firstName || "")}"
|
|
8184
9698
|
required
|
|
8185
9699
|
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"
|
|
8186
9700
|
/>
|
|
@@ -8191,7 +9705,7 @@ function renderUserEditPage(data) {
|
|
|
8191
9705
|
<input
|
|
8192
9706
|
type="text"
|
|
8193
9707
|
name="last_name"
|
|
8194
|
-
value="${
|
|
9708
|
+
value="${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.lastName || "")}"
|
|
8195
9709
|
required
|
|
8196
9710
|
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"
|
|
8197
9711
|
/>
|
|
@@ -8202,7 +9716,7 @@ function renderUserEditPage(data) {
|
|
|
8202
9716
|
<input
|
|
8203
9717
|
type="text"
|
|
8204
9718
|
name="username"
|
|
8205
|
-
value="${
|
|
9719
|
+
value="${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.username || "")}"
|
|
8206
9720
|
required
|
|
8207
9721
|
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"
|
|
8208
9722
|
/>
|
|
@@ -8213,7 +9727,7 @@ function renderUserEditPage(data) {
|
|
|
8213
9727
|
<input
|
|
8214
9728
|
type="email"
|
|
8215
9729
|
name="email"
|
|
8216
|
-
value="${
|
|
9730
|
+
value="${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.email || "")}"
|
|
8217
9731
|
required
|
|
8218
9732
|
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"
|
|
8219
9733
|
/>
|
|
@@ -8224,7 +9738,7 @@ function renderUserEditPage(data) {
|
|
|
8224
9738
|
<input
|
|
8225
9739
|
type="tel"
|
|
8226
9740
|
name="phone"
|
|
8227
|
-
value="${
|
|
9741
|
+
value="${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.phone || "")}"
|
|
8228
9742
|
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"
|
|
8229
9743
|
/>
|
|
8230
9744
|
</div>
|
|
@@ -8238,7 +9752,7 @@ function renderUserEditPage(data) {
|
|
|
8238
9752
|
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"
|
|
8239
9753
|
>
|
|
8240
9754
|
${data.roles.map((role) => `
|
|
8241
|
-
<option value="${
|
|
9755
|
+
<option value="${chunkMYB5RY7H_cjs.escapeHtml(role.value)}" ${data.userToEdit.role === role.value ? "selected" : ""}>${chunkMYB5RY7H_cjs.escapeHtml(role.label)}</option>
|
|
8242
9756
|
`).join("")}
|
|
8243
9757
|
</select>
|
|
8244
9758
|
<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">
|
|
@@ -8247,14 +9761,87 @@ function renderUserEditPage(data) {
|
|
|
8247
9761
|
</div>
|
|
8248
9762
|
</div>
|
|
8249
9763
|
</div>
|
|
9764
|
+
</div>
|
|
9765
|
+
|
|
9766
|
+
<!-- Profile Information -->
|
|
9767
|
+
<div class="mb-8">
|
|
9768
|
+
<h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Profile Information</h3>
|
|
9769
|
+
<p class="text-sm text-zinc-500 dark:text-zinc-400 mb-4">Extended profile data for this user</p>
|
|
9770
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
9771
|
+
<div>
|
|
9772
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Display Name</label>
|
|
9773
|
+
<input
|
|
9774
|
+
type="text"
|
|
9775
|
+
name="profile_display_name"
|
|
9776
|
+
value="${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.profile?.displayName || "")}"
|
|
9777
|
+
placeholder="Public display name"
|
|
9778
|
+
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"
|
|
9779
|
+
/>
|
|
9780
|
+
</div>
|
|
9781
|
+
|
|
9782
|
+
<div>
|
|
9783
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Company</label>
|
|
9784
|
+
<input
|
|
9785
|
+
type="text"
|
|
9786
|
+
name="profile_company"
|
|
9787
|
+
value="${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.profile?.company || "")}"
|
|
9788
|
+
placeholder="Company or organization"
|
|
9789
|
+
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"
|
|
9790
|
+
/>
|
|
9791
|
+
</div>
|
|
9792
|
+
|
|
9793
|
+
<div>
|
|
9794
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Job Title</label>
|
|
9795
|
+
<input
|
|
9796
|
+
type="text"
|
|
9797
|
+
name="profile_job_title"
|
|
9798
|
+
value="${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.profile?.jobTitle || "")}"
|
|
9799
|
+
placeholder="Job title or role"
|
|
9800
|
+
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"
|
|
9801
|
+
/>
|
|
9802
|
+
</div>
|
|
9803
|
+
|
|
9804
|
+
<div>
|
|
9805
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Website</label>
|
|
9806
|
+
<input
|
|
9807
|
+
type="url"
|
|
9808
|
+
name="profile_website"
|
|
9809
|
+
value="${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.profile?.website || "")}"
|
|
9810
|
+
placeholder="https://example.com"
|
|
9811
|
+
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"
|
|
9812
|
+
/>
|
|
9813
|
+
</div>
|
|
9814
|
+
|
|
9815
|
+
<div>
|
|
9816
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Location</label>
|
|
9817
|
+
<input
|
|
9818
|
+
type="text"
|
|
9819
|
+
name="profile_location"
|
|
9820
|
+
value="${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.profile?.location || "")}"
|
|
9821
|
+
placeholder="City, Country"
|
|
9822
|
+
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"
|
|
9823
|
+
/>
|
|
9824
|
+
</div>
|
|
9825
|
+
|
|
9826
|
+
<div>
|
|
9827
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Date of Birth</label>
|
|
9828
|
+
<input
|
|
9829
|
+
type="date"
|
|
9830
|
+
name="profile_date_of_birth"
|
|
9831
|
+
value="${data.userToEdit.profile?.dateOfBirth ? new Date(data.userToEdit.profile.dateOfBirth).toISOString().split("T")[0] : ""}"
|
|
9832
|
+
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"
|
|
9833
|
+
/>
|
|
9834
|
+
</div>
|
|
9835
|
+
</div>
|
|
8250
9836
|
|
|
8251
9837
|
<div class="mt-6">
|
|
8252
9838
|
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Bio</label>
|
|
8253
9839
|
<textarea
|
|
8254
|
-
name="
|
|
9840
|
+
name="profile_bio"
|
|
8255
9841
|
rows="3"
|
|
9842
|
+
placeholder="Short bio or description"
|
|
8256
9843
|
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"
|
|
8257
|
-
>${
|
|
9844
|
+
>${chunkMYB5RY7H_cjs.escapeHtml(data.userToEdit.profile?.bio || "")}</textarea>
|
|
8258
9845
|
</div>
|
|
8259
9846
|
</div>
|
|
8260
9847
|
|
|
@@ -8454,11 +10041,11 @@ function renderUserEditPage(data) {
|
|
|
8454
10041
|
user: data.user,
|
|
8455
10042
|
content: pageContent
|
|
8456
10043
|
};
|
|
8457
|
-
return
|
|
10044
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
8458
10045
|
}
|
|
8459
10046
|
|
|
8460
10047
|
// src/templates/pages/admin-user-new.template.ts
|
|
8461
|
-
|
|
10048
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
8462
10049
|
function renderUserNewPage(data) {
|
|
8463
10050
|
const pageContent = `
|
|
8464
10051
|
<div>
|
|
@@ -8497,8 +10084,8 @@ function renderUserNewPage(data) {
|
|
|
8497
10084
|
|
|
8498
10085
|
<!-- Alert Messages -->
|
|
8499
10086
|
<div id="form-messages">
|
|
8500
|
-
${data.error ?
|
|
8501
|
-
${data.success ?
|
|
10087
|
+
${data.error ? chunkEHSZ6TAN_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
|
|
10088
|
+
${data.success ? chunkEHSZ6TAN_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
|
|
8502
10089
|
</div>
|
|
8503
10090
|
|
|
8504
10091
|
<!-- User New Form -->
|
|
@@ -8742,11 +10329,11 @@ function renderUserNewPage(data) {
|
|
|
8742
10329
|
user: data.user,
|
|
8743
10330
|
content: pageContent
|
|
8744
10331
|
};
|
|
8745
|
-
return
|
|
10332
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
8746
10333
|
}
|
|
8747
10334
|
|
|
8748
10335
|
// src/templates/pages/admin-users-list.template.ts
|
|
8749
|
-
|
|
10336
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
8750
10337
|
function renderUsersListPage(data) {
|
|
8751
10338
|
const columns = [
|
|
8752
10339
|
{
|
|
@@ -8897,8 +10484,8 @@ function renderUsersListPage(data) {
|
|
|
8897
10484
|
</div>
|
|
8898
10485
|
|
|
8899
10486
|
<!-- Alert Messages -->
|
|
8900
|
-
${data.error ?
|
|
8901
|
-
${data.success ?
|
|
10487
|
+
${data.error ? chunkEHSZ6TAN_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
|
|
10488
|
+
${data.success ? chunkEHSZ6TAN_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
|
|
8902
10489
|
|
|
8903
10490
|
<!-- Stats -->
|
|
8904
10491
|
<div class="mb-6">
|
|
@@ -9075,10 +10662,10 @@ function renderUsersListPage(data) {
|
|
|
9075
10662
|
</div>
|
|
9076
10663
|
|
|
9077
10664
|
<!-- Users Table -->
|
|
9078
|
-
${
|
|
10665
|
+
${chunkEHSZ6TAN_cjs.renderTable(tableData)}
|
|
9079
10666
|
|
|
9080
10667
|
<!-- Pagination -->
|
|
9081
|
-
${data.pagination ?
|
|
10668
|
+
${data.pagination ? chunkEHSZ6TAN_cjs.renderPagination(data.pagination) : ""}
|
|
9082
10669
|
</div>
|
|
9083
10670
|
|
|
9084
10671
|
<script>
|
|
@@ -9149,12 +10736,12 @@ function renderUsersListPage(data) {
|
|
|
9149
10736
|
version: data.version,
|
|
9150
10737
|
content: pageContent
|
|
9151
10738
|
};
|
|
9152
|
-
return
|
|
10739
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
9153
10740
|
}
|
|
9154
10741
|
|
|
9155
10742
|
// src/routes/admin-users.ts
|
|
9156
10743
|
var userRoutes = new hono.Hono();
|
|
9157
|
-
userRoutes.use("*",
|
|
10744
|
+
userRoutes.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
9158
10745
|
userRoutes.get("/", (c) => {
|
|
9159
10746
|
return c.redirect("/admin/dashboard");
|
|
9160
10747
|
});
|
|
@@ -9253,12 +10840,12 @@ userRoutes.put("/profile", async (c) => {
|
|
|
9253
10840
|
const db = c.env.DB;
|
|
9254
10841
|
try {
|
|
9255
10842
|
const formData = await c.req.formData();
|
|
9256
|
-
const firstName =
|
|
9257
|
-
const lastName =
|
|
9258
|
-
const username =
|
|
10843
|
+
const firstName = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("first_name")?.toString());
|
|
10844
|
+
const lastName = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("last_name")?.toString());
|
|
10845
|
+
const username = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("username")?.toString());
|
|
9259
10846
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
9260
|
-
const phone =
|
|
9261
|
-
const bio =
|
|
10847
|
+
const phone = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
10848
|
+
const bio = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
9262
10849
|
const timezone = formData.get("timezone")?.toString() || "UTC";
|
|
9263
10850
|
const language = formData.get("language")?.toString() || "en";
|
|
9264
10851
|
const emailNotifications = formData.get("email_notifications") === "1";
|
|
@@ -9309,7 +10896,7 @@ userRoutes.put("/profile", async (c) => {
|
|
|
9309
10896
|
Date.now(),
|
|
9310
10897
|
user.userId
|
|
9311
10898
|
).run();
|
|
9312
|
-
await
|
|
10899
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
9313
10900
|
db,
|
|
9314
10901
|
user.userId,
|
|
9315
10902
|
"profile.update",
|
|
@@ -9372,7 +10959,7 @@ userRoutes.post("/profile/avatar", async (c) => {
|
|
|
9372
10959
|
SELECT first_name, last_name FROM users WHERE id = ?
|
|
9373
10960
|
`);
|
|
9374
10961
|
const userData = await userStmt.bind(user.userId).first();
|
|
9375
|
-
await
|
|
10962
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
9376
10963
|
db,
|
|
9377
10964
|
user.userId,
|
|
9378
10965
|
"profile.avatar_update",
|
|
@@ -9443,7 +11030,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
9443
11030
|
dismissible: true
|
|
9444
11031
|
}));
|
|
9445
11032
|
}
|
|
9446
|
-
const validPassword = await
|
|
11033
|
+
const validPassword = await chunkE2BXLXPW_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
|
|
9447
11034
|
if (!validPassword) {
|
|
9448
11035
|
return c.html(renderAlert2({
|
|
9449
11036
|
type: "error",
|
|
@@ -9451,7 +11038,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
9451
11038
|
dismissible: true
|
|
9452
11039
|
}));
|
|
9453
11040
|
}
|
|
9454
|
-
const newPasswordHash = await
|
|
11041
|
+
const newPasswordHash = await chunkE2BXLXPW_cjs.AuthManager.hashPassword(newPassword);
|
|
9455
11042
|
const historyStmt = db.prepare(`
|
|
9456
11043
|
INSERT INTO password_history (id, user_id, password_hash, created_at)
|
|
9457
11044
|
VALUES (?, ?, ?, ?)
|
|
@@ -9467,7 +11054,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
9467
11054
|
WHERE id = ?
|
|
9468
11055
|
`);
|
|
9469
11056
|
await updateStmt.bind(newPasswordHash, Date.now(), user.userId).run();
|
|
9470
|
-
await
|
|
11057
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
9471
11058
|
db,
|
|
9472
11059
|
user.userId,
|
|
9473
11060
|
"profile.password_change",
|
|
@@ -9534,7 +11121,7 @@ userRoutes.get("/users", async (c) => {
|
|
|
9534
11121
|
`);
|
|
9535
11122
|
const countResult = await countStmt.bind(...params).first();
|
|
9536
11123
|
const totalUsers = countResult?.total || 0;
|
|
9537
|
-
await
|
|
11124
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
9538
11125
|
db,
|
|
9539
11126
|
user.userId,
|
|
9540
11127
|
"users.list_view",
|
|
@@ -9636,12 +11223,12 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
9636
11223
|
const user = c.get("user");
|
|
9637
11224
|
try {
|
|
9638
11225
|
const formData = await c.req.formData();
|
|
9639
|
-
const firstName =
|
|
9640
|
-
const lastName =
|
|
9641
|
-
const username =
|
|
11226
|
+
const firstName = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("first_name")?.toString());
|
|
11227
|
+
const lastName = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("last_name")?.toString());
|
|
11228
|
+
const username = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("username")?.toString());
|
|
9642
11229
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
9643
|
-
const phone =
|
|
9644
|
-
const bio =
|
|
11230
|
+
const phone = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
11231
|
+
const bio = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
9645
11232
|
const role = formData.get("role")?.toString() || "viewer";
|
|
9646
11233
|
const password = formData.get("password")?.toString() || "";
|
|
9647
11234
|
const confirmPassword = formData.get("confirm_password")?.toString() || "";
|
|
@@ -9688,7 +11275,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
9688
11275
|
dismissible: true
|
|
9689
11276
|
}));
|
|
9690
11277
|
}
|
|
9691
|
-
const passwordHash = await
|
|
11278
|
+
const passwordHash = await chunkE2BXLXPW_cjs.AuthManager.hashPassword(password);
|
|
9692
11279
|
const userId = crypto.randomUUID();
|
|
9693
11280
|
const createStmt = db.prepare(`
|
|
9694
11281
|
INSERT INTO users (
|
|
@@ -9711,7 +11298,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
9711
11298
|
Date.now(),
|
|
9712
11299
|
Date.now()
|
|
9713
11300
|
).run();
|
|
9714
|
-
await
|
|
11301
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
9715
11302
|
db,
|
|
9716
11303
|
user.userId,
|
|
9717
11304
|
"user!.create",
|
|
@@ -9749,7 +11336,7 @@ userRoutes.get("/users/:id", async (c) => {
|
|
|
9749
11336
|
if (!userRecord) {
|
|
9750
11337
|
return c.json({ error: "User not found" }, 404);
|
|
9751
11338
|
}
|
|
9752
|
-
await
|
|
11339
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
9753
11340
|
db,
|
|
9754
11341
|
user.userId,
|
|
9755
11342
|
"user!.view",
|
|
@@ -9788,7 +11375,7 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
9788
11375
|
const userId = c.req.param("id");
|
|
9789
11376
|
try {
|
|
9790
11377
|
const userStmt = db.prepare(`
|
|
9791
|
-
SELECT id, email, username, first_name, last_name, phone,
|
|
11378
|
+
SELECT id, email, username, first_name, last_name, phone, avatar_url,
|
|
9792
11379
|
role, is_active, email_verified, two_factor_enabled, created_at, last_login_at
|
|
9793
11380
|
FROM users
|
|
9794
11381
|
WHERE id = ?
|
|
@@ -9801,6 +11388,21 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
9801
11388
|
dismissible: true
|
|
9802
11389
|
}), 404);
|
|
9803
11390
|
}
|
|
11391
|
+
const profileStmt = db.prepare(`
|
|
11392
|
+
SELECT display_name, bio, company, job_title, website, location, date_of_birth
|
|
11393
|
+
FROM user_profiles
|
|
11394
|
+
WHERE user_id = ?
|
|
11395
|
+
`);
|
|
11396
|
+
const profileData = await profileStmt.bind(userId).first();
|
|
11397
|
+
const profile = profileData ? {
|
|
11398
|
+
displayName: profileData.display_name,
|
|
11399
|
+
bio: profileData.bio,
|
|
11400
|
+
company: profileData.company,
|
|
11401
|
+
jobTitle: profileData.job_title,
|
|
11402
|
+
website: profileData.website,
|
|
11403
|
+
location: profileData.location,
|
|
11404
|
+
dateOfBirth: profileData.date_of_birth
|
|
11405
|
+
} : void 0;
|
|
9804
11406
|
const editData = {
|
|
9805
11407
|
id: userToEdit.id,
|
|
9806
11408
|
email: userToEdit.email,
|
|
@@ -9808,14 +11410,14 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
9808
11410
|
firstName: userToEdit.first_name || "",
|
|
9809
11411
|
lastName: userToEdit.last_name || "",
|
|
9810
11412
|
phone: userToEdit.phone,
|
|
9811
|
-
bio: userToEdit.bio,
|
|
9812
11413
|
avatarUrl: userToEdit.avatar_url,
|
|
9813
11414
|
role: userToEdit.role,
|
|
9814
11415
|
isActive: Boolean(userToEdit.is_active),
|
|
9815
11416
|
emailVerified: Boolean(userToEdit.email_verified),
|
|
9816
11417
|
twoFactorEnabled: Boolean(userToEdit.two_factor_enabled),
|
|
9817
11418
|
createdAt: userToEdit.created_at,
|
|
9818
|
-
lastLoginAt: userToEdit.last_login_at
|
|
11419
|
+
lastLoginAt: userToEdit.last_login_at,
|
|
11420
|
+
profile
|
|
9819
11421
|
};
|
|
9820
11422
|
const pageData = {
|
|
9821
11423
|
userToEdit: editData,
|
|
@@ -9831,7 +11433,7 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
9831
11433
|
console.error("User edit page error:", error);
|
|
9832
11434
|
return c.html(renderAlert2({
|
|
9833
11435
|
type: "error",
|
|
9834
|
-
message: "Failed to load user
|
|
11436
|
+
message: "Failed to load user. Please try again.",
|
|
9835
11437
|
dismissible: true
|
|
9836
11438
|
}), 500);
|
|
9837
11439
|
}
|
|
@@ -9842,15 +11444,22 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9842
11444
|
const userId = c.req.param("id");
|
|
9843
11445
|
try {
|
|
9844
11446
|
const formData = await c.req.formData();
|
|
9845
|
-
const firstName =
|
|
9846
|
-
const lastName =
|
|
9847
|
-
const username =
|
|
11447
|
+
const firstName = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("first_name")?.toString());
|
|
11448
|
+
const lastName = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("last_name")?.toString());
|
|
11449
|
+
const username = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("username")?.toString());
|
|
9848
11450
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
9849
|
-
const phone =
|
|
9850
|
-
const bio = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
11451
|
+
const phone = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
9851
11452
|
const role = formData.get("role")?.toString() || "viewer";
|
|
9852
11453
|
const isActive = formData.get("is_active") === "1";
|
|
9853
11454
|
const emailVerified = formData.get("email_verified") === "1";
|
|
11455
|
+
const profileDisplayName = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("profile_display_name")?.toString()) || null;
|
|
11456
|
+
const profileBio = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("profile_bio")?.toString()) || null;
|
|
11457
|
+
const profileCompany = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("profile_company")?.toString()) || null;
|
|
11458
|
+
const profileJobTitle = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("profile_job_title")?.toString()) || null;
|
|
11459
|
+
const profileWebsite = formData.get("profile_website")?.toString()?.trim() || null;
|
|
11460
|
+
const profileLocation = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("profile_location")?.toString()) || null;
|
|
11461
|
+
const profileDateOfBirthStr = formData.get("profile_date_of_birth")?.toString()?.trim() || null;
|
|
11462
|
+
const profileDateOfBirth = profileDateOfBirthStr ? new Date(profileDateOfBirthStr).getTime() : null;
|
|
9854
11463
|
if (!firstName || !lastName || !username || !email) {
|
|
9855
11464
|
return c.html(renderAlert2({
|
|
9856
11465
|
type: "error",
|
|
@@ -9866,6 +11475,17 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9866
11475
|
dismissible: true
|
|
9867
11476
|
}));
|
|
9868
11477
|
}
|
|
11478
|
+
if (profileWebsite) {
|
|
11479
|
+
try {
|
|
11480
|
+
new URL(profileWebsite);
|
|
11481
|
+
} catch {
|
|
11482
|
+
return c.html(renderAlert2({
|
|
11483
|
+
type: "error",
|
|
11484
|
+
message: "Please enter a valid website URL.",
|
|
11485
|
+
dismissible: true
|
|
11486
|
+
}));
|
|
11487
|
+
}
|
|
11488
|
+
}
|
|
9869
11489
|
const checkStmt = db.prepare(`
|
|
9870
11490
|
SELECT id FROM users
|
|
9871
11491
|
WHERE (username = ? OR email = ?) AND id != ?
|
|
@@ -9874,14 +11494,14 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9874
11494
|
if (existingUser) {
|
|
9875
11495
|
return c.html(renderAlert2({
|
|
9876
11496
|
type: "error",
|
|
9877
|
-
message: "Username or email is already taken by another user
|
|
11497
|
+
message: "Username or email is already taken by another user.",
|
|
9878
11498
|
dismissible: true
|
|
9879
11499
|
}));
|
|
9880
11500
|
}
|
|
9881
11501
|
const updateStmt = db.prepare(`
|
|
9882
11502
|
UPDATE users SET
|
|
9883
11503
|
first_name = ?, last_name = ?, username = ?, email = ?,
|
|
9884
|
-
phone = ?,
|
|
11504
|
+
phone = ?, role = ?, is_active = ?, email_verified = ?,
|
|
9885
11505
|
updated_at = ?
|
|
9886
11506
|
WHERE id = ?
|
|
9887
11507
|
`);
|
|
@@ -9891,20 +11511,63 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9891
11511
|
username,
|
|
9892
11512
|
email,
|
|
9893
11513
|
phone,
|
|
9894
|
-
bio,
|
|
9895
11514
|
role,
|
|
9896
11515
|
isActive ? 1 : 0,
|
|
9897
11516
|
emailVerified ? 1 : 0,
|
|
9898
11517
|
Date.now(),
|
|
9899
11518
|
userId
|
|
9900
11519
|
).run();
|
|
9901
|
-
|
|
11520
|
+
const hasProfileData = profileDisplayName || profileBio || profileCompany || profileJobTitle || profileWebsite || profileLocation || profileDateOfBirth;
|
|
11521
|
+
if (hasProfileData) {
|
|
11522
|
+
const now = Date.now();
|
|
11523
|
+
const profileCheckStmt = db.prepare(`SELECT id FROM user_profiles WHERE user_id = ?`);
|
|
11524
|
+
const existingProfile = await profileCheckStmt.bind(userId).first();
|
|
11525
|
+
if (existingProfile) {
|
|
11526
|
+
const updateProfileStmt = db.prepare(`
|
|
11527
|
+
UPDATE user_profiles SET
|
|
11528
|
+
display_name = ?, bio = ?, company = ?, job_title = ?,
|
|
11529
|
+
website = ?, location = ?, date_of_birth = ?, updated_at = ?
|
|
11530
|
+
WHERE user_id = ?
|
|
11531
|
+
`);
|
|
11532
|
+
await updateProfileStmt.bind(
|
|
11533
|
+
profileDisplayName,
|
|
11534
|
+
profileBio,
|
|
11535
|
+
profileCompany,
|
|
11536
|
+
profileJobTitle,
|
|
11537
|
+
profileWebsite,
|
|
11538
|
+
profileLocation,
|
|
11539
|
+
profileDateOfBirth,
|
|
11540
|
+
now,
|
|
11541
|
+
userId
|
|
11542
|
+
).run();
|
|
11543
|
+
} else {
|
|
11544
|
+
const profileId = `profile_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
11545
|
+
const insertProfileStmt = db.prepare(`
|
|
11546
|
+
INSERT INTO user_profiles (id, user_id, display_name, bio, company, job_title, website, location, date_of_birth, created_at, updated_at)
|
|
11547
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
11548
|
+
`);
|
|
11549
|
+
await insertProfileStmt.bind(
|
|
11550
|
+
profileId,
|
|
11551
|
+
userId,
|
|
11552
|
+
profileDisplayName,
|
|
11553
|
+
profileBio,
|
|
11554
|
+
profileCompany,
|
|
11555
|
+
profileJobTitle,
|
|
11556
|
+
profileWebsite,
|
|
11557
|
+
profileLocation,
|
|
11558
|
+
profileDateOfBirth,
|
|
11559
|
+
now,
|
|
11560
|
+
now
|
|
11561
|
+
).run();
|
|
11562
|
+
}
|
|
11563
|
+
}
|
|
11564
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
9902
11565
|
db,
|
|
9903
11566
|
user.userId,
|
|
9904
|
-
"user
|
|
11567
|
+
"user.update",
|
|
9905
11568
|
"users",
|
|
9906
11569
|
userId,
|
|
9907
|
-
{ fields: ["first_name", "last_name", "username", "email", "phone", "
|
|
11570
|
+
{ fields: ["first_name", "last_name", "username", "email", "phone", "role", "is_active", "email_verified", "profile"] },
|
|
9908
11571
|
c.req.header("x-forwarded-for") || c.req.header("cf-connecting-ip"),
|
|
9909
11572
|
c.req.header("user-agent")
|
|
9910
11573
|
);
|
|
@@ -9917,7 +11580,7 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9917
11580
|
console.error("User update error:", error);
|
|
9918
11581
|
return c.html(renderAlert2({
|
|
9919
11582
|
type: "error",
|
|
9920
|
-
message: "Failed to update user
|
|
11583
|
+
message: "Failed to update user. Please try again.",
|
|
9921
11584
|
dismissible: true
|
|
9922
11585
|
}));
|
|
9923
11586
|
}
|
|
@@ -9943,7 +11606,7 @@ userRoutes.post("/users/:id/toggle", async (c) => {
|
|
|
9943
11606
|
UPDATE users SET is_active = ?, updated_at = ? WHERE id = ?
|
|
9944
11607
|
`);
|
|
9945
11608
|
await toggleStmt.bind(active ? 1 : 0, Date.now(), userId).run();
|
|
9946
|
-
await
|
|
11609
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
9947
11610
|
db,
|
|
9948
11611
|
user.userId,
|
|
9949
11612
|
active ? "user.activate" : "user.deactivate",
|
|
@@ -9984,7 +11647,7 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
9984
11647
|
DELETE FROM users WHERE id = ?
|
|
9985
11648
|
`);
|
|
9986
11649
|
await deleteStmt.bind(userId).run();
|
|
9987
|
-
await
|
|
11650
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
9988
11651
|
db,
|
|
9989
11652
|
user.userId,
|
|
9990
11653
|
"user!.hard_delete",
|
|
@@ -10003,7 +11666,7 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
10003
11666
|
UPDATE users SET is_active = 0, updated_at = ? WHERE id = ?
|
|
10004
11667
|
`);
|
|
10005
11668
|
await deleteStmt.bind(Date.now(), userId).run();
|
|
10006
|
-
await
|
|
11669
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
10007
11670
|
db,
|
|
10008
11671
|
user.userId,
|
|
10009
11672
|
"user!.soft_delete",
|
|
@@ -10030,8 +11693,8 @@ userRoutes.post("/invite-user", async (c) => {
|
|
|
10030
11693
|
const formData = await c.req.formData();
|
|
10031
11694
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
10032
11695
|
const role = formData.get("role")?.toString()?.trim() || "viewer";
|
|
10033
|
-
const firstName =
|
|
10034
|
-
const lastName =
|
|
11696
|
+
const firstName = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("first_name")?.toString());
|
|
11697
|
+
const lastName = chunkMYB5RY7H_cjs.sanitizeInput(formData.get("last_name")?.toString());
|
|
10035
11698
|
if (!email || !firstName || !lastName) {
|
|
10036
11699
|
return c.json({ error: "Email, first name, and last name are required" }, 400);
|
|
10037
11700
|
}
|
|
@@ -10069,7 +11732,7 @@ userRoutes.post("/invite-user", async (c) => {
|
|
|
10069
11732
|
Date.now(),
|
|
10070
11733
|
Date.now()
|
|
10071
11734
|
).run();
|
|
10072
|
-
await
|
|
11735
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
10073
11736
|
db,
|
|
10074
11737
|
user.userId,
|
|
10075
11738
|
"user!.invite_sent",
|
|
@@ -10126,7 +11789,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
|
|
|
10126
11789
|
Date.now(),
|
|
10127
11790
|
userId
|
|
10128
11791
|
).run();
|
|
10129
|
-
await
|
|
11792
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
10130
11793
|
db,
|
|
10131
11794
|
user.userId,
|
|
10132
11795
|
"user!.invitation_resent",
|
|
@@ -10162,7 +11825,7 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
|
|
|
10162
11825
|
}
|
|
10163
11826
|
const deleteStmt = db.prepare(`DELETE FROM users WHERE id = ?`);
|
|
10164
11827
|
await deleteStmt.bind(userId).run();
|
|
10165
|
-
await
|
|
11828
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
10166
11829
|
db,
|
|
10167
11830
|
user.userId,
|
|
10168
11831
|
"user!.invitation_cancelled",
|
|
@@ -10245,7 +11908,7 @@ userRoutes.get("/activity-logs", async (c) => {
|
|
|
10245
11908
|
...log,
|
|
10246
11909
|
details: log.details ? JSON.parse(log.details) : null
|
|
10247
11910
|
}));
|
|
10248
|
-
await
|
|
11911
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
10249
11912
|
db,
|
|
10250
11913
|
user.userId,
|
|
10251
11914
|
"activity.logs_viewed",
|
|
@@ -10352,7 +12015,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
|
|
|
10352
12015
|
csvRows.push(row.join(","));
|
|
10353
12016
|
}
|
|
10354
12017
|
const csvContent = csvRows.join("\n");
|
|
10355
|
-
await
|
|
12018
|
+
await chunkE2BXLXPW_cjs.logActivity(
|
|
10356
12019
|
db,
|
|
10357
12020
|
user.userId,
|
|
10358
12021
|
"activity.logs_exported",
|
|
@@ -10570,7 +12233,7 @@ function getFileIcon(mimeType) {
|
|
|
10570
12233
|
}
|
|
10571
12234
|
|
|
10572
12235
|
// src/templates/pages/admin-media-library.template.ts
|
|
10573
|
-
|
|
12236
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
10574
12237
|
function renderMediaLibraryPage(data) {
|
|
10575
12238
|
const pageContent = `
|
|
10576
12239
|
<div>
|
|
@@ -11505,7 +13168,7 @@ function renderMediaLibraryPage(data) {
|
|
|
11505
13168
|
version: data.version,
|
|
11506
13169
|
content: pageContent
|
|
11507
13170
|
};
|
|
11508
|
-
return
|
|
13171
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
11509
13172
|
}
|
|
11510
13173
|
|
|
11511
13174
|
// src/templates/components/media-file-details.template.ts
|
|
@@ -11691,7 +13354,7 @@ var fileValidationSchema2 = zod.z.object({
|
|
|
11691
13354
|
// 50MB max
|
|
11692
13355
|
});
|
|
11693
13356
|
var adminMediaRoutes = new hono.Hono();
|
|
11694
|
-
adminMediaRoutes.use("*",
|
|
13357
|
+
adminMediaRoutes.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
11695
13358
|
adminMediaRoutes.get("/", async (c) => {
|
|
11696
13359
|
try {
|
|
11697
13360
|
const user = c.get("user");
|
|
@@ -12277,7 +13940,7 @@ adminMediaRoutes.put("/:id", async (c) => {
|
|
|
12277
13940
|
`);
|
|
12278
13941
|
}
|
|
12279
13942
|
});
|
|
12280
|
-
adminMediaRoutes.delete("/cleanup",
|
|
13943
|
+
adminMediaRoutes.delete("/cleanup", chunkE2BXLXPW_cjs.requireRole("admin"), async (c) => {
|
|
12281
13944
|
try {
|
|
12282
13945
|
const db = c.env.DB;
|
|
12283
13946
|
const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
|
|
@@ -12527,7 +14190,7 @@ function formatFileSize(bytes) {
|
|
|
12527
14190
|
}
|
|
12528
14191
|
|
|
12529
14192
|
// src/templates/pages/admin-plugins-list.template.ts
|
|
12530
|
-
|
|
14193
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
12531
14194
|
function renderPluginsListPage(data) {
|
|
12532
14195
|
const categories = [
|
|
12533
14196
|
{ value: "content", label: "Content Management" },
|
|
@@ -13007,7 +14670,7 @@ function renderPluginsListPage(data) {
|
|
|
13007
14670
|
version: data.version,
|
|
13008
14671
|
content: pageContent
|
|
13009
14672
|
};
|
|
13010
|
-
return
|
|
14673
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
13011
14674
|
}
|
|
13012
14675
|
function renderPluginCard(plugin) {
|
|
13013
14676
|
const statusColors = {
|
|
@@ -13662,7 +15325,7 @@ function renderPluginSettingsPage(data) {
|
|
|
13662
15325
|
user,
|
|
13663
15326
|
content: pageContent
|
|
13664
15327
|
};
|
|
13665
|
-
return
|
|
15328
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayout(layoutData);
|
|
13666
15329
|
}
|
|
13667
15330
|
function renderStatusBadge(status) {
|
|
13668
15331
|
const statusColors = {
|
|
@@ -14003,7 +15666,7 @@ function formatTimestamp(timestamp) {
|
|
|
14003
15666
|
|
|
14004
15667
|
// src/routes/admin-plugins.ts
|
|
14005
15668
|
var adminPluginRoutes = new hono.Hono();
|
|
14006
|
-
adminPluginRoutes.use("*",
|
|
15669
|
+
adminPluginRoutes.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
14007
15670
|
var AVAILABLE_PLUGINS = [
|
|
14008
15671
|
{
|
|
14009
15672
|
id: "third-party-faq",
|
|
@@ -14108,6 +15771,19 @@ var AVAILABLE_PLUGINS = [
|
|
|
14108
15771
|
permissions: [],
|
|
14109
15772
|
dependencies: [],
|
|
14110
15773
|
is_core: true
|
|
15774
|
+
},
|
|
15775
|
+
{
|
|
15776
|
+
id: "ai-search",
|
|
15777
|
+
name: "ai-search-plugin",
|
|
15778
|
+
display_name: "AI Search",
|
|
15779
|
+
description: "Advanced search with Cloudflare AI Search. Full-text search, semantic search, and advanced filtering across all content collections.",
|
|
15780
|
+
version: "1.0.0",
|
|
15781
|
+
author: "SonicJS Team",
|
|
15782
|
+
category: "search",
|
|
15783
|
+
icon: "\u{1F50D}",
|
|
15784
|
+
permissions: [],
|
|
15785
|
+
dependencies: [],
|
|
15786
|
+
is_core: true
|
|
14111
15787
|
}
|
|
14112
15788
|
];
|
|
14113
15789
|
adminPluginRoutes.get("/", async (c) => {
|
|
@@ -14117,7 +15793,7 @@ adminPluginRoutes.get("/", async (c) => {
|
|
|
14117
15793
|
if (user?.role !== "admin") {
|
|
14118
15794
|
return c.text("Access denied", 403);
|
|
14119
15795
|
}
|
|
14120
|
-
const pluginService = new
|
|
15796
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14121
15797
|
let installedPlugins = [];
|
|
14122
15798
|
let stats = { total: 0, active: 0, inactive: 0, errors: 0, uninstalled: 0 };
|
|
14123
15799
|
try {
|
|
@@ -14186,10 +15862,13 @@ adminPluginRoutes.get("/:id", async (c) => {
|
|
|
14186
15862
|
const user = c.get("user");
|
|
14187
15863
|
const db = c.env.DB;
|
|
14188
15864
|
const pluginId = c.req.param("id");
|
|
15865
|
+
if (pluginId === "ai-search") {
|
|
15866
|
+
return c.text("", 404);
|
|
15867
|
+
}
|
|
14189
15868
|
if (user?.role !== "admin") {
|
|
14190
15869
|
return c.redirect("/admin/plugins");
|
|
14191
15870
|
}
|
|
14192
|
-
const pluginService = new
|
|
15871
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14193
15872
|
const plugin = await pluginService.getPlugin(pluginId);
|
|
14194
15873
|
if (!plugin) {
|
|
14195
15874
|
return c.text("Plugin not found", 404);
|
|
@@ -14243,7 +15922,7 @@ adminPluginRoutes.post("/:id/activate", async (c) => {
|
|
|
14243
15922
|
if (user?.role !== "admin") {
|
|
14244
15923
|
return c.json({ error: "Access denied" }, 403);
|
|
14245
15924
|
}
|
|
14246
|
-
const pluginService = new
|
|
15925
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14247
15926
|
await pluginService.activatePlugin(pluginId);
|
|
14248
15927
|
return c.json({ success: true });
|
|
14249
15928
|
} catch (error) {
|
|
@@ -14260,7 +15939,7 @@ adminPluginRoutes.post("/:id/deactivate", async (c) => {
|
|
|
14260
15939
|
if (user?.role !== "admin") {
|
|
14261
15940
|
return c.json({ error: "Access denied" }, 403);
|
|
14262
15941
|
}
|
|
14263
|
-
const pluginService = new
|
|
15942
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14264
15943
|
await pluginService.deactivatePlugin(pluginId);
|
|
14265
15944
|
return c.json({ success: true });
|
|
14266
15945
|
} catch (error) {
|
|
@@ -14277,7 +15956,7 @@ adminPluginRoutes.post("/install", async (c) => {
|
|
|
14277
15956
|
return c.json({ error: "Access denied" }, 403);
|
|
14278
15957
|
}
|
|
14279
15958
|
const body = await c.req.json();
|
|
14280
|
-
const pluginService = new
|
|
15959
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14281
15960
|
if (body.name === "faq-plugin") {
|
|
14282
15961
|
const faqPlugin = await pluginService.installPlugin({
|
|
14283
15962
|
id: "third-party-faq",
|
|
@@ -14478,6 +16157,33 @@ adminPluginRoutes.post("/install", async (c) => {
|
|
|
14478
16157
|
});
|
|
14479
16158
|
return c.json({ success: true, plugin: easyMdxPlugin2 });
|
|
14480
16159
|
}
|
|
16160
|
+
if (body.name === "ai-search-plugin" || body.name === "ai-search") {
|
|
16161
|
+
const defaultSettings = {
|
|
16162
|
+
enabled: true,
|
|
16163
|
+
ai_mode_enabled: true,
|
|
16164
|
+
selected_collections: [],
|
|
16165
|
+
dismissed_collections: [],
|
|
16166
|
+
autocomplete_enabled: true,
|
|
16167
|
+
cache_duration: 1,
|
|
16168
|
+
results_limit: 20,
|
|
16169
|
+
index_media: false
|
|
16170
|
+
};
|
|
16171
|
+
const aiSearchPlugin = await pluginService.installPlugin({
|
|
16172
|
+
id: "ai-search",
|
|
16173
|
+
name: "ai-search-plugin",
|
|
16174
|
+
display_name: "AI Search",
|
|
16175
|
+
description: "Advanced search with Cloudflare AI Search. Full-text search, semantic search, and advanced filtering across all content collections.",
|
|
16176
|
+
version: "1.0.0",
|
|
16177
|
+
author: "SonicJS Team",
|
|
16178
|
+
category: "search",
|
|
16179
|
+
icon: "\u{1F50D}",
|
|
16180
|
+
permissions: [],
|
|
16181
|
+
dependencies: [],
|
|
16182
|
+
is_core: true,
|
|
16183
|
+
settings: defaultSettings
|
|
16184
|
+
});
|
|
16185
|
+
return c.json({ success: true, plugin: aiSearchPlugin });
|
|
16186
|
+
}
|
|
14481
16187
|
if (body.name === "turnstile-plugin") {
|
|
14482
16188
|
const turnstilePlugin = await pluginService.installPlugin({
|
|
14483
16189
|
id: "turnstile",
|
|
@@ -14520,7 +16226,7 @@ adminPluginRoutes.post("/:id/uninstall", async (c) => {
|
|
|
14520
16226
|
if (user?.role !== "admin") {
|
|
14521
16227
|
return c.json({ error: "Access denied" }, 403);
|
|
14522
16228
|
}
|
|
14523
|
-
const pluginService = new
|
|
16229
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14524
16230
|
await pluginService.uninstallPlugin(pluginId);
|
|
14525
16231
|
return c.json({ success: true });
|
|
14526
16232
|
} catch (error) {
|
|
@@ -14538,7 +16244,7 @@ adminPluginRoutes.post("/:id/settings", async (c) => {
|
|
|
14538
16244
|
return c.json({ error: "Access denied" }, 403);
|
|
14539
16245
|
}
|
|
14540
16246
|
const settings = await c.req.json();
|
|
14541
|
-
const pluginService = new
|
|
16247
|
+
const pluginService = new chunkMPT5PA6U_cjs.PluginService(db);
|
|
14542
16248
|
await pluginService.updatePluginSettings(pluginId, settings);
|
|
14543
16249
|
return c.json({ success: true });
|
|
14544
16250
|
} catch (error) {
|
|
@@ -14559,7 +16265,7 @@ function formatLastUpdated(timestamp) {
|
|
|
14559
16265
|
}
|
|
14560
16266
|
|
|
14561
16267
|
// src/templates/pages/admin-logs-list.template.ts
|
|
14562
|
-
|
|
16268
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
14563
16269
|
function renderLogsListPage(data) {
|
|
14564
16270
|
const { logs, pagination, filters, user } = data;
|
|
14565
16271
|
const content = `
|
|
@@ -14870,7 +16576,7 @@ function renderLogsListPage(data) {
|
|
|
14870
16576
|
user,
|
|
14871
16577
|
content
|
|
14872
16578
|
};
|
|
14873
|
-
return
|
|
16579
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
14874
16580
|
}
|
|
14875
16581
|
function renderLogDetailsPage(data) {
|
|
14876
16582
|
const { log, user } = data;
|
|
@@ -15082,7 +16788,7 @@ function renderLogDetailsPage(data) {
|
|
|
15082
16788
|
</div>
|
|
15083
16789
|
</div>
|
|
15084
16790
|
`;
|
|
15085
|
-
return
|
|
16791
|
+
return chunkEHSZ6TAN_cjs.adminLayoutV2({
|
|
15086
16792
|
title: `Log Details - ${log.id}`,
|
|
15087
16793
|
user,
|
|
15088
16794
|
content
|
|
@@ -15325,7 +17031,7 @@ function renderLogConfigPage(data) {
|
|
|
15325
17031
|
|
|
15326
17032
|
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
|
|
15327
17033
|
`;
|
|
15328
|
-
return
|
|
17034
|
+
return chunkEHSZ6TAN_cjs.adminLayoutV2({
|
|
15329
17035
|
title: "Log Configuration",
|
|
15330
17036
|
user,
|
|
15331
17037
|
content
|
|
@@ -15334,7 +17040,7 @@ function renderLogConfigPage(data) {
|
|
|
15334
17040
|
|
|
15335
17041
|
// src/routes/admin-logs.ts
|
|
15336
17042
|
var adminLogsRoutes = new hono.Hono();
|
|
15337
|
-
adminLogsRoutes.use("*",
|
|
17043
|
+
adminLogsRoutes.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
15338
17044
|
adminLogsRoutes.get("/", async (c) => {
|
|
15339
17045
|
try {
|
|
15340
17046
|
const user = c.get("user");
|
|
@@ -15706,7 +17412,7 @@ adminDesignRoutes.get("/", (c) => {
|
|
|
15706
17412
|
role: user.role
|
|
15707
17413
|
} : void 0
|
|
15708
17414
|
};
|
|
15709
|
-
return c.html(
|
|
17415
|
+
return c.html(chunkEHSZ6TAN_cjs.renderDesignPage(pageData));
|
|
15710
17416
|
});
|
|
15711
17417
|
var adminCheckboxRoutes = new hono.Hono();
|
|
15712
17418
|
adminCheckboxRoutes.get("/", (c) => {
|
|
@@ -15718,7 +17424,7 @@ adminCheckboxRoutes.get("/", (c) => {
|
|
|
15718
17424
|
role: user.role
|
|
15719
17425
|
} : void 0
|
|
15720
17426
|
};
|
|
15721
|
-
return c.html(
|
|
17427
|
+
return c.html(chunkEHSZ6TAN_cjs.renderCheckboxPage(pageData));
|
|
15722
17428
|
});
|
|
15723
17429
|
|
|
15724
17430
|
// src/templates/pages/admin-testimonials-form.template.ts
|
|
@@ -15746,7 +17452,7 @@ function renderTestimonialsForm(data) {
|
|
|
15746
17452
|
</div>
|
|
15747
17453
|
</div>
|
|
15748
17454
|
|
|
15749
|
-
${message ?
|
|
17455
|
+
${message ? chunkEHSZ6TAN_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
|
|
15750
17456
|
|
|
15751
17457
|
<!-- Form -->
|
|
15752
17458
|
<div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
|
|
@@ -15975,7 +17681,7 @@ function renderTestimonialsForm(data) {
|
|
|
15975
17681
|
user: data.user,
|
|
15976
17682
|
content: pageContent
|
|
15977
17683
|
};
|
|
15978
|
-
return
|
|
17684
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayout(layoutData);
|
|
15979
17685
|
}
|
|
15980
17686
|
function escapeHtml4(unsafe) {
|
|
15981
17687
|
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -16001,7 +17707,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
|
|
|
16001
17707
|
const offset = (currentPage - 1) * limit;
|
|
16002
17708
|
const db = c.env?.DB;
|
|
16003
17709
|
if (!db) {
|
|
16004
|
-
return c.html(
|
|
17710
|
+
return c.html(chunkEHSZ6TAN_cjs.renderTestimonialsList({
|
|
16005
17711
|
testimonials: [],
|
|
16006
17712
|
totalCount: 0,
|
|
16007
17713
|
currentPage: 1,
|
|
@@ -16041,7 +17747,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
|
|
|
16041
17747
|
`;
|
|
16042
17748
|
const { results: testimonials } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
|
|
16043
17749
|
const totalPages = Math.ceil(totalCount / limit);
|
|
16044
|
-
return c.html(
|
|
17750
|
+
return c.html(chunkEHSZ6TAN_cjs.renderTestimonialsList({
|
|
16045
17751
|
testimonials: testimonials || [],
|
|
16046
17752
|
totalCount,
|
|
16047
17753
|
currentPage,
|
|
@@ -16055,7 +17761,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
|
|
|
16055
17761
|
} catch (error) {
|
|
16056
17762
|
console.error("Error fetching testimonials:", error);
|
|
16057
17763
|
const user = c.get("user");
|
|
16058
|
-
return c.html(
|
|
17764
|
+
return c.html(chunkEHSZ6TAN_cjs.renderTestimonialsList({
|
|
16059
17765
|
testimonials: [],
|
|
16060
17766
|
totalCount: 0,
|
|
16061
17767
|
currentPage: 1,
|
|
@@ -16374,7 +18080,7 @@ function renderCodeExamplesForm(data) {
|
|
|
16374
18080
|
</div>
|
|
16375
18081
|
</div>
|
|
16376
18082
|
|
|
16377
|
-
${message ?
|
|
18083
|
+
${message ? chunkEHSZ6TAN_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
|
|
16378
18084
|
|
|
16379
18085
|
<!-- Form -->
|
|
16380
18086
|
<div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
|
|
@@ -16644,7 +18350,7 @@ function renderCodeExamplesForm(data) {
|
|
|
16644
18350
|
user: data.user,
|
|
16645
18351
|
content: pageContent
|
|
16646
18352
|
};
|
|
16647
|
-
return
|
|
18353
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayout(layoutData);
|
|
16648
18354
|
}
|
|
16649
18355
|
function escapeHtml5(unsafe) {
|
|
16650
18356
|
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -16671,7 +18377,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
|
|
|
16671
18377
|
const offset = (currentPage - 1) * limit;
|
|
16672
18378
|
const db = c.env?.DB;
|
|
16673
18379
|
if (!db) {
|
|
16674
|
-
return c.html(
|
|
18380
|
+
return c.html(chunkEHSZ6TAN_cjs.renderCodeExamplesList({
|
|
16675
18381
|
codeExamples: [],
|
|
16676
18382
|
totalCount: 0,
|
|
16677
18383
|
currentPage: 1,
|
|
@@ -16711,7 +18417,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
|
|
|
16711
18417
|
`;
|
|
16712
18418
|
const { results: codeExamples } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
|
|
16713
18419
|
const totalPages = Math.ceil(totalCount / limit);
|
|
16714
|
-
return c.html(
|
|
18420
|
+
return c.html(chunkEHSZ6TAN_cjs.renderCodeExamplesList({
|
|
16715
18421
|
codeExamples: codeExamples || [],
|
|
16716
18422
|
totalCount,
|
|
16717
18423
|
currentPage,
|
|
@@ -16725,7 +18431,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
|
|
|
16725
18431
|
} catch (error) {
|
|
16726
18432
|
console.error("Error fetching code examples:", error);
|
|
16727
18433
|
const user = c.get("user");
|
|
16728
|
-
return c.html(
|
|
18434
|
+
return c.html(chunkEHSZ6TAN_cjs.renderCodeExamplesList({
|
|
16729
18435
|
codeExamples: [],
|
|
16730
18436
|
totalCount: 0,
|
|
16731
18437
|
currentPage: 1,
|
|
@@ -17114,7 +18820,7 @@ function renderDashboardPage(data) {
|
|
|
17114
18820
|
version: data.version,
|
|
17115
18821
|
content: pageContent
|
|
17116
18822
|
};
|
|
17117
|
-
return
|
|
18823
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayout(layoutData);
|
|
17118
18824
|
}
|
|
17119
18825
|
function renderStatsCards(stats) {
|
|
17120
18826
|
const cards = [
|
|
@@ -17662,9 +19368,9 @@ function renderStorageUsage(databaseSizeBytes, mediaSizeBytes) {
|
|
|
17662
19368
|
}
|
|
17663
19369
|
|
|
17664
19370
|
// src/routes/admin-dashboard.ts
|
|
17665
|
-
var VERSION =
|
|
19371
|
+
var VERSION = chunkMYB5RY7H_cjs.getCoreVersion();
|
|
17666
19372
|
var router = new hono.Hono();
|
|
17667
|
-
router.use("*",
|
|
19373
|
+
router.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
17668
19374
|
router.get("/", async (c) => {
|
|
17669
19375
|
const user = c.get("user");
|
|
17670
19376
|
try {
|
|
@@ -17889,7 +19595,7 @@ router.get("/system-status", async (c) => {
|
|
|
17889
19595
|
});
|
|
17890
19596
|
|
|
17891
19597
|
// src/templates/pages/admin-collections-list.template.ts
|
|
17892
|
-
|
|
19598
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
17893
19599
|
|
|
17894
19600
|
// src/templates/components/table.template.ts
|
|
17895
19601
|
function renderTable2(data) {
|
|
@@ -18363,11 +20069,11 @@ function renderCollectionsListPage(data) {
|
|
|
18363
20069
|
version: data.version,
|
|
18364
20070
|
content: pageContent
|
|
18365
20071
|
};
|
|
18366
|
-
return
|
|
20072
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
18367
20073
|
}
|
|
18368
20074
|
|
|
18369
20075
|
// src/templates/pages/admin-collections-form.template.ts
|
|
18370
|
-
|
|
20076
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
18371
20077
|
function getFieldTypeBadge(fieldType) {
|
|
18372
20078
|
const typeLabels = {
|
|
18373
20079
|
"text": "Text",
|
|
@@ -18378,7 +20084,8 @@ function getFieldTypeBadge(fieldType) {
|
|
|
18378
20084
|
"boolean": "Boolean",
|
|
18379
20085
|
"date": "Date",
|
|
18380
20086
|
"select": "Select",
|
|
18381
|
-
"media": "Media"
|
|
20087
|
+
"media": "Media",
|
|
20088
|
+
"reference": "Reference"
|
|
18382
20089
|
};
|
|
18383
20090
|
const typeColors = {
|
|
18384
20091
|
"text": "bg-blue-500/10 dark:bg-blue-400/10 text-blue-700 dark:text-blue-300 ring-blue-500/20 dark:ring-blue-400/20",
|
|
@@ -18389,7 +20096,8 @@ function getFieldTypeBadge(fieldType) {
|
|
|
18389
20096
|
"boolean": "bg-amber-500/10 dark:bg-amber-400/10 text-amber-700 dark:text-amber-300 ring-amber-500/20 dark:ring-amber-400/20",
|
|
18390
20097
|
"date": "bg-cyan-500/10 dark:bg-cyan-400/10 text-cyan-700 dark:text-cyan-300 ring-cyan-500/20 dark:ring-cyan-400/20",
|
|
18391
20098
|
"select": "bg-indigo-500/10 dark:bg-indigo-400/10 text-indigo-700 dark:text-indigo-300 ring-indigo-500/20 dark:ring-indigo-400/20",
|
|
18392
|
-
"media": "bg-rose-500/10 dark:bg-rose-400/10 text-rose-700 dark:text-rose-300 ring-rose-500/20 dark:ring-rose-400/20"
|
|
20099
|
+
"media": "bg-rose-500/10 dark:bg-rose-400/10 text-rose-700 dark:text-rose-300 ring-rose-500/20 dark:ring-rose-400/20",
|
|
20100
|
+
"reference": "bg-teal-500/10 dark:bg-teal-400/10 text-teal-700 dark:text-teal-300 ring-teal-500/20 dark:ring-teal-400/20"
|
|
18393
20101
|
};
|
|
18394
20102
|
const label = typeLabels[fieldType] || fieldType;
|
|
18395
20103
|
const color = typeColors[fieldType] || "bg-zinc-500/10 dark:bg-zinc-400/10 text-zinc-700 dark:text-zinc-300 ring-zinc-500/20 dark:ring-zinc-400/20";
|
|
@@ -18628,7 +20336,7 @@ function renderCollectionFormPage(data) {
|
|
|
18628
20336
|
}
|
|
18629
20337
|
</style>
|
|
18630
20338
|
|
|
18631
|
-
${
|
|
20339
|
+
${chunkEHSZ6TAN_cjs.renderForm(formData)}
|
|
18632
20340
|
|
|
18633
20341
|
${isEdit && data.managed ? `
|
|
18634
20342
|
<!-- Read-Only Fields Display for Managed Collections -->
|
|
@@ -18869,6 +20577,7 @@ function renderCollectionFormPage(data) {
|
|
|
18869
20577
|
<option value="date">Date</option>
|
|
18870
20578
|
<option value="select">Select</option>
|
|
18871
20579
|
<option value="media">Media</option>
|
|
20580
|
+
<option value="reference">Reference</option>
|
|
18872
20581
|
</select>
|
|
18873
20582
|
<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-blue-600 dark:text-blue-400 sm:size-4">
|
|
18874
20583
|
<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" />
|
|
@@ -19186,11 +20895,14 @@ function renderCollectionFormPage(data) {
|
|
|
19186
20895
|
}
|
|
19187
20896
|
|
|
19188
20897
|
// Show/hide options container based on field type
|
|
19189
|
-
|
|
20898
|
+
// Use the dropdown's actual value (not field.field_type) to ensure consistency
|
|
20899
|
+
const fieldType = fieldTypeSelect?.value || field.field_type;
|
|
19190
20900
|
const optionsContainer = document.getElementById('field-options-container');
|
|
19191
20901
|
const helpText = document.getElementById('field-type-help');
|
|
19192
20902
|
|
|
19193
|
-
|
|
20903
|
+
console.log('[Edit Field] Showing options for field type:', fieldType, '(original:', field.field_type, ')');
|
|
20904
|
+
|
|
20905
|
+
if (['select', 'media', 'richtext', 'reference'].includes(fieldType)) {
|
|
19194
20906
|
optionsContainer.classList.remove('hidden');
|
|
19195
20907
|
|
|
19196
20908
|
// Set help text based on type
|
|
@@ -19204,6 +20916,9 @@ function renderCollectionFormPage(data) {
|
|
|
19204
20916
|
case 'richtext':
|
|
19205
20917
|
helpText.textContent = 'Full-featured WYSIWYG text editor with formatting options';
|
|
19206
20918
|
break;
|
|
20919
|
+
case 'reference':
|
|
20920
|
+
helpText.textContent = 'Link to content from other collections';
|
|
20921
|
+
break;
|
|
19207
20922
|
}
|
|
19208
20923
|
} else {
|
|
19209
20924
|
optionsContainer.classList.add('hidden');
|
|
@@ -19338,7 +21053,7 @@ function renderCollectionFormPage(data) {
|
|
|
19338
21053
|
const fieldNameInput = document.getElementById('modal-field-name');
|
|
19339
21054
|
|
|
19340
21055
|
// Show/hide options based on field type
|
|
19341
|
-
if (['select', 'media', 'richtext', 'guid'].includes(this.value)) {
|
|
21056
|
+
if (['select', 'media', 'richtext', 'guid', 'reference'].includes(this.value)) {
|
|
19342
21057
|
optionsContainer.classList.remove('hidden');
|
|
19343
21058
|
|
|
19344
21059
|
// Set default options and help text based on type
|
|
@@ -19355,6 +21070,10 @@ function renderCollectionFormPage(data) {
|
|
|
19355
21070
|
fieldOptions.value = '{"toolbar": "full", "height": 400}';
|
|
19356
21071
|
helpText.textContent = 'Full-featured WYSIWYG text editor with formatting options';
|
|
19357
21072
|
break;
|
|
21073
|
+
case 'reference':
|
|
21074
|
+
fieldOptions.value = '{"collection": ["pages", "posts"]}';
|
|
21075
|
+
helpText.textContent = 'Link to content from other collections';
|
|
21076
|
+
break;
|
|
19358
21077
|
}
|
|
19359
21078
|
} else {
|
|
19360
21079
|
optionsContainer.classList.add('hidden');
|
|
@@ -19417,12 +21136,12 @@ function renderCollectionFormPage(data) {
|
|
|
19417
21136
|
version: data.version,
|
|
19418
21137
|
content: pageContent
|
|
19419
21138
|
};
|
|
19420
|
-
return
|
|
21139
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
19421
21140
|
}
|
|
19422
21141
|
|
|
19423
21142
|
// src/routes/admin-collections.ts
|
|
19424
21143
|
var adminCollectionsRoutes = new hono.Hono();
|
|
19425
|
-
adminCollectionsRoutes.use("*",
|
|
21144
|
+
adminCollectionsRoutes.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
19426
21145
|
adminCollectionsRoutes.get("/", async (c) => {
|
|
19427
21146
|
try {
|
|
19428
21147
|
const user = c.get("user");
|
|
@@ -19923,6 +21642,8 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
|
|
|
19923
21642
|
fieldConfig.type = "quill";
|
|
19924
21643
|
} else if (fieldType === "mdxeditor") {
|
|
19925
21644
|
fieldConfig.type = "mdxeditor";
|
|
21645
|
+
} else if (fieldType === "reference") {
|
|
21646
|
+
fieldConfig.type = "reference";
|
|
19926
21647
|
}
|
|
19927
21648
|
schema.properties[fieldName] = fieldConfig;
|
|
19928
21649
|
if (isRequired && !schema.required.includes(fieldName)) {
|
|
@@ -20011,8 +21732,15 @@ adminCollectionsRoutes.put("/:collectionId/fields/:fieldId", async (c) => {
|
|
|
20011
21732
|
schema.required = [];
|
|
20012
21733
|
}
|
|
20013
21734
|
if (schema.properties[fieldName]) {
|
|
21735
|
+
let parsedFieldOptions = {};
|
|
21736
|
+
try {
|
|
21737
|
+
parsedFieldOptions = JSON.parse(fieldOptions);
|
|
21738
|
+
} catch (e) {
|
|
21739
|
+
console.error("[Field Update] Error parsing field options:", e);
|
|
21740
|
+
}
|
|
20014
21741
|
const updatedFieldConfig = {
|
|
20015
21742
|
...schema.properties[fieldName],
|
|
21743
|
+
...parsedFieldOptions,
|
|
20016
21744
|
type: fieldType,
|
|
20017
21745
|
title: fieldLabel,
|
|
20018
21746
|
searchable: isSearchable
|
|
@@ -20138,7 +21866,7 @@ adminCollectionsRoutes.post("/:collectionId/fields/reorder", async (c) => {
|
|
|
20138
21866
|
});
|
|
20139
21867
|
|
|
20140
21868
|
// src/templates/pages/admin-settings.template.ts
|
|
20141
|
-
|
|
21869
|
+
chunkEHSZ6TAN_cjs.init_admin_layout_catalyst_template();
|
|
20142
21870
|
function renderSettingsPage(data) {
|
|
20143
21871
|
const activeTab = data.activeTab || "general";
|
|
20144
21872
|
const pageContent = `
|
|
@@ -20520,7 +22248,7 @@ function renderSettingsPage(data) {
|
|
|
20520
22248
|
version: data.version,
|
|
20521
22249
|
content: pageContent
|
|
20522
22250
|
};
|
|
20523
|
-
return
|
|
22251
|
+
return chunkEHSZ6TAN_cjs.renderAdminLayoutCatalyst(layoutData);
|
|
20524
22252
|
}
|
|
20525
22253
|
function renderTabButton(tabId, label, iconPath, activeTab) {
|
|
20526
22254
|
const isActive = activeTab === tabId;
|
|
@@ -21602,7 +23330,7 @@ function renderDatabaseToolsSettings(settings) {
|
|
|
21602
23330
|
|
|
21603
23331
|
// src/routes/admin-settings.ts
|
|
21604
23332
|
var adminSettingsRoutes = new hono.Hono();
|
|
21605
|
-
adminSettingsRoutes.use("*",
|
|
23333
|
+
adminSettingsRoutes.use("*", chunkE2BXLXPW_cjs.requireAuth());
|
|
21606
23334
|
function getMockSettings(user) {
|
|
21607
23335
|
return {
|
|
21608
23336
|
general: {
|
|
@@ -21770,7 +23498,7 @@ adminSettingsRoutes.get("/database-tools", (c) => {
|
|
|
21770
23498
|
adminSettingsRoutes.get("/api/migrations/status", async (c) => {
|
|
21771
23499
|
try {
|
|
21772
23500
|
const db = c.env.DB;
|
|
21773
|
-
const migrationService = new
|
|
23501
|
+
const migrationService = new chunkYRFAQ6MI_cjs.MigrationService(db);
|
|
21774
23502
|
const status = await migrationService.getMigrationStatus();
|
|
21775
23503
|
return c.json({
|
|
21776
23504
|
success: true,
|
|
@@ -21794,7 +23522,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
|
|
|
21794
23522
|
}, 403);
|
|
21795
23523
|
}
|
|
21796
23524
|
const db = c.env.DB;
|
|
21797
|
-
const migrationService = new
|
|
23525
|
+
const migrationService = new chunkYRFAQ6MI_cjs.MigrationService(db);
|
|
21798
23526
|
const result = await migrationService.runPendingMigrations();
|
|
21799
23527
|
return c.json({
|
|
21800
23528
|
success: result.success,
|
|
@@ -21812,7 +23540,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
|
|
|
21812
23540
|
adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
|
|
21813
23541
|
try {
|
|
21814
23542
|
const db = c.env.DB;
|
|
21815
|
-
const migrationService = new
|
|
23543
|
+
const migrationService = new chunkYRFAQ6MI_cjs.MigrationService(db);
|
|
21816
23544
|
const validation = await migrationService.validateSchema();
|
|
21817
23545
|
return c.json({
|
|
21818
23546
|
success: true,
|
|
@@ -22053,9 +23781,10 @@ exports.api_default = api_default;
|
|
|
22053
23781
|
exports.api_media_default = api_media_default;
|
|
22054
23782
|
exports.api_system_default = api_system_default;
|
|
22055
23783
|
exports.auth_default = auth_default;
|
|
22056
|
-
exports.
|
|
23784
|
+
exports.getConfirmationDialogScript = getConfirmationDialogScript2;
|
|
23785
|
+
exports.renderConfirmationDialog = renderConfirmationDialog2;
|
|
22057
23786
|
exports.router = router;
|
|
22058
23787
|
exports.test_cleanup_default = test_cleanup_default;
|
|
22059
23788
|
exports.userRoutes = userRoutes;
|
|
22060
|
-
//# sourceMappingURL=chunk-
|
|
22061
|
-
//# sourceMappingURL=chunk-
|
|
23789
|
+
//# sourceMappingURL=chunk-J7F3NPAP.cjs.map
|
|
23790
|
+
//# sourceMappingURL=chunk-J7F3NPAP.cjs.map
|