@sonicjs-cms/core 2.3.12 → 2.3.14

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.
Files changed (54) hide show
  1. package/dist/{chunk-REY542YK.js → chunk-AVPUX57O.js} +3 -3
  2. package/dist/{chunk-REY542YK.js.map → chunk-AVPUX57O.js.map} +1 -1
  3. package/dist/{chunk-RIOIKM3Y.cjs → chunk-AZLU3ROK.cjs} +4 -2
  4. package/dist/chunk-AZLU3ROK.cjs.map +1 -0
  5. package/dist/{chunk-NTXPL746.js → chunk-CAJOP354.js} +34 -2
  6. package/dist/chunk-CAJOP354.js.map +1 -0
  7. package/dist/{chunk-HTJLBF6F.cjs → chunk-D4PJFFOV.cjs} +652 -475
  8. package/dist/chunk-D4PJFFOV.cjs.map +1 -0
  9. package/dist/{chunk-P6NMVNJJ.cjs → chunk-ETS5XSAG.cjs} +34 -2
  10. package/dist/chunk-ETS5XSAG.cjs.map +1 -0
  11. package/dist/{chunk-EIE35JCC.js → chunk-H34L445M.js} +3 -3
  12. package/dist/{chunk-EIE35JCC.js.map → chunk-H34L445M.js.map} +1 -1
  13. package/dist/{chunk-74RYBO6J.js → chunk-SKPETEM5.js} +10 -5
  14. package/dist/chunk-SKPETEM5.js.map +1 -0
  15. package/dist/{chunk-IB6UBZVD.cjs → chunk-SZE3XVET.cjs} +10 -5
  16. package/dist/chunk-SZE3XVET.cjs.map +1 -0
  17. package/dist/{chunk-HDSRB23N.js → chunk-T4XRPNX2.js} +507 -330
  18. package/dist/chunk-T4XRPNX2.js.map +1 -0
  19. package/dist/{chunk-KQCYQKSV.js → chunk-V5LBQN3I.js} +4 -2
  20. package/dist/chunk-V5LBQN3I.js.map +1 -0
  21. package/dist/{chunk-OJ5WUCSH.cjs → chunk-XWPGIFS7.cjs} +4 -4
  22. package/dist/{chunk-OJ5WUCSH.cjs.map → chunk-XWPGIFS7.cjs.map} +1 -1
  23. package/dist/{chunk-K6BFUYJH.cjs → chunk-YIXSSJWD.cjs} +5 -5
  24. package/dist/{chunk-K6BFUYJH.cjs.map → chunk-YIXSSJWD.cjs.map} +1 -1
  25. package/dist/index.cjs +1080 -87
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +1003 -10
  28. package/dist/index.js.map +1 -1
  29. package/dist/middleware.cjs +23 -23
  30. package/dist/middleware.js +2 -2
  31. package/dist/migrations-3A53GREK.cjs +13 -0
  32. package/dist/{migrations-DQ74P6V4.cjs.map → migrations-3A53GREK.cjs.map} +1 -1
  33. package/dist/migrations-WF6VIVU2.js +4 -0
  34. package/dist/{migrations-YAFC5JVO.js.map → migrations-WF6VIVU2.js.map} +1 -1
  35. package/dist/routes.cjs +25 -25
  36. package/dist/routes.js +5 -5
  37. package/dist/services.cjs +2 -2
  38. package/dist/services.js +1 -1
  39. package/dist/templates.cjs +17 -17
  40. package/dist/templates.js +2 -2
  41. package/dist/utils.cjs +11 -11
  42. package/dist/utils.js +1 -1
  43. package/migrations/025_add_easymde_plugin.sql +25 -0
  44. package/package.json +8 -3
  45. package/dist/chunk-74RYBO6J.js.map +0 -1
  46. package/dist/chunk-HDSRB23N.js.map +0 -1
  47. package/dist/chunk-HTJLBF6F.cjs.map +0 -1
  48. package/dist/chunk-IB6UBZVD.cjs.map +0 -1
  49. package/dist/chunk-KQCYQKSV.js.map +0 -1
  50. package/dist/chunk-NTXPL746.js.map +0 -1
  51. package/dist/chunk-P6NMVNJJ.cjs.map +0 -1
  52. package/dist/chunk-RIOIKM3Y.cjs.map +0 -1
  53. package/dist/migrations-DQ74P6V4.cjs +0 -13
  54. package/dist/migrations-YAFC5JVO.js +0 -4
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var chunk7FOAMNTI_cjs = require('./chunk-7FOAMNTI.cjs');
4
- var chunkOJ5WUCSH_cjs = require('./chunk-OJ5WUCSH.cjs');
4
+ var chunkXWPGIFS7_cjs = require('./chunk-XWPGIFS7.cjs');
5
5
  var chunkILZ3DP4I_cjs = require('./chunk-ILZ3DP4I.cjs');
6
- var chunkP6NMVNJJ_cjs = require('./chunk-P6NMVNJJ.cjs');
7
- var chunkRIOIKM3Y_cjs = require('./chunk-RIOIKM3Y.cjs');
8
- var chunkIB6UBZVD_cjs = require('./chunk-IB6UBZVD.cjs');
6
+ var chunkETS5XSAG_cjs = require('./chunk-ETS5XSAG.cjs');
7
+ var chunkAZLU3ROK_cjs = require('./chunk-AZLU3ROK.cjs');
8
+ var chunkSZE3XVET_cjs = require('./chunk-SZE3XVET.cjs');
9
9
  var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
10
10
  var hono = require('hono');
11
11
  var cors = require('hono/cors');
@@ -44,7 +44,7 @@ apiContentCrudRoutes.get("/:id", async (c) => {
44
44
  }, 500);
45
45
  }
46
46
  });
