@sonicjs-cms/core 2.19.0 → 3.0.0-beta.11

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 (230) hide show
  1. package/README.md +52 -52
  2. package/dist/admin-documents-form.template-DDSH6ROU.js +6 -0
  3. package/dist/{admin-layout-catalyst.template-UMTIN66R.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-HFD37TY5.cjs.map → admin-documents-form.template-LSZKGA5J.cjs.map} +1 -1
  6. package/dist/{filter-bar.template-DlVYMk-T.d.cts → admin-layout-catalyst.template-DrwDUfsE.d.cts} +25 -1
  7. package/dist/{filter-bar.template-DlVYMk-T.d.ts → 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-KDHKVLXR.cjs.map +1 -0
  10. package/dist/admin-layout-catalyst.template-YQ4EMF2J.js +7 -0
  11. package/dist/admin-layout-catalyst.template-YQ4EMF2J.js.map +1 -0
  12. package/dist/app-Bo0X1OWX.d.ts +1268 -0
  13. package/dist/app-Do66yCcV.d.cts +1268 -0
  14. package/dist/cache-DDARE4QE.js +4 -0
  15. package/dist/cache-DDARE4QE.js.map +1 -0
  16. package/dist/cache-LVYS4BPL.cjs +33 -0
  17. package/dist/cache-LVYS4BPL.cjs.map +1 -0
  18. package/dist/chunk-2CB4KY7I.cjs +771 -0
  19. package/dist/chunk-2CB4KY7I.cjs.map +1 -0
  20. package/dist/{chunk-4NPCDK6B.js → chunk-3PU4WVU6.js} +557 -90
  21. package/dist/chunk-3PU4WVU6.js.map +1 -0
  22. package/dist/chunk-4BTBSXMR.cjs +912 -0
  23. package/dist/chunk-4BTBSXMR.cjs.map +1 -0
  24. package/dist/{chunk-55RDMDOP.js → chunk-5V62WT6M.js} +181 -57
  25. package/dist/chunk-5V62WT6M.js.map +1 -0
  26. package/dist/chunk-6H66MSSL.js +273 -0
  27. package/dist/chunk-6H66MSSL.js.map +1 -0
  28. package/dist/chunk-AI663NBO.js +821 -0
  29. package/dist/chunk-AI663NBO.js.map +1 -0
  30. package/dist/chunk-BLMTL57B.js +767 -0
  31. package/dist/chunk-BLMTL57B.js.map +1 -0
  32. package/dist/{chunk-4ZSNJDLS.cjs → chunk-CRGUD4KC.cjs} +9 -9
  33. package/dist/chunk-CRGUD4KC.cjs.map +1 -0
  34. package/dist/chunk-GCDZZNIN.js +192 -0
  35. package/dist/chunk-GCDZZNIN.js.map +1 -0
  36. package/dist/chunk-HIKBY7MS.cjs +70 -0
  37. package/dist/chunk-HIKBY7MS.cjs.map +1 -0
  38. package/dist/chunk-HPAJKZAQ.js +387 -0
  39. package/dist/chunk-HPAJKZAQ.js.map +1 -0
  40. package/dist/chunk-IESEVHXL.js +66 -0
  41. package/dist/chunk-IESEVHXL.js.map +1 -0
  42. package/dist/chunk-IVPRUGTY.js +242 -0
  43. package/dist/chunk-IVPRUGTY.js.map +1 -0
  44. package/dist/{chunk-JZVHLLSI.cjs → chunk-IXUHXTHW.cjs} +2 -151
  45. package/dist/chunk-IXUHXTHW.cjs.map +1 -0
  46. package/dist/chunk-J6JTWD2A.cjs +100 -0
  47. package/dist/chunk-J6JTWD2A.cjs.map +1 -0
  48. package/dist/chunk-JEQ7FLOD.cjs +199 -0
  49. package/dist/chunk-JEQ7FLOD.cjs.map +1 -0
  50. package/dist/{chunk-ON5ZMSU4.js → chunk-JQISFW6U.js} +3 -3
  51. package/dist/chunk-JQISFW6U.js.map +1 -0
  52. package/dist/chunk-K25XHMM3.js +566 -0
  53. package/dist/chunk-K25XHMM3.js.map +1 -0
  54. package/dist/{chunk-R4FOLLFB.cjs → chunk-K342JMA3.cjs} +8730 -11520
  55. package/dist/chunk-K342JMA3.cjs.map +1 -0
  56. package/dist/{chunk-UYJ6TJHX.cjs → chunk-K623Q6WD.cjs} +181 -56
  57. package/dist/chunk-K623Q6WD.cjs.map +1 -0
  58. package/dist/chunk-KV3CM5RK.cjs +158 -0
  59. package/dist/chunk-KV3CM5RK.cjs.map +1 -0
  60. package/dist/{chunk-ABB34XUS.cjs → chunk-MKKGA3C4.cjs} +667 -19
  61. package/dist/chunk-MKKGA3C4.cjs.map +1 -0
  62. package/dist/chunk-N32OWET6.cjs +327 -0
  63. package/dist/chunk-N32OWET6.cjs.map +1 -0
  64. package/dist/chunk-NUKJ54GA.cjs +245 -0
  65. package/dist/chunk-NUKJ54GA.cjs.map +1 -0
  66. package/dist/{chunk-XWIA3HVX.js → chunk-OBA2RYZN.js} +6 -1249
  67. package/dist/chunk-OBA2RYZN.js.map +1 -0
  68. package/dist/chunk-ORF4CT74.cjs +276 -0
  69. package/dist/chunk-ORF4CT74.cjs.map +1 -0
  70. package/dist/{chunk-TFNTM3OA.js → chunk-PDYRDYXI.js} +645 -15
  71. package/dist/chunk-PDYRDYXI.js.map +1 -0
  72. package/dist/{chunk-OHYBNCVL.cjs → chunk-PXNTCCPE.cjs} +10 -1256
  73. package/dist/chunk-PXNTCCPE.cjs.map +1 -0
  74. package/dist/{chunk-E4YFJBM2.cjs → chunk-QJNKSFDJ.cjs} +876 -829
  75. package/dist/chunk-QJNKSFDJ.cjs.map +1 -0
  76. package/dist/chunk-QLFTG3QJ.js +1828 -0
  77. package/dist/chunk-QLFTG3QJ.js.map +1 -0
  78. package/dist/{chunk-BU7SFHGP.js → chunk-QZGABF2M.js} +3 -149
  79. package/dist/chunk-QZGABF2M.js.map +1 -0
  80. package/dist/chunk-RMRJGMDE.js +323 -0
  81. package/dist/chunk-RMRJGMDE.js.map +1 -0
  82. package/dist/chunk-RNZFGN4R.js +88 -0
  83. package/dist/chunk-RNZFGN4R.js.map +1 -0
  84. package/dist/chunk-RQ6N3FTV.js +900 -0
  85. package/dist/chunk-RQ6N3FTV.js.map +1 -0
  86. package/dist/{chunk-OCL3HMEG.js → chunk-SXLVXD2X.js} +7004 -9807
  87. package/dist/chunk-SXLVXD2X.js.map +1 -0
  88. package/dist/chunk-UHRHZXVR.cjs +408 -0
  89. package/dist/chunk-UHRHZXVR.cjs.map +1 -0
  90. package/dist/chunk-YA3TJ65D.cjs +575 -0
  91. package/dist/chunk-YA3TJ65D.cjs.map +1 -0
  92. package/dist/{chunk-7A4CB7T3.cjs → chunk-YJEBDJDV.cjs} +561 -91
  93. package/dist/chunk-YJEBDJDV.cjs.map +1 -0
  94. package/dist/chunk-YP7GW2G5.cjs +866 -0
  95. package/dist/chunk-YP7GW2G5.cjs.map +1 -0
  96. package/dist/chunk-ZUEIQFE5.js +154 -0
  97. package/dist/chunk-ZUEIQFE5.js.map +1 -0
  98. package/dist/{collection-config-B4PG-AaF.d.cts → collection-config-JgHOpFCG.d.cts} +30 -2
  99. package/dist/{collection-config-B4PG-AaF.d.ts → collection-config-JgHOpFCG.d.ts} +30 -2
  100. package/dist/config-HFXANXCC.js +6 -0
  101. package/dist/config-HFXANXCC.js.map +1 -0
  102. package/dist/config-ON6FNMYX.cjs +19 -0
  103. package/dist/config-ON6FNMYX.cjs.map +1 -0
  104. package/dist/define-plugin-BzNHc1ZI.d.ts +1321 -0
  105. package/dist/define-plugin-IWDKYaVm.d.cts +1321 -0
  106. package/dist/document-projection-TDWRJX3Z.cjs +13 -0
  107. package/dist/document-projection-TDWRJX3Z.cjs.map +1 -0
  108. package/dist/document-projection-YYMC6I4U.js +4 -0
  109. package/dist/document-projection-YYMC6I4U.js.map +1 -0
  110. package/dist/index.cjs +13739 -4328
  111. package/dist/index.cjs.map +1 -1
  112. package/dist/index.d.cts +331 -493
  113. package/dist/index.d.ts +331 -493
  114. package/dist/index.js +13456 -4067
  115. package/dist/index.js.map +1 -1
  116. package/dist/middleware.cjs +38 -32
  117. package/dist/middleware.d.cts +50 -7
  118. package/dist/middleware.d.ts +50 -7
  119. package/dist/middleware.js +9 -3
  120. package/dist/migrations-XQLBY7E5.js +4 -0
  121. package/dist/{migrations-H5IXZNCO.js.map → migrations-XQLBY7E5.js.map} +1 -1
  122. package/dist/migrations-ZXJEUTFA.cjs +13 -0
  123. package/dist/{migrations-566IIPS2.cjs.map → migrations-ZXJEUTFA.cjs.map} +1 -1
  124. package/dist/{plugin-bootstrap-DfVerYV4.d.cts → plugin-bootstrap-B8ThJU21.d.cts} +4315 -1661
  125. package/dist/{plugin-bootstrap-P_ciLp_C.d.ts → plugin-bootstrap-qu8hJgUt.d.ts} +4315 -1661
  126. package/dist/plugins.cjs +171 -12
  127. package/dist/plugins.d.cts +36 -2
  128. package/dist/plugins.d.ts +36 -2
  129. package/dist/plugins.js +5 -2
  130. package/dist/rbac-O73MFKDA.js +5 -0
  131. package/dist/rbac-O73MFKDA.js.map +1 -0
  132. package/dist/rbac-VONLJJKB.cjs +14 -0
  133. package/dist/rbac-VONLJJKB.cjs.map +1 -0
  134. package/dist/routes.cjs +42 -46
  135. package/dist/routes.d.cts +56 -146
  136. package/dist/routes.d.ts +56 -146
  137. package/dist/routes.js +18 -10
  138. package/dist/services.cjs +43 -76
  139. package/dist/services.d.cts +93 -55
  140. package/dist/services.d.ts +93 -55
  141. package/dist/services.js +6 -3
  142. package/dist/{telemetry-B9vIV4wh.d.cts → telemetry-Cku1ax74.d.cts} +1 -1
  143. package/dist/{telemetry-B9vIV4wh.d.ts → telemetry-Cku1ax74.d.ts} +1 -1
  144. package/dist/templates.cjs +17 -29
  145. package/dist/templates.d.cts +2 -89
  146. package/dist/templates.d.ts +2 -89
  147. package/dist/templates.js +3 -3
  148. package/dist/types-Dea1eNxU.d.cts +286 -0
  149. package/dist/types-Dea1eNxU.d.ts +286 -0
  150. package/dist/types.d.cts +2 -2
  151. package/dist/types.d.ts +2 -2
  152. package/dist/utils.cjs +21 -20
  153. package/dist/utils.d.cts +2 -2
  154. package/dist/utils.d.ts +2 -2
  155. package/dist/utils.js +3 -2
  156. package/migrations/0001_core.sql +184 -0
  157. package/migrations/0002_documents.sql +163 -0
  158. package/package.json +12 -7
  159. package/dist/admin-layout-catalyst.template-HFD37TY5.cjs +0 -17
  160. package/dist/admin-layout-catalyst.template-UMTIN66R.js +0 -7
  161. package/dist/app-C9esKLmh.d.cts +0 -112
  162. package/dist/app-C9esKLmh.d.ts +0 -112
  163. package/dist/chunk-4NPCDK6B.js.map +0 -1
  164. package/dist/chunk-4ZSNJDLS.cjs.map +0 -1
  165. package/dist/chunk-55RDMDOP.js.map +0 -1
  166. package/dist/chunk-635JAMSE.cjs +0 -653
  167. package/dist/chunk-635JAMSE.cjs.map +0 -1
  168. package/dist/chunk-7A4CB7T3.cjs.map +0 -1
  169. package/dist/chunk-ABB34XUS.cjs.map +0 -1
  170. package/dist/chunk-BU7SFHGP.js.map +0 -1
  171. package/dist/chunk-E4YFJBM2.cjs.map +0 -1
  172. package/dist/chunk-EXNEW5US.js +0 -648
  173. package/dist/chunk-EXNEW5US.js.map +0 -1
  174. package/dist/chunk-JZV22DEV.js +0 -1783
  175. package/dist/chunk-JZV22DEV.js.map +0 -1
  176. package/dist/chunk-JZVHLLSI.cjs.map +0 -1
  177. package/dist/chunk-OCL3HMEG.js.map +0 -1
  178. package/dist/chunk-OHYBNCVL.cjs.map +0 -1
  179. package/dist/chunk-ON5ZMSU4.js.map +0 -1
  180. package/dist/chunk-QFWHAFEO.js +0 -1843
  181. package/dist/chunk-QFWHAFEO.js.map +0 -1
  182. package/dist/chunk-R4FOLLFB.cjs.map +0 -1
  183. package/dist/chunk-RLMUFFUD.cjs +0 -2219
  184. package/dist/chunk-RLMUFFUD.cjs.map +0 -1
  185. package/dist/chunk-TFNTM3OA.js.map +0 -1
  186. package/dist/chunk-UYJ6TJHX.cjs.map +0 -1
  187. package/dist/chunk-WAEQXGCX.cjs +0 -1898
  188. package/dist/chunk-WAEQXGCX.cjs.map +0 -1
  189. package/dist/chunk-XWIA3HVX.js.map +0 -1
  190. package/dist/chunk-ZYAYUIZE.js +0 -2217
  191. package/dist/chunk-ZYAYUIZE.js.map +0 -1
  192. package/dist/migrations-566IIPS2.cjs +0 -13
  193. package/dist/migrations-H5IXZNCO.js +0 -4
  194. package/dist/plugin-manager-BoM3Q7o7.d.cts +0 -328
  195. package/dist/plugin-manager-Efx9RyDX.d.ts +0 -328
  196. package/migrations/001_initial_schema.sql +0 -170
  197. package/migrations/002_faq_plugin.sql +0 -86
  198. package/migrations/003_stage5_enhancements.sql +0 -121
  199. package/migrations/004_stage6_user_management.sql +0 -183
  200. package/migrations/005_stage7_workflow_automation.sql +0 -294
  201. package/migrations/006_plugin_system.sql +0 -155
  202. package/migrations/007_demo_login_plugin.sql +0 -23
  203. package/migrations/008_fix_slug_validation.sql +0 -22
  204. package/migrations/009_system_logging.sql +0 -57
  205. package/migrations/011_config_managed_collections.sql +0 -15
  206. package/migrations/012_testimonials_plugin.sql +0 -80
  207. package/migrations/013_code_examples_plugin.sql +0 -177
  208. package/migrations/014_fix_plugin_registry.sql +0 -88
  209. package/migrations/015_add_remaining_plugins.sql +0 -89
  210. package/migrations/016_remove_duplicate_cache_plugin.sql +0 -17
  211. package/migrations/017_auth_configurable_fields.sql +0 -49
  212. package/migrations/018_settings_table.sql +0 -23
  213. package/migrations/019_remove_blog_posts_collection.sql +0 -15
  214. package/migrations/020_add_email_plugin.sql +0 -22
  215. package/migrations/021_add_magic_link_auth_plugin.sql +0 -42
  216. package/migrations/022_add_tinymce_plugin.sql +0 -25
  217. package/migrations/023_add_easy_mdx_plugin.sql +0 -25
  218. package/migrations/024_add_quill_editor_plugin.sql +0 -25
  219. package/migrations/025_add_easymde_plugin.sql +0 -25
  220. package/migrations/026_add_otp_login.sql +0 -42
  221. package/migrations/027_fix_slug_field_type.sql +0 -18
  222. package/migrations/028_fix_slug_field_type_in_schemas.sql +0 -30
  223. package/migrations/029_add_forms_system.sql +0 -184
  224. package/migrations/030_add_turnstile_to_forms.sql +0 -14
  225. package/migrations/031_ai_search_plugin.sql +0 -45
  226. package/migrations/032_user_profiles.sql +0 -37
  227. package/migrations/033_form_content_integration.sql +0 -19
  228. package/migrations/034_security_audit_plugin.sql +0 -27
  229. package/migrations/035_user_profiles_data_column.sql +0 -16
  230. package/migrations/036_analytics_events.sql +0 -22
@@ -0,0 +1,323 @@
1
+ import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-5V62WT6M.js';
2
+ import { escapeHtml } from './chunk-TQABQWOP.js';
3
+
4
+ // src/templates/pages/admin-documents-form.template.ts
5
+ init_admin_layout_catalyst_template();
6
+
7
+ // src/templates/components/alert.template.ts
8
+ function renderAlert(data) {
9
+ const typeClasses = {
10
+ success: "bg-green-50 dark:bg-green-500/10 border border-green-600/20 dark:border-green-500/20",
11
+ error: "bg-error/10 border border-red-600/20 dark:border-red-500/20",
12
+ warning: "bg-amber-50 dark:bg-amber-500/10 border border-amber-600/20 dark:border-amber-500/20",
13
+ info: "bg-blue-50 dark:bg-blue-500/10 border border-blue-600/20 dark:border-blue-500/20"
14
+ };
15
+ const iconClasses = {
16
+ success: "text-green-600 dark:text-green-400",
17
+ error: "text-red-600 dark:text-red-400",
18
+ warning: "text-amber-600 dark:text-amber-400",
19
+ info: "text-blue-600 dark:text-blue-400"
20
+ };
21
+ const textClasses = {
22
+ success: "text-green-900 dark:text-green-300",
23
+ error: "text-red-900 dark:text-red-300",
24
+ warning: "text-amber-900 dark:text-amber-300",
25
+ info: "text-blue-900 dark:text-blue-300"
26
+ };
27
+ const messageTextClasses = {
28
+ success: "text-green-700 dark:text-green-400",
29
+ error: "text-red-700 dark:text-red-400",
30
+ warning: "text-amber-700 dark:text-amber-400",
31
+ info: "text-blue-700 dark:text-blue-400"
32
+ };
33
+ const icons = {
34
+ success: `<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />`,
35
+ error: `<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />`,
36
+ warning: `<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />`,
37
+ info: `<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />`
38
+ };
39
+ return `
40
+ <div class="rounded-lg p-4 ${typeClasses[data.type]} ${data.className || ""}" ${data.dismissible ? 'id="dismissible-alert"' : ""}>
41
+ <div class="flex">
42
+ ${data.icon !== false ? `
43
+ <div class="flex-shrink-0">
44
+ <svg class="h-5 w-5 ${iconClasses[data.type]}" viewBox="0 0 20 20" fill="currentColor">
45
+ ${icons[data.type]}
46
+ </svg>
47
+ </div>
48
+ ` : ""}
49
+ <div class="${data.icon !== false ? "ml-3" : ""}">
50
+ ${data.title ? `
51
+ <h3 class="text-sm font-semibold ${textClasses[data.type]}">
52
+ ${escapeHtml(data.title)}
53
+ </h3>
54
+ ` : ""}
55
+ <div class="${data.title ? "mt-1 text-sm" : "text-sm"} ${messageTextClasses[data.type]}">
56
+ <p>${escapeHtml(data.message)}</p>
57
+ </div>
58
+ </div>
59
+ ${data.dismissible ? `
60
+ <div class="ml-auto pl-3">
61
+ <div class="-mx-1.5 -my-1.5">
62
+ <button
63
+ type="button"
64
+ class="inline-flex rounded-md p-1.5 ${iconClasses[data.type]} hover:bg-opacity-20 focus:outline-none focus:ring-2 focus:ring-offset-2"
65
+ onclick="document.getElementById('dismissible-alert').remove()"
66
+ >
67
+ <span class="sr-only">Dismiss</span>
68
+ <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
69
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
70
+ </svg>
71
+ </button>
72
+ </div>
73
+ </div>
74
+ ` : ""}
75
+ </div>
76
+ </div>
77
+ `;
78
+ }
79
+
80
+ // src/templates/pages/admin-documents-form.template.ts
81
+ function inputClass(error) {
82
+ const base = "block w-full rounded-lg border bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors";
83
+ return error ? `${base} border-red-400 dark:border-red-500` : `${base} border-zinc-300 dark:border-zinc-700`;
84
+ }
85
+ function renderFieldInput(field, value, error) {
86
+ const id = `data_${field.name}`;
87
+ const name = `data[${field.name}]`;
88
+ const label = field.name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
89
+ const strVal = value != null ? String(value) : "";
90
+ let input;
91
+ if (field.type === "integer" || field.type === "number") {
92
+ input = `<input type="number" id="${id}" name="${name}" value="${escapeHtml(strVal)}"
93
+ step="${field.type === "integer" ? "1" : "any"}"
94
+ class="${inputClass(error)}">`;
95
+ } else if (field.type === "boolean") {
96
+ input = `<div class="flex items-center gap-2">
97
+ <input type="hidden" name="${name}" value="false">
98
+ <input type="checkbox" id="${id}" name="${name}" value="true" ${strVal === "true" ? "checked" : ""}
99
+ class="h-4 w-4 rounded border-zinc-300 dark:border-zinc-600 text-blue-600 focus:ring-blue-500">
100
+ <span class="text-sm text-zinc-700 dark:text-zinc-300">${label}</span>
101
+ </div>`;
102
+ return `
103
+ <div>
104
+ ${input}
105
+ ${error ? `<p class="mt-1 text-xs text-red-500">${escapeHtml(error)}</p>` : ""}
106
+ </div>`;
107
+ } else if (field.kind === "facet") {
108
+ const arrVal = Array.isArray(value) ? value.join(", ") : strVal;
109
+ input = `<input type="text" id="${id}" name="${name}" value="${escapeHtml(arrVal)}"
110
+ placeholder="Comma-separated values"
111
+ class="${inputClass(error)}">`;
112
+ } else {
113
+ input = `<input type="text" id="${id}" name="${name}" value="${escapeHtml(strVal)}"
114
+ class="${inputClass(error)}">`;
115
+ }
116
+ return `
117
+ <div>
118
+ <label for="${id}" class="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">
119
+ ${label}
120
+ </label>
121
+ ${input}
122
+ ${error ? `<p class="mt-1 text-xs text-red-500">${escapeHtml(error)}</p>` : ""}
123
+ </div>`;
124
+ }
125
+ function renderRemainingFields(allData, queryableFields, errors) {
126
+ const knownNames = new Set(queryableFields.map((f) => f.name));
127
+ const remainingKeys = Object.keys(allData).filter((k) => !knownNames.has(k));
128
+ if (remainingKeys.length === 0) return "";
129
+ const inputs = remainingKeys.map((key) => {
130
+ const val = allData[key];
131
+ const id = `data_${key}`;
132
+ const name = `data[${key}]`;
133
+ const label = key.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
134
+ const strVal = typeof val === "object" ? JSON.stringify(val, null, 2) : String(val ?? "");
135
+ const isMultiline = strVal.includes("\n") || strVal.length > 100;
136
+ return `
137
+ <div>
138
+ <label for="${id}" class="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">${escapeHtml(label)}</label>
139
+ ${isMultiline ? `<textarea id="${id}" name="${name}" rows="4" class="${inputClass(errors[key])}">${escapeHtml(strVal)}</textarea>` : `<input type="text" id="${id}" name="${name}" value="${escapeHtml(strVal)}" class="${inputClass(errors[key])}">`}
140
+ ${errors[key] ? `<p class="mt-1 text-xs text-red-500">${escapeHtml(errors[key])}</p>` : ""}
141
+ </div>`;
142
+ }).join("");
143
+ return `
144
+ <div class="border-t border-zinc-200 dark:border-zinc-700 pt-6">
145
+ <h3 class="text-sm font-medium text-zinc-500 dark:text-zinc-400 mb-4">Additional Fields</h3>
146
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">${inputs}</div>
147
+ </div>`;
148
+ }
149
+ function renderDocumentFormPage(data) {
150
+ const { docType, doc, publishedDoc, isEdit, errors = {} } = data;
151
+ const queryableFields = docType.queryableFields ?? [];
152
+ const docData = doc?.data ?? {};
153
+ const isAdmin = data.user?.role === "admin";
154
+ const isEditor = isAdmin || data.user?.role === "editor";
155
+ const hasNewerDraft = isEdit && doc && !doc.isPublished && publishedDoc;
156
+ const isPublishedAndDraft = isEdit && doc?.isPublished && doc?.isCurrentDraft;
157
+ const formAction = isEdit ? `/admin/content/documents/${escapeHtml(docType.id)}/${escapeHtml(doc.rootId)}` : `/admin/content/documents/${escapeHtml(docType.id)}/new`;
158
+ const publishBannerHtml = (() => {
159
+ if (!isEdit || !doc) return "";
160
+ if (isPublishedAndDraft) {
161
+ return renderAlert({
162
+ type: "success",
163
+ message: 'This document is live. Saving creates a new draft. Use "Publish" to push changes live.'
164
+ });
165
+ }
166
+ if (hasNewerDraft) {
167
+ return renderAlert({
168
+ type: "info",
169
+ message: `A published version (v${publishedDoc.versionNumber}) is still live. This is an unpublished draft (v${doc.versionNumber}).`
170
+ });
171
+ }
172
+ return "";
173
+ })();
174
+ const queryableInputs = queryableFields.filter((f) => f.kind !== "reference").map((f) => renderFieldInput(f, docData[f.name], errors[`data.${f.name}`])).join("");
175
+ const remainingHtml = renderRemainingFields(docData, queryableFields, errors);
176
+ const content = `
177
+ <div class="w-full px-4 sm:px-6 lg:px-8 py-6 space-y-6">
178
+ <!-- Header -->
179
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between">
180
+ <div>
181
+ <div class="flex items-center gap-2 text-sm text-zinc-500 dark:text-zinc-400 mb-1">
182
+ <a href="/admin/content" class="hover:text-zinc-950 dark:hover:text-white">Content</a>
183
+ <svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd"/></svg>
184
+ <a href="/admin/content?model=doc:${escapeHtml(docType.id)}" class="hover:text-zinc-950 dark:hover:text-white">${escapeHtml(docType.displayName)}</a>
185
+ <svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd"/></svg>
186
+ <span class="text-zinc-950 dark:text-white font-medium">${isEdit ? "Edit" : "New"}</span>
187
+ </div>
188
+ <h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">
189
+ ${isEdit ? `Edit ${escapeHtml(docType.displayName)}` : `New ${escapeHtml(docType.displayName)}`}
190
+ </h1>
191
+ ${isEdit && doc ? `<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">v${doc.versionNumber} \xB7 root: <code class="font-mono">${escapeHtml(doc.rootId)}</code></p>` : ""}
192
+ </div>
193
+
194
+ <!-- Publish controls (edit mode, versioning types only) -->
195
+ ${isEdit && doc && isEditor && data.versioningEnabled ? `
196
+ <div class="mt-4 sm:mt-0 flex gap-2">
197
+ ${!doc.isPublished ? `
198
+ <form method="POST" action="/admin/content/documents/${escapeHtml(docType.id)}/${escapeHtml(doc.id)}/publish">
199
+ <button type="submit"
200
+ class="inline-flex items-center rounded-lg bg-green-600 px-3.5 py-2.5 text-sm font-semibold text-white hover:bg-green-500 transition-colors shadow-sm">
201
+ Publish
202
+ </button>
203
+ </form>` : `
204
+ <form method="POST" action="/admin/content/documents/${escapeHtml(docType.id)}/${escapeHtml(doc.id)}/unpublish">
205
+ <button type="submit"
206
+ class="inline-flex items-center rounded-lg bg-amber-500 px-3.5 py-2.5 text-sm font-semibold text-white hover:bg-amber-400 transition-colors shadow-sm">
207
+ Unpublish
208
+ </button>
209
+ </form>`}
210
+ </div>` : ""}
211
+ </div>
212
+
213
+ ${publishBannerHtml}
214
+ ${data.message ? renderAlert({ type: data.messageType ?? "info", message: data.message, dismissible: true }) : ""}
215
+
216
+ <!-- Form -->
217
+ <form method="POST" action="${formAction}">
218
+ ${isEdit ? `<input type="hidden" name="_method" value="PUT">` : ""}
219
+
220
+ <div class="relative rounded-xl">
221
+ <div class="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-purple-500/5 dark:from-blue-400/10 dark:to-purple-400/10 rounded-xl"></div>
222
+ <div class="relative bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 rounded-xl p-6 space-y-6">
223
+
224
+ <!-- Standard fields -->
225
+ <div>
226
+ <h3 class="text-sm font-semibold text-zinc-700 dark:text-zinc-200 mb-4">Document</h3>
227
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
228
+ <div>
229
+ <label for="title" class="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">Title</label>
230
+ <input type="text" id="title" name="title" value="${escapeHtml(doc?.title ?? "")}"
231
+ class="${inputClass(errors.title)}">
232
+ ${errors.title ? `<p class="mt-1 text-xs text-red-500">${escapeHtml(errors.title)}</p>` : ""}
233
+ </div>
234
+ <div>
235
+ <label for="slug" class="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">Slug</label>
236
+ <input type="text" id="slug" name="slug" value="${escapeHtml(doc?.slug ?? "")}"
237
+ placeholder="auto-generated-if-empty"
238
+ class="${inputClass(errors.slug)}">
239
+ ${errors.slug ? `<p class="mt-1 text-xs text-red-500">${escapeHtml(errors.slug)}</p>` : ""}
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ <!-- Queryable data fields -->
245
+ ${queryableFields.length > 0 ? `
246
+ <div class="border-t border-zinc-200 dark:border-zinc-700 pt-6">
247
+ <h3 class="text-sm font-semibold text-zinc-700 dark:text-zinc-200 mb-4">Content</h3>
248
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
249
+ ${queryableInputs}
250
+ </div>
251
+ </div>` : ""}
252
+
253
+ <!-- Remaining data fields not in queryable fields -->
254
+ ${remainingHtml}
255
+
256
+ <!-- Actions -->
257
+ <div class="border-t border-zinc-200 dark:border-zinc-700 pt-6 flex items-center justify-between">
258
+ <a href="/admin/content?model=doc:${escapeHtml(docType.id)}"
259
+ class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-700 dark:text-zinc-200 ring-1 ring-inset ring-zinc-300 dark:ring-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
260
+ Cancel
261
+ </a>
262
+ <div class="flex gap-3">
263
+ <button type="submit"
264
+ class="inline-flex items-center rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm">
265
+ ${isEdit ? data.versioningEnabled ? "Save Draft" : "Update" : "Create"}
266
+ </button>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </form>
272
+
273
+ <!-- Version history (edit mode, versioning opt-in only) -->
274
+ ${isEdit && doc && data.versioningEnabled ? `
275
+ <details class="group">
276
+ <summary class="cursor-pointer text-sm text-zinc-500 dark:text-zinc-400 hover:text-zinc-950 dark:hover:text-white flex items-center gap-2 py-2">
277
+ <svg class="h-4 w-4 transition-transform group-open:rotate-90" viewBox="0 0 20 20" fill="currentColor">
278
+ <path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd"/>
279
+ </svg>
280
+ Version history
281
+ </summary>
282
+ <div class="mt-3 rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10 overflow-hidden">
283
+ <div id="version-history-placeholder" class="px-6 py-4 text-sm text-zinc-500 dark:text-zinc-400"
284
+ hx-get="/admin/versioning/${escapeHtml(doc.rootId)}"
285
+ hx-trigger="revealed"
286
+ hx-swap="outerHTML">
287
+ Loading version history\u2026
288
+ </div>
289
+ </div>
290
+ </details>` : ""}
291
+ </div>
292
+ `;
293
+ return renderAdminLayoutCatalyst({
294
+ title: `${isEdit ? "Edit" : "New"} ${docType.displayName} \u2014 Documents`,
295
+ currentPath: "/admin/content",
296
+ user: data.user,
297
+ version: data.version,
298
+ content
299
+ });
300
+ }
301
+ function renderVersionHistoryFragment(data) {
302
+ if (data.versions.length === 0) {
303
+ return `<div class="px-6 py-4 text-sm text-zinc-500 dark:text-zinc-400">No versions found.</div>`;
304
+ }
305
+ const rows = data.versions.map((v) => `
306
+ <div class="flex items-center justify-between px-6 py-3 border-b border-zinc-100 dark:border-zinc-800 last:border-0">
307
+ <div class="flex items-center gap-3">
308
+ <span class="text-sm font-medium text-zinc-950 dark:text-white">v${v.versionNumber}</span>
309
+ ${v.isPublished ? `<span class="inline-flex items-center rounded-md bg-green-50 dark:bg-green-500/10 px-1.5 py-0.5 text-xs font-medium text-green-700 dark:text-green-400">live</span>` : ""}
310
+ ${v.isCurrentDraft ? `<span class="inline-flex items-center rounded-md bg-blue-50 dark:bg-blue-500/10 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:text-blue-400">draft</span>` : ""}
311
+ </div>
312
+ <div class="flex items-center gap-4 text-xs text-zinc-500 dark:text-zinc-400">
313
+ <span>${v.createdBy ?? "\u2014"}</span>
314
+ <span>${new Date(v.updatedAt * 1e3).toLocaleString("en-US", { dateStyle: "short", timeStyle: "short" })}</span>
315
+ </div>
316
+ </div>
317
+ `).join("");
318
+ return `<div>${rows}</div>`;
319
+ }
320
+
321
+ export { renderAlert, renderDocumentFormPage, renderVersionHistoryFragment };
322
+ //# sourceMappingURL=chunk-RMRJGMDE.js.map
323
+ //# sourceMappingURL=chunk-RMRJGMDE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/templates/pages/admin-documents-form.template.ts","../src/templates/components/alert.template.ts"],"names":[],"mappings":"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","file":"chunk-RMRJGMDE.js","sourcesContent":["import { renderAdminLayoutCatalyst } from '../layouts/admin-layout-catalyst.template'\nimport { renderAlert } from '../components/alert.template'\nimport { escapeHtml } from '../../utils/sanitize'\nimport type { Document, DocumentType, QueryableField } from '../../schemas/document'\n\nexport interface DocumentFormData {\n docType: DocumentType\n doc?: Document\n publishedDoc?: Document | null // Published revision when different from current draft\n isEdit: boolean\n errors?: Record<string, string>\n message?: string\n messageType?: 'success' | 'error' | 'warning' | 'info'\n user?: { name: string; email: string; role: string }\n version?: string\n versioningEnabled?: boolean // Whether versioning is opt-in for this document type\n}\n\nfunction inputClass(error?: string): string {\n const base = 'block w-full rounded-lg border bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors'\n return error\n ? `${base} border-red-400 dark:border-red-500`\n : `${base} border-zinc-300 dark:border-zinc-700`\n}\n\nfunction renderFieldInput(field: QueryableField, value: unknown, error?: string): string {\n const id = `data_${field.name}`\n const name = `data[${field.name}]`\n const label = field.name.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase())\n const strVal = value != null ? String(value) : ''\n\n let input: string\n if (field.type === 'integer' || field.type === 'number') {\n input = `<input type=\"number\" id=\"${id}\" name=\"${name}\" value=\"${escapeHtml(strVal)}\"\n step=\"${field.type === 'integer' ? '1' : 'any'}\"\n class=\"${inputClass(error)}\">`\n } else if (field.type === 'boolean') {\n // Hidden 'false' before the checkbox: an unchecked checkbox submits nothing, so without this a\n // boolean could never be set back to false (D15). When checked, the checkbox value wins.\n input = `<div class=\"flex items-center gap-2\">\n <input type=\"hidden\" name=\"${name}\" value=\"false\">\n <input type=\"checkbox\" id=\"${id}\" name=\"${name}\" value=\"true\" ${strVal === 'true' ? 'checked' : ''}\n class=\"h-4 w-4 rounded border-zinc-300 dark:border-zinc-600 text-blue-600 focus:ring-blue-500\">\n <span class=\"text-sm text-zinc-700 dark:text-zinc-300\">${label}</span>\n </div>`\n return `\n <div>\n ${input}\n ${error ? `<p class=\"mt-1 text-xs text-red-500\">${escapeHtml(error)}</p>` : ''}\n </div>`\n } else if (field.kind === 'facet') {\n // Multi-value: comma-separated list\n const arrVal = Array.isArray(value) ? (value as string[]).join(', ') : strVal\n input = `<input type=\"text\" id=\"${id}\" name=\"${name}\" value=\"${escapeHtml(arrVal)}\"\n placeholder=\"Comma-separated values\"\n class=\"${inputClass(error)}\">`\n } else {\n input = `<input type=\"text\" id=\"${id}\" name=\"${name}\" value=\"${escapeHtml(strVal)}\"\n class=\"${inputClass(error)}\">`\n }\n\n return `\n <div>\n <label for=\"${id}\" class=\"block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1\">\n ${label}\n </label>\n ${input}\n ${error ? `<p class=\"mt-1 text-xs text-red-500\">${escapeHtml(error)}</p>` : ''}\n </div>`\n}\n\nfunction renderRemainingFields(\n allData: Record<string, unknown>,\n queryableFields: QueryableField[],\n errors: Record<string, string>,\n): string {\n const knownNames = new Set(queryableFields.map(f => f.name))\n const remainingKeys = Object.keys(allData).filter(k => !knownNames.has(k))\n if (remainingKeys.length === 0) return ''\n\n const inputs = remainingKeys.map(key => {\n const val = allData[key]\n const id = `data_${key}`\n const name = `data[${key}]`\n const label = key.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase())\n const strVal = typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val ?? '')\n const isMultiline = strVal.includes('\\n') || strVal.length > 100\n\n return `\n <div>\n <label for=\"${id}\" class=\"block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1\">${escapeHtml(label)}</label>\n ${isMultiline\n ? `<textarea id=\"${id}\" name=\"${name}\" rows=\"4\" class=\"${inputClass(errors[key])}\">${escapeHtml(strVal)}</textarea>`\n : `<input type=\"text\" id=\"${id}\" name=\"${name}\" value=\"${escapeHtml(strVal)}\" class=\"${inputClass(errors[key])}\">`\n }\n ${errors[key] ? `<p class=\"mt-1 text-xs text-red-500\">${escapeHtml(errors[key])}</p>` : ''}\n </div>`\n }).join('')\n\n return `\n <div class=\"border-t border-zinc-200 dark:border-zinc-700 pt-6\">\n <h3 class=\"text-sm font-medium text-zinc-500 dark:text-zinc-400 mb-4\">Additional Fields</h3>\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">${inputs}</div>\n </div>`\n}\n\nexport function renderDocumentFormPage(data: DocumentFormData): string {\n const { docType, doc, publishedDoc, isEdit, errors = {} } = data\n const queryableFields = docType.queryableFields ?? []\n const docData = (doc?.data ?? {}) as Record<string, unknown>\n\n const isAdmin = data.user?.role === 'admin'\n const isEditor = isAdmin || data.user?.role === 'editor'\n\n const hasNewerDraft = isEdit && doc && !doc.isPublished && publishedDoc\n const isPublishedAndDraft = isEdit && doc?.isPublished && doc?.isCurrentDraft\n\n // Real document CRUD lives under /admin/content/documents/:typeId/... (admin-content.ts), NOT\n // /admin/documents/ui (those are GET redirects only). Edit/save POSTs to :rootId; create to /new.\n const formAction = isEdit\n ? `/admin/content/documents/${escapeHtml(docType.id)}/${escapeHtml(doc!.rootId)}`\n : `/admin/content/documents/${escapeHtml(docType.id)}/new`\n\n const publishBannerHtml = (() => {\n if (!isEdit || !doc) return ''\n if (isPublishedAndDraft) {\n return renderAlert({\n type: 'success',\n message: 'This document is live. Saving creates a new draft. Use \"Publish\" to push changes live.',\n })\n }\n if (hasNewerDraft) {\n return renderAlert({\n type: 'info',\n message: `A published version (v${publishedDoc!.versionNumber}) is still live. This is an unpublished draft (v${doc.versionNumber}).`,\n })\n }\n return ''\n })()\n\n // Reference-kind fields are intentionally not rendered yet (D27) — fine for FAQ/testimonial, which\n // have none. When media references land (Phase 6), render a root-id picker here.\n const queryableInputs = queryableFields\n .filter(f => f.kind !== 'reference')\n .map(f => renderFieldInput(f, docData[f.name], errors[`data.${f.name}`]))\n .join('')\n\n const remainingHtml = renderRemainingFields(docData, queryableFields, errors)\n\n const content = `\n <div class=\"w-full px-4 sm:px-6 lg:px-8 py-6 space-y-6\">\n <!-- Header -->\n <div class=\"flex flex-col sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <div class=\"flex items-center gap-2 text-sm text-zinc-500 dark:text-zinc-400 mb-1\">\n <a href=\"/admin/content\" class=\"hover:text-zinc-950 dark:hover:text-white\">Content</a>\n <svg class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\"/></svg>\n <a href=\"/admin/content?model=doc:${escapeHtml(docType.id)}\" class=\"hover:text-zinc-950 dark:hover:text-white\">${escapeHtml(docType.displayName)}</a>\n <svg class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\"/></svg>\n <span class=\"text-zinc-950 dark:text-white font-medium\">${isEdit ? 'Edit' : 'New'}</span>\n </div>\n <h1 class=\"text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8\">\n ${isEdit ? `Edit ${escapeHtml(docType.displayName)}` : `New ${escapeHtml(docType.displayName)}`}\n </h1>\n ${isEdit && doc ? `<p class=\"mt-1 text-xs text-zinc-500 dark:text-zinc-400\">v${doc.versionNumber} · root: <code class=\"font-mono\">${escapeHtml(doc.rootId)}</code></p>` : ''}\n </div>\n\n <!-- Publish controls (edit mode, versioning types only) -->\n ${isEdit && doc && isEditor && data.versioningEnabled ? `\n <div class=\"mt-4 sm:mt-0 flex gap-2\">\n ${!doc.isPublished ? `\n <form method=\"POST\" action=\"/admin/content/documents/${escapeHtml(docType.id)}/${escapeHtml(doc.id)}/publish\">\n <button type=\"submit\"\n class=\"inline-flex items-center rounded-lg bg-green-600 px-3.5 py-2.5 text-sm font-semibold text-white hover:bg-green-500 transition-colors shadow-sm\">\n Publish\n </button>\n </form>` : `\n <form method=\"POST\" action=\"/admin/content/documents/${escapeHtml(docType.id)}/${escapeHtml(doc.id)}/unpublish\">\n <button type=\"submit\"\n class=\"inline-flex items-center rounded-lg bg-amber-500 px-3.5 py-2.5 text-sm font-semibold text-white hover:bg-amber-400 transition-colors shadow-sm\">\n Unpublish\n </button>\n </form>`}\n </div>` : ''}\n </div>\n\n ${publishBannerHtml}\n ${data.message ? renderAlert({ type: data.messageType ?? 'info', message: data.message, dismissible: true }) : ''}\n\n <!-- Form -->\n <form method=\"POST\" action=\"${formAction}\">\n ${isEdit ? `<input type=\"hidden\" name=\"_method\" value=\"PUT\">` : ''}\n\n <div class=\"relative rounded-xl\">\n <div class=\"absolute inset-0 bg-gradient-to-br from-blue-500/5 to-purple-500/5 dark:from-blue-400/10 dark:to-purple-400/10 rounded-xl\"></div>\n <div class=\"relative bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 rounded-xl p-6 space-y-6\">\n\n <!-- Standard fields -->\n <div>\n <h3 class=\"text-sm font-semibold text-zinc-700 dark:text-zinc-200 mb-4\">Document</h3>\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label for=\"title\" class=\"block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1\">Title</label>\n <input type=\"text\" id=\"title\" name=\"title\" value=\"${escapeHtml(doc?.title ?? '')}\"\n class=\"${inputClass(errors.title)}\">\n ${errors.title ? `<p class=\"mt-1 text-xs text-red-500\">${escapeHtml(errors.title)}</p>` : ''}\n </div>\n <div>\n <label for=\"slug\" class=\"block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1\">Slug</label>\n <input type=\"text\" id=\"slug\" name=\"slug\" value=\"${escapeHtml(doc?.slug ?? '')}\"\n placeholder=\"auto-generated-if-empty\"\n class=\"${inputClass(errors.slug)}\">\n ${errors.slug ? `<p class=\"mt-1 text-xs text-red-500\">${escapeHtml(errors.slug)}</p>` : ''}\n </div>\n </div>\n </div>\n\n <!-- Queryable data fields -->\n ${queryableFields.length > 0 ? `\n <div class=\"border-t border-zinc-200 dark:border-zinc-700 pt-6\">\n <h3 class=\"text-sm font-semibold text-zinc-700 dark:text-zinc-200 mb-4\">Content</h3>\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n ${queryableInputs}\n </div>\n </div>` : ''}\n\n <!-- Remaining data fields not in queryable fields -->\n ${remainingHtml}\n\n <!-- Actions -->\n <div class=\"border-t border-zinc-200 dark:border-zinc-700 pt-6 flex items-center justify-between\">\n <a href=\"/admin/content?model=doc:${escapeHtml(docType.id)}\"\n class=\"inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-700 dark:text-zinc-200 ring-1 ring-inset ring-zinc-300 dark:ring-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors\">\n Cancel\n </a>\n <div class=\"flex gap-3\">\n <button type=\"submit\"\n class=\"inline-flex items-center rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm\">\n ${isEdit ? (data.versioningEnabled ? 'Save Draft' : 'Update') : 'Create'}\n </button>\n </div>\n </div>\n </div>\n </div>\n </form>\n\n <!-- Version history (edit mode, versioning opt-in only) -->\n ${isEdit && doc && data.versioningEnabled ? `\n <details class=\"group\">\n <summary class=\"cursor-pointer text-sm text-zinc-500 dark:text-zinc-400 hover:text-zinc-950 dark:hover:text-white flex items-center gap-2 py-2\">\n <svg class=\"h-4 w-4 transition-transform group-open:rotate-90\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\"/>\n </svg>\n Version history\n </summary>\n <div class=\"mt-3 rounded-xl bg-white dark:bg-zinc-900 ring-1 ring-zinc-950/5 dark:ring-white/10 overflow-hidden\">\n <div id=\"version-history-placeholder\" class=\"px-6 py-4 text-sm text-zinc-500 dark:text-zinc-400\"\n hx-get=\"/admin/versioning/${escapeHtml(doc.rootId)}\"\n hx-trigger=\"revealed\"\n hx-swap=\"outerHTML\">\n Loading version history…\n </div>\n </div>\n </details>` : ''}\n </div>\n `\n\n return renderAdminLayoutCatalyst({\n title: `${isEdit ? 'Edit' : 'New'} ${docType.displayName} — Documents`,\n currentPath: '/admin/content',\n user: data.user,\n version: data.version,\n content,\n })\n}\n\n// ─── Version history fragment (HTMX target) ──────────────────────────────────\n\nexport interface VersionHistoryData {\n versions: Array<{\n id: string\n versionNumber: number\n isCurrentDraft: boolean\n isPublished: boolean\n status: string\n updatedAt: number\n createdBy: string | null\n }>\n docType: DocumentType\n rootId: string\n}\n\nexport function renderVersionHistoryFragment(data: VersionHistoryData): string {\n if (data.versions.length === 0) {\n return `<div class=\"px-6 py-4 text-sm text-zinc-500 dark:text-zinc-400\">No versions found.</div>`\n }\n\n const rows = data.versions.map(v => `\n <div class=\"flex items-center justify-between px-6 py-3 border-b border-zinc-100 dark:border-zinc-800 last:border-0\">\n <div class=\"flex items-center gap-3\">\n <span class=\"text-sm font-medium text-zinc-950 dark:text-white\">v${v.versionNumber}</span>\n ${v.isPublished ? `<span class=\"inline-flex items-center rounded-md bg-green-50 dark:bg-green-500/10 px-1.5 py-0.5 text-xs font-medium text-green-700 dark:text-green-400\">live</span>` : ''}\n ${v.isCurrentDraft ? `<span class=\"inline-flex items-center rounded-md bg-blue-50 dark:bg-blue-500/10 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:text-blue-400\">draft</span>` : ''}\n </div>\n <div class=\"flex items-center gap-4 text-xs text-zinc-500 dark:text-zinc-400\">\n <span>${v.createdBy ?? '—'}</span>\n <span>${new Date(v.updatedAt * 1000).toLocaleString('en-US', { dateStyle: 'short', timeStyle: 'short' })}</span>\n </div>\n </div>\n `).join('')\n\n return `<div>${rows}</div>`\n}\n","import { escapeHtml } from '../../utils/sanitize'\n\nexport type AlertType = 'success' | 'error' | 'warning' | 'info'\n\nexport interface AlertData {\n type: AlertType\n title?: string\n message: string\n dismissible?: boolean\n className?: string\n icon?: boolean\n}\n\nexport function renderAlert(data: AlertData): string {\n const typeClasses = {\n success: 'bg-green-50 dark:bg-green-500/10 border border-green-600/20 dark:border-green-500/20',\n error: 'bg-error/10 border border-red-600/20 dark:border-red-500/20',\n warning: 'bg-amber-50 dark:bg-amber-500/10 border border-amber-600/20 dark:border-amber-500/20',\n info: 'bg-blue-50 dark:bg-blue-500/10 border border-blue-600/20 dark:border-blue-500/20'\n }\n\n const iconClasses = {\n success: 'text-green-600 dark:text-green-400',\n error: 'text-red-600 dark:text-red-400',\n warning: 'text-amber-600 dark:text-amber-400',\n info: 'text-blue-600 dark:text-blue-400'\n }\n\n const textClasses = {\n success: 'text-green-900 dark:text-green-300',\n error: 'text-red-900 dark:text-red-300',\n warning: 'text-amber-900 dark:text-amber-300',\n info: 'text-blue-900 dark:text-blue-300'\n }\n\n const messageTextClasses = {\n success: 'text-green-700 dark:text-green-400',\n error: 'text-red-700 dark:text-red-400',\n warning: 'text-amber-700 dark:text-amber-400',\n info: 'text-blue-700 dark:text-blue-400'\n }\n\n const icons = {\n success: `<path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clip-rule=\"evenodd\" />`,\n error: `<path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z\" clip-rule=\"evenodd\" />`,\n warning: `<path fill-rule=\"evenodd\" d=\"M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z\" clip-rule=\"evenodd\" />`,\n info: `<path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z\" clip-rule=\"evenodd\" />`\n }\n\n return `\n <div class=\"rounded-lg p-4 ${typeClasses[data.type]} ${data.className || ''}\" ${data.dismissible ? 'id=\"dismissible-alert\"' : ''}>\n <div class=\"flex\">\n ${data.icon !== false ? `\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 ${iconClasses[data.type]}\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n ${icons[data.type]}\n </svg>\n </div>\n ` : ''}\n <div class=\"${data.icon !== false ? 'ml-3' : ''}\">\n ${data.title ? `\n <h3 class=\"text-sm font-semibold ${textClasses[data.type]}\">\n ${escapeHtml(data.title)}\n </h3>\n ` : ''}\n <div class=\"${data.title ? 'mt-1 text-sm' : 'text-sm'} ${messageTextClasses[data.type]}\">\n <p>${escapeHtml(data.message)}</p>\n </div>\n </div>\n ${data.dismissible ? `\n <div class=\"ml-auto pl-3\">\n <div class=\"-mx-1.5 -my-1.5\">\n <button\n type=\"button\"\n class=\"inline-flex rounded-md p-1.5 ${iconClasses[data.type]} hover:bg-opacity-20 focus:outline-none focus:ring-2 focus:ring-offset-2\"\n onclick=\"document.getElementById('dismissible-alert').remove()\"\n >\n <span class=\"sr-only\">Dismiss</span>\n <svg class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z\" clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n </div>\n ` : ''}\n </div>\n </div>\n `\n}\n\nexport function renderSuccessAlert(message: string, title?: string): string {\n return renderAlert({ type: 'success', message, title })\n}\n\nexport function renderErrorAlert(message: string, title?: string): string {\n return renderAlert({ type: 'error', message, title })\n}\n\nexport function renderWarningAlert(message: string, title?: string): string {\n return renderAlert({ type: 'warning', message, title })\n}\n\nexport function renderInfoAlert(message: string, title?: string): string {\n return renderAlert({ type: 'info', message, title })\n}\n"]}
@@ -0,0 +1,88 @@
1
+ // src/plugins/hooks/catalog.ts
2
+ var HOOK_EVENT_NAMES = [
3
+ "content:read",
4
+ "content:before:create",
5
+ "content:before:update",
6
+ "content:before:delete",
7
+ "content:after:create",
8
+ "content:after:update",
9
+ "content:after:delete",
10
+ "content:after:publish",
11
+ "auth:registration:completed",
12
+ "auth:password-reset:requested",
13
+ "auth:password-reset:completed",
14
+ "auth:magic-link:consumed",
15
+ "auth:otp:verified"
16
+ ];
17
+ function isKnownHookEvent(name) {
18
+ return HOOK_EVENT_NAMES.includes(name);
19
+ }
20
+ var LEGACY_EVENT_ALIASES = {
21
+ "content:create": "content:after:create",
22
+ "content:update": "content:after:update",
23
+ "content:delete": "content:after:delete",
24
+ "content:publish": "content:after:publish",
25
+ "content:save": "content:after:update"
26
+ };
27
+ function isLegacyHookEvent(name) {
28
+ return Object.prototype.hasOwnProperty.call(LEGACY_EVENT_ALIASES, name);
29
+ }
30
+ function resolveHookEventName(name) {
31
+ if (isKnownHookEvent(name)) return name;
32
+ if (isLegacyHookEvent(name)) return LEGACY_EVENT_ALIASES[name];
33
+ return void 0;
34
+ }
35
+
36
+ // src/plugins/hooks/typed-hooks.ts
37
+ var warnedLegacyEvents = /* @__PURE__ */ new Set();
38
+ function createTypedHooks(hookSystem) {
39
+ return {
40
+ on(event, handler, priority) {
41
+ const canonical = resolveHookEventName(event) ?? event;
42
+ if (isLegacyHookEvent(event) && !warnedLegacyEvents.has(event)) {
43
+ warnedLegacyEvents.add(event);
44
+ console.warn(
45
+ `[hooks] event "${event}" is deprecated; subscribe to "${canonical}" instead. The alias will be removed in a future release.`
46
+ );
47
+ }
48
+ hookSystem.register(
49
+ canonical,
50
+ async (data, context) => {
51
+ const result = await handler(data, context ?? {});
52
+ return result === void 0 ? data : result;
53
+ },
54
+ priority
55
+ );
56
+ },
57
+ async dispatch(event, payload, context) {
58
+ return await hookSystem.execute(event, payload, context);
59
+ }
60
+ };
61
+ }
62
+
63
+ // src/plugins/hooks/hook-system-singleton.ts
64
+ var current;
65
+ function setHookSystem(hookSystem) {
66
+ current = hookSystem;
67
+ }
68
+ function getHookSystem() {
69
+ if (!current) {
70
+ throw new Error(
71
+ "Hook system has not been initialized. setHookSystem() must be called (the app factory does this at construction) before getHookSystem()."
72
+ );
73
+ }
74
+ return current;
75
+ }
76
+ function hasHookSystem() {
77
+ return current !== void 0;
78
+ }
79
+ function resetHookSystem() {
80
+ current = void 0;
81
+ }
82
+ function getTypedHooks() {
83
+ return createTypedHooks(getHookSystem());
84
+ }
85
+
86
+ export { HOOK_EVENT_NAMES, LEGACY_EVENT_ALIASES, createTypedHooks, getHookSystem, getTypedHooks, hasHookSystem, isKnownHookEvent, isLegacyHookEvent, resetHookSystem, resolveHookEventName, setHookSystem };
87
+ //# sourceMappingURL=chunk-RNZFGN4R.js.map
88
+ //# sourceMappingURL=chunk-RNZFGN4R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugins/hooks/catalog.ts","../src/plugins/hooks/typed-hooks.ts","../src/plugins/hooks/hook-system-singleton.ts"],"names":[],"mappings":";AAkHO,IAAM,gBAAA,GAAmB;AAAA,EAC9B,cAAA;AAAA,EACA,uBAAA;AAAA,EACA,uBAAA;AAAA,EACA,uBAAA;AAAA,EACA,sBAAA;AAAA,EACA,sBAAA;AAAA,EACA,sBAAA;AAAA,EACA,uBAAA;AAAA,EACA,6BAAA;AAAA,EACA,+BAAA;AAAA,EACA,+BAAA;AAAA,EACA,0BAAA;AAAA,EACA;AACF;AAGO,SAAS,iBAAiB,IAAA,EAAqC;AACpE,EAAA,OAAQ,gBAAA,CAAuC,SAAS,IAAI,CAAA;AAC9D;AAmBO,IAAM,oBAAA,GAAuB;AAAA,EAClC,gBAAA,EAAkB,sBAAA;AAAA,EAClB,gBAAA,EAAkB,sBAAA;AAAA,EAClB,gBAAA,EAAkB,sBAAA;AAAA,EAClB,iBAAA,EAAmB,uBAAA;AAAA,EACnB,cAAA,EAAgB;AAClB;AAOO,SAAS,kBAAkB,IAAA,EAA2C;AAC3E,EAAA,OAAO,MAAA,CAAO,SAAA,CAAU,cAAA,CAAe,IAAA,CAAK,sBAAsB,IAAI,CAAA;AACxE;AAOO,SAAS,qBAAqB,IAAA,EAAyC;AAC5E,EAAA,IAAI,gBAAA,CAAiB,IAAI,CAAA,EAAG,OAAO,IAAA;AACnC,EAAA,IAAI,iBAAA,CAAkB,IAAI,CAAA,EAAG,OAAO,qBAAqB,IAAI,CAAA;AAC7D,EAAA,OAAO,MAAA;AACT;;;AC9FA,IAAM,kBAAA,uBAAyB,GAAA,EAAY;AAWpC,SAAS,iBAAiB,UAAA,EAAwC;AACvE,EAAA,OAAO;AAAA,IACL,EAAA,CAAG,KAAA,EAAO,OAAA,EAAS,QAAA,EAAU;AAC3B,MAAA,MAAM,SAAA,GAAY,oBAAA,CAAqB,KAAK,CAAA,IAAK,KAAA;AACjD,MAAA,IAAI,kBAAkB,KAAK,CAAA,IAAK,CAAC,kBAAA,CAAmB,GAAA,CAAI,KAAK,CAAA,EAAG;AAC9D,QAAA,kBAAA,CAAmB,IAAI,KAAK,CAAA;AAE5B,QAAA,OAAA,CAAQ,IAAA;AAAA,UACN,CAAA,eAAA,EAAkB,KAAK,CAAA,+BAAA,EAAkC,SAAS,CAAA,yDAAA;AAAA,SAEpE;AAAA,MACF;AACA,MAAA,UAAA,CAAW,QAAA;AAAA,QACT,SAAA;AAAA,QACA,OAAO,MAAW,OAAA,KAAiB;AACjC,UAAA,MAAM,SAAS,MAAM,OAAA,CAAQ,IAAA,EAAM,OAAA,IAAW,EAAE,CAAA;AAChD,UAAA,OAAO,MAAA,KAAW,SAAY,IAAA,GAAO,MAAA;AAAA,QACvC,CAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF,CAAA;AAAA,IACA,MAAM,QAAA,CAAS,KAAA,EAAO,OAAA,EAAS,OAAA,EAAS;AACtC,MAAA,OAAQ,MAAM,UAAA,CAAW,OAAA,CAAQ,KAAA,EAAO,SAAS,OAAO,CAAA;AAAA,IAC1D;AAAA,GACF;AACF;;;ACrGA,IAAI,OAAA;AAGG,SAAS,cAAc,UAAA,EAAkC;AAC9D,EAAA,OAAA,GAAU,UAAA;AACZ;AAMO,SAAS,aAAA,GAAgC;AAC9C,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AAGO,SAAS,aAAA,GAAyB;AACvC,EAAA,OAAO,OAAA,KAAY,MAAA;AACrB;AAGO,SAAS,eAAA,GAAwB;AACtC,EAAA,OAAA,GAAU,MAAA;AACZ;AAGO,SAAS,aAAA,GAA4B;AAC1C,EAAA,OAAO,gBAAA,CAAiB,eAAe,CAAA;AACzC","file":"chunk-RNZFGN4R.js","sourcesContent":["/**\n * Typed hook event catalog\n *\n * The single source of truth for which lifecycle events a plugin may subscribe\n * to, and the payload shape each one carries. Subscribing through the typed\n * facade (`createTypedHooks`) gives a plugin the narrowed payload type with no\n * casting — TypeScript rejects a wrong field name at the `.on()` call site.\n *\n * Scope note: this catalog lists the events that are (or are being) dispatched\n * in production. The legacy string-keyed `HOOKS` map in `../types` declared many\n * more events than were ever fired; reconciling that list down to what actually\n * dispatches is tracked in the plugin-overhaul plan. Add an event here only when\n * a real dispatch site exists (or is landing in the same change).\n */\n\n/**\n * The acting user on a hook event. ONE canonical shape across every event —\n * always `id` (never `userId`), so a plugin reading `payload.user.id` works on\n * content events and auth events alike.\n */\nexport interface HookActor {\n id: string\n email: string\n role?: string\n}\n\n/** Common shape for content lifecycle events. */\nexport interface ContentEventPayload {\n /** Collection / content-type slug the event is about. */\n collection: string\n /** Content row id, when known (absent for pre-create events). */\n id?: string\n /** The content data being read/written. Mutable by `before` handlers in the chain. */\n data: Record<string, unknown>\n /** The acting user, when the event originates from an authenticated request. */\n user?: HookActor\n}\n\n/** Emitted after a user completes self-registration. */\nexport interface AuthRegistrationCompletedPayload {\n user: HookActor\n}\n\n/** Emitted when a password reset is requested (carries the reset token internally). */\nexport interface AuthPasswordResetRequestedPayload {\n user: HookActor\n /** Single-use reset token. Never expose this in an API response. */\n resetToken: string\n}\n\n/** Emitted after a password reset is confirmed. */\nexport interface AuthPasswordResetCompletedPayload {\n user: HookActor\n}\n\n/** Emitted after a magic-link sign-in link is successfully consumed. */\nexport interface AuthMagicLinkConsumedPayload {\n user: HookActor\n}\n\n/** Emitted after an OTP code is successfully verified. */\nexport interface AuthOtpVerifiedPayload {\n user: HookActor\n}\n\n/**\n * The catalog: event name → payload type.\n *\n * Keep keys in sync with `HookEventName` (derived below) and with the dispatch\n * sites. This is an interface (not a const) so it participates in type-level\n * lookups and can be augmented via declaration merging if a downstream package\n * needs to extend it.\n */\n/* eslint-disable @typescript-eslint/naming-convention -- event names are domain identifiers that contain colons (e.g. content:after:create) */\nexport interface HookEventPayloads {\n // Content lifecycle — read\n 'content:read': ContentEventPayload\n\n // Content lifecycle — before (gate/transform; handlers may mutate the payload\n // or throw to cancel the write)\n 'content:before:create': ContentEventPayload\n 'content:before:update': ContentEventPayload\n 'content:before:delete': ContentEventPayload\n\n // Content lifecycle — after (side effects; the write has happened)\n 'content:after:create': ContentEventPayload\n 'content:after:update': ContentEventPayload\n 'content:after:delete': ContentEventPayload\n 'content:after:publish': ContentEventPayload\n\n // Auth events\n 'auth:registration:completed': AuthRegistrationCompletedPayload\n 'auth:password-reset:requested': AuthPasswordResetRequestedPayload\n 'auth:password-reset:completed': AuthPasswordResetCompletedPayload\n 'auth:magic-link:consumed': AuthMagicLinkConsumedPayload\n 'auth:otp:verified': AuthOtpVerifiedPayload\n}\n/* eslint-enable @typescript-eslint/naming-convention */\n\n/** Union of all catalog event names. */\nexport type HookEventName = keyof HookEventPayloads\n\n/** The payload type for a given event name. */\nexport type HookPayload<E extends HookEventName> = HookEventPayloads[E]\n\n/**\n * Runtime list of catalog event names.\n *\n * Useful for validation (e.g. \"is this a known event?\") and for diagnostics.\n * Kept as a typed tuple so it can't silently drift from the interface: any new\n * key added to `HookEventPayloads` should be added here too, and the\n * `satisfies` check below fails the build if the list references an unknown\n * event.\n */\nexport const HOOK_EVENT_NAMES = [\n 'content:read',\n 'content:before:create',\n 'content:before:update',\n 'content:before:delete',\n 'content:after:create',\n 'content:after:update',\n 'content:after:delete',\n 'content:after:publish',\n 'auth:registration:completed',\n 'auth:password-reset:requested',\n 'auth:password-reset:completed',\n 'auth:magic-link:consumed',\n 'auth:otp:verified',\n] as const satisfies readonly HookEventName[]\n\n/** True if `name` is a canonical catalog event. */\nexport function isKnownHookEvent(name: string): name is HookEventName {\n return (HOOK_EVENT_NAMES as readonly string[]).includes(name)\n}\n\n// ── Legacy event aliases (one-release deprecation window) ────────────────────\n// The pre-before/after names. Subscribing to one still works but resolves to the\n// canonical name and emits a one-time deprecation warning (see typed-hooks `on`).\n// Dispatch is canonical-only — the host controls dispatch sites.\n\n/** Deprecated event names mapped to their canonical payload type. */\n/* eslint-disable @typescript-eslint/naming-convention -- legacy event identifiers contain colons */\nexport interface LegacyHookEventPayloads {\n 'content:create': ContentEventPayload\n 'content:update': ContentEventPayload\n 'content:delete': ContentEventPayload\n 'content:publish': ContentEventPayload\n /** No after-only successor; folded into update. */\n 'content:save': ContentEventPayload\n}\n\n/** Map each deprecated name to the canonical name it resolves to. */\nexport const LEGACY_EVENT_ALIASES = {\n 'content:create': 'content:after:create',\n 'content:update': 'content:after:update',\n 'content:delete': 'content:after:delete',\n 'content:publish': 'content:after:publish',\n 'content:save': 'content:after:update',\n} as const satisfies Record<keyof LegacyHookEventPayloads, HookEventName>\n/* eslint-enable @typescript-eslint/naming-convention */\n\n/** A deprecated event name accepted (with a warning) at subscribe time. */\nexport type LegacyHookEventName = keyof LegacyHookEventPayloads\n\n/** True if `name` is a deprecated alias. */\nexport function isLegacyHookEvent(name: string): name is LegacyHookEventName {\n return Object.prototype.hasOwnProperty.call(LEGACY_EVENT_ALIASES, name)\n}\n\n/**\n * Resolve a subscribe-time event name to its canonical form: returns the name\n * itself if canonical, the aliased canonical name if deprecated, or `undefined`\n * if unknown.\n */\nexport function resolveHookEventName(name: string): HookEventName | undefined {\n if (isKnownHookEvent(name)) return name\n if (isLegacyHookEvent(name)) return LEGACY_EVENT_ALIASES[name]\n return undefined\n}\n","/**\n * Typed hook facade\n *\n * Wraps the (string-keyed, untyped) hook system in a catalog-aware API:\n *\n * const hooks = createTypedHooks(hookSystem)\n * hooks.on('auth:registration:completed', (payload) => {\n * payload.user.email // ✓ narrowed — no cast\n * payload.user.nope // ✗ type error\n * })\n * await hooks.dispatch('auth:registration:completed', { user: {...} })\n *\n * The facade is intentionally structural about the underlying hook system (see\n * `HookSystemLike`) so both `HookSystemImpl` and `ScopedHookSystem` — and the\n * `src`/`dist` duplicate type identities — all satisfy it without casts.\n */\n\nimport type { HookEventName, HookPayload, LegacyHookEventName, LegacyHookEventPayloads } from './catalog'\nimport { isLegacyHookEvent, resolveHookEventName } from './catalog'\n\n/** A name accepted at subscribe time: a canonical event or a deprecated alias. */\nexport type SubscribableEvent = HookEventName | LegacyHookEventName\n\n/** The payload type for a subscribable name — canonical payload, even for aliases. */\nexport type PayloadForEvent<E extends SubscribableEvent> = E extends HookEventName\n ? HookPayload<E>\n : E extends LegacyHookEventName\n ? LegacyHookEventPayloads[E]\n : never\n\n/**\n * Minimal structural contract the typed facade needs from a hook system.\n * Satisfied by `HookSystemImpl` and `ScopedHookSystem`.\n */\nexport interface HookSystemLike {\n register(hookName: string, handler: (data: any, context: any) => any, priority?: number): void\n execute(hookName: string, data: any, context?: any): Promise<any>\n unregister?(hookName: string, handler: (data: any, context: any) => any): void\n}\n\n/** Context passed to a typed hook handler (kept loose; mirrors the legacy HookContext). */\nexport interface TypedHookContext {\n /** Plugin that registered the hook, if known. */\n plugin?: string\n /** Cancel the remaining hook chain. */\n cancel?: () => void\n [key: string]: unknown\n}\n\n/**\n * A typed hook handler. May mutate and return the payload (threaded to the next\n * handler), or return nothing (the current payload is preserved).\n */\nexport type TypedHookHandler<E extends HookEventName> = (\n payload: HookPayload<E>,\n context: TypedHookContext\n) => HookPayload<E> | void | Promise<HookPayload<E> | void>\n\nexport interface TypedHooks {\n /**\n * Subscribe to a catalog event. Accepts canonical names and (for one release)\n * deprecated aliases — an alias resolves to its canonical name and emits a\n * one-time deprecation warning. Lower priority runs earlier (default 10).\n */\n on<E extends SubscribableEvent>(\n event: E,\n handler: (\n payload: PayloadForEvent<E>,\n context: TypedHookContext\n ) => PayloadForEvent<E> | void | Promise<PayloadForEvent<E> | void>,\n priority?: number\n ): void\n /**\n * Dispatch a catalog event through the handler chain. Canonical names only —\n * the host owns dispatch sites. Returns the (possibly mutated) payload.\n */\n dispatch<E extends HookEventName>(\n event: E,\n payload: HookPayload<E>,\n context?: TypedHookContext\n ): Promise<HookPayload<E>>\n}\n\n// One-time deprecation warnings, keyed by the deprecated name (process-wide).\nconst warnedLegacyEvents = new Set<string>()\n\n/**\n * Build a typed facade over a hook system.\n *\n * `on()` resolves deprecated aliases to canonical names (warning once), then\n * registers under the canonical name so a legacy subscriber fires when the host\n * dispatches the canonical event. Returning `void` from a handler preserves the\n * current payload in the chain (the underlying `execute()` threads whatever each\n * handler returns, so we coalesce `undefined` back to the incoming data).\n */\nexport function createTypedHooks(hookSystem: HookSystemLike): TypedHooks {\n return {\n on(event, handler, priority) {\n const canonical = resolveHookEventName(event) ?? event\n if (isLegacyHookEvent(event) && !warnedLegacyEvents.has(event)) {\n warnedLegacyEvents.add(event)\n // eslint-disable-next-line no-console\n console.warn(\n `[hooks] event \"${event}\" is deprecated; subscribe to \"${canonical}\" instead. ` +\n `The alias will be removed in a future release.`\n )\n }\n hookSystem.register(\n canonical,\n async (data: any, context: any) => {\n const result = await handler(data, context ?? {})\n return result === undefined ? data : result\n },\n priority\n )\n },\n async dispatch(event, payload, context) {\n return (await hookSystem.execute(event, payload, context)) as any\n },\n }\n}\n","/**\n * Hook-system singleton\n *\n * Gives env-independent access to the app's hook system. Code that runs outside\n * the HTTP request context — most importantly scheduled (cron) handlers, which\n * have no per-request `c.env` — needs a way to reach the hook system without\n * threading it through every call. The app sets the singleton eagerly at\n * construction; everything else reads it.\n *\n * Contract: `getHookSystem()` throws if read before the app has set one\n * (throw-before-get), which surfaces wiring-order bugs loudly instead of\n * silently no-oping. `setHookSystem()` is idempotent (last write wins) so that\n * constructing multiple apps in one process — e.g. across tests — does not\n * throw; call `resetHookSystem()` in test teardown for isolation.\n */\n\nimport type { HookSystemLike, TypedHooks } from './typed-hooks'\nimport { createTypedHooks } from './typed-hooks'\n\nlet current: HookSystemLike | undefined\n\n/** Set the process-wide hook system. Last write wins. */\nexport function setHookSystem(hookSystem: HookSystemLike): void {\n current = hookSystem\n}\n\n/**\n * Get the process-wide hook system.\n * @throws if no hook system has been set yet.\n */\nexport function getHookSystem(): HookSystemLike {\n if (!current) {\n throw new Error(\n 'Hook system has not been initialized. ' +\n 'setHookSystem() must be called (the app factory does this at construction) before getHookSystem().'\n )\n }\n return current\n}\n\n/** True if a hook system has been set. */\nexport function hasHookSystem(): boolean {\n return current !== undefined\n}\n\n/** Clear the singleton. Intended for test isolation. */\nexport function resetHookSystem(): void {\n current = undefined\n}\n\n/** Convenience: a typed facade over the current singleton hook system. */\nexport function getTypedHooks(): TypedHooks {\n return createTypedHooks(getHookSystem())\n}\n"]}