@sonicjs-cms/core 3.0.0-beta.2 → 3.0.0-beta.21

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 (170) hide show
  1. package/README.md +48 -49
  2. package/dist/admin-documents-form.template-DDSH6ROU.js +6 -0
  3. package/dist/{admin-documents-form.template-NLSI6Z42.js.map → admin-documents-form.template-DDSH6ROU.js.map} +1 -1
  4. package/dist/admin-documents-form.template-LSZKGA5J.cjs +19 -0
  5. package/dist/{admin-layout-catalyst.template-WHJGSWWD.js.map → admin-documents-form.template-LSZKGA5J.cjs.map} +1 -1
  6. package/dist/{filter-bar.template-DlVYMk-T.d.ts → admin-layout-catalyst.template-DrwDUfsE.d.cts} +25 -1
  7. package/dist/{filter-bar.template-DlVYMk-T.d.cts → admin-layout-catalyst.template-DrwDUfsE.d.ts} +25 -1
  8. package/dist/admin-layout-catalyst.template-KDHKVLXR.cjs +21 -0
  9. package/dist/{admin-layout-catalyst.template-ZK5HD545.cjs.map → admin-layout-catalyst.template-KDHKVLXR.cjs.map} +1 -1
  10. package/dist/admin-layout-catalyst.template-YQ4EMF2J.js +7 -0
  11. package/dist/{admin-documents-form.template-KN7JF66Q.cjs.map → admin-layout-catalyst.template-YQ4EMF2J.js.map} +1 -1
  12. package/dist/{app-Bo0X1OWX.d.ts → app-01M0dIwt.d.ts} +1 -1
  13. package/dist/{app-Do66yCcV.d.cts → app-aj39TGHB.d.cts} +1 -1
  14. package/dist/{chunk-ZEZ245PW.js → chunk-2XAQTPAE.js} +4 -236
  15. package/dist/chunk-2XAQTPAE.js.map +1 -0
  16. package/dist/{chunk-BDDABDAB.cjs → chunk-36Z3AKX3.cjs} +3 -239
  17. package/dist/chunk-36Z3AKX3.cjs.map +1 -0
  18. package/dist/{chunk-BLMTL57B.js → chunk-3TMTYJAJ.js} +26 -4
  19. package/dist/chunk-3TMTYJAJ.js.map +1 -0
  20. package/dist/{chunk-ZGGXCFR6.js → chunk-4LVVMVQI.js} +282 -4
  21. package/dist/chunk-4LVVMVQI.js.map +1 -0
  22. package/dist/{chunk-RZ6H7OZK.js → chunk-4UYJPHSV.js} +87 -12
  23. package/dist/chunk-4UYJPHSV.js.map +1 -0
  24. package/dist/{chunk-NLJVSER2.js → chunk-5U75XP74.js} +3 -2
  25. package/dist/chunk-5U75XP74.js.map +1 -0
  26. package/dist/{chunk-H2AXVCLS.cjs → chunk-5UXU3LP4.cjs} +141 -74
  27. package/dist/chunk-5UXU3LP4.cjs.map +1 -0
  28. package/dist/{chunk-3TB6AT6X.js → chunk-5V62WT6M.js} +35 -4
  29. package/dist/chunk-5V62WT6M.js.map +1 -0
  30. package/dist/{chunk-6OUHGKFD.js → chunk-7OTOXWEB.js} +3 -3
  31. package/dist/{chunk-6OUHGKFD.js.map → chunk-7OTOXWEB.js.map} +1 -1
  32. package/dist/{chunk-VXE42MYF.cjs → chunk-7RUW244J.cjs} +710 -434
  33. package/dist/chunk-7RUW244J.cjs.map +1 -0
  34. package/dist/{chunk-XW56B23A.cjs → chunk-A4KUJ3LD.cjs} +4 -4
  35. package/dist/{chunk-XW56B23A.cjs.map → chunk-A4KUJ3LD.cjs.map} +1 -1
  36. package/dist/{chunk-LRZIAW7U.cjs → chunk-AOUVUF32.cjs} +3 -3
  37. package/dist/chunk-AOUVUF32.cjs.map +1 -0
  38. package/dist/{chunk-WULONYGB.cjs → chunk-CRGUD4KC.cjs} +9 -9
  39. package/dist/{chunk-WULONYGB.cjs.map → chunk-CRGUD4KC.cjs.map} +1 -1
  40. package/dist/{chunk-AAWNRBRB.cjs → chunk-FCLKX2OS.cjs} +95 -20
  41. package/dist/chunk-FCLKX2OS.cjs.map +1 -0
  42. package/dist/{chunk-YHSQVQXX.js → chunk-GICS5AIO.js} +135 -68
  43. package/dist/chunk-GICS5AIO.js.map +1 -0
  44. package/dist/{chunk-NUKJ54GA.cjs → chunk-GMMCCIQO.cjs} +6 -6
  45. package/dist/{chunk-NUKJ54GA.cjs.map → chunk-GMMCCIQO.cjs.map} +1 -1
  46. package/dist/{chunk-NMPEMSU4.js → chunk-JEX3UPZL.js} +3 -3
  47. package/dist/chunk-JEX3UPZL.js.map +1 -0
  48. package/dist/{chunk-6JQOUUOB.js → chunk-JQISFW6U.js} +3 -3
  49. package/dist/{chunk-6JQOUUOB.js.map → chunk-JQISFW6U.js.map} +1 -1
  50. package/dist/{chunk-NAVPFIG5.cjs → chunk-K623Q6WD.cjs} +35 -3
  51. package/dist/chunk-K623Q6WD.cjs.map +1 -0
  52. package/dist/{chunk-JEQ7FLOD.cjs → chunk-KIGVVO6N.cjs} +16 -10
  53. package/dist/chunk-KIGVVO6N.cjs.map +1 -0
  54. package/dist/{chunk-QAYFOER6.cjs → chunk-KVBHB7QP.cjs} +285 -3
  55. package/dist/chunk-KVBHB7QP.cjs.map +1 -0
  56. package/dist/{chunk-2CB4KY7I.cjs → chunk-LTZ5WYOK.cjs} +29 -7
  57. package/dist/chunk-LTZ5WYOK.cjs.map +1 -0
  58. package/dist/{chunk-DNQCEKUK.cjs → chunk-N32OWET6.cjs} +5 -5
  59. package/dist/{chunk-DNQCEKUK.cjs.map → chunk-N32OWET6.cjs.map} +1 -1
  60. package/dist/{chunk-HDWE5FRJ.js → chunk-OBA2RYZN.js} +3 -3
  61. package/dist/{chunk-HDWE5FRJ.js.map → chunk-OBA2RYZN.js.map} +1 -1
  62. package/dist/{chunk-DSA4UX5B.cjs → chunk-OJC4G5JE.cjs} +3 -2
  63. package/dist/chunk-OJC4G5JE.cjs.map +1 -0
  64. package/dist/{chunk-MVIZJOO5.cjs → chunk-PXNTCCPE.cjs} +8 -8
  65. package/dist/{chunk-MVIZJOO5.cjs.map → chunk-PXNTCCPE.cjs.map} +1 -1
  66. package/dist/{chunk-EF2NQUIQ.js → chunk-RMRJGMDE.js} +3 -3
  67. package/dist/{chunk-EF2NQUIQ.js.map → chunk-RMRJGMDE.js.map} +1 -1
  68. package/dist/{chunk-IVPRUGTY.js → chunk-SOZUG52O.js} +4 -4
  69. package/dist/{chunk-IVPRUGTY.js.map → chunk-SOZUG52O.js.map} +1 -1
  70. package/dist/{chunk-GCDZZNIN.js → chunk-UWUP2VMT.js} +16 -10
  71. package/dist/chunk-UWUP2VMT.js.map +1 -0
  72. package/dist/{chunk-IESEVHXL.js → chunk-VVYVFCML.js} +12 -3
  73. package/dist/chunk-VVYVFCML.js.map +1 -0
  74. package/dist/{chunk-HIKBY7MS.cjs → chunk-XWVDSM2L.cjs} +12 -3
  75. package/dist/chunk-XWVDSM2L.cjs.map +1 -0
  76. package/dist/{chunk-VD2EA3WT.js → chunk-ZP77LTN4.js} +343 -68
  77. package/dist/chunk-ZP77LTN4.js.map +1 -0
  78. package/dist/{collection-config-JgHOpFCG.d.ts → collection-config-BTzAvKt2.d.cts} +25 -0
  79. package/dist/{collection-config-JgHOpFCG.d.cts → collection-config-BTzAvKt2.d.ts} +25 -0
  80. package/dist/config-6FB2BXMP.js +6 -0
  81. package/dist/{config-HFXANXCC.js.map → config-6FB2BXMP.js.map} +1 -1
  82. package/dist/config-OCH7S4WK.cjs +19 -0
  83. package/dist/{config-ON6FNMYX.cjs.map → config-OCH7S4WK.cjs.map} +1 -1
  84. package/dist/{define-plugin-BzNHc1ZI.d.ts → define-plugin-BYIey6sp.d.ts} +1 -1
  85. package/dist/{define-plugin-IWDKYaVm.d.cts → define-plugin-DsKiGu1q.d.cts} +1 -1
  86. package/dist/document-projection-4HG75S5S.js +4 -0
  87. package/dist/{document-projection-YYMC6I4U.js.map → document-projection-4HG75S5S.js.map} +1 -1
  88. package/dist/document-projection-JQHSTSFD.cjs +13 -0
  89. package/dist/{document-projection-TDWRJX3Z.cjs.map → document-projection-JQHSTSFD.cjs.map} +1 -1
  90. package/dist/index.cjs +1119 -440
  91. package/dist/index.cjs.map +1 -1
  92. package/dist/index.d.cts +200 -12
  93. package/dist/index.d.ts +200 -12
  94. package/dist/index.js +803 -137
  95. package/dist/index.js.map +1 -1
  96. package/dist/middleware.cjs +37 -34
  97. package/dist/middleware.d.cts +2 -2
  98. package/dist/middleware.d.ts +2 -2
  99. package/dist/middleware.js +8 -5
  100. package/dist/migrations-HMX5SPN2.js +4 -0
  101. package/dist/{migrations-WCAVBD7C.js.map → migrations-HMX5SPN2.js.map} +1 -1
  102. package/dist/migrations-V73ARYV5.cjs +13 -0
  103. package/dist/{migrations-NJJWQUKK.cjs.map → migrations-V73ARYV5.cjs.map} +1 -1
  104. package/dist/{plugin-bootstrap-B8ThJU21.d.cts → plugin-bootstrap-BHWQ2df8.d.cts} +1 -1
  105. package/dist/{plugin-bootstrap-qu8hJgUt.d.ts → plugin-bootstrap-Bj4fxX1q.d.ts} +1 -1
  106. package/dist/plugins.cjs +40 -40
  107. package/dist/plugins.d.cts +2 -2
  108. package/dist/plugins.d.ts +2 -2
  109. package/dist/plugins.js +3 -3
  110. package/dist/rbac-7QZ4JZ2E.cjs +14 -0
  111. package/dist/{rbac-VONLJJKB.cjs.map → rbac-7QZ4JZ2E.cjs.map} +1 -1
  112. package/dist/rbac-Y43IEQOP.js +5 -0
  113. package/dist/{rbac-O73MFKDA.js.map → rbac-Y43IEQOP.js.map} +1 -1
  114. package/dist/routes.cjs +34 -34
  115. package/dist/routes.d.cts +2 -2
  116. package/dist/routes.d.ts +2 -2
  117. package/dist/routes.js +14 -14
  118. package/dist/services.cjs +42 -42
  119. package/dist/services.d.cts +16 -3
  120. package/dist/services.d.ts +16 -3
  121. package/dist/services.js +3 -3
  122. package/dist/{telemetry-B9vIV4wh.d.cts → telemetry-Cku1ax74.d.cts} +1 -1
  123. package/dist/{telemetry-B9vIV4wh.d.ts → telemetry-Cku1ax74.d.ts} +1 -1
  124. package/dist/templates.cjs +17 -17
  125. package/dist/templates.d.cts +2 -24
  126. package/dist/templates.d.ts +2 -24
  127. package/dist/templates.js +3 -3
  128. package/dist/{types-Dea1eNxU.d.ts → types-DT7XN5TX.d.cts} +1 -1
  129. package/dist/{types-Dea1eNxU.d.cts → types-DT7XN5TX.d.ts} +1 -1
  130. package/dist/types.d.cts +2 -2
  131. package/dist/types.d.ts +2 -2
  132. package/dist/utils.cjs +8 -8
  133. package/dist/utils.d.cts +2 -2
  134. package/dist/utils.d.ts +2 -2
  135. package/dist/utils.js +2 -2
  136. package/package.json +1 -1
  137. package/dist/admin-documents-form.template-KN7JF66Q.cjs +0 -19
  138. package/dist/admin-documents-form.template-NLSI6Z42.js +0 -6
  139. package/dist/admin-layout-catalyst.template-WHJGSWWD.js +0 -7
  140. package/dist/admin-layout-catalyst.template-ZK5HD545.cjs +0 -17
  141. package/dist/chunk-2CB4KY7I.cjs.map +0 -1
  142. package/dist/chunk-3TB6AT6X.js.map +0 -1
  143. package/dist/chunk-AAWNRBRB.cjs.map +0 -1
  144. package/dist/chunk-BDDABDAB.cjs.map +0 -1
  145. package/dist/chunk-BLMTL57B.js.map +0 -1
  146. package/dist/chunk-DSA4UX5B.cjs.map +0 -1
  147. package/dist/chunk-GCDZZNIN.js.map +0 -1
  148. package/dist/chunk-H2AXVCLS.cjs.map +0 -1
  149. package/dist/chunk-HIKBY7MS.cjs.map +0 -1
  150. package/dist/chunk-IESEVHXL.js.map +0 -1
  151. package/dist/chunk-JEQ7FLOD.cjs.map +0 -1
  152. package/dist/chunk-LRZIAW7U.cjs.map +0 -1
  153. package/dist/chunk-NAVPFIG5.cjs.map +0 -1
  154. package/dist/chunk-NLJVSER2.js.map +0 -1
  155. package/dist/chunk-NMPEMSU4.js.map +0 -1
  156. package/dist/chunk-QAYFOER6.cjs.map +0 -1
  157. package/dist/chunk-RZ6H7OZK.js.map +0 -1
  158. package/dist/chunk-VD2EA3WT.js.map +0 -1
  159. package/dist/chunk-VXE42MYF.cjs.map +0 -1
  160. package/dist/chunk-YHSQVQXX.js.map +0 -1
  161. package/dist/chunk-ZEZ245PW.js.map +0 -1
  162. package/dist/chunk-ZGGXCFR6.js.map +0 -1
  163. package/dist/config-HFXANXCC.js +0 -6
  164. package/dist/config-ON6FNMYX.cjs +0 -19
  165. package/dist/document-projection-TDWRJX3Z.cjs +0 -13
  166. package/dist/document-projection-YYMC6I4U.js +0 -4
  167. package/dist/migrations-NJJWQUKK.cjs +0 -13
  168. package/dist/migrations-WCAVBD7C.js +0 -4
  169. package/dist/rbac-O73MFKDA.js +0 -5
  170. package/dist/rbac-VONLJJKB.cjs +0 -14