47
- apiContentCrudRoutes.post("/", chunkOJ5WUCSH_cjs.requireAuth(), async (c) => {
47
+ apiContentCrudRoutes.post("/", chunkXWPGIFS7_cjs.requireAuth(), async (c) => {
48
48
  try {
49
49
  const db = c.env.DB;
50
50
  const user = c.get("user");
@@ -110,7 +110,7 @@ apiContentCrudRoutes.post("/", chunkOJ5WUCSH_cjs.requireAuth(), async (c) => {
110
110
  }, 500);
111
111
  }
112
112
  });
113
- apiContentCrudRoutes.put("/:id", chunkOJ5WUCSH_cjs.requireAuth(), async (c) => {
113
+ apiContentCrudRoutes.put("/:id", chunkXWPGIFS7_cjs.requireAuth(), async (c) => {
114
114
  try {
115
115
  const id = c.req.param("id");
116
116
  const db = c.env.DB;
@@ -174,7 +174,7 @@ apiContentCrudRoutes.put("/:id", chunkOJ5WUCSH_cjs.requireAuth(), async (c) => {
174
174
  }, 500);
175
175
  }
176
176
  });
177
- apiContentCrudRoutes.delete("/:id", chunkOJ5WUCSH_cjs.requireAuth(), async (c) => {
177
+ apiContentCrudRoutes.delete("/:id", chunkXWPGIFS7_cjs.requireAuth(), async (c) => {
178
178
  try {
179
179
  const id = c.req.param("id");
180
180
  const db = c.env.DB;
@@ -210,7 +210,7 @@ apiRoutes.use("*", async (c, next) => {
210
210
  c.header("X-Response-Time", `${totalTime}ms`);
211
211
  });
212
212
  apiRoutes.use("*", async (c, next) => {
213
- const cacheEnabled = await chunkOJ5WUCSH_cjs.isPluginActive(c.env.DB, "core-cache");
213
+ const cacheEnabled = await chunkXWPGIFS7_cjs.isPluginActive(c.env.DB, "core-cache");
214
214
  c.set("cacheEnabled", cacheEnabled);
215
215
  await next();
216
216
  });
@@ -335,12 +335,12 @@ apiRoutes.get("/content", async (c) => {
335
335
  });
336
336
  }
337
337
  }
338
- const filter = chunkIB6UBZVD_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
338
+ const filter = chunkSZE3XVET_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
339
339
  if (!filter.limit) {
340
340
  filter.limit = 50;
341
341
  }
342
342
  filter.limit = Math.min(filter.limit, 1e3);
343
- const builder3 = new chunkIB6UBZVD_cjs.QueryFilterBuilder();
343
+ const builder3 = new chunkSZE3XVET_cjs.QueryFilterBuilder();
344
344
  const queryResult = builder3.build("content", filter);
345
345
  if (queryResult.errors.length > 0) {
346
346
  return c.json({
@@ -427,7 +427,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
427
427
  if (!collectionResult) {
428
428
  return c.json({ error: "Collection not found" }, 404);
429
429
  }
430
- const filter = chunkIB6UBZVD_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
430
+ const filter = chunkSZE3XVET_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
431
431
  if (!filter.where) {
432
432
  filter.where = { and: [] };
433
433
  }
@@ -443,7 +443,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
443
443
  filter.limit = 50;
444
444
  }
445
445
  filter.limit = Math.min(filter.limit, 1e3);
446
- const builder3 = new chunkIB6UBZVD_cjs.QueryFilterBuilder();
446
+ const builder3 = new chunkSZE3XVET_cjs.QueryFilterBuilder();
447
447
  const queryResult = builder3.build("content", filter);
448
448
  if (queryResult.errors.length > 0) {
449
449
  return c.json({
@@ -568,7 +568,7 @@ var fileValidationSchema = zod.z.object({
568
568
  // 50MB max
569
569
  });
570
570
  var apiMediaRoutes = new hono.Hono();
571
- apiMediaRoutes.use("*", chunkOJ5WUCSH_cjs.requireAuth());
571
+ apiMediaRoutes.use("*", chunkXWPGIFS7_cjs.requireAuth());
572
572
  apiMediaRoutes.post("/upload", async (c) => {
573
573
  try {
574
574
  const user = c.get("user");
@@ -1312,8 +1312,8 @@ apiSystemRoutes.get("/env", (c) => {
1312
1312
  });
1313
1313
  var api_system_default = apiSystemRoutes;
1314
1314
  var adminApiRoutes = new hono.Hono();
1315
- adminApiRoutes.use("*", chunkOJ5WUCSH_cjs.requireAuth());
1316
- adminApiRoutes.use("*", chunkOJ5WUCSH_cjs.requireRole(["admin", "editor"]));
1315
+ adminApiRoutes.use("*", chunkXWPGIFS7_cjs.requireAuth());
1316
+ adminApiRoutes.use("*", chunkXWPGIFS7_cjs.requireRole(["admin", "editor"]));
1317
1317
  adminApiRoutes.get("/stats", async (c) => {
1318
1318
  try {
1319
1319
  const db = c.env.DB;
@@ -1571,7 +1571,7 @@ adminApiRoutes.post("/collections", async (c) => {
1571
1571
  }
1572
1572
  const validatedData = validation.data;
1573
1573
  const db = c.env.DB;
1574
- const ____user = c.get("user");
1574
+ const _user = c.get("user");
1575
1575
  const displayName = validatedData.displayName || validatedData.display_name || "";
1576
1576
  const existingStmt = db.prepare("SELECT id FROM collections WHERE name = ?");
1577
1577
  const existing = await existingStmt.bind(validatedData.name).first();
@@ -1722,7 +1722,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
1722
1722
  });
1723
1723
  adminApiRoutes.get("/migrations/status", async (c) => {
1724
1724
  try {
1725
- const { MigrationService: MigrationService2 } = await import('./migrations-DQ74P6V4.cjs');
1725
+ const { MigrationService: MigrationService2 } = await import('./migrations-3A53GREK.cjs');
1726
1726
  const db = c.env.DB;
1727
1727
  const migrationService = new MigrationService2(db);
1728
1728
  const status = await migrationService.getMigrationStatus();
@@ -1747,7 +1747,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
1747
1747
  error: "Unauthorized. Admin access required."
1748
1748
  }, 403);
1749
1749
  }
1750
- const { MigrationService: MigrationService2 } = await import('./migrations-DQ74P6V4.cjs');
1750
+ const { MigrationService: MigrationService2 } = await import('./migrations-3A53GREK.cjs');
1751
1751
  const db = c.env.DB;
1752
1752
  const migrationService = new MigrationService2(db);
1753
1753
  const result = await migrationService.runPendingMigrations();
@@ -1766,7 +1766,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
1766
1766
  });
1767
1767
  adminApiRoutes.get("/migrations/validate", async (c) => {
1768
1768
  try {
1769
- const { MigrationService: MigrationService2 } = await import('./migrations-DQ74P6V4.cjs');
1769
+ const { MigrationService: MigrationService2 } = await import('./migrations-3A53GREK.cjs');
1770
1770
  const db = c.env.DB;
1771
1771
  const migrationService = new MigrationService2(db);
1772
1772
  const validation = await migrationService.validateSchema();
@@ -1841,8 +1841,8 @@ function renderLoginPage(data, demoLoginActive = false) {
1841
1841
  <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
1842
1842
  <div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
1843
1843
  <!-- Alerts -->
1844
- ${data.error ? `<div class="mb-6">${chunkRIOIKM3Y_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
1845
- ${data.message ? `<div class="mb-6">${chunkRIOIKM3Y_cjs.renderAlert({ type: "success", message: data.message })}</div>` : ""}
1844
+ ${data.error ? `<div class="mb-6">${chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
1845
+ ${data.message ? `<div class="mb-6">${chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.message })}</div>` : ""}
1846
1846
 
1847
1847
  <!-- Form Response (HTMX target) -->
1848
1848
  <div id="form-response" class="mb-6"></div>
@@ -2006,7 +2006,7 @@ function renderRegisterPage(data) {
2006
2006
  <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
2007
2007
  <div class="bg-zinc-900 shadow-sm ring-1 ring-white/10 rounded-xl px-6 py-8 sm:px-10">
2008
2008
  <!-- Alerts -->
2009
- ${data.error ? `<div class="mb-6">${chunkRIOIKM3Y_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
2009
+ ${data.error ? `<div class="mb-6">${chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error })}</div>` : ""}
2010
2010
 
2011
2011
  <!-- Form -->
2012
2012
  <form
@@ -2119,6 +2119,27 @@ function renderRegisterPage(data) {
2119
2119
  </html>
2120
2120
  `;
2121
2121
  }
2122
+ async function isRegistrationEnabled(db) {
2123
+ try {
2124
+ const plugin = await db.prepare("SELECT settings FROM plugins WHERE id = ?").bind("core-auth").first();
2125
+ if (plugin?.settings) {
2126
+ const settings = JSON.parse(plugin.settings);
2127
+ const enabled = settings?.registration?.enabled;
2128
+ return enabled !== false && enabled !== 0;
2129
+ }
2130
+ return true;
2131
+ } catch {
2132
+ return true;
2133
+ }
2134
+ }
2135
+ async function isFirstUserRegistration(db) {
2136
+ try {
2137
+ const result = await db.prepare("SELECT COUNT(*) as count FROM users").first();
2138
+ return result?.count === 0;
2139
+ } catch {
2140
+ return false;
2141
+ }
2142
+ }
2122
2143
  var baseRegistrationSchema = zod.z.object({
2123
2144
  email: zod.z.string().email("Valid email is required"),
2124
2145
  password: zod.z.string().min(8, "Password must be at least 8 characters"),
@@ -2170,7 +2191,15 @@ authRoutes.get("/login", async (c) => {
2170
2191
  }
2171
2192
  return c.html(renderLoginPage(pageData, demoLoginActive));
2172
2193
  });
2173
- authRoutes.get("/register", (c) => {
2194
+ authRoutes.get("/register", async (c) => {
2195
+ const db = c.env.DB;
2196
+ const isFirstUser = await isFirstUserRegistration(db);
2197
+ if (!isFirstUser) {
2198
+ const registrationEnabled = await isRegistrationEnabled(db);
2199
+ if (!registrationEnabled) {
2200
+ return c.redirect("/auth/login?error=Registration is currently disabled");
2201
+ }
2202
+ }
2174
2203
  const error = c.req.query("error");
2175
2204
  const pageData = {
2176
2205
  error: error || void 0
@@ -2186,6 +2215,13 @@ authRoutes.post(
2186
2215
  async (c) => {
2187
2216
  try {
2188
2217
  const db = c.env.DB;
2218
+ const isFirstUser = await isFirstUserRegistration(db);
2219
+ if (!isFirstUser) {
2220
+ const registrationEnabled = await isRegistrationEnabled(db);
2221
+ if (!registrationEnabled) {
2222
+ return c.json({ error: "Registration is currently disabled" }, 403);
2223
+ }
2224
+ }
2189
2225
  let requestData;
2190
2226
  try {
2191
2227
  requestData = await c.req.json();
@@ -2212,7 +2248,7 @@ authRoutes.post(
2212
2248
  if (existingUser) {
2213
2249
  return c.json({ error: "User with this email or username already exists" }, 400);
2214
2250
  }
2215
- const passwordHash = await chunkOJ5WUCSH_cjs.AuthManager.hashPassword(password);
2251
+ const passwordHash = await chunkXWPGIFS7_cjs.AuthManager.hashPassword(password);
2216
2252
  const userId = crypto.randomUUID();
2217
2253
  const now = /* @__PURE__ */ new Date();
2218
2254
  await db.prepare(`
@@ -2232,7 +2268,7 @@ authRoutes.post(
2232
2268
  now.getTime(),
2233
2269
  now.getTime()
2234
2270
  ).run();
2235
- const token = await chunkOJ5WUCSH_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer");
2271
+ const token = await chunkXWPGIFS7_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer");
2236
2272
  cookie.setCookie(c, "auth_token", token, {
2237
2273
  httpOnly: true,
2238
2274
  secure: true,
@@ -2285,11 +2321,11 @@ authRoutes.post("/login", async (c) => {
2285
2321
  if (!user) {
2286
2322
  return c.json({ error: "Invalid email or password" }, 401);
2287
2323
  }
2288
- const isValidPassword = await chunkOJ5WUCSH_cjs.AuthManager.verifyPassword(password, user.password_hash);
2324
+ const isValidPassword = await chunkXWPGIFS7_cjs.AuthManager.verifyPassword(password, user.password_hash);
2289
2325
  if (!isValidPassword) {
2290
2326
  return c.json({ error: "Invalid email or password" }, 401);
2291
2327
  }
2292
- const token = await chunkOJ5WUCSH_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2328
+ const token = await chunkXWPGIFS7_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2293
2329
  cookie.setCookie(c, "auth_token", token, {
2294
2330
  httpOnly: true,
2295
2331
  secure: true,
@@ -2338,7 +2374,7 @@ authRoutes.get("/logout", (c) => {
2338
2374
  });
2339
2375
  return c.redirect("/auth/login?message=You have been logged out successfully");
2340
2376
  });
2341
- authRoutes.get("/me", chunkOJ5WUCSH_cjs.requireAuth(), async (c) => {
2377
+ authRoutes.get("/me", chunkXWPGIFS7_cjs.requireAuth(), async (c) => {
2342
2378
  try {
2343
2379
  const user = c.get("user");
2344
2380
  if (!user) {
@@ -2355,13 +2391,13 @@ authRoutes.get("/me", chunkOJ5WUCSH_cjs.requireAuth(), async (c) => {
2355
2391
  return c.json({ error: "Failed to get user" }, 500);
2356
2392
  }
2357
2393
  });
2358
- authRoutes.post("/refresh", chunkOJ5WUCSH_cjs.requireAuth(), async (c) => {
2394
+ authRoutes.post("/refresh", chunkXWPGIFS7_cjs.requireAuth(), async (c) => {
2359
2395
  try {
2360
2396
  const user = c.get("user");
2361
2397
  if (!user) {
2362
2398
  return c.json({ error: "Not authenticated" }, 401);
2363
2399
  }
2364
- const token = await chunkOJ5WUCSH_cjs.AuthManager.generateToken(user.userId, user.email, user.role);
2400
+ const token = await chunkXWPGIFS7_cjs.AuthManager.generateToken(user.userId, user.email, user.role);
2365
2401
  cookie.setCookie(c, "auth_token", token, {
2366
2402
  httpOnly: true,
2367
2403
  secure: true,
@@ -2378,6 +2414,17 @@ authRoutes.post("/refresh", chunkOJ5WUCSH_cjs.requireAuth(), async (c) => {
2378
2414
  authRoutes.post("/register/form", async (c) => {
2379
2415
  try {
2380
2416
  const db = c.env.DB;
2417
+ const isFirstUser = await isFirstUserRegistration(db);
2418
+ if (!isFirstUser) {
2419
+ const registrationEnabled = await isRegistrationEnabled(db);
2420
+ if (!registrationEnabled) {
2421
+ return c.html(html.html`
2422
+ <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
2423
+ Registration is currently disabled. Please contact an administrator.
2424
+ </div>
2425
+ `);
2426
+ }
2427
+ }
2381
2428
  const formData = await c.req.formData();
2382
2429
  const requestData = {
2383
2430
  email: formData.get("email"),
@@ -2410,7 +2457,8 @@ authRoutes.post("/register/form", async (c) => {
2410
2457
  </div>
2411
2458
  `);
2412
2459
  }
2413
- const passwordHash = await chunkOJ5WUCSH_cjs.AuthManager.hashPassword(password);
2460
+ const passwordHash = await chunkXWPGIFS7_cjs.AuthManager.hashPassword(password);
2461
+ const role = isFirstUser ? "admin" : "viewer";
2414
2462
  const userId = crypto.randomUUID();
2415
2463
  const now = /* @__PURE__ */ new Date();
2416
2464
  await db.prepare(`
@@ -2423,14 +2471,13 @@ authRoutes.post("/register/form", async (c) => {
2423
2471
  firstName,
2424
2472
  lastName,
2425
2473
  passwordHash,
2426
- "admin",
2427
- // First user gets admin role
2474
+ role,
2428
2475
  1,
2429
2476
  // is_active
2430
2477
  now.getTime(),
2431
2478
  now.getTime()
2432
2479
  ).run();
2433
- const token = await chunkOJ5WUCSH_cjs.AuthManager.generateToken(userId, normalizedEmail, "admin");
2480
+ const token = await chunkXWPGIFS7_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
2434
2481
  cookie.setCookie(c, "auth_token", token, {
2435
2482
  httpOnly: true,
2436
2483
  secure: false,
@@ -2439,12 +2486,13 @@ authRoutes.post("/register/form", async (c) => {
2439
2486
  maxAge: 60 * 60 * 24
2440
2487
  // 24 hours
2441
2488
  });
2489
+ const redirectUrl = role === "admin" ? "/admin/dashboard" : "/admin/dashboard";
2442
2490
  return c.html(html.html`
2443
2491
  <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
2444
- Account created successfully! Redirecting to admin dashboard...
2492
+ Account created successfully! Redirecting...
2445
2493
  <script>
2446
2494
  setTimeout(() => {
2447
- window.location.href = '/admin/dashboard';
2495
+ window.location.href = '${redirectUrl}';
2448
2496
  }, 2000);
2449
2497
  </script>
2450
2498
  </div>
@@ -2481,7 +2529,7 @@ authRoutes.post("/login/form", async (c) => {
2481
2529
  </div>
2482
2530
  `);
2483
2531
  }
2484
- const isValidPassword = await chunkOJ5WUCSH_cjs.AuthManager.verifyPassword(password, user.password_hash);
2532
+ const isValidPassword = await chunkXWPGIFS7_cjs.AuthManager.verifyPassword(password, user.password_hash);
2485
2533
  if (!isValidPassword) {
2486
2534
  return c.html(html.html`
2487
2535
  <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
@@ -2489,7 +2537,7 @@ authRoutes.post("/login/form", async (c) => {
2489
2537
  </div>
2490
2538
  `);
2491
2539
  }
2492
- const token = await chunkOJ5WUCSH_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2540
+ const token = await chunkXWPGIFS7_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2493
2541
  cookie.setCookie(c, "auth_token", token, {
2494
2542
  httpOnly: true,
2495
2543
  secure: false,
@@ -2548,7 +2596,7 @@ authRoutes.post("/seed-admin", async (c) => {
2548
2596
  `).run();
2549
2597
  const existingAdmin = await db.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind("admin@sonicjs.com", "admin").first();
2550
2598
  if (existingAdmin) {
2551
- const passwordHash2 = await chunkOJ5WUCSH_cjs.AuthManager.hashPassword("sonicjs!");
2599
+ const passwordHash2 = await chunkXWPGIFS7_cjs.AuthManager.hashPassword("sonicjs!");
2552
2600
  await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
2553
2601
  return c.json({
2554
2602
  message: "Admin user already exists (password updated)",
@@ -2560,7 +2608,7 @@ authRoutes.post("/seed-admin", async (c) => {
2560
2608
  }
2561
2609
  });
2562
2610
  }
2563
- const passwordHash = await chunkOJ5WUCSH_cjs.AuthManager.hashPassword("sonicjs!");
2611
+ const passwordHash = await chunkXWPGIFS7_cjs.AuthManager.hashPassword("sonicjs!");
2564
2612
  const userId = "admin-user-id";
2565
2613
  const now = Date.now();
2566
2614
  const adminEmail = "admin@sonicjs.com".toLowerCase();
@@ -2780,7 +2828,7 @@ authRoutes.post("/accept-invitation", async (c) => {
2780
2828
  if (existingUsername) {
2781
2829
  return c.json({ error: "Username is already taken" }, 400);
2782
2830
  }
2783
- const passwordHash = await chunkOJ5WUCSH_cjs.AuthManager.hashPassword(password);
2831
+ const passwordHash = await chunkXWPGIFS7_cjs.AuthManager.hashPassword(password);
2784
2832
  const updateStmt = db.prepare(`
2785
2833
  UPDATE users SET
2786
2834
  username = ?,
@@ -2799,7 +2847,7 @@ authRoutes.post("/accept-invitation", async (c) => {
2799
2847
  Date.now(),
2800
2848
  invitedUser.id
2801
2849
  ).run();
2802
- const authToken = await chunkOJ5WUCSH_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role);
2850
+ const authToken = await chunkXWPGIFS7_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role);
2803
2851
  cookie.setCookie(c, "auth_token", authToken, {
2804
2852
  httpOnly: true,
2805
2853
  secure: true,
@@ -3029,7 +3077,7 @@ authRoutes.post("/reset-password", async (c) => {
3029
3077
  if (Date.now() > user.password_reset_expires) {
3030
3078
  return c.json({ error: "Reset token has expired" }, 400);
3031
3079
  }
3032
- const newPasswordHash = await chunkOJ5WUCSH_cjs.AuthManager.hashPassword(password);
3080
+ const newPasswordHash = await chunkXWPGIFS7_cjs.AuthManager.hashPassword(password);
3033
3081
  try {
3034
3082
  const historyStmt = db.prepare(`
3035
3083
  INSERT INTO password_history (id, user_id, password_hash, created_at)
@@ -3287,7 +3335,7 @@ app.post("/test-cleanup/content", async (c) => {
3287
3335
  var test_cleanup_default = app;
3288
3336
 
3289
3337
  // src/templates/pages/admin-content-form.template.ts
3290
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
3338
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
3291
3339
 
3292
3340
  // src/templates/components/dynamic-field.template.ts
3293
3341
  function renderDynamicField(field, options = {}) {
@@ -4428,6 +4476,13 @@ function getMDXEditorInitScript(config) {
4428
4476
  // Store reference to editor instance
4429
4477
  textarea.easyMDEInstance = easyMDE;
4430
4478
 
4479
+ // Sync changes back to textarea
4480
+ easyMDE.codemirror.on("change", () => {
4481
+ textarea.value = easyMDE.value();
4482
+ textarea.dispatchEvent(new Event("input", { bubbles: true }));
4483
+ textarea.dispatchEvent(new Event("change", { bubbles: true }));
4484
+ });
4485
+
4431
4486
  console.log('EasyMDE initialized for field:', textarea.id || textarea.name);
4432
4487
  } catch (error) {
4433
4488
  console.error('Error initializing EasyMDE:', error);
@@ -4526,8 +4581,8 @@ function renderContentFormPage(data) {
4526
4581
  <!-- Form Content -->
4527
4582
  <div class="px-6 py-6">
4528
4583
  <div id="form-messages">
4529
- ${data.error ? chunkRIOIKM3Y_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
4530
- ${data.success ? chunkRIOIKM3Y_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
4584
+ ${data.error ? chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
4585
+ ${data.success ? chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
4531
4586
  </div>
4532
4587
 
4533
4588
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
@@ -4762,7 +4817,7 @@ function renderContentFormPage(data) {
4762
4817
  </div>
4763
4818
 
4764
4819
  <!-- Confirmation Dialogs -->
4765
- ${chunkRIOIKM3Y_cjs.renderConfirmationDialog({
4820
+ ${chunkAZLU3ROK_cjs.renderConfirmationDialog({
4766
4821
  id: "duplicate-content-confirm",
4767
4822
  title: "Duplicate Content",
4768
4823
  message: "Create a copy of this content?",
@@ -4773,7 +4828,7 @@ function renderContentFormPage(data) {
4773
4828
  onConfirm: "performDuplicateContent()"
4774
4829
  })}
4775
4830
 
4776
- ${chunkRIOIKM3Y_cjs.renderConfirmationDialog({
4831
+ ${chunkAZLU3ROK_cjs.renderConfirmationDialog({
4777
4832
  id: "delete-content-confirm",
4778
4833
  title: "Delete Content",
4779
4834
  message: "Are you sure you want to delete this content? This action cannot be undone.",
@@ -4784,7 +4839,7 @@ function renderContentFormPage(data) {
4784
4839
  onConfirm: `performDeleteContent('${data.id}')`
4785
4840
  })}
4786
4841
 
4787
- ${chunkRIOIKM3Y_cjs.getConfirmationDialogScript()}
4842
+ ${chunkAZLU3ROK_cjs.getConfirmationDialogScript()}
4788
4843
 
4789
4844
  ${data.tinymceEnabled ? getTinyMCEScript(data.tinymceSettings?.apiKey) : "<!-- TinyMCE plugin not active -->"}
4790
4845
 
@@ -5089,11 +5144,11 @@ function renderContentFormPage(data) {
5089
5144
  content: pageContent,
5090
5145
  version: data.version
5091
5146
  };
5092
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
5147
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
5093
5148
  }
5094
5149
 
5095
5150
  // src/templates/pages/admin-content-list.template.ts
5096
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
5151
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
5097
5152
  function renderContentListPage(data) {
5098
5153
  const urlParams = new URLSearchParams();
5099
5154
  if (data.modelName && data.modelName !== "all") urlParams.set("model", data.modelName);
@@ -5498,8 +5553,8 @@ function renderContentListPage(data) {
5498
5553
 
5499
5554
  <!-- Content List -->
5500
5555
  <div id="content-list">
5501
- ${chunkRIOIKM3Y_cjs.renderTable(tableData)}
5502
- ${chunkRIOIKM3Y_cjs.renderPagination(paginationData)}
5556
+ ${chunkAZLU3ROK_cjs.renderTable(tableData)}
5557
+ ${chunkAZLU3ROK_cjs.renderPagination(paginationData)}
5503
5558
  </div>
5504
5559
 
5505
5560
  </div>
@@ -5708,7 +5763,7 @@ function renderContentListPage(data) {
5708
5763
  </script>
5709
5764
 
5710
5765
  <!-- Confirmation Dialog for Bulk Actions -->
5711
- ${chunkRIOIKM3Y_cjs.renderConfirmationDialog({
5766
+ ${chunkAZLU3ROK_cjs.renderConfirmationDialog({
5712
5767
  id: "bulk-action-confirm",
5713
5768
  title: "Confirm Bulk Action",
5714
5769
  message: "Are you sure you want to perform this action? This operation will affect multiple items.",
@@ -5720,7 +5775,7 @@ function renderContentListPage(data) {
5720
5775
  })}
5721
5776
 
5722
5777
  <!-- Confirmation Dialog Script -->
5723
- ${chunkRIOIKM3Y_cjs.getConfirmationDialogScript()}
5778
+ ${chunkAZLU3ROK_cjs.getConfirmationDialogScript()}
5724
5779
  `;
5725
5780
  const layoutData = {
5726
5781
  title: "Content Management",
@@ -5730,7 +5785,7 @@ function renderContentListPage(data) {
5730
5785
  version: data.version,
5731
5786
  content: pageContent
5732
5787
  };
5733
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
5788
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
5734
5789
  }
5735
5790
 
5736
5791
  // src/templates/components/version-history.template.ts
@@ -5924,7 +5979,7 @@ async function isPluginActive2(db, pluginId) {
5924
5979
 
5925
5980
  // src/routes/admin-content.ts
5926
5981
  var adminContentRoutes = new hono.Hono();
5927
- adminContentRoutes.use("*", chunkOJ5WUCSH_cjs.requireAuth());
5982
+ adminContentRoutes.use("*", chunkXWPGIFS7_cjs.requireAuth());
5928
5983
  async function getCollectionFields(db, collectionId) {
5929
5984
  const cache = chunk7FOAMNTI_cjs.getCacheService(chunk7FOAMNTI_cjs.CACHE_CONFIGS.collection);
5930
5985
  return cache.getOrSet(
@@ -6457,10 +6512,9 @@ adminContentRoutes.post("/", async (c) => {
6457
6512
  const insertStmt = db.prepare(`
6458
6513
  INSERT INTO content (
6459
6514
  id, collection_id, slug, title, data, status,
6460
- scheduled_publish_at, scheduled_unpublish_at,
6461
- meta_title, meta_description, author_id, created_by, created_at, updated_at
6515
+ author_id, created_at, updated_at
6462
6516
  )
6463
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6517
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
6464
6518
  `);
6465
6519
  await insertStmt.bind(
6466
6520
  contentId,
@@ -6469,11 +6523,6 @@ adminContentRoutes.post("/", async (c) => {
6469
6523
  data.title || "Untitled",
6470
6524
  JSON.stringify(data),
6471
6525
  status,
6472
- scheduledPublishAt ? new Date(scheduledPublishAt).getTime() : null,
6473
- scheduledUnpublishAt ? new Date(scheduledUnpublishAt).getTime() : null,
6474
- data.meta_title || null,
6475
- data.meta_description || null,
6476
- user?.userId || "unknown",
6477
6526
  user?.userId || "unknown",
6478
6527
  now,
6479
6528
  now
@@ -7139,7 +7188,7 @@ ${JSON.stringify(data, null, 2)}
7139
7188
  var admin_content_default = adminContentRoutes;
7140
7189
 
7141
7190
  // src/templates/pages/admin-profile.template.ts
7142
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
7191
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
7143
7192
  function renderAvatarImage(avatarUrl, firstName, lastName) {
7144
7193
  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">
7145
7194
  ${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>`}
@@ -7159,8 +7208,8 @@ function renderProfilePage(data) {
7159
7208
  </div>
7160
7209
 
7161
7210
  <!-- Alert Messages -->
7162
- ${data.error ? chunkRIOIKM3Y_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
7163
- ${data.success ? chunkRIOIKM3Y_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
7211
+ ${data.error ? chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
7212
+ ${data.success ? chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
7164
7213
 
7165
7214
  <!-- Profile Form -->
7166
7215
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
@@ -7547,7 +7596,7 @@ function renderProfilePage(data) {
7547
7596
  version: data.version,
7548
7597
  content: pageContent
7549
7598
  };
7550
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
7599
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
7551
7600
  }
7552
7601
 
7553
7602
  // src/templates/components/alert.template.ts
@@ -7830,7 +7879,7 @@ function renderActivityLogsPage(data) {
7830
7879
  user: data.user,
7831
7880
  content: pageContent
7832
7881
  };
7833
- return chunkRIOIKM3Y_cjs.renderAdminLayout(layoutData);
7882
+ return chunkAZLU3ROK_cjs.renderAdminLayout(layoutData);
7834
7883
  }
7835
7884
  function getActionBadgeClass(action) {
7836
7885
  if (action.includes("login") || action.includes("logout")) {
@@ -7850,7 +7899,7 @@ function formatAction(action) {
7850
7899
  }
7851
7900
 
7852
7901
  // src/templates/pages/admin-user-edit.template.ts
7853
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
7902
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
7854
7903
 
7855
7904
  // src/templates/components/confirmation-dialog.template.ts
7856
7905
  function renderConfirmationDialog2(options) {
@@ -7971,8 +8020,8 @@ function renderUserEditPage(data) {
7971
8020
 
7972
8021
  <!-- Alert Messages -->
7973
8022
  <div id="form-messages">
7974
- ${data.error ? chunkRIOIKM3Y_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
7975
- ${data.success ? chunkRIOIKM3Y_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
8023
+ ${data.error ? chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
8024
+ ${data.success ? chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
7976
8025
  </div>
7977
8026
 
7978
8027
  <!-- User Edit Form -->
@@ -7991,7 +8040,7 @@ function renderUserEditPage(data) {
7991
8040
  <input
7992
8041
  type="text"
7993
8042
  name="first_name"
7994
- value="${chunkIB6UBZVD_cjs.escapeHtml(data.userToEdit.firstName || "")}"
8043
+ value="${chunkSZE3XVET_cjs.escapeHtml(data.userToEdit.firstName || "")}"
7995
8044
  required
7996
8045
  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"
7997
8046
  />
@@ -8002,7 +8051,7 @@ function renderUserEditPage(data) {
8002
8051
  <input
8003
8052
  type="text"
8004
8053
  name="last_name"
8005
- value="${chunkIB6UBZVD_cjs.escapeHtml(data.userToEdit.lastName || "")}"
8054
+ value="${chunkSZE3XVET_cjs.escapeHtml(data.userToEdit.lastName || "")}"
8006
8055
  required
8007
8056
  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"
8008
8057
  />
@@ -8013,7 +8062,7 @@ function renderUserEditPage(data) {
8013
8062
  <input
8014
8063
  type="text"
8015
8064
  name="username"
8016
- value="${chunkIB6UBZVD_cjs.escapeHtml(data.userToEdit.username || "")}"
8065
+ value="${chunkSZE3XVET_cjs.escapeHtml(data.userToEdit.username || "")}"
8017
8066
  required
8018
8067
  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"
8019
8068
  />
@@ -8024,7 +8073,7 @@ function renderUserEditPage(data) {
8024
8073
  <input
8025
8074
  type="email"
8026
8075
  name="email"
8027
- value="${chunkIB6UBZVD_cjs.escapeHtml(data.userToEdit.email || "")}"
8076
+ value="${chunkSZE3XVET_cjs.escapeHtml(data.userToEdit.email || "")}"
8028
8077
  required
8029
8078
  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"
8030
8079
  />
@@ -8035,7 +8084,7 @@ function renderUserEditPage(data) {
8035
8084
  <input
8036
8085
  type="tel"
8037
8086
  name="phone"
8038
- value="${chunkIB6UBZVD_cjs.escapeHtml(data.userToEdit.phone || "")}"
8087
+ value="${chunkSZE3XVET_cjs.escapeHtml(data.userToEdit.phone || "")}"
8039
8088
  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"
8040
8089
  />
8041
8090
  </div>
@@ -8049,7 +8098,7 @@ function renderUserEditPage(data) {
8049
8098
  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"
8050
8099
  >
8051
8100
  ${data.roles.map((role) => `
8052
- <option value="${chunkIB6UBZVD_cjs.escapeHtml(role.value)}" ${data.userToEdit.role === role.value ? "selected" : ""}>${chunkIB6UBZVD_cjs.escapeHtml(role.label)}</option>
8101
+ <option value="${chunkSZE3XVET_cjs.escapeHtml(role.value)}" ${data.userToEdit.role === role.value ? "selected" : ""}>${chunkSZE3XVET_cjs.escapeHtml(role.label)}</option>
8053
8102
  `).join("")}
8054
8103
  </select>
8055
8104
  <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">
@@ -8065,7 +8114,7 @@ function renderUserEditPage(data) {
8065
8114
  name="bio"
8066
8115
  rows="3"
8067
8116
  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"
8068
- >${chunkIB6UBZVD_cjs.escapeHtml(data.userToEdit.bio || "")}</textarea>
8117
+ >${chunkSZE3XVET_cjs.escapeHtml(data.userToEdit.bio || "")}</textarea>
8069
8118
  </div>
8070
8119
  </div>
8071
8120
 
@@ -8265,11 +8314,11 @@ function renderUserEditPage(data) {
8265
8314
  user: data.user,
8266
8315
  content: pageContent
8267
8316
  };
8268
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
8317
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
8269
8318
  }
8270
8319
 
8271
8320
  // src/templates/pages/admin-user-new.template.ts
8272
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
8321
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
8273
8322
  function renderUserNewPage(data) {
8274
8323
  const pageContent = `
8275
8324
  <div>
@@ -8308,8 +8357,8 @@ function renderUserNewPage(data) {
8308
8357
 
8309
8358
  <!-- Alert Messages -->
8310
8359
  <div id="form-messages">
8311
- ${data.error ? chunkRIOIKM3Y_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
8312
- ${data.success ? chunkRIOIKM3Y_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
8360
+ ${data.error ? chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
8361
+ ${data.success ? chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
8313
8362
  </div>
8314
8363
 
8315
8364
  <!-- User New Form -->
@@ -8553,11 +8602,11 @@ function renderUserNewPage(data) {
8553
8602
  user: data.user,
8554
8603
  content: pageContent
8555
8604
  };
8556
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
8605
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
8557
8606
  }
8558
8607
 
8559
8608
  // src/templates/pages/admin-users-list.template.ts
8560
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
8609
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
8561
8610
  function renderUsersListPage(data) {
8562
8611
  const columns = [
8563
8612
  {
@@ -8708,8 +8757,8 @@ function renderUsersListPage(data) {
8708
8757
  </div>
8709
8758
 
8710
8759
  <!-- Alert Messages -->
8711
- ${data.error ? chunkRIOIKM3Y_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
8712
- ${data.success ? chunkRIOIKM3Y_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
8760
+ ${data.error ? chunkAZLU3ROK_cjs.renderAlert({ type: "error", message: data.error, dismissible: true }) : ""}
8761
+ ${data.success ? chunkAZLU3ROK_cjs.renderAlert({ type: "success", message: data.success, dismissible: true }) : ""}
8713
8762
 
8714
8763
  <!-- Stats -->
8715
8764
  <div class="mb-6">
@@ -8886,10 +8935,10 @@ function renderUsersListPage(data) {
8886
8935
  </div>
8887
8936
 
8888
8937
  <!-- Users Table -->
8889
- ${chunkRIOIKM3Y_cjs.renderTable(tableData)}
8938
+ ${chunkAZLU3ROK_cjs.renderTable(tableData)}
8890
8939
 
8891
8940
  <!-- Pagination -->
8892
- ${data.pagination ? chunkRIOIKM3Y_cjs.renderPagination(data.pagination) : ""}
8941
+ ${data.pagination ? chunkAZLU3ROK_cjs.renderPagination(data.pagination) : ""}
8893
8942
  </div>
8894
8943
 
8895
8944
  <script>
@@ -8960,12 +9009,12 @@ function renderUsersListPage(data) {
8960
9009
  version: data.version,
8961
9010
  content: pageContent
8962
9011
  };
8963
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
9012
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
8964
9013
  }
8965
9014
 
8966
9015
  // src/routes/admin-users.ts
8967
9016
  var userRoutes = new hono.Hono();
8968
- userRoutes.use("*", chunkOJ5WUCSH_cjs.requireAuth());
9017
+ userRoutes.use("*", chunkXWPGIFS7_cjs.requireAuth());
8969
9018
  userRoutes.get("/", (c) => {
8970
9019
  return c.redirect("/admin/dashboard");
8971
9020
  });
@@ -9064,12 +9113,12 @@ userRoutes.put("/profile", async (c) => {
9064
9113
  const db = c.env.DB;
9065
9114
  try {
9066
9115
  const formData = await c.req.formData();
9067
- const firstName = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("first_name")?.toString());
9068
- const lastName = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("last_name")?.toString());
9069
- const username = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("username")?.toString());
9116
+ const firstName = chunkSZE3XVET_cjs.sanitizeInput(formData.get("first_name")?.toString());
9117
+ const lastName = chunkSZE3XVET_cjs.sanitizeInput(formData.get("last_name")?.toString());
9118
+ const username = chunkSZE3XVET_cjs.sanitizeInput(formData.get("username")?.toString());
9070
9119
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9071
- const phone = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9072
- const bio = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9120
+ const phone = chunkSZE3XVET_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9121
+ const bio = chunkSZE3XVET_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9073
9122
  const timezone = formData.get("timezone")?.toString() || "UTC";
9074
9123
  const language = formData.get("language")?.toString() || "en";
9075
9124
  const emailNotifications = formData.get("email_notifications") === "1";
@@ -9120,7 +9169,7 @@ userRoutes.put("/profile", async (c) => {
9120
9169
  Date.now(),
9121
9170
  user.userId
9122
9171
  ).run();
9123
- await chunkOJ5WUCSH_cjs.logActivity(
9172
+ await chunkXWPGIFS7_cjs.logActivity(
9124
9173
  db,
9125
9174
  user.userId,
9126
9175
  "profile.update",
@@ -9183,7 +9232,7 @@ userRoutes.post("/profile/avatar", async (c) => {
9183
9232
  SELECT first_name, last_name FROM users WHERE id = ?
9184
9233
  `);
9185
9234
  const userData = await userStmt.bind(user.userId).first();
9186
- await chunkOJ5WUCSH_cjs.logActivity(
9235
+ await chunkXWPGIFS7_cjs.logActivity(
9187
9236
  db,
9188
9237
  user.userId,
9189
9238
  "profile.avatar_update",
@@ -9254,7 +9303,7 @@ userRoutes.post("/profile/password", async (c) => {
9254
9303
  dismissible: true
9255
9304
  }));
9256
9305
  }
9257
- const validPassword = await chunkOJ5WUCSH_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
9306
+ const validPassword = await chunkXWPGIFS7_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
9258
9307
  if (!validPassword) {
9259
9308
  return c.html(renderAlert2({
9260
9309
  type: "error",
@@ -9262,7 +9311,7 @@ userRoutes.post("/profile/password", async (c) => {
9262
9311
  dismissible: true
9263
9312
  }));
9264
9313
  }
9265
- const newPasswordHash = await chunkOJ5WUCSH_cjs.AuthManager.hashPassword(newPassword);
9314
+ const newPasswordHash = await chunkXWPGIFS7_cjs.AuthManager.hashPassword(newPassword);
9266
9315
  const historyStmt = db.prepare(`
9267
9316
  INSERT INTO password_history (id, user_id, password_hash, created_at)
9268
9317
  VALUES (?, ?, ?, ?)
@@ -9278,7 +9327,7 @@ userRoutes.post("/profile/password", async (c) => {
9278
9327
  WHERE id = ?
9279
9328
  `);
9280
9329
  await updateStmt.bind(newPasswordHash, Date.now(), user.userId).run();
9281
- await chunkOJ5WUCSH_cjs.logActivity(
9330
+ await chunkXWPGIFS7_cjs.logActivity(
9282
9331
  db,
9283
9332
  user.userId,
9284
9333
  "profile.password_change",
@@ -9345,7 +9394,7 @@ userRoutes.get("/users", async (c) => {
9345
9394
  `);
9346
9395
  const countResult = await countStmt.bind(...params).first();
9347
9396
  const totalUsers = countResult?.total || 0;
9348
- await chunkOJ5WUCSH_cjs.logActivity(
9397
+ await chunkXWPGIFS7_cjs.logActivity(
9349
9398
  db,
9350
9399
  user.userId,
9351
9400
  "users.list_view",
@@ -9447,12 +9496,12 @@ userRoutes.post("/users/new", async (c) => {
9447
9496
  const user = c.get("user");
9448
9497
  try {
9449
9498
  const formData = await c.req.formData();
9450
- const firstName = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("first_name")?.toString());
9451
- const lastName = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("last_name")?.toString());
9452
- const username = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("username")?.toString());
9499
+ const firstName = chunkSZE3XVET_cjs.sanitizeInput(formData.get("first_name")?.toString());
9500
+ const lastName = chunkSZE3XVET_cjs.sanitizeInput(formData.get("last_name")?.toString());
9501
+ const username = chunkSZE3XVET_cjs.sanitizeInput(formData.get("username")?.toString());
9453
9502
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9454
- const phone = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9455
- const bio = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9503
+ const phone = chunkSZE3XVET_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9504
+ const bio = chunkSZE3XVET_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9456
9505
  const role = formData.get("role")?.toString() || "viewer";
9457
9506
  const password = formData.get("password")?.toString() || "";
9458
9507
  const confirmPassword = formData.get("confirm_password")?.toString() || "";
@@ -9499,7 +9548,7 @@ userRoutes.post("/users/new", async (c) => {
9499
9548
  dismissible: true
9500
9549
  }));
9501
9550
  }
9502
- const passwordHash = await chunkOJ5WUCSH_cjs.AuthManager.hashPassword(password);
9551
+ const passwordHash = await chunkXWPGIFS7_cjs.AuthManager.hashPassword(password);
9503
9552
  const userId = crypto.randomUUID();
9504
9553
  const createStmt = db.prepare(`
9505
9554
  INSERT INTO users (
@@ -9522,7 +9571,7 @@ userRoutes.post("/users/new", async (c) => {
9522
9571
  Date.now(),
9523
9572
  Date.now()
9524
9573
  ).run();
9525
- await chunkOJ5WUCSH_cjs.logActivity(
9574
+ await chunkXWPGIFS7_cjs.logActivity(
9526
9575
  db,
9527
9576
  user.userId,
9528
9577
  "user!.create",
@@ -9560,7 +9609,7 @@ userRoutes.get("/users/:id", async (c) => {
9560
9609
  if (!userRecord) {
9561
9610
  return c.json({ error: "User not found" }, 404);
9562
9611
  }
9563
- await chunkOJ5WUCSH_cjs.logActivity(
9612
+ await chunkXWPGIFS7_cjs.logActivity(
9564
9613
  db,
9565
9614
  user.userId,
9566
9615
  "user!.view",
@@ -9653,12 +9702,12 @@ userRoutes.put("/users/:id", async (c) => {
9653
9702
  const userId = c.req.param("id");
9654
9703
  try {
9655
9704
  const formData = await c.req.formData();
9656
- const firstName = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("first_name")?.toString());
9657
- const lastName = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("last_name")?.toString());
9658
- const username = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("username")?.toString());
9705
+ const firstName = chunkSZE3XVET_cjs.sanitizeInput(formData.get("first_name")?.toString());
9706
+ const lastName = chunkSZE3XVET_cjs.sanitizeInput(formData.get("last_name")?.toString());
9707
+ const username = chunkSZE3XVET_cjs.sanitizeInput(formData.get("username")?.toString());
9659
9708
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9660
- const phone = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9661
- const bio = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9709
+ const phone = chunkSZE3XVET_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9710
+ const bio = chunkSZE3XVET_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9662
9711
  const role = formData.get("role")?.toString() || "viewer";
9663
9712
  const isActive = formData.get("is_active") === "1";
9664
9713
  const emailVerified = formData.get("email_verified") === "1";
@@ -9709,7 +9758,7 @@ userRoutes.put("/users/:id", async (c) => {
9709
9758
  Date.now(),
9710
9759
  userId
9711
9760
  ).run();
9712
- await chunkOJ5WUCSH_cjs.logActivity(
9761
+ await chunkXWPGIFS7_cjs.logActivity(
9713
9762
  db,
9714
9763
  user.userId,
9715
9764
  "user!.update",
@@ -9754,7 +9803,7 @@ userRoutes.post("/users/:id/toggle", async (c) => {
9754
9803
  UPDATE users SET is_active = ?, updated_at = ? WHERE id = ?
9755
9804
  `);
9756
9805
  await toggleStmt.bind(active ? 1 : 0, Date.now(), userId).run();
9757
- await chunkOJ5WUCSH_cjs.logActivity(
9806
+ await chunkXWPGIFS7_cjs.logActivity(
9758
9807
  db,
9759
9808
  user.userId,
9760
9809
  active ? "user.activate" : "user.deactivate",
@@ -9795,7 +9844,7 @@ userRoutes.delete("/users/:id", async (c) => {
9795
9844
  DELETE FROM users WHERE id = ?
9796
9845
  `);
9797
9846
  await deleteStmt.bind(userId).run();
9798
- await chunkOJ5WUCSH_cjs.logActivity(
9847
+ await chunkXWPGIFS7_cjs.logActivity(
9799
9848
  db,
9800
9849
  user.userId,
9801
9850
  "user!.hard_delete",
@@ -9814,7 +9863,7 @@ userRoutes.delete("/users/:id", async (c) => {
9814
9863
  UPDATE users SET is_active = 0, updated_at = ? WHERE id = ?
9815
9864
  `);
9816
9865
  await deleteStmt.bind(Date.now(), userId).run();
9817
- await chunkOJ5WUCSH_cjs.logActivity(
9866
+ await chunkXWPGIFS7_cjs.logActivity(
9818
9867
  db,
9819
9868
  user.userId,
9820
9869
  "user!.soft_delete",
@@ -9841,8 +9890,8 @@ userRoutes.post("/invite-user", async (c) => {
9841
9890
  const formData = await c.req.formData();
9842
9891
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9843
9892
  const role = formData.get("role")?.toString()?.trim() || "viewer";
9844
- const firstName = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("first_name")?.toString());
9845
- const lastName = chunkIB6UBZVD_cjs.sanitizeInput(formData.get("last_name")?.toString());
9893
+ const firstName = chunkSZE3XVET_cjs.sanitizeInput(formData.get("first_name")?.toString());
9894
+ const lastName = chunkSZE3XVET_cjs.sanitizeInput(formData.get("last_name")?.toString());
9846
9895
  if (!email || !firstName || !lastName) {
9847
9896
  return c.json({ error: "Email, first name, and last name are required" }, 400);
9848
9897
  }
@@ -9880,7 +9929,7 @@ userRoutes.post("/invite-user", async (c) => {
9880
9929
  Date.now(),
9881
9930
  Date.now()
9882
9931
  ).run();
9883
- await chunkOJ5WUCSH_cjs.logActivity(
9932
+ await chunkXWPGIFS7_cjs.logActivity(
9884
9933
  db,
9885
9934
  user.userId,
9886
9935
  "user!.invite_sent",
@@ -9937,7 +9986,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
9937
9986
  Date.now(),
9938
9987
  userId
9939
9988
  ).run();
9940
- await chunkOJ5WUCSH_cjs.logActivity(
9989
+ await chunkXWPGIFS7_cjs.logActivity(
9941
9990
  db,
9942
9991
  user.userId,
9943
9992
  "user!.invitation_resent",
@@ -9973,7 +10022,7 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
9973
10022
  }
9974
10023
  const deleteStmt = db.prepare(`DELETE FROM users WHERE id = ?`);
9975
10024
  await deleteStmt.bind(userId).run();
9976
- await chunkOJ5WUCSH_cjs.logActivity(
10025
+ await chunkXWPGIFS7_cjs.logActivity(
9977
10026
  db,
9978
10027
  user.userId,
9979
10028
  "user!.invitation_cancelled",
@@ -10056,7 +10105,7 @@ userRoutes.get("/activity-logs", async (c) => {
10056
10105
  ...log,
10057
10106
  details: log.details ? JSON.parse(log.details) : null
10058
10107
  }));
10059
- await chunkOJ5WUCSH_cjs.logActivity(
10108
+ await chunkXWPGIFS7_cjs.logActivity(
10060
10109
  db,
10061
10110
  user.userId,
10062
10111
  "activity.logs_viewed",
@@ -10163,7 +10212,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
10163
10212
  csvRows.push(row.join(","));
10164
10213
  }
10165
10214
  const csvContent = csvRows.join("\n");
10166
- await chunkOJ5WUCSH_cjs.logActivity(
10215
+ await chunkXWPGIFS7_cjs.logActivity(
10167
10216
  db,
10168
10217
  user.userId,
10169
10218
  "activity.logs_exported",
@@ -10381,7 +10430,7 @@ function getFileIcon(mimeType) {
10381
10430
  }
10382
10431
 
10383
10432
  // src/templates/pages/admin-media-library.template.ts
10384
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
10433
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
10385
10434
  function renderMediaLibraryPage(data) {
10386
10435
  const pageContent = `
10387
10436
  <div>
@@ -11316,7 +11365,7 @@ function renderMediaLibraryPage(data) {
11316
11365
  version: data.version,
11317
11366
  content: pageContent
11318
11367
  };
11319
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
11368
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
11320
11369
  }
11321
11370
 
11322
11371
  // src/templates/components/media-file-details.template.ts
@@ -11502,7 +11551,7 @@ var fileValidationSchema2 = zod.z.object({
11502
11551
  // 50MB max
11503
11552
  });
11504
11553
  var adminMediaRoutes = new hono.Hono();
11505
- adminMediaRoutes.use("*", chunkOJ5WUCSH_cjs.requireAuth());
11554
+ adminMediaRoutes.use("*", chunkXWPGIFS7_cjs.requireAuth());
11506
11555
  adminMediaRoutes.get("/", async (c) => {
11507
11556
  try {
11508
11557
  const user = c.get("user");
@@ -11511,7 +11560,7 @@ adminMediaRoutes.get("/", async (c) => {
11511
11560
  const type = searchParams.get("type") || "all";
11512
11561
  const view = searchParams.get("view") || "grid";
11513
11562
  const page = parseInt(searchParams.get("page") || "1");
11514
- const ____cacheBust = searchParams.get("t");
11563
+ const _cacheBust = searchParams.get("t");
11515
11564
  const limit = 24;
11516
11565
  const offset = (page - 1) * limit;
11517
11566
  const db = c.env.DB;
@@ -12088,7 +12137,7 @@ adminMediaRoutes.put("/:id", async (c) => {
12088
12137
  `);
12089
12138
  }
12090
12139
  });
12091
- adminMediaRoutes.delete("/cleanup", chunkOJ5WUCSH_cjs.requireRole("admin"), async (c) => {
12140
+ adminMediaRoutes.delete("/cleanup", chunkXWPGIFS7_cjs.requireRole("admin"), async (c) => {
12092
12141
  try {
12093
12142
  const db = c.env.DB;
12094
12143
  const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
@@ -12338,12 +12387,39 @@ function formatFileSize(bytes) {
12338
12387
  }
12339
12388
 
12340
12389
  // src/templates/pages/admin-plugins-list.template.ts
12341
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
12390
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
12342
12391
  function renderPluginsListPage(data) {
12392
+ const categories = [
12393
+ { value: "content", label: "Content Management" },
12394
+ { value: "media", label: "Media" },
12395
+ { value: "editor", label: "Editors" },
12396
+ { value: "seo", label: "SEO & Analytics" },
12397
+ { value: "security", label: "Security" },
12398
+ { value: "utilities", label: "Utilities" },
12399
+ { value: "system", label: "System" },
12400
+ { value: "development", label: "Development" },
12401
+ { value: "demo", label: "Demo" }
12402
+ ];
12403
+ const statuses = [
12404
+ { value: "active", label: "Active" },
12405
+ { value: "inactive", label: "Inactive" },
12406
+ { value: "uninstalled", label: "Available to Install" },
12407
+ { value: "error", label: "Error" }
12408
+ ];
12409
+ const categoryCounts = {};
12410
+ categories.forEach((cat) => {
12411
+ categoryCounts[cat.value] = data.plugins.filter((p) => p.category === cat.value).length;
12412
+ });
12413
+ categories.sort((a, b) => (categoryCounts[b.value] || 0) - (categoryCounts[a.value] || 0));
12414
+ const statusCounts = {};
12415
+ statuses.forEach((status) => {
12416
+ statusCounts[status.value] = data.plugins.filter((p) => p.status === status.value).length;
12417
+ });
12418
+ statuses.sort((a, b) => (statusCounts[b.value] || 0) - (statusCounts[a.value] || 0));
12343
12419
  const pageContent = `
12344
12420
  <div>
12345
12421
  <!-- Header -->
12346
- <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
12422
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-8">
12347
12423
  <div>
12348
12424
  <h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">Plugins</h1>
12349
12425
  <p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">Manage and extend functionality with plugins</p>
@@ -12351,7 +12427,7 @@ function renderPluginsListPage(data) {
12351
12427
  </div>
12352
12428
 
12353
12429
  <!-- Experimental Notice -->
12354
- <div class="mb-6 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800/50 p-4">
12430
+ <div class="mb-8 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800/50 p-4">
12355
12431
  <div class="flex items-start">
12356
12432
  <div class="flex-shrink-0">
12357
12433
  <svg class="h-5 w-5 text-amber-600 dark:text-amber-400" viewBox="0 0 20 20" fill="currentColor">
@@ -12372,176 +12448,174 @@ function renderPluginsListPage(data) {
12372
12448
  </div>
12373
12449
  </div>
12374
12450
 
12375
- <!-- Stats -->
12376
- <div class="mb-6">
12377
- <h3 class="text-base font-semibold text-zinc-950 dark:text-white">Plugin Statistics</h3>
12378
- <dl class="mt-5 grid grid-cols-1 divide-zinc-950/5 dark:divide-white/10 overflow-hidden rounded-lg bg-zinc-800/75 dark:bg-zinc-800/75 ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 md:grid-cols-5 md:divide-x md:divide-y-0">
12379
- <div class="px-4 py-5 sm:p-6">
12380
- <dt class="text-base font-normal text-zinc-700 dark:text-zinc-100">Total Plugins</dt>
12381
- <dd class="mt-1 flex items-baseline justify-between md:block lg:flex">
12382
- <div class="flex items-baseline text-2xl font-semibold text-cyan-400">
12383
- ${data.stats?.total || 0}
12384
- </div>
12385
- <div class="inline-flex items-baseline rounded-full bg-lime-400/10 text-lime-600 dark:text-lime-400 px-2.5 py-0.5 text-sm font-medium md:mt-2 lg:mt-0">
12386
- <svg viewBox="0 0 20 20" fill="currentColor" class="-ml-1 mr-0.5 size-5 shrink-0 self-center">
12387
- <path d="M10 17a.75.75 0 0 1-.75-.75V5.612L5.29 9.77a.75.75 0 0 1-1.08-1.04l5.25-5.5a.75.75 0 0 1 1.08 0l5.25 5.5a.75.75 0 1 1-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0 1 10 17Z" clip-rule="evenodd" fill-rule="evenodd" />
12388
- </svg>
12389
- <span class="sr-only">Increased by</span>
12390
- 8.5%
12391
- </div>
12392
- </dd>
12393
- </div>
12394
- <div class="px-4 py-5 sm:p-6">
12395
- <dt class="text-base font-normal text-zinc-700 dark:text-zinc-100">Active Plugins</dt>
12396
- <dd class="mt-1 flex items-baseline justify-between md:block lg:flex">
12397
- <div class="flex items-baseline text-2xl font-semibold text-lime-400">
12398
- ${data.stats?.active || 0}
12399
- </div>
12400
- <div class="inline-flex items-baseline rounded-full bg-lime-400/10 text-lime-600 dark:text-lime-400 px-2.5 py-0.5 text-sm font-medium md:mt-2 lg:mt-0">
12401
- <svg viewBox="0 0 20 20" fill="currentColor" class="-ml-1 mr-0.5 size-5 shrink-0 self-center">
12402
- <path d="M10 17a.75.75 0 0 1-.75-.75V5.612L5.29 9.77a.75.75 0 0 1-1.08-1.04l5.25-5.5a.75.75 0 0 1 1.08 0l5.25 5.5a.75.75 0 1 1-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0 1 10 17Z" clip-rule="evenodd" fill-rule="evenodd" />
12403
- </svg>
12404
- <span class="sr-only">Increased by</span>
12405
- 12.3%
12406
- </div>
12407
- </dd>
12451
+ <div class="flex flex-col lg:flex-row gap-8">
12452
+ <!-- Sidebar Filters -->
12453
+ <aside class="w-full lg:w-48 flex-shrink-0 space-y-8 lg:sticky lg:top-6 lg:self-start">
12454
+ <!-- Categories Filter -->
12455
+ <div>
12456
+ <h3 class="text-sm font-semibold text-zinc-950 dark:text-white mb-4">Categories</h3>
12457
+ <div class="space-y-3">
12458
+ ${categories.map((cat) => {
12459
+ const count = categoryCounts[cat.value] || 0;
12460
+ const isDisabled = count === 0;
12461
+ return `
12462
+ <div class="flex items-center ${isDisabled ? "opacity-50" : ""}">
12463
+ <input
12464
+ id="category-${cat.value}"
12465
+ name="category"
12466
+ value="${cat.value}"
12467
+ type="checkbox"
12468
+ onchange="filterAndSortPlugins()"
12469
+ class="h-4 w-4 rounded border-zinc-300 dark:border-zinc-700 text-zinc-900 focus:ring-zinc-600 dark:bg-zinc-900 disabled:cursor-not-allowed"
12470
+ ${isDisabled ? "disabled" : ""}
12471
+ >
12472
+ <label for="category-${cat.value}" class="ml-3 text-sm text-zinc-600 dark:text-zinc-400 select-none ${isDisabled ? "cursor-not-allowed" : ""}">
12473
+ ${cat.label} <span class="text-zinc-400 dark:text-zinc-500">(${count})</span>
12474
+ </label>
12475
+ </div>
12476
+ `;
12477
+ }).join("")}
12478
+ </div>
12408
12479
  </div>
12409
- <div class="px-4 py-5 sm:p-6">
12410
- <dt class="text-base font-normal text-zinc-700 dark:text-zinc-100">Inactive Plugins</dt>
12411
- <dd class="mt-1 flex items-baseline justify-between md:block lg:flex">
12412
- <div class="flex items-baseline text-2xl font-semibold text-purple-400">
12413
- ${data.stats?.inactive || 0}
12414
- </div>
12415
- <div class="inline-flex items-baseline rounded-full bg-pink-400/10 text-pink-600 dark:text-pink-400 px-2.5 py-0.5 text-sm font-medium md:mt-2 lg:mt-0">
12416
- <svg viewBox="0 0 20 20" fill="currentColor" class="-ml-1 mr-0.5 size-5 shrink-0 self-center">
12417
- <path d="M10 3a.75.75 0 0 1 .75.75v10.638l3.96-4.158a.75.75 0 1 1 1.08 1.04l-5.25 5.5a.75.75 0 0 1-1.08 0l-5.25-5.5a.75.75 0 1 1 1.08-1.04l3.96 4.158V3.75A.75.75 0 0 1 10 3Z" clip-rule="evenodd" fill-rule="evenodd" />
12418
- </svg>
12419
- <span class="sr-only">Decreased by</span>
12420
- 3.2%
12421
- </div>
12422
- </dd>
12480
+
12481
+ <div class="h-px bg-zinc-200 dark:bg-zinc-800 lg:hidden"></div>
12482
+
12483
+ <!-- Status Filter -->
12484
+ <div>
12485
+ <h3 class="text-sm font-semibold text-zinc-950 dark:text-white mb-4">Status</h3>
12486
+ <div class="space-y-3">
12487
+ ${statuses.map((status) => {
12488
+ const count = statusCounts[status.value] || 0;
12489
+ const isDisabled = count === 0;
12490
+ let colorClass = "";
12491
+ let ringClass = "";
12492
+ let dotClass = "";
12493
+ switch (status.value) {
12494
+ case "active":
12495
+ colorClass = "text-emerald-700 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10";
12496
+ ringClass = "ring-emerald-600/20";
12497
+ dotClass = "bg-emerald-500 dark:bg-emerald-400";
12498
+ break;
12499
+ case "inactive":
12500
+ colorClass = "text-zinc-700 dark:text-zinc-400 bg-zinc-50 dark:bg-zinc-500/10";
12501
+ ringClass = "ring-zinc-600/20";
12502
+ dotClass = "bg-zinc-500 dark:bg-zinc-400";
12503
+ break;
12504
+ case "error":
12505
+ colorClass = "text-red-700 dark:text-red-400 bg-red-50 dark:bg-red-500/10";
12506
+ ringClass = "ring-red-600/20";
12507
+ dotClass = "bg-red-500 dark:bg-red-400";
12508
+ break;
12509
+ case "uninstalled":
12510
+ colorClass = "text-yellow-700 dark:text-yellow-400 bg-yellow-50 dark:bg-yellow-500/10";
12511
+ ringClass = "ring-yellow-600/20";
12512
+ dotClass = "bg-yellow-500 dark:bg-yellow-400";
12513
+ break;
12514
+ default:
12515
+ colorClass = "text-zinc-700 dark:text-zinc-400 bg-zinc-50 dark:bg-zinc-500/10";
12516
+ ringClass = "ring-zinc-600/20";
12517
+ dotClass = "bg-zinc-500 dark:bg-zinc-400";
12518
+ }
12519
+ return `
12520
+ <div class="flex items-center ${isDisabled ? "opacity-50" : ""}">
12521
+ <input
12522
+ id="status-${status.value}"
12523
+ name="status"
12524
+ value="${status.value}"
12525
+ type="checkbox"
12526
+ onchange="filterAndSortPlugins()"
12527
+ class="h-4 w-4 rounded border-zinc-300 dark:border-zinc-700 text-zinc-900 focus:ring-zinc-600 dark:bg-zinc-900 disabled:cursor-not-allowed"
12528
+ ${isDisabled ? "disabled" : ""}
12529
+ >
12530
+ <label for="status-${status.value}" class="ml-3 cursor-pointer select-none flex items-center ${isDisabled ? "cursor-not-allowed" : ""}">
12531
+ <span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset ${colorClass} ${ringClass}">
12532
+ <span class="mr-1.5 h-1.5 w-1.5 rounded-full ${dotClass}"></span>
12533
+ ${status.label}
12534
+ </span>
12535
+ <span class="ml-2 text-xs text-zinc-500 dark:text-zinc-400">(${count})</span>
12536
+ </label>
12537
+ </div>
12538
+ `;
12539
+ }).join("")}
12540
+ </div>
12423
12541
  </div>
12424
- <div class="px-4 py-5 sm:p-6">
12425
- <dt class="text-base font-normal text-zinc-700 dark:text-zinc-100">Plugin Errors</dt>
12426
- <dd class="mt-1 flex items-baseline justify-between md:block lg:flex">
12427
- <div class="flex items-baseline text-2xl font-semibold text-pink-400">
12428
- ${data.stats?.errors || 0}
12429
- </div>
12430
- <div class="inline-flex items-baseline rounded-full bg-pink-400/10 text-pink-600 dark:text-pink-400 px-2.5 py-0.5 text-sm font-medium md:mt-2 lg:mt-0">
12431
- <svg viewBox="0 0 20 20" fill="currentColor" class="-ml-1 mr-0.5 size-5 shrink-0 self-center">
12432
- <path d="M10 3a.75.75 0 0 1 .75.75v10.638l3.96-4.158a.75.75 0 1 1 1.08 1.04l-5.25 5.5a.75.75 0 0 1-1.08 0l-5.25-5.5a.75.75 0 1 1 1.08-1.04l3.96 4.158V3.75A.75.75 0 0 1 10 3Z" clip-rule="evenodd" fill-rule="evenodd" />
12433
- </svg>
12434
- <span class="sr-only">Decreased by</span>
12435
- 1.5%
12436
- </div>
12437
- </dd>
12542
+ </aside>
12543
+
12544
+ <!-- Main Content -->
12545
+ <div class="flex-1 min-w-0">
12546
+ <!-- Stats Row (Compact) -->
12547
+ <div class="flex flex-wrap gap-4 mb-6">
12548
+ <div class="min-w-[140px] rounded-lg bg-zinc-50 dark:bg-zinc-800/50 p-3 ring-1 ring-inset ring-zinc-950/5 dark:ring-white/5">
12549
+ <div class="text-xs font-medium text-zinc-500 dark:text-zinc-400">Total</div>
12550
+ <div class="mt-1 text-lg font-semibold text-zinc-900 dark:text-white">${data.stats?.total || 0}</div>
12551
+ </div>
12552
+ <div class="min-w-[140px] rounded-lg bg-zinc-50 dark:bg-zinc-800/50 p-3 ring-1 ring-inset ring-zinc-950/5 dark:ring-white/5">
12553
+ <div class="text-xs font-medium text-zinc-500 dark:text-zinc-400">Active</div>
12554
+ <div class="mt-1 text-lg font-semibold text-emerald-600 dark:text-emerald-400">${data.stats?.active || 0}</div>
12555
+ </div>
12556
+ <div class="min-w-[140px] rounded-lg bg-zinc-50 dark:bg-zinc-800/50 p-3 ring-1 ring-inset ring-zinc-950/5 dark:ring-white/5">
12557
+ <div class="text-xs font-medium text-zinc-500 dark:text-zinc-400">Available</div>
12558
+ <div class="mt-1 text-lg font-semibold text-zinc-600 dark:text-zinc-400">${data.stats?.uninstalled || 0}</div>
12559
+ </div>
12560
+ <div class="min-w-[140px] rounded-lg bg-zinc-50 dark:bg-zinc-800/50 p-3 ring-1 ring-inset ring-zinc-950/5 dark:ring-white/5">
12561
+ <div class="text-xs font-medium text-zinc-500 dark:text-zinc-400">Errors</div>
12562
+ <div class="mt-1 text-lg font-semibold text-red-600 dark:text-red-400">${data.stats?.errors || 0}</div>
12563
+ </div>
12438
12564
  </div>
12439
- <div class="px-4 py-5 sm:p-6">
12440
- <dt class="text-base font-normal text-zinc-700 dark:text-zinc-100">Available to Install</dt>
12441
- <dd class="mt-1 flex items-baseline justify-between md:block lg:flex">
12442
- <div class="flex items-baseline text-2xl font-semibold text-zinc-400">
12443
- ${data.stats?.uninstalled || 0}
12444
- </div>
12445
- <div class="inline-flex items-baseline rounded-full bg-zinc-400/10 text-zinc-600 dark:text-zinc-400 px-2.5 py-0.5 text-sm font-medium md:mt-2 lg:mt-0">
12446
- <svg viewBox="0 0 20 20" fill="currentColor" class="-ml-1 mr-0.5 size-5 shrink-0 self-center">
12447
- <path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
12565
+
12566
+ <!-- Toolbar -->
12567
+ <div class="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between mb-6">
12568
+ <div class="relative flex-1 w-full">
12569
+ <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
12570
+ <svg class="h-4 w-4 text-zinc-400" viewBox="0 0 20 20" fill="currentColor">
12571
+ <path fill-rule="evenodd" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clip-rule="evenodd" />
12448
12572
  </svg>
12449
- <span class="sr-only">Available</span>
12450
- Ready
12451
12573
  </div>
12452
- </dd>
12453
- </div>
12454
- </dl>
12455
- </div>
12574
+ <input
12575
+ id="search-input"
12576
+ type="text"
12577
+ placeholder="Search plugins..."
12578
+ oninput="filterAndSortPlugins()"
12579
+ class="block w-full h-9 rounded-md border-0 py-1.5 pl-10 text-zinc-900 ring-1 ring-inset ring-zinc-300 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-zinc-600 dark:bg-zinc-900 dark:text-white dark:ring-zinc-700 dark:focus:ring-zinc-500 sm:text-sm sm:leading-6"
12580
+ >
12581
+ </div>
12456
12582
 
12457
- <!-- Filters -->
12458
- <div class="relative rounded-xl overflow-hidden mb-6">
12459
- <!-- Gradient Background -->
12460
- <div class="absolute inset-0 bg-gradient-to-r from-cyan-500/10 via-blue-500/10 to-purple-500/10 dark:from-cyan-400/20 dark:via-blue-400/20 dark:to-purple-400/20"></div>
12583
+ <div class="flex items-center gap-3 w-full sm:w-auto">
12584
+ <select id="sort-filter" onchange="filterAndSortPlugins()" class="block w-full sm:w-auto h-9 rounded-md border-0 py-1.5 pl-3 pr-8 text-zinc-900 ring-1 ring-inset ring-zinc-300 focus:ring-2 focus:ring-inset focus:ring-zinc-600 dark:bg-zinc-900 dark:text-white dark:ring-zinc-700 dark:focus:ring-zinc-500 sm:text-sm sm:leading-6">
12585
+ <option value="name-asc">Name (A-Z)</option>
12586
+ <option value="name-desc">Name (Z-A)</option>
12587
+ <option value="newest">Newest Installed</option>
12588
+ <option value="updated">Recently Updated</option>
12589
+ <option value="popular">Popularity</option>
12590
+ <option value="rating">Highest Rated</option>
12591
+ </select>
12461
12592
 
12462
- <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">
12463
- <div class="px-6 py-5">
12464
- <div class="flex items-center justify-between">
12465
- <div class="flex items-center space-x-4 flex-1">
12466
- <div>
12467
- <label class="block text-sm/6 font-medium text-zinc-950 dark:text-white">Category</label>
12468
- <div class="mt-2 grid grid-cols-1">
12469
- <select id="category-filter" onchange="filterPlugins()" 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-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">
12470
- <option value="">All Categories</option>
12471
- <option value="content">Content Management</option>
12472
- <option value="media">Media</option>
12473
- <option value="seo">SEO & Analytics</option>
12474
- <option value="security">Security</option>
12475
- <option value="utilities">Utilities</option>
12476
- <option value="system">System</option>
12477
- <option value="development">Development</option>
12478
- <option value="demo">Demo</option>
12479
- </select>
12480
- <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">
12481
- <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" />
12482
- </svg>
12483
- </div>
12484
- </div>
12485
- <div>
12486
- <label class="block text-sm/6 font-medium text-zinc-950 dark:text-white">Status</label>
12487
- <div class="mt-2 grid grid-cols-1">
12488
- <select id="status-filter" onchange="filterPlugins()" 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-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">
12489
- <option value="">All Status</option>
12490
- <option value="active">Active</option>
12491
- <option value="inactive">Inactive</option>
12492
- <option value="uninstalled">Available to Install</option>
12493
- <option value="error">Error</option>
12494
- </select>
12495
- <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">
12496
- <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" />
12497
- </svg>
12498
- </div>
12499
- </div>
12500
- <div class="flex-1 max-w-md">
12501
- <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Search</label>
12502
- <div class="relative group">
12503
- <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">
12504
- <svg class="h-3 w-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
12505
- <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
12506
- </svg>
12507
- </div>
12508
- <input
12509
- id="search-input"
12510
- type="text"
12511
- placeholder="Search plugins..."
12512
- oninput="filterPlugins()"
12513
- class="w-full rounded-full bg-transparent px-11 py-2 text-sm text-zinc-950 dark:text-white placeholder-zinc-500 dark:placeholder-zinc-400 border-2 border-cyan-200/50 dark:border-cyan-700/50 focus:outline-none focus:border-cyan-500 dark:focus:border-cyan-400 focus:shadow-lg focus:shadow-cyan-500/20 dark:focus:shadow-cyan-400/20 transition-all duration-300"
12514
- />
12515
- </div>
12516
- </div>
12517
- </div>
12518
- <div class="flex items-center gap-x-3 ml-4">
12519
- <button
12520
- onclick="location.reload()"
12521
- class="inline-flex items-center gap-x-1.5 px-3 py-1.5 bg-white/90 dark:bg-zinc-800/90 backdrop-blur-sm text-zinc-950 dark:text-white text-sm font-medium rounded-full ring-1 ring-inset ring-cyan-200/50 dark:ring-cyan-700/50 hover:bg-gradient-to-r hover:from-cyan-50 hover:to-blue-50 dark:hover:from-cyan-900/30 dark:hover:to-blue-900/30 hover:ring-cyan-300 dark:hover:ring-cyan-600 transition-all duration-200"
12522
- >
12523
- <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12524
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
12525
- </svg>
12526
- Refresh
12527
- </button>
12528
- </div>
12593
+ <button
12594
+ onclick="location.reload()"
12595
+ class="inline-flex items-center gap-x-1.5 rounded-md bg-white dark:bg-zinc-900 px-3 py-1.5 h-9 text-sm font-semibold text-zinc-900 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-800"
12596
+ >
12597
+ <svg class="h-4 w-4 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12598
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
12599
+ </svg>
12600
+ </button>
12529
12601
  </div>
12530
12602
  </div>
12603
+
12604
+ <!-- Plugins Grid -->
12605
+ <div id="plugins-grid" class="grid gap-6" style="grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));">
12606
+ ${data.plugins.map((plugin) => renderPluginCard(plugin)).join("")}
12607
+ </div>
12531
12608
  </div>
12532
12609
  </div>
12533
-
12534
- <!-- Plugins Grid -->
12535
- <div id="plugins-grid" class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
12536
- ${data.plugins.map((plugin) => renderPluginCard(plugin)).join("")}
12537
12610
  </div>
12538
12611
 
12539
12612
  <script>
12540
- async function togglePlugin(pluginId, action) {
12541
- const button = event.target;
12542
- const originalText = button.textContent;
12613
+ async function togglePlugin(pluginId, action, event) {
12614
+ const button = event.target.closest('button');
12615
+ if (!button) return;
12616
+
12543
12617
  button.disabled = true;
12544
- button.textContent = action === 'activate' ? 'Activating...' : 'Deactivating...';
12618
+ button.classList.add('opacity-50', 'cursor-wait');
12545
12619
 
12546
12620
  try {
12547
12621
  const response = await fetch(\`/admin/plugins/\${pluginId}/\${action}\`, {
@@ -12557,27 +12631,36 @@ function renderPluginsListPage(data) {
12557
12631
  // Update UI
12558
12632
  const card = button.closest('.plugin-card');
12559
12633
  const statusBadge = card.querySelector('.status-badge');
12634
+ const knob = button.querySelector('.toggle-knob');
12560
12635
 
12561
12636
  if (action === 'activate') {
12562
12637
  // Update status badge
12563
- statusBadge.className = 'status-badge inline-flex items-center rounded-md px-2.5 py-1 text-sm font-medium ring-1 ring-inset bg-lime-50 dark:bg-lime-500/10 text-lime-700 dark:text-lime-300 ring-lime-700/10 dark:ring-lime-400/20';
12564
- statusBadge.innerHTML = '<div class="w-2 h-2 bg-lime-500 dark:bg-lime-400 rounded-full mr-2"></div>Active';
12565
- // Update card border to green
12566
- card.className = 'plugin-card rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-[3px] ring-lime-500 dark:ring-lime-400 p-6 hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-all';
12567
- // Update button
12568
- button.textContent = 'Deactivate';
12569
- button.onclick = () => togglePlugin(pluginId, 'deactivate');
12570
- button.className = 'bg-red-600 dark:bg-red-700 hover:bg-red-700 dark:hover:bg-red-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-colors';
12638
+ statusBadge.className = 'status-badge inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium ring-1 ring-inset bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 ring-emerald-600/20';
12639
+ statusBadge.innerHTML = '<div class="w-1.5 h-1.5 bg-emerald-500 dark:bg-emerald-400 rounded-full mr-1.5"></div>Active';
12640
+
12641
+ // Update button state to Active
12642
+ button.className = 'bg-emerald-600 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-emerald-600 focus:ring-offset-2 toggle-button';
12643
+ button.setAttribute('aria-checked', 'true');
12644
+ button.onclick = (event) => togglePlugin(pluginId, 'deactivate', event);
12645
+
12646
+ // Update knob position
12647
+ if (knob) {
12648
+ knob.className = 'translate-x-5 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out toggle-knob';
12649
+ }
12571
12650
  } else {
12572
12651
  // Update status badge
12573
- statusBadge.className = 'status-badge inline-flex items-center rounded-md px-2.5 py-1 text-sm font-medium ring-1 ring-inset bg-zinc-50 dark:bg-zinc-500/10 text-zinc-700 dark:text-zinc-400 ring-zinc-700/10 dark:ring-zinc-400/20';
12574
- statusBadge.innerHTML = '<div class="w-2 h-2 bg-zinc-500 dark:bg-zinc-400 rounded-full mr-2"></div>Inactive';
12575
- // Update card border to pink
12576
- card.className = 'plugin-card rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-[3px] ring-pink-500 dark:ring-pink-400 p-6 hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-all';
12577
- // Update button
12578
- button.textContent = 'Activate';
12579
- button.onclick = () => togglePlugin(pluginId, 'activate');
12580
- button.className = 'bg-lime-600 dark:bg-lime-700 hover:bg-lime-700 dark:hover:bg-lime-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-colors';
12652
+ statusBadge.className = 'status-badge inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium ring-1 ring-inset bg-zinc-50 dark:bg-zinc-500/10 text-zinc-700 dark:text-zinc-400 ring-zinc-600/20';
12653
+ statusBadge.innerHTML = '<div class="w-1.5 h-1.5 bg-zinc-500 dark:bg-zinc-400 rounded-full mr-1.5"></div>Inactive';
12654
+
12655
+ // Update button state to Inactive
12656
+ button.className = 'bg-zinc-200 dark:bg-zinc-700 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-emerald-600 focus:ring-offset-2 toggle-button';
12657
+ button.setAttribute('aria-checked', 'false');
12658
+ button.onclick = (event) => togglePlugin(pluginId, 'activate', event);
12659
+
12660
+ // Update knob position
12661
+ if (knob) {
12662
+ knob.className = 'translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out toggle-knob';
12663
+ }
12581
12664
  }
12582
12665
 
12583
12666
  showNotification(\`Plugin \${action}d successfully\`, 'success');
@@ -12586,9 +12669,9 @@ function renderPluginsListPage(data) {
12586
12669
  }
12587
12670
  } catch (error) {
12588
12671
  showNotification(error.message, 'error');
12589
- button.textContent = originalText;
12590
12672
  } finally {
12591
12673
  button.disabled = false;
12674
+ button.classList.remove('opacity-50', 'cursor-wait');
12592
12675
  }
12593
12676
  }
12594
12677
 
@@ -12672,81 +12755,92 @@ function renderPluginsListPage(data) {
12672
12755
  showNotification('Plugin details coming soon!', 'info');
12673
12756
  }
12674
12757
 
12675
- function showNotification(message, type) {
12676
- const notification = document.createElement('div');
12677
- const bgColor = type === 'success' ? 'bg-green-600' : type === 'error' ? 'bg-red-600' : 'bg-blue-600';
12678
- notification.className = \`fixed top-4 right-4 px-4 py-2 rounded-lg text-white z-50 \${bgColor}\`;
12679
- notification.textContent = message;
12680
- document.body.appendChild(notification);
12681
-
12682
- setTimeout(() => {
12683
- notification.remove();
12684
- }, 3000);
12685
- }
12686
-
12687
- function filterPlugins() {
12688
- const categoryFilter = document.getElementById('category-filter').value.toLowerCase();
12689
- const statusFilter = document.getElementById('status-filter').value.toLowerCase();
12758
+ function filterAndSortPlugins() {
12759
+ // Get checked categories
12760
+ const checkedCategories = Array.from(document.querySelectorAll('input[name="category"]:checked'))
12761
+ .map(cb => cb.value.toLowerCase());
12762
+
12763
+ // Get checked statuses
12764
+ const checkedStatuses = Array.from(document.querySelectorAll('input[name="status"]:checked'))
12765
+ .map(cb => cb.value.toLowerCase());
12766
+
12690
12767
  const searchInput = document.getElementById('search-input').value.toLowerCase();
12768
+ const sortValue = document.getElementById('sort-filter').value;
12691
12769
 
12692
- const pluginCards = document.querySelectorAll('.plugin-card');
12693
- let visibleCount = 0;
12694
-
12695
- pluginCards.forEach(card => {
12696
- // Get plugin data from card attributes
12770
+ const pluginsGrid = document.getElementById('plugins-grid');
12771
+ const pluginCards = Array.from(pluginsGrid.querySelectorAll('.plugin-card'));
12772
+
12773
+ // Filter
12774
+ const visibleCards = pluginCards.filter(card => {
12697
12775
  const category = card.getAttribute('data-category')?.toLowerCase() || '';
12698
12776
  const status = card.getAttribute('data-status')?.toLowerCase() || '';
12699
12777
  const name = card.getAttribute('data-name')?.toLowerCase() || '';
12700
12778
  const description = card.getAttribute('data-description')?.toLowerCase() || '';
12701
12779
 
12702
- // Check if plugin matches all filters
12703
- let matches = true;
12704
-
12705
- // Category filter
12706
- if (categoryFilter && category !== categoryFilter) {
12707
- matches = false;
12708
- }
12709
-
12710
- // Status filter
12711
- if (statusFilter && status !== statusFilter) {
12712
- matches = false;
12713
- }
12714
-
12715
- // Search filter - check if search term is in name or description
12716
- if (searchInput && !name.includes(searchInput) && !description.includes(searchInput)) {
12717
- matches = false;
12718
- }
12780
+ // Category filter: if any selected, must match one of them
12781
+ if (checkedCategories.length > 0 && !checkedCategories.includes(category)) return false;
12782
+
12783
+ // Status filter: if any selected, must match one of them
12784
+ if (checkedStatuses.length > 0 && !checkedStatuses.includes(status)) return false;
12785
+
12786
+ // Search filter
12787
+ if (searchInput && !name.includes(searchInput) && !description.includes(searchInput)) return false;
12788
+
12789
+ return true;
12790
+ });
12719
12791
 
12720
- // Show/hide card
12721
- if (matches) {
12722
- card.style.display = '';
12723
- visibleCount++;
12724
- } else {
12725
- card.style.display = 'none';
12792
+ // Sort
12793
+ visibleCards.sort((a, b) => {
12794
+ const aName = a.getAttribute('data-name') || '';
12795
+ const bName = b.getAttribute('data-name') || '';
12796
+ const aInstalled = parseInt(a.getAttribute('data-installed') || '0');
12797
+ const bInstalled = parseInt(b.getAttribute('data-installed') || '0');
12798
+ const aUpdated = parseInt(a.getAttribute('data-updated') || '0');
12799
+ const bUpdated = parseInt(b.getAttribute('data-updated') || '0');
12800
+ const aDownloads = parseInt(a.getAttribute('data-downloads') || '0');
12801
+ const bDownloads = parseInt(b.getAttribute('data-downloads') || '0');
12802
+ const aRating = parseFloat(a.getAttribute('data-rating') || '0');
12803
+ const bRating = parseFloat(b.getAttribute('data-rating') || '0');
12804
+
12805
+ switch (sortValue) {
12806
+ case 'name-desc': return bName.localeCompare(aName);
12807
+ case 'newest': return bInstalled - aInstalled;
12808
+ case 'updated': return bUpdated - aUpdated;
12809
+ case 'popular': return bDownloads - aDownloads;
12810
+ case 'rating': return bRating - aRating;
12811
+ case 'name-asc':
12812
+ default: return aName.localeCompare(bName);
12726
12813
  }
12727
12814
  });
12728
12815
 
12729
- // Show/hide "no results" message
12816
+ // Re-append
12817
+ pluginCards.forEach(card => card.style.display = 'none'); // Hide all first
12818
+
12819
+ // If no results
12730
12820
  let noResultsMsg = document.getElementById('no-results-message');
12731
- if (visibleCount === 0) {
12821
+ if (visibleCards.length === 0) {
12732
12822
  if (!noResultsMsg) {
12733
12823
  noResultsMsg = document.createElement('div');
12734
12824
  noResultsMsg.id = 'no-results-message';
12735
- noResultsMsg.className = 'col-span-full text-center py-12';
12825
+ noResultsMsg.className = 'col-span-full text-center py-12 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg border border-dashed border-zinc-300 dark:border-zinc-700';
12736
12826
  noResultsMsg.innerHTML = \`
12737
12827
  <div class="flex flex-col items-center">
12738
- <svg class="w-16 h-16 text-zinc-400 dark:text-zinc-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12828
+ <svg class="w-12 h-12 text-zinc-400 dark:text-zinc-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12739
12829
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
12740
12830
  </svg>
12741
- <h3 class="text-lg font-semibold text-zinc-950 dark:text-white mb-2">No plugins found</h3>
12831
+ <h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-1">No plugins found</h3>
12742
12832
  <p class="text-sm text-zinc-500 dark:text-zinc-400">Try adjusting your filters or search terms</p>
12743
12833
  </div>
12744
12834
  \`;
12745
- document.getElementById('plugins-grid').appendChild(noResultsMsg);
12835
+ pluginsGrid.appendChild(noResultsMsg);
12746
12836
  }
12747
12837
  noResultsMsg.style.display = '';
12748
- } else if (noResultsMsg) {
12749
- noResultsMsg.style.display = 'none';
12838
+ } else {
12839
+ if (noResultsMsg) noResultsMsg.style.display = 'none';
12840
+ visibleCards.forEach(card => {
12841
+ card.style.display = '';
12842
+ pluginsGrid.appendChild(card); // Re-appending moves it to the end, effectively sorting
12843
+ });
12750
12844
  }
12751
12845
  }
12752
12846
  </script>
@@ -12773,120 +12867,115 @@ function renderPluginsListPage(data) {
12773
12867
  version: data.version,
12774
12868
  content: pageContent
12775
12869
  };
12776
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
12870
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
12777
12871
  }
12778
12872
  function renderPluginCard(plugin) {
12779
12873
  const statusColors = {
12780
- active: "bg-lime-50 dark:bg-lime-500/10 text-lime-700 dark:text-lime-300 ring-lime-700/10 dark:ring-lime-400/20",
12781
- inactive: "bg-zinc-50 dark:bg-zinc-500/10 text-zinc-700 dark:text-zinc-400 ring-zinc-700/10 dark:ring-zinc-400/20",
12782
- error: "bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-400 ring-red-700/10 dark:ring-red-400/20",
12783
- uninstalled: "bg-zinc-100 dark:bg-zinc-600/10 text-zinc-600 dark:text-zinc-500 ring-zinc-600/10 dark:ring-zinc-500/20"
12874
+ active: "bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 ring-emerald-600/20",
12875
+ inactive: "bg-zinc-50 dark:bg-zinc-500/10 text-zinc-700 dark:text-zinc-400 ring-zinc-600/20",
12876
+ error: "bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-400 ring-red-600/20",
12877
+ uninstalled: "bg-zinc-50 dark:bg-zinc-500/10 text-zinc-600 dark:text-zinc-500 ring-zinc-600/20"
12784
12878
  };
12785
12879
  const statusIcons = {
12786
- active: '<div class="w-2 h-2 bg-lime-500 dark:bg-lime-400 rounded-full mr-2"></div>',
12787
- inactive: '<div class="w-2 h-2 bg-zinc-500 dark:bg-zinc-400 rounded-full mr-2"></div>',
12788
- error: '<div class="w-2 h-2 bg-red-500 dark:bg-red-400 rounded-full mr-2"></div>',
12789
- uninstalled: '<div class="w-2 h-2 bg-zinc-400 dark:bg-zinc-600 rounded-full mr-2"></div>'
12790
- };
12791
- const borderColors = {
12792
- active: "ring-[3px] ring-lime-500 dark:ring-lime-400",
12793
- inactive: "ring-[3px] ring-pink-500 dark:ring-pink-400",
12794
- error: "ring-[3px] ring-red-500 dark:ring-red-400",
12795
- uninstalled: "ring-[3px] ring-zinc-400 dark:ring-zinc-600"
12880
+ active: '<div class="w-1.5 h-1.5 bg-emerald-500 dark:bg-emerald-400 rounded-full mr-1.5"></div>',
12881
+ inactive: '<div class="w-1.5 h-1.5 bg-zinc-500 dark:bg-zinc-400 rounded-full mr-1.5"></div>',
12882
+ error: '<div class="w-1.5 h-1.5 bg-red-500 dark:bg-red-400 rounded-full mr-1.5"></div>',
12883
+ uninstalled: '<div class="w-1.5 h-1.5 bg-zinc-400 dark:bg-zinc-600 rounded-full mr-1.5"></div>'
12796
12884
  };
12797
12885
  const criticalCorePlugins = ["core-auth", "core-media"];
12798
12886
  const canToggle = !criticalCorePlugins.includes(plugin.id);
12799
12887
  let actionButton = "";
12800
12888
  if (plugin.status === "uninstalled") {
12801
- actionButton = `<button onclick="installPlugin('${plugin.name}')" class="bg-cyan-600 dark:bg-cyan-700 hover:bg-cyan-700 dark:hover:bg-cyan-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-colors">Install</button>`;
12802
- } else if (plugin.status === "active") {
12803
- actionButton = `<button onclick="togglePlugin('${plugin.id}', 'deactivate')" class="bg-red-600 dark:bg-red-700 hover:bg-red-700 dark:hover:bg-red-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-colors">Deactivate</button>`;
12889
+ actionButton = `<button onclick="installPlugin('${plugin.name}')" class="w-full sm:w-auto bg-zinc-900 dark:bg-white hover:bg-zinc-800 dark:hover:bg-zinc-100 text-white dark:text-zinc-900 px-3 py-1.5 rounded-md text-xs font-medium transition-colors shadow-sm">Install</button>`;
12804
12890
  } else {
12805
- actionButton = `<button onclick="togglePlugin('${plugin.id}', 'activate')" class="bg-lime-600 dark:bg-lime-700 hover:bg-lime-700 dark:hover:bg-lime-600 text-white px-3 py-1.5 rounded-lg text-sm font-medium transition-colors">Activate</button>`;
12891
+ const isActive = plugin.status === "active";
12892
+ const action = isActive ? "deactivate" : "activate";
12893
+ const bgClass = isActive ? "bg-emerald-600" : "bg-zinc-200 dark:bg-zinc-700";
12894
+ const translateClass = isActive ? "translate-x-5" : "translate-x-0";
12895
+ if (canToggle) {
12896
+ actionButton = `
12897
+ <button onclick="togglePlugin('${plugin.id}', '${action}', event)" type="button" class="${bgClass} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-emerald-600 focus:ring-offset-2 toggle-button" role="switch" aria-checked="${isActive}">
12898
+ <span class="sr-only">Toggle plugin</span>
12899
+ <span aria-hidden="true" class="${translateClass} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out toggle-knob"></span>
12900
+ </button>
12901
+ `;
12902
+ } else {
12903
+ actionButton = `
12904
+ <div class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-not-allowed rounded-full border-2 border-transparent bg-emerald-600/50 opacity-50" title="Core plugin cannot be disabled">
12905
+ <span class="translate-x-5 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0"></span>
12906
+ </div>
12907
+ `;
12908
+ }
12806
12909
  }
12807
12910
  return `
12808
- <div class="plugin-card rounded-xl bg-white dark:bg-zinc-900 shadow-sm ${borderColors[plugin.status]} p-6 hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-all" data-category="${plugin.category}" data-status="${plugin.status}" data-name="${plugin.displayName}" data-description="${plugin.description}">
12911
+ <div class="plugin-card flex flex-col h-full rounded-md bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/10 dark:ring-white/10 p-5 transition-all hover:shadow-md"
12912
+ data-category="${plugin.category}"
12913
+ data-status="${plugin.status}"
12914
+ data-name="${plugin.displayName}"
12915
+ data-description="${plugin.description}"
12916
+ data-downloads="${plugin.downloadCount || 0}"
12917
+ data-rating="${plugin.rating || 0}">
12809
12918
  <div class="flex items-start justify-between mb-4">
12810
12919
  <div class="flex items-center gap-3">
12811
- <div class="w-12 h-12 rounded-lg flex items-center justify-center ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 bg-zinc-50 dark:bg-zinc-800">
12920
+ <div class="w-10 h-10 rounded-md flex items-center justify-center bg-zinc-50 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 ring-1 ring-inset ring-zinc-200 dark:ring-zinc-700/50">
12812
12921
  ${plugin.icon || getDefaultPluginIcon(plugin.category)}
12813
12922
  </div>
12814
12923
  <div>
12815
- <h3 class="text-lg font-semibold text-zinc-950 dark:text-white">${plugin.displayName}</h3>
12816
- <p class="text-sm text-zinc-500 dark:text-zinc-400">v${plugin.version} by ${plugin.author}</p>
12924
+ <div class="flex items-center gap-2">
12925
+ <h3 class="text-sm font-semibold text-zinc-900 dark:text-white">${plugin.displayName}</h3>
12926
+ <span class="status-badge inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium ring-1 ring-inset ${statusColors[plugin.status]}">
12927
+ ${statusIcons[plugin.status]}${plugin.status.charAt(0).toUpperCase() + plugin.status.slice(1)}
12928
+ </span>
12929
+ </div>
12930
+ <p class="text-xs text-zinc-500 dark:text-zinc-400">v${plugin.version} \u2022 ${plugin.author}</p>
12817
12931
  </div>
12818
12932
  </div>
12819
- <div class="flex flex-col items-end gap-2">
12820
- <span class="status-badge inline-flex items-center rounded-md px-2.5 py-1 text-sm font-medium ring-1 ring-inset ${statusColors[plugin.status]}">
12821
- ${statusIcons[plugin.status]}${plugin.status.charAt(0).toUpperCase() + plugin.status.slice(1)}
12822
- </span>
12823
- ${plugin.isCore ? '<span class="inline-flex items-center rounded-md px-2.5 py-1 text-sm font-medium bg-cyan-50 dark:bg-cyan-500/10 text-cyan-700 dark:text-cyan-300 ring-1 ring-inset ring-cyan-700/10 dark:ring-cyan-400/20">Core</span>' : ""}
12933
+
12934
+ <div class="flex items-center gap-1">
12935
+ ${plugin.status !== "uninstalled" ? `
12936
+ <button onclick="showPluginDetails('${plugin.id}')" class="text-zinc-400 hover:text-zinc-600 dark:text-zinc-500 dark:hover:text-zinc-300 p-1.5 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" title="Plugin Details">
12937
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12938
+ <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"/>
12939
+ </svg>
12940
+ </button>
12941
+ ` : ""}
12942
+
12943
+ ${!plugin.isCore && plugin.status !== "uninstalled" ? `
12944
+ <button onclick="uninstallPlugin('${plugin.id}')" class="text-zinc-400 hover:text-red-600 dark:text-zinc-500 dark:hover:text-red-400 p-1.5 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" title="Uninstall Plugin">
12945
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12946
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
12947
+ </svg>
12948
+ </button>
12949
+ ` : ""}
12824
12950
  </div>
12825
12951
  </div>
12826
12952
 
12827
- <p class="text-zinc-600 dark:text-zinc-300 text-sm mb-4 line-clamp-3">${plugin.description}</p>
12953
+ <p class="text-zinc-600 dark:text-zinc-400 text-sm mb-4 line-clamp-2 flex-grow">${plugin.description}</p>
12828
12954
 
12829
- <div class="flex items-center gap-4 mb-4 text-xs text-zinc-500 dark:text-zinc-400">
12830
- <span class="flex items-center gap-1">
12831
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12832
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/>
12833
- </svg>
12955
+ <div class="flex flex-wrap items-center gap-2 mb-5">
12956
+ <span class="inline-flex items-center rounded-md bg-zinc-100 dark:bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-400">
12834
12957
  ${plugin.category}
12835
12958
  </span>
12836
-
12837
- ${plugin.downloadCount ? `
12838
- <span class="flex items-center gap-1">
12839
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12840
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
12841
- </svg>
12842
- ${plugin.downloadCount.toLocaleString()}
12843
- </span>
12844
- ` : ""}
12845
-
12846
- ${plugin.rating ? `
12847
- <span class="flex items-center gap-1">
12848
- <svg class="w-4 h-4 text-yellow-500 dark:text-yellow-400 fill-current" viewBox="0 0 24 24">
12849
- <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
12850
- </svg>
12851
- ${plugin.rating}
12852
- </span>
12853
- ` : ""}
12854
-
12855
- <span>${plugin.lastUpdated}</span>
12856
- </div>
12857
-
12858
- ${plugin.dependencies && plugin.dependencies.length > 0 ? `
12859
- <div class="mb-4">
12860
- <p class="text-xs text-zinc-500 dark:text-zinc-400 mb-2">Dependencies:</p>
12861
- <div class="flex flex-wrap gap-1">
12862
- ${plugin.dependencies.map((dep) => `<span class="inline-block bg-zinc-100 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 text-xs px-2 py-1 rounded">${dep}</span>`).join("")}
12863
- </div>
12959
+ ${plugin.isCore ? '<span class="inline-flex items-center rounded-md bg-zinc-100 dark:bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-400">Core</span>' : ""}
12960
+
12961
+ ${plugin.dependencies && plugin.dependencies.map((dep) => `
12962
+ <span class="inline-flex items-center rounded-md bg-zinc-100 dark:bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-400">
12963
+ ${dep}
12964
+ </span>
12965
+ `).join("") || ""}
12864
12966
  </div>
12865
- ` : ""}
12866
12967
 
12867
- <div class="flex items-center justify-between">
12968
+ <div class="flex items-center justify-between pt-4 border-t border-zinc-100 dark:border-zinc-800 mt-auto">
12868
12969
  <div class="flex gap-2">
12869
- ${plugin.status === "uninstalled" ? actionButton : canToggle ? actionButton : ""}
12870
- ${plugin.status !== "uninstalled" ? `
12871
- <button onclick="openPluginSettings('${plugin.id}')" class="bg-white dark:bg-zinc-800 hover:bg-zinc-50 dark:hover:bg-zinc-700 text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors">
12872
- Settings
12873
- </button>
12874
- ` : ""}
12970
+ ${actionButton}
12875
12971
  </div>
12876
12972
 
12877
12973
  <div class="flex items-center gap-2">
12878
12974
  ${plugin.status !== "uninstalled" ? `
12879
- <button onclick="showPluginDetails('${plugin.id}')" class="text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300 p-1.5 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" title="Plugin Details">
12880
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12881
- <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"/>
12882
- </svg>
12883
- </button>
12884
- ` : ""}
12885
-
12886
- ${!plugin.isCore && plugin.status !== "uninstalled" ? `
12887
- <button onclick="uninstallPlugin('${plugin.id}')" class="text-zinc-500 dark:text-zinc-400 hover:text-red-600 dark:hover:text-red-400 p-1.5 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" title="Uninstall Plugin">
12888
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12889
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
12975
+ <button onclick="openPluginSettings('${plugin.id}')" class="text-zinc-400 hover:text-zinc-600 dark:text-zinc-500 dark:hover:text-zinc-300 p-1.5 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors" title="Settings">
12976
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12977
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
12978
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
12890
12979
  </svg>
12891
12980
  </button>
12892
12981
  ` : ""}
@@ -13430,7 +13519,7 @@ function renderPluginSettingsPage(data) {
13430
13519
  user,
13431
13520
  content: pageContent
13432
13521
  };
13433
- return chunkRIOIKM3Y_cjs.renderAdminLayout(layoutData);
13522
+ return chunkAZLU3ROK_cjs.renderAdminLayout(layoutData);
13434
13523
  }
13435
13524
  function renderStatusBadge(status) {
13436
13525
  const statusColors = {
@@ -13693,7 +13782,7 @@ function formatTimestamp(timestamp) {
13693
13782
 
13694
13783
  // src/routes/admin-plugins.ts
13695
13784
  var adminPluginRoutes = new hono.Hono();
13696
- adminPluginRoutes.use("*", chunkOJ5WUCSH_cjs.requireAuth());
13785
+ adminPluginRoutes.use("*", chunkXWPGIFS7_cjs.requireAuth());
13697
13786
  var AVAILABLE_PLUGINS = [
13698
13787
  {
13699
13788
  id: "third-party-faq",
@@ -14209,7 +14298,7 @@ function formatLastUpdated(timestamp) {
14209
14298
  }
14210
14299
 
14211
14300
  // src/templates/pages/admin-logs-list.template.ts
14212
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
14301
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
14213
14302
  function renderLogsListPage(data) {
14214
14303
  const { logs, pagination, filters, user } = data;
14215
14304
  const content = `
@@ -14520,7 +14609,7 @@ function renderLogsListPage(data) {
14520
14609
  user,
14521
14610
  content
14522
14611
  };
14523
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
14612
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
14524
14613
  }
14525
14614
  function renderLogDetailsPage(data) {
14526
14615
  const { log, user } = data;
@@ -14732,7 +14821,7 @@ function renderLogDetailsPage(data) {
14732
14821
  </div>
14733
14822
  </div>
14734
14823
  `;
14735
- return chunkRIOIKM3Y_cjs.adminLayoutV2({
14824
+ return chunkAZLU3ROK_cjs.adminLayoutV2({
14736
14825
  title: `Log Details - ${log.id}`,
14737
14826
  user,
14738
14827
  content
@@ -14975,7 +15064,7 @@ function renderLogConfigPage(data) {
14975
15064
 
14976
15065
  <script src="https://unpkg.com/htmx.org@1.9.6"></script>
14977
15066
  `;
14978
- return chunkRIOIKM3Y_cjs.adminLayoutV2({
15067
+ return chunkAZLU3ROK_cjs.adminLayoutV2({
14979
15068
  title: "Log Configuration",
14980
15069
  user,
14981
15070
  content
@@ -14984,7 +15073,7 @@ function renderLogConfigPage(data) {
14984
15073
 
14985
15074
  // src/routes/admin-logs.ts
14986
15075
  var adminLogsRoutes = new hono.Hono();
14987
- adminLogsRoutes.use("*", chunkOJ5WUCSH_cjs.requireAuth());
15076
+ adminLogsRoutes.use("*", chunkXWPGIFS7_cjs.requireAuth());
14988
15077
  adminLogsRoutes.get("/", async (c) => {
14989
15078
  try {
14990
15079
  const user = c.get("user");
@@ -15356,7 +15445,7 @@ adminDesignRoutes.get("/", (c) => {
15356
15445
  role: user.role
15357
15446
  } : void 0
15358
15447
  };
15359
- return c.html(chunkRIOIKM3Y_cjs.renderDesignPage(pageData));
15448
+ return c.html(chunkAZLU3ROK_cjs.renderDesignPage(pageData));
15360
15449
  });
15361
15450
  var adminCheckboxRoutes = new hono.Hono();
15362
15451
  adminCheckboxRoutes.get("/", (c) => {
@@ -15368,7 +15457,7 @@ adminCheckboxRoutes.get("/", (c) => {
15368
15457
  role: user.role
15369
15458
  } : void 0
15370
15459
  };
15371
- return c.html(chunkRIOIKM3Y_cjs.renderCheckboxPage(pageData));
15460
+ return c.html(chunkAZLU3ROK_cjs.renderCheckboxPage(pageData));
15372
15461
  });
15373
15462
 
15374
15463
  // src/templates/pages/admin-testimonials-form.template.ts
@@ -15396,7 +15485,7 @@ function renderTestimonialsForm(data) {
15396
15485
  </div>
15397
15486
  </div>
15398
15487
 
15399
- ${message ? chunkRIOIKM3Y_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
15488
+ ${message ? chunkAZLU3ROK_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
15400
15489
 
15401
15490
  <!-- Form -->
15402
15491
  <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
@@ -15625,7 +15714,7 @@ function renderTestimonialsForm(data) {
15625
15714
  user: data.user,
15626
15715
  content: pageContent
15627
15716
  };
15628
- return chunkRIOIKM3Y_cjs.renderAdminLayout(layoutData);
15717
+ return chunkAZLU3ROK_cjs.renderAdminLayout(layoutData);
15629
15718
  }
15630
15719
  function escapeHtml4(unsafe) {
15631
15720
  return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
@@ -15651,7 +15740,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
15651
15740
  const offset = (currentPage - 1) * limit;
15652
15741
  const db = c.env?.DB;
15653
15742
  if (!db) {
15654
- return c.html(chunkRIOIKM3Y_cjs.renderTestimonialsList({
15743
+ return c.html(chunkAZLU3ROK_cjs.renderTestimonialsList({
15655
15744
  testimonials: [],
15656
15745
  totalCount: 0,
15657
15746
  currentPage: 1,
@@ -15691,7 +15780,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
15691
15780
  `;
15692
15781
  const { results: testimonials } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
15693
15782
  const totalPages = Math.ceil(totalCount / limit);
15694
- return c.html(chunkRIOIKM3Y_cjs.renderTestimonialsList({
15783
+ return c.html(chunkAZLU3ROK_cjs.renderTestimonialsList({
15695
15784
  testimonials: testimonials || [],
15696
15785
  totalCount,
15697
15786
  currentPage,
@@ -15705,7 +15794,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
15705
15794
  } catch (error) {
15706
15795
  console.error("Error fetching testimonials:", error);
15707
15796
  const user = c.get("user");
15708
- return c.html(chunkRIOIKM3Y_cjs.renderTestimonialsList({
15797
+ return c.html(chunkAZLU3ROK_cjs.renderTestimonialsList({
15709
15798
  testimonials: [],
15710
15799
  totalCount: 0,
15711
15800
  currentPage: 1,
@@ -16024,7 +16113,7 @@ function renderCodeExamplesForm(data) {
16024
16113
  </div>
16025
16114
  </div>
16026
16115
 
16027
- ${message ? chunkRIOIKM3Y_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
16116
+ ${message ? chunkAZLU3ROK_cjs.renderAlert({ type: messageType || "info", message, dismissible: true }) : ""}
16028
16117
 
16029
16118
  <!-- Form -->
16030
16119
  <div class="backdrop-blur-xl bg-white/10 rounded-xl border border-white/20 shadow-2xl">
@@ -16294,7 +16383,7 @@ function renderCodeExamplesForm(data) {
16294
16383
  user: data.user,
16295
16384
  content: pageContent
16296
16385
  };
16297
- return chunkRIOIKM3Y_cjs.renderAdminLayout(layoutData);
16386
+ return chunkAZLU3ROK_cjs.renderAdminLayout(layoutData);
16298
16387
  }
16299
16388
  function escapeHtml5(unsafe) {
16300
16389
  return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
@@ -16321,7 +16410,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
16321
16410
  const offset = (currentPage - 1) * limit;
16322
16411
  const db = c.env?.DB;
16323
16412
  if (!db) {
16324
- return c.html(chunkRIOIKM3Y_cjs.renderCodeExamplesList({
16413
+ return c.html(chunkAZLU3ROK_cjs.renderCodeExamplesList({
16325
16414
  codeExamples: [],
16326
16415
  totalCount: 0,
16327
16416
  currentPage: 1,
@@ -16361,7 +16450,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
16361
16450
  `;
16362
16451
  const { results: codeExamples } = await db.prepare(dataQuery).bind(...params, limit, offset).all();
16363
16452
  const totalPages = Math.ceil(totalCount / limit);
16364
- return c.html(chunkRIOIKM3Y_cjs.renderCodeExamplesList({
16453
+ return c.html(chunkAZLU3ROK_cjs.renderCodeExamplesList({
16365
16454
  codeExamples: codeExamples || [],
16366
16455
  totalCount,
16367
16456
  currentPage,
@@ -16375,7 +16464,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
16375
16464
  } catch (error) {
16376
16465
  console.error("Error fetching code examples:", error);
16377
16466
  const user = c.get("user");
16378
- return c.html(chunkRIOIKM3Y_cjs.renderCodeExamplesList({
16467
+ return c.html(chunkAZLU3ROK_cjs.renderCodeExamplesList({
16379
16468
  codeExamples: [],
16380
16469
  totalCount: 0,
16381
16470
  currentPage: 1,
@@ -16764,7 +16853,7 @@ function renderDashboardPage(data) {
16764
16853
  version: data.version,
16765
16854
  content: pageContent
16766
16855
  };
16767
- return chunkRIOIKM3Y_cjs.renderAdminLayout(layoutData);
16856
+ return chunkAZLU3ROK_cjs.renderAdminLayout(layoutData);
16768
16857
  }
16769
16858
  function renderStatsCards(stats) {
16770
16859
  const cards = [
@@ -17312,9 +17401,9 @@ function renderStorageUsage(databaseSizeBytes, mediaSizeBytes) {
17312
17401
  }
17313
17402
 
17314
17403
  // src/routes/admin-dashboard.ts
17315
- var VERSION = chunkIB6UBZVD_cjs.getCoreVersion();
17404
+ var VERSION = chunkSZE3XVET_cjs.getCoreVersion();
17316
17405
  var router = new hono.Hono();
17317
- router.use("*", chunkOJ5WUCSH_cjs.requireAuth());
17406
+ router.use("*", chunkXWPGIFS7_cjs.requireAuth());
17318
17407
  router.get("/", async (c) => {
17319
17408
  const user = c.get("user");
17320
17409
  try {
@@ -17539,7 +17628,7 @@ router.get("/system-status", async (c) => {
17539
17628
  });
17540
17629
 
17541
17630
  // src/templates/pages/admin-collections-list.template.ts
17542
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
17631
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
17543
17632
 
17544
17633
  // src/templates/components/table.template.ts
17545
17634
  function renderTable2(data) {
@@ -18013,11 +18102,11 @@ function renderCollectionsListPage(data) {
18013
18102
  version: data.version,
18014
18103
  content: pageContent
18015
18104
  };
18016
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
18105
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
18017
18106
  }
18018
18107
 
18019
18108
  // src/templates/pages/admin-collections-form.template.ts
18020
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
18109
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
18021
18110
  function getFieldTypeBadge(fieldType) {
18022
18111
  const typeLabels = {
18023
18112
  "text": "Text",
@@ -18278,7 +18367,7 @@ function renderCollectionFormPage(data) {
18278
18367
  }
18279
18368
  </style>
18280
18369
 
18281
- ${chunkRIOIKM3Y_cjs.renderForm(formData)}
18370
+ ${chunkAZLU3ROK_cjs.renderForm(formData)}
18282
18371
 
18283
18372
  ${isEdit && data.managed ? `
18284
18373
  <!-- Read-Only Fields Display for Managed Collections -->
@@ -18959,6 +19048,8 @@ function renderCollectionFormPage(data) {
18959
19048
  })
18960
19049
  .then(data => {
18961
19050
  if (data.success) {
19051
+ // Close modal before reloading
19052
+ closeFieldModal();
18962
19053
  location.reload();
18963
19054
  } else {
18964
19055
  alert('Error saving field: ' + (data.error || 'Unknown error'));
@@ -19065,12 +19156,12 @@ function renderCollectionFormPage(data) {
19065
19156
  version: data.version,
19066
19157
  content: pageContent
19067
19158
  };
19068
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
19159
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
19069
19160
  }
19070
19161
 
19071
19162
  // src/routes/admin-collections.ts
19072
19163
  var adminCollectionsRoutes = new hono.Hono();
19073
- adminCollectionsRoutes.use("*", chunkOJ5WUCSH_cjs.requireAuth());
19164
+ adminCollectionsRoutes.use("*", chunkXWPGIFS7_cjs.requireAuth());
19074
19165
  adminCollectionsRoutes.get("/", async (c) => {
19075
19166
  try {
19076
19167
  const user = c.get("user");
@@ -19509,11 +19600,65 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
19509
19600
  return c.json({ success: false, error: "Field name must contain only lowercase letters, numbers, and underscores." });
19510
19601
  }
19511
19602
  const db = c.env.DB;
19603
+ const getCollectionStmt = db.prepare("SELECT * FROM collections WHERE id = ?");
19604
+ const collection = await getCollectionStmt.bind(collectionId).first();
19605
+ if (!collection) {
19606
+ return c.json({ success: false, error: "Collection not found." });
19607
+ }
19608
+ let schema = collection.schema ? typeof collection.schema === "string" ? JSON.parse(collection.schema) : collection.schema : null;
19609
+ if (schema && schema.properties && schema.properties[fieldName]) {
19610
+ return c.json({ success: false, error: "A field with this name already exists." });
19611
+ }
19512
19612
  const existingStmt = db.prepare("SELECT id FROM content_fields WHERE collection_id = ? AND field_name = ?");
19513
19613
  const existing = await existingStmt.bind(collectionId, fieldName).first();
19514
19614
  if (existing) {
19515
19615
  return c.json({ success: false, error: "A field with this name already exists." });
19516
19616
  }
19617
+ let parsedOptions = {};
19618
+ try {
19619
+ parsedOptions = fieldOptions ? JSON.parse(fieldOptions) : {};
19620
+ } catch (e) {
19621
+ console.error("Error parsing field options:", e);
19622
+ }
19623
+ if (schema) {
19624
+ if (!schema.properties) {
19625
+ schema.properties = {};
19626
+ }
19627
+ if (!schema.required) {
19628
+ schema.required = [];
19629
+ }
19630
+ const fieldConfig = {
19631
+ type: fieldType === "number" ? "number" : fieldType === "boolean" ? "boolean" : "string",
19632
+ title: fieldLabel,
19633
+ searchable: isSearchable,
19634
+ ...parsedOptions
19635
+ };
19636
+ if (fieldType === "richtext") {
19637
+ fieldConfig.format = "richtext";
19638
+ } else if (fieldType === "date") {
19639
+ fieldConfig.format = "date-time";
19640
+ } else if (fieldType === "select") {
19641
+ fieldConfig.enum = parsedOptions.options || [];
19642
+ } else if (fieldType === "media") {
19643
+ fieldConfig.format = "media";
19644
+ } else if (fieldType === "quill") {
19645
+ fieldConfig.type = "quill";
19646
+ } else if (fieldType === "mdxeditor") {
19647
+ fieldConfig.type = "mdxeditor";
19648
+ }
19649
+ schema.properties[fieldName] = fieldConfig;
19650
+ if (isRequired && !schema.required.includes(fieldName)) {
19651
+ schema.required.push(fieldName);
19652
+ }
19653
+ const updateSchemaStmt = db.prepare(`
19654
+ UPDATE collections
19655
+ SET schema = ?, updated_at = ?
19656
+ WHERE id = ?
19657
+ `);
19658
+ await updateSchemaStmt.bind(JSON.stringify(schema), Date.now(), collectionId).run();
19659
+ console.log("[Add Field] Added field to schema:", fieldName, fieldConfig);
19660
+ return c.json({ success: true, fieldId: `schema-${fieldName}` });
19661
+ }
19517
19662
  const orderStmt = db.prepare("SELECT MAX(field_order) as max_order FROM content_fields WHERE collection_id = ?");
19518
19663
  const orderResult = await orderStmt.bind(collectionId).first();
19519
19664
  const nextOrder = (orderResult?.max_order || 0) + 1;
@@ -19654,7 +19799,39 @@ adminCollectionsRoutes.put("/:collectionId/fields/:fieldId", async (c) => {
19654
19799
  adminCollectionsRoutes.delete("/:collectionId/fields/:fieldId", async (c) => {
19655
19800
  try {
19656
19801
  const fieldId = c.req.param("fieldId");
19802
+ const collectionId = c.req.param("collectionId");
19657
19803
  const db = c.env.DB;
19804
+ if (fieldId.startsWith("schema-")) {
19805
+ const fieldName = fieldId.replace("schema-", "");
19806
+ const getCollectionStmt = db.prepare("SELECT * FROM collections WHERE id = ?");
19807
+ const collection = await getCollectionStmt.bind(collectionId).first();
19808
+ if (!collection) {
19809
+ return c.json({ success: false, error: "Collection not found." });
19810
+ }
19811
+ let schema = typeof collection.schema === "string" ? JSON.parse(collection.schema) : collection.schema;
19812
+ if (!schema || !schema.properties) {
19813
+ return c.json({ success: false, error: "Field not found in schema." });
19814
+ }
19815
+ if (schema.properties[fieldName]) {
19816
+ delete schema.properties[fieldName];
19817
+ if (schema.required && Array.isArray(schema.required)) {
19818
+ const requiredIndex = schema.required.indexOf(fieldName);
19819
+ if (requiredIndex !== -1) {
19820
+ schema.required.splice(requiredIndex, 1);
19821
+ }
19822
+ }
19823
+ const updateCollectionStmt = db.prepare(`
19824
+ UPDATE collections
19825
+ SET schema = ?, updated_at = ?
19826
+ WHERE id = ?
19827
+ `);
19828
+ await updateCollectionStmt.bind(JSON.stringify(schema), Date.now(), collectionId).run();
19829
+ console.log("[Delete Field] Removed field from schema:", fieldName);
19830
+ return c.json({ success: true });
19831
+ } else {
19832
+ return c.json({ success: false, error: "Field not found in schema." });
19833
+ }
19834
+ }
19658
19835
  const deleteStmt = db.prepare("DELETE FROM content_fields WHERE id = ?");
19659
19836
  await deleteStmt.bind(fieldId).run();
19660
19837
  return c.json({ success: true });
@@ -19683,7 +19860,7 @@ adminCollectionsRoutes.post("/:collectionId/fields/reorder", async (c) => {
19683
19860
  });
19684
19861
 
19685
19862
  // src/templates/pages/admin-settings.template.ts
19686
- chunkRIOIKM3Y_cjs.init_admin_layout_catalyst_template();
19863
+ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
19687
19864
  function renderSettingsPage(data) {
19688
19865
  const activeTab = data.activeTab || "general";
19689
19866
  const pageContent = `
@@ -20065,7 +20242,7 @@ function renderSettingsPage(data) {
20065
20242
  version: data.version,
20066
20243
  content: pageContent
20067
20244
  };
20068
- return chunkRIOIKM3Y_cjs.renderAdminLayoutCatalyst(layoutData);
20245
+ return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
20069
20246
  }
20070
20247
  function renderTabButton(tabId, label, iconPath, activeTab) {
20071
20248
  const isActive = activeTab === tabId;
@@ -21147,7 +21324,7 @@ function renderDatabaseToolsSettings(settings) {
21147
21324
 
21148
21325
  // src/routes/admin-settings.ts
21149
21326
  var adminSettingsRoutes = new hono.Hono();
21150
- adminSettingsRoutes.use("*", chunkOJ5WUCSH_cjs.requireAuth());
21327
+ adminSettingsRoutes.use("*", chunkXWPGIFS7_cjs.requireAuth());
21151
21328
  function getMockSettings(user) {
21152
21329
  return {
21153
21330
  general: {
@@ -21315,7 +21492,7 @@ adminSettingsRoutes.get("/database-tools", (c) => {
21315
21492
  adminSettingsRoutes.get("/api/migrations/status", async (c) => {
21316
21493
  try {
21317
21494
  const db = c.env.DB;
21318
- const migrationService = new chunkP6NMVNJJ_cjs.MigrationService(db);
21495
+ const migrationService = new chunkETS5XSAG_cjs.MigrationService(db);
21319
21496
  const status = await migrationService.getMigrationStatus();
21320
21497
  return c.json({
21321
21498
  success: true,
@@ -21339,7 +21516,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
21339
21516
  }, 403);
21340
21517
  }
21341
21518
  const db = c.env.DB;
21342
- const migrationService = new chunkP6NMVNJJ_cjs.MigrationService(db);
21519
+ const migrationService = new chunkETS5XSAG_cjs.MigrationService(db);
21343
21520
  const result = await migrationService.runPendingMigrations();
21344
21521
  return c.json({
21345
21522
  success: result.success,
@@ -21357,7 +21534,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
21357
21534
  adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
21358
21535
  try {
21359
21536
  const db = c.env.DB;
21360
- const migrationService = new chunkP6NMVNJJ_cjs.MigrationService(db);
21537
+ const migrationService = new chunkETS5XSAG_cjs.MigrationService(db);
21361
21538
  const validation = await migrationService.validateSchema();
21362
21539
  return c.json({
21363
21540
  success: true,
@@ -21602,5 +21779,5 @@ exports.auth_default = auth_default;
21602
21779
  exports.router = router;
21603
21780
  exports.test_cleanup_default = test_cleanup_default;
21604
21781
  exports.userRoutes = userRoutes;
21605
- //# sourceMappingURL=chunk-HTJLBF6F.cjs.map
21606
- //# sourceMappingURL=chunk-HTJLBF6F.cjs.map
21782
+ //# sourceMappingURL=chunk-D4PJFFOV.cjs.map
21783
+ //# sourceMappingURL=chunk-D4PJFFOV.cjs.map