@@ -1,22 +1,21 @@
1
- import { isFirstUserRegistration, isRegistrationEnabled, authValidationService } from './chunk-IESEVHXL.js';
2
- import { renderDocumentFormPage, renderAlert } from './chunk-EF2NQUIQ.js';
3
- import { getCacheService, CACHE_CONFIGS, SettingsService, getLogger, getAppInstance, buildRouteList, CATEGORY_INFO } from './chunk-ZEZ245PW.js';
4
- import { DocumentTypeRegistry, requireAuth, requireRole, optionalAuth, isPluginActive, rateLimit, AuthManager, getJwtExpirySecondsFromDb, getJwtRefreshGraceSecondsFromDb, logActivity, generateCsrfToken } from './chunk-RZ6H7OZK.js';
5
- import { DocumentsService, documentSecondsToMs, RbacService } from './chunk-BLMTL57B.js';
6
- import { nanoid } from './chunk-GCDZZNIN.js';
7
- import { getCollectionRegistry, collectionRecordToRow, loadCollectionConfigs, getVisibleCollections, PluginService, PLUGIN_REGISTRY, findPluginByCodeName, isCodeCollectionInternal, isDbDocTypeInternal, invalidateTenantCache } from './chunk-ZGGXCFR6.js';
8
- import { MigrationService } from './chunk-NLJVSER2.js';
9
- import { renderCheckboxPage, renderAlert as renderAlert$1, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-HDWE5FRJ.js';
10
- import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-3TB6AT6X.js';
11
- import { createServiceSingleton, definePlugin, getPluginDefinition, applySchemaDefaults, renderSchemaFields, parseFormDataToSettings } from './chunk-6OUHGKFD.js';
1
+ import { isFirstUserRegistration, isRegistrationEnabled, authValidationService } from './chunk-VVYVFCML.js';
2
+ import { renderDocumentFormPage, renderAlert } from './chunk-RMRJGMDE.js';
3
+ import { getCacheService, CACHE_CONFIGS, SettingsService, getLogger, getAppInstance, buildRouteList, CATEGORY_INFO } from './chunk-2XAQTPAE.js';
4
+ import { DocumentTypeRegistry, requireAuth, requireRole, optionalAuth, isPluginActive, rateLimit, AuthManager, getJwtExpirySecondsFromDb, getJwtRefreshGraceSecondsFromDb, logActivity, generateCsrfToken } from './chunk-4UYJPHSV.js';
5
+ import { DocumentsService, documentSecondsToMs, RbacService } from './chunk-3TMTYJAJ.js';
6
+ import { nanoid } from './chunk-UWUP2VMT.js';
7
+ import { getCollectionRegistry, collectionRecordToRow, loadCollectionConfigs, getVisibleCollections, PluginService, PLUGIN_REGISTRY, findPluginByCodeName, isCodeCollectionInternal, isDbDocTypeInternal, invalidateTenantCache } from './chunk-4LVVMVQI.js';
8
+ import { MigrationService } from './chunk-5U75XP74.js';
9
+ import { renderCheckboxPage, renderAlert as renderAlert$1, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-OBA2RYZN.js';
10
+ import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-5V62WT6M.js';
11
+ import { createServiceSingleton, definePlugin, getPluginDefinition, applySchemaDefaults, renderSchemaFields, parseFormDataToSettings } from './chunk-7OTOXWEB.js';
12
12
  import { hasHookSystem, getTypedHooks } from './chunk-RNZFGN4R.js';
13
13
  import { QueryFilterBuilder, getBlocksFieldConfig, parseBlocksValue } from './chunk-QZGABF2M.js';
14
14
  import { metricsTracker } from './chunk-FICTAGD4.js';
15
- import { getCoreVersion } from './chunk-NMPEMSU4.js';
15
+ import { getCoreVersion } from './chunk-JEX3UPZL.js';
16
16
  import { escapeHtml, sanitizeRichText, sanitizeInput } from './chunk-TQABQWOP.js';
17
17
  import { Hono } from 'hono';
18
18
  import { z } from 'zod';
19
- import { cors } from 'hono/cors';
20
19
  import { setCookie, getCookie } from 'hono/cookie';
21
20
  import { html, raw } from 'hono/html';
22
21
 
@@ -25,6 +24,7 @@ var DocumentPermissionsService = class {
25
24
  constructor(db) {
26
25
  this.db = db;
27
26
  }
27
+ db;
28
28
  // Deny wins. Overrides beat base grants. Empty ACL falls back to base grants.
29
29
  async isAllowed(principalSet, rootId, permission, typeSettings, tenantId) {
30
30
  const overrides = await this.getDocumentPermissions(rootId, principalSet, permission, tenantId);
@@ -133,6 +133,8 @@ var DocumentRepository = class {
133
133
  this.tenantId = tenantId;
134
134
  this.permissions = new DocumentPermissionsService(db);
135
135
  }
136
+ db;
137
+ tenantId;
136
138
  permissions;
137
139
  // ─── Reads ───────────────────────────────────────────────────────────────
138
140
  async getById(id) {
@@ -748,7 +750,7 @@ adminDocumentsRoutes.post("/types/:typeId/reindex", async (c) => {
748
750
  const reindexTenant = effectiveTenantForType(getRequestTenant(c), docType.settings);
749
751
  const reindexDenied = await denyIfNotAllowed(c, db, "", "manage", docType.settings, reindexTenant);
750
752
  if (reindexDenied) return reindexDenied;
751
- const { DocumentProjection } = await import('./document-projection-YYMC6I4U.js');
753
+ const { DocumentProjection } = await import('./document-projection-4HG75S5S.js');
752
754
  const projection = new DocumentProjection(db);
753
755
  const rebuilt = await projection.reindexType(typeId, reindexTenant, docType.queryableFields);
754
756
  return c.json({ rebuilt });
@@ -1260,16 +1262,6 @@ apiRoutes.use("*", async (c, next) => {
1260
1262
  c.set("cacheEnabled", cacheEnabled);
1261
1263
  await next();
1262
1264
  });
1263
- apiRoutes.use("*", cors({
1264
- origin: (origin, c) => {
1265
- const allowed = c.env?.CORS_ORIGINS;
1266
- if (!allowed) return null;
1267
- const list = allowed.split(",").map((s) => s.trim());
1268
- return list.includes(origin) ? origin : null;
1269
- },
1270
- allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
1271
- allowHeaders: ["Content-Type", "Authorization", "X-API-Key"]
1272
- }));
1273
1265
  function addTimingMeta(c, meta = {}, executionStartTime) {
1274
1266
  const totalTime = Date.now() - c.get("startTime");
1275
1267
  const executionTime = executionStartTime ? Date.now() - executionStartTime : void 0;
@@ -2202,6 +2194,8 @@ var MediaDocumentService = class {
2202
2194
  this.db = db;
2203
2195
  this.tenantId = tenantId;
2204
2196
  }
2197
+ db;
2198
+ tenantId;
2205
2199
  /** Create a media_asset document from R2 upload metadata. Published-on-create (immediately usable). */
2206
2200
  async createFromUpload(meta, createdBy) {
2207
2201
  const svc = new DocumentsService(this.db, { queryableFields: MEDIA_QUERYABLE, tenantId: this.tenantId, maxVersionsPerRoot: 5 });
@@ -2255,12 +2249,17 @@ var MediaDocumentService = class {
2255
2249
  where.push("(json_extract(data, '$.filename') LIKE ? OR json_extract(data, '$.originalName') LIKE ? OR json_extract(data, '$.alt') LIKE ?)");
2256
2250
  params.push(term, term, term);
2257
2251
  }
2258
- const listed = await this.db.prepare(`SELECT * FROM documents WHERE ${where.join(" AND ")} ORDER BY updated_at DESC LIMIT ? OFFSET ?`).bind(...params, limit, offset).all();
2252
+ const [listed, countResult] = await Promise.all([
2253
+ this.db.prepare(`SELECT * FROM documents WHERE ${where.join(" AND ")} ORDER BY updated_at DESC LIMIT ? OFFSET ?`).bind(...params, limit, offset).all(),
2254
+ this.db.prepare(`SELECT COUNT(*) AS total FROM documents WHERE ${where.join(" AND ")}`).bind(...params).first()
2255
+ ]);
2259
2256
  const files = (listed.results ?? []).map(rowToMinimalDoc);
2257
+ const total = countResult?.total ?? 0;
2260
2258
  const folderAgg = await this.db.prepare(`SELECT q_media_folder AS folder, COUNT(*) AS count, SUM(q_media_size) AS totalSize FROM documents WHERE ${base} GROUP BY q_media_folder ORDER BY q_media_folder`).bind(this.tenantId).all();
2261
2259
  const typeAgg = await this.db.prepare(`SELECT CASE WHEN q_media_mime LIKE 'image/%' THEN 'images' WHEN q_media_mime LIKE 'video/%' THEN 'videos' WHEN q_media_mime IN ('application/pdf', 'text/plain') THEN 'documents' ELSE 'other' END AS type, COUNT(*) AS count FROM documents WHERE ${base} GROUP BY type`).bind(this.tenantId).all();
2262
2260
  return {
2263
2261
  files,
2262
+ total,
2264
2263
  folders: (folderAgg.results ?? []).map((f) => ({ folder: f.folder, count: f.count, totalSize: f.totalSize ?? 0 })),
2265
2264
  types: (typeAgg.results ?? []).map((t) => ({ type: t.type, count: t.count }))
2266
2265
  };
@@ -3400,7 +3399,7 @@ adminApiRoutes.patch("/collections/:id", collectionsReadOnly);
3400
3399
  adminApiRoutes.delete("/collections/:id", collectionsReadOnly);
3401
3400
  adminApiRoutes.get("/migrations/status", async (c) => {
3402
3401
  try {
3403
- const { MigrationService: MigrationService2 } = await import('./migrations-WCAVBD7C.js');
3402
+ const { MigrationService: MigrationService2 } = await import('./migrations-HMX5SPN2.js');
3404
3403
  const db = c.env.DB;
3405
3404
  const migrationService = new MigrationService2(db);
3406
3405
  const status = await migrationService.getMigrationStatus();
@@ -3443,7 +3442,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
3443
3442
  });
3444
3443
  adminApiRoutes.get("/migrations/validate", async (c) => {
3445
3444
  try {
3446
- const { MigrationService: MigrationService2 } = await import('./migrations-WCAVBD7C.js');
3445
+ const { MigrationService: MigrationService2 } = await import('./migrations-HMX5SPN2.js');
3447
3446
  const db = c.env.DB;
3448
3447
  const migrationService = new MigrationService2(db);
3449
3448
  const validation = await migrationService.validateSchema();
@@ -3602,7 +3601,7 @@ function renderLoginPage(data, demoLoginActive = false) {
3602
3601
  <!-- Form -->
3603
3602
  <form
3604
3603
  id="login-form"
3605
- hx-post="/auth/login/form"
3604
+ hx-post="/auth/login/form${data.redirect ? `?redirect=${encodeURIComponent(data.redirect)}` : ""}"
3606
3605
  hx-target="#form-response"
3607
3606
  hx-swap="innerHTML"
3608
3607
  class="space-y-6"
@@ -5590,6 +5589,79 @@ function renderDynamicField(field, options = {}) {
5590
5589
  `;
5591
5590
  break;
5592
5591
  }
5592
+ case "json": {
5593
+ const jsonParsedValue = value !== null && value !== void 0 && typeof value !== "string" ? value : typeof value === "string" && value !== "" ? (() => {
5594
+ try {
5595
+ return JSON.parse(value);
5596
+ } catch {
5597
+ return value;
5598
+ }
5599
+ })() : null;
5600
+ const jsonDisplayValue = jsonParsedValue !== null && typeof jsonParsedValue !== "string" ? JSON.stringify(jsonParsedValue, null, 2) : typeof jsonParsedValue === "string" ? jsonParsedValue : "";
5601
+ const viewerId = `${fieldId}-viewer`;
5602
+ const textareaId = `${fieldId}-textarea`;
5603
+ const jsonDataAttr = escapeHtml2(JSON.stringify(jsonParsedValue ?? null));
5604
+ const isEmpty = jsonParsedValue === null || jsonDisplayValue === "";
5605
+ if (disabled) {
5606
+ fieldHTML = `
5607
+ <div
5608
+ id="${viewerId}"
5609
+ class="json-viewer-host rounded-lg px-3 py-2 text-xs bg-white dark:bg-zinc-800 shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 min-h-[2.5rem] overflow-auto"
5610
+ data-json-viewer
5611
+ data-json="${jsonDataAttr}"
5612
+ data-open="${opts.open ?? 2}"
5613
+ >${isEmpty ? '<span class="text-zinc-400 dark:text-zinc-500 italic">empty</span>' : ""}</div>
5614
+ ${getJsonViewerScript()}
5615
+ `;
5616
+ } else {
5617
+ fieldHTML = `
5618
+ <div class="json-field-wrapper space-y-2">
5619
+ <div class="flex items-center justify-end">
5620
+ <button
5621
+ type="button"
5622
+ id="${fieldId}-toggle"
5623
+ class="text-xs text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 underline transition-colors"
5624
+ data-json-toggle
5625
+ data-viewer="${viewerId}"
5626
+ data-textarea="${textareaId}"
5627
+ data-hidden-input="${fieldId}"
5628
+ >Edit JSON</button>
5629
+ </div>
5630
+
5631
+ <div
5632
+ id="${viewerId}"
5633
+ class="json-viewer-host rounded-lg px-3 py-2 text-xs bg-white dark:bg-zinc-800 shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 min-h-[2.5rem] overflow-auto"
5634
+ data-json-viewer
5635
+ data-json="${jsonDataAttr}"
5636
+ data-open="${opts.open ?? 2}"
5637
+ >${isEmpty ? '<span class="text-zinc-400 dark:text-zinc-500 italic">empty \u2014 click &quot;Edit JSON&quot; to add data</span>' : ""}</div>
5638
+
5639
+ <textarea
5640
+ id="${textareaId}"
5641
+ rows="${opts.rows || 8}"
5642
+ placeholder="${opts.placeholder || "{}"}"
5643
+ class="${baseClasses} ${errorClasses} resize-y font-mono text-xs hidden"
5644
+ aria-label="${escapeHtml2(field.field_label)}"
5645
+ data-json-edit-area
5646
+ data-viewer="${viewerId}"
5647
+ data-hidden-input="${fieldId}"
5648
+ >${escapeHtml2(jsonDisplayValue)}</textarea>
5649
+
5650
+ <input
5651
+ type="hidden"
5652
+ id="${fieldId}"
5653
+ name="${fieldName}"
5654
+ value="${escapeHtml2(jsonDisplayValue)}"
5655
+ ${required}
5656
+ >
5657
+
5658
+ <div id="${fieldId}-json-error" class="text-xs text-pink-500 dark:text-pink-400 hidden"></div>
5659
+ </div>
5660
+ ${getJsonViewerScript()}
5661
+ `;
5662
+ }
5663
+ break;
5664
+ }
5593
5665
  default:
5594
5666
  fieldHTML = `
5595
5667
  <input
@@ -6086,6 +6158,120 @@ function normalizeBlockField(fieldConfig, fieldName) {
6086
6158
  options
6087
6159
  };
6088
6160
  }
6161
+ function getJsonViewerScript() {
6162
+ return `
6163
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/json-formatter-js@2.5.23/dist/json-formatter.min.css" id="json-formatter-css" onload="this.removeAttribute('onload')" onerror="">
6164
+ <script>
6165
+ (function() {
6166
+ var JS_URL = 'https://cdn.jsdelivr.net/npm/json-formatter-js@2.5.23/dist/json-formatter.umd.min.js';
6167
+
6168
+ function isDark() {
6169
+ return document.documentElement.classList.contains('dark') ||
6170
+ document.documentElement.dataset.theme === 'dark' ||
6171
+ window.matchMedia('(prefers-color-scheme: dark)').matches;
6172
+ }
6173
+
6174
+ function initViewer(el) {
6175
+ var raw = el.dataset.json;
6176
+ var open = parseInt(el.dataset.open || '2', 10);
6177
+ if (!raw || raw === 'null') return;
6178
+ var data;
6179
+ try { data = JSON.parse(raw); } catch(e) { el.textContent = raw; return; }
6180
+ var fmt = new JSONFormatter(data, open, {
6181
+ hoverPreviewEnabled: true,
6182
+ hoverPreviewArrayCount: 5,
6183
+ hoverPreviewFieldCount: 5,
6184
+ theme: isDark() ? 'dark' : '',
6185
+ animateOpen: true,
6186
+ animateClose: true,
6187
+ });
6188
+ el.innerHTML = '';
6189
+ el.appendChild(fmt.render());
6190
+ }
6191
+
6192
+ function initAllViewers() {
6193
+ document.querySelectorAll('[data-json-viewer]').forEach(function(el) { initViewer(el); });
6194
+ }
6195
+
6196
+ function ensureLoaded(cb) {
6197
+ if (window.JSONFormatter) { cb(); return; }
6198
+ if (window.__jsonFormatterCallbacks) { window.__jsonFormatterCallbacks.push(cb); return; }
6199
+ window.__jsonFormatterCallbacks = [cb];
6200
+ var s = document.createElement('script');
6201
+ s.src = JS_URL;
6202
+ s.onload = function() {
6203
+ (window.__jsonFormatterCallbacks || []).forEach(function(fn) { fn(); });
6204
+ window.__jsonFormatterCallbacks = null;
6205
+ };
6206
+ document.head.appendChild(s);
6207
+ }
6208
+
6209
+ if (!window.__sonicJsonViewerInit) {
6210
+ window.__sonicJsonViewerInit = true;
6211
+
6212
+ // Toggle between viewer and edit textarea
6213
+ document.addEventListener('click', function(e) {
6214
+ var btn = e.target.closest('[data-json-toggle]');
6215
+ if (!btn) return;
6216
+ var viewerId = btn.dataset.viewer;
6217
+ var textareaId = btn.dataset.textarea;
6218
+ var hiddenId = btn.dataset.hiddenInput;
6219
+ var viewer = document.getElementById(viewerId);
6220
+ var textarea = document.getElementById(textareaId);
6221
+ var hidden = document.getElementById(hiddenId);
6222
+ var errorEl = hidden ? document.getElementById(hiddenId + '-json-error') : null;
6223
+ if (!viewer || !textarea) return;
6224
+ var isEditing = !textarea.classList.contains('hidden');
6225
+ if (isEditing) {
6226
+ // Switching back to viewer \u2014 validate + refresh
6227
+ var raw = textarea.value.trim();
6228
+ if (raw) {
6229
+ try {
6230
+ var parsed = JSON.parse(raw);
6231
+ if (hidden) hidden.value = raw;
6232
+ viewer.dataset.json = JSON.stringify(parsed);
6233
+ ensureLoaded(function() { initViewer(viewer); });
6234
+ if (errorEl) { errorEl.textContent = ''; errorEl.classList.add('hidden'); }
6235
+ } catch(ex) {
6236
+ if (errorEl) { errorEl.textContent = 'Invalid JSON: ' + ex.message; errorEl.classList.remove('hidden'); }
6237
+ return;
6238
+ }
6239
+ } else {
6240
+ if (hidden) hidden.value = '';
6241
+ viewer.innerHTML = '<span class="text-zinc-400 dark:text-zinc-500 italic">empty</span>';
6242
+ }
6243
+ textarea.classList.add('hidden');
6244
+ viewer.classList.remove('hidden');
6245
+ btn.textContent = 'Edit JSON';
6246
+ } else {
6247
+ // Switching to edit \u2014 populate textarea from hidden input
6248
+ if (hidden && hidden.value) {
6249
+ try {
6250
+ textarea.value = JSON.stringify(JSON.parse(hidden.value), null, 2);
6251
+ } catch(e) {
6252
+ textarea.value = hidden.value;
6253
+ }
6254
+ }
6255
+ viewer.classList.add('hidden');
6256
+ textarea.classList.remove('hidden');
6257
+ btn.textContent = 'Preview';
6258
+ }
6259
+ });
6260
+ }
6261
+
6262
+ function run() {
6263
+ ensureLoaded(initAllViewers);
6264
+ }
6265
+
6266
+ if (document.readyState === 'loading') {
6267
+ document.addEventListener('DOMContentLoaded', run);
6268
+ } else {
6269
+ run();
6270
+ }
6271
+ })();
6272
+ </script>
6273
+ `;
6274
+ }
6089
6275
  function getStructuredFieldScript() {
6090
6276
  return `
6091
6277
  ${getReadFieldValueScript()}
@@ -6946,10 +7132,12 @@ var authRoutes = new Hono();
6946
7132
  authRoutes.get("/login", async (c) => {
6947
7133
  const error = c.req.query("error");
6948
7134
  const message = c.req.query("message");
7135
+ const redirect = c.req.query("redirect");
6949
7136
  const pageData = {
6950
7137
  error: error || void 0,
6951
7138
  message: message || void 0,
6952
- version: c.get("appVersion")
7139
+ version: c.get("appVersion"),
7140
+ redirect: redirect && redirect.startsWith("/") ? redirect : void 0
6953
7141
  };
6954
7142
  const db = c.env.DB;
6955
7143
  let demoLoginActive = false;
@@ -7093,7 +7281,7 @@ authRoutes.post(
7093
7281
  }
7094
7282
  const { email, password } = validation.data;
7095
7283
  const normalizedEmail = email.toLowerCase();
7096
- const { createAuth } = await import('./config-HFXANXCC.js');
7284
+ const { createAuth } = await import('./config-6FB2BXMP.js');
7097
7285
  const auth = createAuth(c.env);
7098
7286
  const baReq = new Request(new URL("/auth/sign-in/email", c.req.url).href, {
7099
7287
  method: "POST",
@@ -7115,6 +7303,20 @@ authRoutes.post(
7115
7303
  await setCsrfCookie(c);
7116
7304
  const baBody = await baRes.json();
7117
7305
  const user = baBody.user ?? {};
7306
+ const tokenTtl = await getJwtExpirySecondsFromDb(c.env.DB, c.env);
7307
+ const token = await AuthManager.generateToken(
7308
+ user.id,
7309
+ user.email,
7310
+ user.role ?? "viewer",
7311
+ c.env.JWT_SECRET,
7312
+ tokenTtl
7313
+ );
7314
+ setCookie(c, "auth_token", token, {
7315
+ httpOnly: true,
7316
+ secure: true,
7317
+ sameSite: "Strict",
7318
+ maxAge: tokenTtl
7319
+ });
7118
7320
  return c.json({
7119
7321
  user: {
7120
7322
  id: user.id,
@@ -7122,7 +7324,8 @@ authRoutes.post(
7122
7324
  firstName: user.name?.split(" ")[0] ?? "",
7123
7325
  lastName: user.name?.split(" ").slice(1).join(" ") ?? "",
7124
7326
  role: user.role ?? "viewer"
7125
- }
7327
+ },
7328
+ token
7126
7329
  });
7127
7330
  } catch (error) {
7128
7331
  console.error("Login error:", error);
@@ -7132,7 +7335,7 @@ authRoutes.post(
7132
7335
  );
7133
7336
  authRoutes.post("/logout", async (c) => {
7134
7337
  try {
7135
- const { createAuth } = await import('./config-HFXANXCC.js');
7338
+ const { createAuth } = await import('./config-6FB2BXMP.js');
7136
7339
  const auth = createAuth(c.env);
7137
7340
  const baReq = new Request(new URL("/auth/sign-out", c.req.url).href, {
7138
7341
  method: "POST",
@@ -7148,7 +7351,7 @@ authRoutes.post("/logout", async (c) => {
7148
7351
  });
7149
7352
  authRoutes.get("/logout", async (c) => {
7150
7353
  try {
7151
- const { createAuth } = await import('./config-HFXANXCC.js');
7354
+ const { createAuth } = await import('./config-6FB2BXMP.js');
7152
7355
  const auth = createAuth(c.env);
7153
7356
  const baReq = new Request(new URL("/auth/sign-out", c.req.url).href, {
7154
7357
  method: "POST",
@@ -7355,7 +7558,7 @@ authRoutes.post(
7355
7558
  </div>
7356
7559
  `);
7357
7560
  }
7358
- const { createAuth } = await import('./config-HFXANXCC.js');
7561
+ const { createAuth } = await import('./config-6FB2BXMP.js');
7359
7562
  const auth = createAuth(c.env);
7360
7563
  const baReq = new Request(new URL("/auth/sign-in/email", c.req.url).href, {
7361
7564
  method: "POST",
@@ -7379,6 +7582,8 @@ authRoutes.post(
7379
7582
  }
7380
7583
  }
7381
7584
  await setCsrfCookie(c);
7585
+ const rawRedirect = c.req.query("redirect");
7586
+ const redirectUrl = rawRedirect && rawRedirect.startsWith("/") ? rawRedirect : "/admin/content";
7382
7587
  return c.html(html`
7383
7588
  <div id="form-response">
7384
7589
  <div class="rounded-lg bg-green-100 dark:bg-lime-500/10 p-4 ring-1 ring-green-400 dark:ring-lime-500/20">
@@ -7391,7 +7596,7 @@ authRoutes.post(
7391
7596
  </div>
7392
7597
  </div>
7393
7598
  <script>
7394
- setTimeout(() => { window.location.href = '/admin/content'; }, 500);
7599
+ setTimeout(() => { window.location.href = '${redirectUrl}'; }, 500);
7395
7600
  </script>
7396
7601
  </div>
7397
7602
  </div>
@@ -12866,7 +13071,7 @@ adminContentRoutes.post("/documents/:typeId/:documentId/delete", async (c) => {
12866
13071
  adminContentRoutes.get("/documents/:typeId/:rootId/versions", async (c) => {
12867
13072
  const { typeId, rootId } = c.req.param();
12868
13073
  const db = c.env.DB;
12869
- const { renderVersionHistoryFragment } = await import('./admin-documents-form.template-NLSI6Z42.js');
13074
+ const { renderVersionHistoryFragment } = await import('./admin-documents-form.template-DDSH6ROU.js');
12870
13075
  const registry = new DocumentTypeRegistry(db);
12871
13076
  const docType = await registry.findById(typeId);
12872
13077
  if (!docType) return c.html("<div>Unknown type.</div>", 404);
@@ -14739,6 +14944,7 @@ var TenantService = class {
14739
14944
  constructor(db) {
14740
14945
  this.db = db;
14741
14946
  }
14947
+ db;
14742
14948
  async listTenants() {
14743
14949
  const { results } = await this.db.prepare(`
14744
14950
  SELECT * FROM auth_tenant
@@ -17614,7 +17820,7 @@ adminMediaRoutes.get("/", async (c) => {
17614
17820
  const limit = 24;
17615
17821
  const offset = (page - 1) * limit;
17616
17822
  const mediaSvc = new MediaDocumentService(c.env.DB, getRequestTenant(c));
17617
- const { files, folders: folderAgg, types: typeAgg } = await mediaSvc.list({ folder, type, limit, offset });
17823
+ const { files, total, folders: folderAgg, types: typeAgg } = await mediaSvc.list({ folder, type, limit, offset });
17618
17824
  const mediaFiles = files.map((doc) => mediaDocToFile(doc));
17619
17825
  const pageData = {
17620
17826
  files: mediaFiles,
@@ -17624,8 +17830,8 @@ adminMediaRoutes.get("/", async (c) => {
17624
17830
  currentType: type,
17625
17831
  currentView: view,
17626
17832
  currentPage: page,
17627
- totalFiles: mediaFiles.length,
17628
- hasNextPage: mediaFiles.length === limit,
17833
+ totalFiles: total,
17834
+ hasNextPage: offset + mediaFiles.length < total,
17629
17835
  user: {
17630
17836
  name: user.email,
17631
17837
  email: user.email,
@@ -17665,12 +17871,14 @@ adminMediaRoutes.get("/selector", async (c) => {
17665
17871
  <input
17666
17872
  type="search"
17667
17873
  id="media-selector-search"
17874
+ name="search"
17668
17875
  placeholder="Search files..."
17669
17876
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-4 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"
17670
17877
  hx-get="/admin/media/selector"
17671
17878
  hx-trigger="keyup changed delay:300ms"
17672
17879
  hx-target="#media-selector-grid"
17673
- hx-include="[name='search']"
17880
+ hx-swap="outerHTML"
17881
+ hx-select="#media-selector-grid"
17674
17882
  >
17675
17883
  </div>
17676
17884
 
@@ -19137,6 +19345,8 @@ function escapeHtmlAttr(value) {
19137
19345
  }
19138
19346
  function renderPluginSettingsPage(data) {
19139
19347
  const { plugin, activity = [], user } = data;
19348
+ const hasUserSettings = Object.keys(plugin.settings || {}).some((k) => !k.startsWith("_"));
19349
+ const defaultTab = hasUserSettings ? "settings" : "info";
19140
19350
  const pageContent = `
19141
19351
  <div class="w-full px-4 sm:px-6 lg:px-8 py-6">
19142
19352
  <!-- Header with Back Button -->
@@ -19147,13 +19357,24 @@ function renderPluginSettingsPage(data) {
19147
19357
  ${plugin.description}
19148
19358
  </p>
19149
19359
  </div>
19150
- <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
19360
+ <div class="mt-4 sm:mt-0 sm:ml-16 flex items-center gap-3">
19151
19361
  <a href="/admin/plugins" class="inline-flex items-center justify-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors shadow-sm">
19152
19362
  <svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
19153
19363
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
19154
19364
  </svg>
19155
19365
  Back to Plugins
19156
19366
  </a>
19367
+ ${plugin.settings?._adminPath ? `
19368
+ <a
19369
+ href="${plugin.settings._adminPath}"
19370
+ class="inline-flex items-center justify-center gap-1.5 rounded-lg bg-gradient-to-r from-cyan-500 to-purple-600 hover:from-cyan-400 hover:to-purple-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm transition-all duration-200"
19371
+ >
19372
+ <svg class="-ml-0.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
19373
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
19374
+ </svg>
19375
+ View Page
19376
+ </a>
19377
+ ` : ""}
19157
19378
  </div>
19158
19379
  </div>
19159
19380
 
@@ -19186,23 +19407,25 @@ function renderPluginSettingsPage(data) {
19186
19407
  <!-- Tabs -->
19187
19408
  <div class="mb-6">
19188
19409
  <nav class="flex space-x-8" aria-label="Tabs">
19189
- <button onclick="showTab('settings')" id="settings-tab" class="tab-button active border-b-2 border-blue-400 py-2 px-1 text-sm font-medium text-blue-400">
19410
+ ${hasUserSettings ? `
19411
+ <a href="#settings" onclick="showTab('settings')" id="settings-tab" class="tab-button border-b-2 border-transparent py-2 px-1 text-sm font-medium text-gray-400 hover:text-gray-300">
19190
19412
  Settings
19191
- </button>
19192
- <button onclick="showTab('activity')" id="activity-tab" class="tab-button border-b-2 border-transparent py-2 px-1 text-sm font-medium text-gray-400 hover:text-gray-300">
19193
- Activity Log
19194
- </button>
19195
- <button onclick="showTab('info')" id="info-tab" class="tab-button border-b-2 border-transparent py-2 px-1 text-sm font-medium text-gray-400 hover:text-gray-300">
19413
+ </a>
19414
+ ` : ""}
19415
+ <a href="#info" onclick="showTab('info')" id="info-tab" class="tab-button border-b-2 border-transparent py-2 px-1 text-sm font-medium text-gray-400 hover:text-gray-300">
19196
19416
  Information
19197
- </button>
19417
+ </a>
19418
+ <a href="#activity" onclick="showTab('activity')" id="activity-tab" class="tab-button border-b-2 border-transparent py-2 px-1 text-sm font-medium text-gray-400 hover:text-gray-300">
19419
+ Activity Log
19420
+ </a>
19198
19421
  </nav>
19199
19422
  </div>
19200
19423
 
19201
19424
  <!-- Tab Content -->
19202
19425
  <div id="tab-content">
19203
19426
  <!-- Settings Tab -->
19204
- <div id="settings-content" class="tab-content">
19205
- ${renderSettingsTab(plugin)}
19427
+ <div id="settings-content" class="tab-content hidden">
19428
+ ${hasUserSettings ? renderSettingsTab(plugin) : ""}
19206
19429
  </div>
19207
19430
 
19208
19431
  <!-- Activity Tab -->
@@ -19218,27 +19441,42 @@ function renderPluginSettingsPage(data) {
19218
19441
  </div>
19219
19442
 
19220
19443
  <script>
19444
+ const VALID_TABS = [${hasUserSettings ? "'settings', " : ""}'activity', 'info'];
19445
+
19221
19446
  function showTab(tabName) {
19222
- // Hide all tab contents
19447
+ if (!VALID_TABS.includes(tabName)) tabName = '${defaultTab}';
19448
+
19223
19449
  document.querySelectorAll('.tab-content').forEach(content => {
19224
19450
  content.classList.add('hidden');
19225
19451
  });
19226
-
19227
- // Remove active class from all tabs
19452
+
19228
19453
  document.querySelectorAll('.tab-button').forEach(tab => {
19229
19454
  tab.classList.remove('active', 'border-blue-400', 'text-blue-400');
19230
19455
  tab.classList.add('border-transparent', 'text-gray-400');
19231
19456
  });
19232
-
19233
- // Show selected tab content
19234
- document.getElementById(tabName + '-content').classList.remove('hidden');
19235
-
19236
- // Add active class to selected tab
19457
+
19458
+ const contentEl = document.getElementById(tabName + '-content');
19459
+ if (contentEl) contentEl.classList.remove('hidden');
19460
+
19237
19461
  const activeTab = document.getElementById(tabName + '-tab');
19238
- activeTab.classList.add('active', 'border-blue-400', 'text-blue-400');
19239
- activeTab.classList.remove('border-transparent', 'text-gray-400');
19462
+ if (activeTab) {
19463
+ activeTab.classList.add('active', 'border-blue-400', 'text-blue-400');
19464
+ activeTab.classList.remove('border-transparent', 'text-gray-400');
19465
+ }
19466
+
19467
+ if (window.location.hash !== '#' + tabName) {
19468
+ history.replaceState(null, '', '#' + tabName);
19469
+ }
19470
+ }
19471
+
19472
+ function initTabFromHash() {
19473
+ const hash = window.location.hash.replace('#', '');
19474
+ showTab(VALID_TABS.includes(hash) ? hash : '${defaultTab}');
19240
19475
  }
19241
19476
 
19477
+ window.addEventListener('hashchange', initTabFromHash);
19478
+ initTabFromHash();
19479
+
19242
19480
  async function togglePlugin(pluginId, action) {
19243
19481
  const button = event.target;
19244
19482
  const originalText = button.textContent;
@@ -19411,6 +19649,7 @@ function renderToggleButton(plugin) {
19411
19649
  function renderSettingsTab(plugin) {
19412
19650
  const settings = plugin.settings || {};
19413
19651
  const pluginId = plugin.id || plugin.name;
19652
+ const hasUserKeys = Object.keys(settings).some((k) => !k.startsWith("_"));
19414
19653
  const customRenderer = pluginSettingsComponents[pluginId];
19415
19654
  if (customRenderer) {
19416
19655
  return `
@@ -19446,9 +19685,9 @@ function renderSettingsTab(plugin) {
19446
19685
  `}
19447
19686
 
19448
19687
  <form id="settings-form" class="space-y-6">
19449
- ${isAuthPlugin && Object.keys(settings).length > 0 ? renderAuthSettingsForm(settings) : isTurnstilePlugin && Object.keys(settings).length > 0 ? renderTurnstileSettingsForm(settings) : Object.keys(settings).length > 0 ? renderSettingsFields(settings) : renderNoSettings(plugin)}
19688
+ ${isAuthPlugin && hasUserKeys ? renderAuthSettingsForm(settings) : isTurnstilePlugin && hasUserKeys ? renderTurnstileSettingsForm(settings) : hasUserKeys ? renderSettingsFields(settings) : renderNoSettings(plugin)}
19450
19689
 
19451
- ${Object.keys(settings).length > 0 ? `
19690
+ ${hasUserKeys ? `
19452
19691
  <div class="flex items-center justify-end pt-6 border-t border-white/10">
19453
19692
  <button
19454
19693
  type="button"
@@ -19465,7 +19704,7 @@ function renderSettingsTab(plugin) {
19465
19704
  `;
19466
19705
  }
19467
19706
  function renderSettingsFields(settings) {
19468
- return Object.entries(settings).map(([key, value]) => {
19707
+ return Object.entries(settings).filter(([key]) => !key.startsWith("_")).map(([key, value]) => {
19469
19708
  const fieldId = `setting_${key}`;
19470
19709
  const displayName = key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
19471
19710
  if (typeof value === "boolean") {
@@ -19654,6 +19893,38 @@ function renderActivityTab(activity) {
19654
19893
  }
19655
19894
  function renderInformationTab(plugin) {
19656
19895
  return `
19896
+ ${plugin.description ? `
19897
+ <!-- About -->
19898
+ <div class="backdrop-blur-md bg-black/20 rounded-xl border border-white/10 shadow-xl p-6 mb-6">
19899
+ <h2 class="text-xl font-semibold text-white mb-3">About</h2>
19900
+ <p class="text-gray-300 leading-relaxed">${plugin.description}</p>
19901
+ </div>
19902
+ ` : ""}
19903
+
19904
+ ${(plugin.settings?._routes?.length ?? 0) > 0 ? `
19905
+ <!-- Routes -->
19906
+ <div class="backdrop-blur-md bg-black/20 rounded-xl border border-white/10 shadow-xl p-6 mb-6">
19907
+ <h2 class="text-xl font-semibold text-white mb-4">Routes</h2>
19908
+ <div class="space-y-2">
19909
+ ${plugin.settings._routes.map((r) => {
19910
+ const badge = r.method === "GET" ? "bg-emerald-500/20 text-emerald-300 border border-emerald-500/30" : r.method === "POST" ? "bg-blue-500/20 text-blue-300 border border-blue-500/30" : r.method === "DELETE" ? "bg-red-500/20 text-red-300 border border-red-500/30" : "bg-white/10 text-gray-300 border border-white/20";
19911
+ const pathHtml = r.method === "GET" ? `<a href="${r.path}" class="font-mono text-sm text-blue-400 hover:text-blue-300 hover:underline">${r.path}</a>` : `<span class="font-mono text-sm text-gray-200">${r.path}</span>`;
19912
+ const authBadge = r.requiresAuth ? `<span class="ml-2 text-xs text-amber-400/80">\u{1F512} auth</span>` : "";
19913
+ return `
19914
+ <div class="flex items-start gap-3 py-2 border-b border-white/5 last:border-0">
19915
+ <span class="shrink-0 inline-block px-2 py-0.5 rounded text-xs font-bold ${badge}">${r.method}</span>
19916
+ <div class="min-w-0">
19917
+ <div class="flex items-center gap-1 flex-wrap">
19918
+ ${pathHtml}${authBadge}
19919
+ </div>
19920
+ ${r.description ? `<p class="text-xs text-gray-400 mt-0.5">${r.description}</p>` : ""}
19921
+ </div>
19922
+ </div>`;
19923
+ }).join("")}
19924
+ </div>
19925
+ </div>
19926
+ ` : ""}
19927
+
19657
19928
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
19658
19929
  <!-- Plugin Details -->
19659
19930
  <div class="backdrop-blur-md bg-black/20 rounded-xl border border-white/10 shadow-xl p-6">
@@ -20684,7 +20955,9 @@ adminPluginRoutes.post("/:id/configure", async (c) => {
20684
20955
  description: def.description,
20685
20956
  author: typeof def.author === "object" ? def.author?.name : def.author
20686
20957
  });
20687
- await pluginService.updatePluginSettings(pluginId, parsed);
20958
+ const existingPlugin = await pluginService.getPlugin(pluginId);
20959
+ const existingSettings = existingPlugin?.settings ?? {};
20960
+ await pluginService.updatePluginSettings(pluginId, { ...existingSettings, ...parsed });
20688
20961
  return c.redirect(`/admin/plugins/${encodeURIComponent(pluginId)}/configure`);
20689
20962
  } catch (error) {
20690
20963
  console.error("Error saving schema settings:", error);
@@ -20786,7 +21059,9 @@ adminPluginRoutes.post("/:id/settings", async (c) => {
20786
21059
  }
20787
21060
  const settings = await c.req.json();
20788
21061
  const pluginService = new PluginService(db);
20789
- await pluginService.updatePluginSettings(pluginId, settings);
21062
+ const existingPlugin = await pluginService.getPlugin(pluginId);
21063
+ const existingSettings = existingPlugin?.settings ?? {};
21064
+ await pluginService.updatePluginSettings(pluginId, { ...existingSettings, ...settings });
20790
21065
  if (pluginId === "core-auth") {
20791
21066
  try {
20792
21067
  const cacheKv = c.env.CACHE_KV;
@@ -26638,6 +26913,6 @@ var ROUTES_INFO = {
26638
26913
  reference: "https://github.com/sonicjs/sonicjs"
26639
26914
  };
26640
26915
 
26641
- export { DocumentRepository, ROUTES_INFO, TenantService, VALID_MEMBER_ROLES, adminCheckboxRoutes, adminCollectionsRoutes, adminDocumentsRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_content_default, apiDocumentsRoutes, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, createUserProfilesPlugin, defineUserProfile, denyIfNotAllowed, dispatchHookEvent, getConfirmationDialogScript2 as getConfirmationDialogScript, getCustomData, getEmailService, getUserProfileConfig, getVariablesCached, hasEmailService, invalidateVariablesCache, isValidMemberRole, lexicalEditorPlugin, renderConfirmationDialog2 as renderConfirmationDialog, renderTable2 as renderTable, resolveDocScope, resolveVariablesInObject, router, router2, setEmailService, setVariablesCache, test_cleanup_default, userProfilesPlugin, userRoutes };
26642
- //# sourceMappingURL=chunk-VD2EA3WT.js.map
26643
- //# sourceMappingURL=chunk-VD2EA3WT.js.map
26916
+ export { DocumentRepository, ROUTES_INFO, TenantService, VALID_MEMBER_ROLES, adminCheckboxRoutes, adminCollectionsRoutes, adminDocumentsRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_content_default, apiDocumentsRoutes, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, createDocumentSchema, createUserProfilesPlugin, defineUserProfile, denyIfNotAllowed, dispatchHookEvent, getConfirmationDialogScript2 as getConfirmationDialogScript, getCustomData, getEmailService, getUserProfileConfig, getVariablesCached, hasEmailService, invalidateVariablesCache, isValidMemberRole, lexicalEditorPlugin, renderConfirmationDialog2 as renderConfirmationDialog, renderTable2 as renderTable, resolveDocScope, resolveVariablesInObject, router, router2, setEmailService, setVariablesCache, test_cleanup_default, userProfilesPlugin, userRoutes };
26917
+ //# sourceMappingURL=chunk-ZP77LTN4.js.map
26918
+ //# sourceMappingURL=chunk-ZP77LTN4.js.map