@geekity/cms 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (637) hide show
  1. package/README.md +2091 -214
  2. package/admin/components/fields.njk +165 -0
  3. package/admin/layouts/base.njk +1 -1
  4. package/admin/layouts/settings-page.njk +26 -0
  5. package/admin/layouts/shell.njk +17 -3
  6. package/admin/pages/account/forgot.njk +47 -0
  7. package/admin/pages/account/login.njk +26 -0
  8. package/admin/pages/account/reset.njk +38 -0
  9. package/admin/pages/account/setup.njk +28 -0
  10. package/admin/pages/appearance/themes.njk +69 -0
  11. package/admin/pages/comments/all.njk +133 -0
  12. package/admin/{layouts/dashboard.njk → pages/dashboard/home.njk} +8 -15
  13. package/admin/{layouts/document-conflict.njk → pages/documents/conflict.njk} +1 -0
  14. package/admin/pages/documents/editor.njk +196 -0
  15. package/admin/{layouts/document-list.njk → pages/documents/list.njk} +19 -2
  16. package/admin/pages/documents/taxonomy.njk +95 -0
  17. package/admin/pages/federation/followers.njk +238 -0
  18. package/admin/pages/media/library.njk +160 -0
  19. package/admin/pages/messages/all.njk +110 -0
  20. package/admin/{layouts → pages}/placeholder.njk +5 -1
  21. package/admin/pages/settings/discussion.njk +99 -0
  22. package/admin/pages/settings/email.njk +203 -0
  23. package/admin/pages/settings/federation.njk +75 -0
  24. package/admin/pages/settings/general.njk +29 -0
  25. package/admin/pages/settings/permalinks.njk +50 -0
  26. package/admin/pages/settings/reading.njk +72 -0
  27. package/admin/pages/users/edit.njk +239 -0
  28. package/admin/pages/users/list.njk +48 -0
  29. package/admin/pages/users/new.njk +41 -0
  30. package/admin/static/admin.css +498 -3
  31. package/admin/static/copy.js +52 -0
  32. package/admin/static/editor.js +16 -16
  33. package/admin/static/slug.js +61 -0
  34. package/dist/admin/accounts.d.ts +413 -0
  35. package/dist/admin/accounts.d.ts.map +1 -0
  36. package/dist/admin/accounts.js +680 -0
  37. package/dist/admin/accounts.js.map +1 -0
  38. package/dist/admin/appearance.d.ts +80 -0
  39. package/dist/admin/appearance.d.ts.map +1 -0
  40. package/dist/admin/appearance.js +136 -0
  41. package/dist/admin/appearance.js.map +1 -0
  42. package/dist/admin/comments.d.ts +90 -0
  43. package/dist/admin/comments.d.ts.map +1 -0
  44. package/dist/admin/comments.js +244 -0
  45. package/dist/admin/comments.js.map +1 -0
  46. package/dist/admin/credentials.d.ts +26 -2
  47. package/dist/admin/credentials.d.ts.map +1 -1
  48. package/dist/admin/credentials.js +35 -2
  49. package/dist/admin/credentials.js.map +1 -1
  50. package/dist/admin/documents.d.ts +127 -10
  51. package/dist/admin/documents.d.ts.map +1 -1
  52. package/dist/admin/documents.js +360 -41
  53. package/dist/admin/documents.js.map +1 -1
  54. package/dist/admin/federation.d.ts +151 -50
  55. package/dist/admin/federation.d.ts.map +1 -1
  56. package/dist/admin/federation.js +225 -93
  57. package/dist/admin/federation.js.map +1 -1
  58. package/dist/admin/formatting.d.ts +20 -0
  59. package/dist/admin/formatting.d.ts.map +1 -0
  60. package/dist/admin/formatting.js +72 -0
  61. package/dist/admin/formatting.js.map +1 -0
  62. package/dist/admin/headers.d.ts +65 -0
  63. package/dist/admin/headers.d.ts.map +1 -0
  64. package/dist/admin/headers.js +104 -0
  65. package/dist/admin/headers.js.map +1 -0
  66. package/dist/admin/index.d.ts +38 -12
  67. package/dist/admin/index.d.ts.map +1 -1
  68. package/dist/admin/index.js +25 -7
  69. package/dist/admin/index.js.map +1 -1
  70. package/dist/admin/media.d.ts +168 -0
  71. package/dist/admin/media.d.ts.map +1 -0
  72. package/dist/admin/media.js +295 -0
  73. package/dist/admin/media.js.map +1 -0
  74. package/dist/admin/menu.d.ts +79 -0
  75. package/dist/admin/menu.d.ts.map +1 -0
  76. package/dist/admin/menu.js +126 -0
  77. package/dist/admin/menu.js.map +1 -0
  78. package/dist/admin/messages.d.ts +80 -0
  79. package/dist/admin/messages.d.ts.map +1 -0
  80. package/dist/admin/messages.js +152 -0
  81. package/dist/admin/messages.js.map +1 -0
  82. package/dist/admin/preview.d.ts.map +1 -1
  83. package/dist/admin/preview.js +31 -7
  84. package/dist/admin/preview.js.map +1 -1
  85. package/dist/admin/recovery.d.ts +92 -0
  86. package/dist/admin/recovery.d.ts.map +1 -0
  87. package/dist/admin/recovery.js +229 -0
  88. package/dist/admin/recovery.js.map +1 -0
  89. package/dist/admin/routes.d.ts +0 -20
  90. package/dist/admin/routes.d.ts.map +1 -1
  91. package/dist/admin/routes.js +155 -105
  92. package/dist/admin/routes.js.map +1 -1
  93. package/dist/admin/settings-discussion.d.ts +37 -0
  94. package/dist/admin/settings-discussion.d.ts.map +1 -0
  95. package/dist/admin/settings-discussion.js +117 -0
  96. package/dist/admin/settings-discussion.js.map +1 -0
  97. package/dist/admin/settings-email.d.ts +54 -0
  98. package/dist/admin/settings-email.d.ts.map +1 -0
  99. package/dist/admin/settings-email.js +189 -0
  100. package/dist/admin/settings-email.js.map +1 -0
  101. package/dist/admin/settings-federation.d.ts +39 -0
  102. package/dist/admin/settings-federation.d.ts.map +1 -0
  103. package/dist/admin/settings-federation.js +103 -0
  104. package/dist/admin/settings-federation.js.map +1 -0
  105. package/dist/admin/settings-general.d.ts +12 -0
  106. package/dist/admin/settings-general.d.ts.map +1 -0
  107. package/dist/admin/settings-general.js +48 -0
  108. package/dist/admin/settings-general.js.map +1 -0
  109. package/dist/admin/settings-page.d.ts +102 -0
  110. package/dist/admin/settings-page.d.ts.map +1 -0
  111. package/dist/admin/settings-page.js +126 -0
  112. package/dist/admin/settings-page.js.map +1 -0
  113. package/dist/admin/settings-pages.d.ts +19 -0
  114. package/dist/admin/settings-pages.d.ts.map +1 -0
  115. package/dist/admin/settings-pages.js +33 -0
  116. package/dist/admin/settings-pages.js.map +1 -0
  117. package/dist/admin/settings-permalinks.d.ts +16 -0
  118. package/dist/admin/settings-permalinks.d.ts.map +1 -0
  119. package/dist/admin/settings-permalinks.js +32 -0
  120. package/dist/admin/settings-permalinks.js.map +1 -0
  121. package/dist/admin/settings-reading.d.ts +33 -0
  122. package/dist/admin/settings-reading.d.ts.map +1 -0
  123. package/dist/admin/settings-reading.js +48 -0
  124. package/dist/admin/settings-reading.js.map +1 -0
  125. package/dist/admin/settings.d.ts +325 -96
  126. package/dist/admin/settings.d.ts.map +1 -1
  127. package/dist/admin/settings.js +650 -308
  128. package/dist/admin/settings.js.map +1 -1
  129. package/dist/admin/store.d.ts +606 -152
  130. package/dist/admin/store.d.ts.map +1 -1
  131. package/dist/admin/store.js +863 -268
  132. package/dist/admin/store.js.map +1 -1
  133. package/dist/admin/taxonomy.d.ts +114 -0
  134. package/dist/admin/taxonomy.d.ts.map +1 -0
  135. package/dist/admin/taxonomy.js +289 -0
  136. package/dist/admin/taxonomy.js.map +1 -0
  137. package/dist/admin/templates.d.ts +44 -14
  138. package/dist/admin/templates.d.ts.map +1 -1
  139. package/dist/admin/templates.js +44 -14
  140. package/dist/admin/templates.js.map +1 -1
  141. package/dist/admin/throttle.d.ts +84 -0
  142. package/dist/admin/throttle.d.ts.map +1 -0
  143. package/dist/admin/throttle.js +143 -0
  144. package/dist/admin/throttle.js.map +1 -0
  145. package/dist/admin/uploads.d.ts +28 -2
  146. package/dist/admin/uploads.d.ts.map +1 -1
  147. package/dist/admin/uploads.js +43 -4
  148. package/dist/admin/uploads.js.map +1 -1
  149. package/dist/admin/users.d.ts +100 -13
  150. package/dist/admin/users.d.ts.map +1 -1
  151. package/dist/admin/users.js +412 -39
  152. package/dist/admin/users.js.map +1 -1
  153. package/dist/cache.d.ts +141 -0
  154. package/dist/cache.d.ts.map +1 -0
  155. package/dist/cache.js +207 -0
  156. package/dist/cache.js.map +1 -0
  157. package/dist/cli.d.ts +14 -2
  158. package/dist/cli.d.ts.map +1 -1
  159. package/dist/cli.js +424 -21
  160. package/dist/cli.js.map +1 -1
  161. package/dist/comments/akismet.d.ts +130 -0
  162. package/dist/comments/akismet.d.ts.map +1 -0
  163. package/dist/comments/akismet.js +274 -0
  164. package/dist/comments/akismet.js.map +1 -0
  165. package/dist/comments/conversation.d.ts +29 -0
  166. package/dist/comments/conversation.d.ts.map +1 -0
  167. package/dist/comments/conversation.js +60 -0
  168. package/dist/comments/conversation.js.map +1 -0
  169. package/dist/comments/form.d.ts +75 -0
  170. package/dist/comments/form.d.ts.map +1 -0
  171. package/dist/comments/form.js +64 -0
  172. package/dist/comments/form.js.map +1 -0
  173. package/dist/comments/index.d.ts +15 -0
  174. package/dist/comments/index.d.ts.map +1 -0
  175. package/dist/comments/index.js +9 -0
  176. package/dist/comments/index.js.map +1 -0
  177. package/dist/comments/markdown.d.ts +8 -0
  178. package/dist/comments/markdown.d.ts.map +1 -0
  179. package/dist/comments/markdown.js +85 -0
  180. package/dist/comments/markdown.js.map +1 -0
  181. package/dist/comments/moderate.d.ts +71 -0
  182. package/dist/comments/moderate.d.ts.map +1 -0
  183. package/dist/comments/moderate.js +88 -0
  184. package/dist/comments/moderate.js.map +1 -0
  185. package/dist/comments/policy.d.ts +57 -0
  186. package/dist/comments/policy.d.ts.map +1 -0
  187. package/dist/comments/policy.js +80 -0
  188. package/dist/comments/policy.js.map +1 -0
  189. package/dist/comments/records.d.ts +266 -0
  190. package/dist/comments/records.d.ts.map +1 -0
  191. package/dist/comments/records.js +487 -0
  192. package/dist/comments/records.js.map +1 -0
  193. package/dist/comments/routes.d.ts +58 -0
  194. package/dist/comments/routes.d.ts.map +1 -0
  195. package/dist/comments/routes.js +177 -0
  196. package/dist/comments/routes.js.map +1 -0
  197. package/dist/comments/submission.d.ts +263 -0
  198. package/dist/comments/submission.d.ts.map +1 -0
  199. package/dist/comments/submission.js +241 -0
  200. package/dist/comments/submission.js.map +1 -0
  201. package/dist/config.d.ts +190 -5
  202. package/dist/config.d.ts.map +1 -1
  203. package/dist/config.js +118 -6
  204. package/dist/config.js.map +1 -1
  205. package/dist/contact/delivery.d.ts +60 -0
  206. package/dist/contact/delivery.d.ts.map +1 -0
  207. package/dist/contact/delivery.js +87 -0
  208. package/dist/contact/delivery.js.map +1 -0
  209. package/dist/contact/form.d.ts +130 -0
  210. package/dist/contact/form.d.ts.map +1 -0
  211. package/dist/contact/form.js +163 -0
  212. package/dist/contact/form.js.map +1 -0
  213. package/dist/contact/index.d.ts +17 -0
  214. package/dist/contact/index.d.ts.map +1 -0
  215. package/dist/contact/index.js +13 -0
  216. package/dist/contact/index.js.map +1 -0
  217. package/dist/contact/records.d.ts +124 -0
  218. package/dist/contact/records.d.ts.map +1 -0
  219. package/dist/contact/records.js +218 -0
  220. package/dist/contact/records.js.map +1 -0
  221. package/dist/contact/routes.d.ts +17 -0
  222. package/dist/contact/routes.d.ts.map +1 -0
  223. package/dist/contact/routes.js +127 -0
  224. package/dist/contact/routes.js.map +1 -0
  225. package/dist/contact/submission.d.ts +103 -0
  226. package/dist/contact/submission.d.ts.map +1 -0
  227. package/dist/contact/submission.js +166 -0
  228. package/dist/contact/submission.js.map +1 -0
  229. package/dist/content/document.d.ts +7 -2
  230. package/dist/content/document.d.ts.map +1 -1
  231. package/dist/content/document.js +1 -0
  232. package/dist/content/document.js.map +1 -1
  233. package/dist/content/index.d.ts +7 -2
  234. package/dist/content/index.d.ts.map +1 -1
  235. package/dist/content/index.js +4 -1
  236. package/dist/content/index.js.map +1 -1
  237. package/dist/content/markdown.d.ts.map +1 -1
  238. package/dist/content/markdown.js +24 -2
  239. package/dist/content/markdown.js.map +1 -1
  240. package/dist/content/parser.d.ts.map +1 -1
  241. package/dist/content/parser.js +11 -4
  242. package/dist/content/parser.js.map +1 -1
  243. package/dist/content/save.d.ts +5 -0
  244. package/dist/content/save.d.ts.map +1 -1
  245. package/dist/content/save.js +44 -1
  246. package/dist/content/save.js.map +1 -1
  247. package/dist/content/schedule.d.ts +125 -0
  248. package/dist/content/schedule.d.ts.map +1 -0
  249. package/dist/content/schedule.js +207 -0
  250. package/dist/content/schedule.js.map +1 -0
  251. package/dist/content/search.d.ts +64 -0
  252. package/dist/content/search.d.ts.map +1 -0
  253. package/dist/content/search.js +136 -0
  254. package/dist/content/search.js.map +1 -0
  255. package/dist/content/store.d.ts +202 -7
  256. package/dist/content/store.d.ts.map +1 -1
  257. package/dist/content/store.js +374 -67
  258. package/dist/content/store.js.map +1 -1
  259. package/dist/content/sync.d.ts +7 -1
  260. package/dist/content/sync.d.ts.map +1 -1
  261. package/dist/content/sync.js +18 -10
  262. package/dist/content/sync.js.map +1 -1
  263. package/dist/content/time.d.ts +56 -0
  264. package/dist/content/time.d.ts.map +1 -0
  265. package/dist/content/time.js +141 -0
  266. package/dist/content/time.js.map +1 -0
  267. package/dist/content/writer.d.ts.map +1 -1
  268. package/dist/content/writer.js +3 -0
  269. package/dist/content/writer.js.map +1 -1
  270. package/dist/env.d.ts +58 -4
  271. package/dist/env.d.ts.map +1 -1
  272. package/dist/federation/actor.d.ts +95 -41
  273. package/dist/federation/actor.d.ts.map +1 -1
  274. package/dist/federation/actor.js +180 -53
  275. package/dist/federation/actor.js.map +1 -1
  276. package/dist/federation/article.d.ts +44 -17
  277. package/dist/federation/article.d.ts.map +1 -1
  278. package/dist/federation/article.js +98 -46
  279. package/dist/federation/article.js.map +1 -1
  280. package/dist/federation/delivery.d.ts +63 -15
  281. package/dist/federation/delivery.d.ts.map +1 -1
  282. package/dist/federation/delivery.js +154 -80
  283. package/dist/federation/delivery.js.map +1 -1
  284. package/dist/federation/federation.d.ts +22 -17
  285. package/dist/federation/federation.d.ts.map +1 -1
  286. package/dist/federation/federation.js +98 -77
  287. package/dist/federation/federation.js.map +1 -1
  288. package/dist/federation/followers.d.ts +3 -2
  289. package/dist/federation/followers.d.ts.map +1 -1
  290. package/dist/federation/followers.js +6 -5
  291. package/dist/federation/followers.js.map +1 -1
  292. package/dist/federation/import-wordpress.d.ts +128 -0
  293. package/dist/federation/import-wordpress.d.ts.map +1 -0
  294. package/dist/federation/import-wordpress.js +426 -0
  295. package/dist/federation/import-wordpress.js.map +1 -0
  296. package/dist/federation/inbox.d.ts +36 -13
  297. package/dist/federation/inbox.d.ts.map +1 -1
  298. package/dist/federation/inbox.js +120 -49
  299. package/dist/federation/inbox.js.map +1 -1
  300. package/dist/federation/index.d.ts +22 -10
  301. package/dist/federation/index.d.ts.map +1 -1
  302. package/dist/federation/index.js +13 -8
  303. package/dist/federation/index.js.map +1 -1
  304. package/dist/federation/keys.d.ts +98 -20
  305. package/dist/federation/keys.d.ts.map +1 -1
  306. package/dist/federation/keys.js +269 -39
  307. package/dist/federation/keys.js.map +1 -1
  308. package/dist/federation/mount.d.ts +42 -10
  309. package/dist/federation/mount.d.ts.map +1 -1
  310. package/dist/federation/mount.js +266 -26
  311. package/dist/federation/mount.js.map +1 -1
  312. package/dist/federation/paths.d.ts +23 -35
  313. package/dist/federation/paths.d.ts.map +1 -1
  314. package/dist/federation/paths.js +26 -39
  315. package/dist/federation/paths.js.map +1 -1
  316. package/dist/federation/records.d.ts +223 -0
  317. package/dist/federation/records.d.ts.map +1 -0
  318. package/dist/federation/records.js +506 -0
  319. package/dist/federation/records.js.map +1 -0
  320. package/dist/federation/relays.d.ts +114 -0
  321. package/dist/federation/relays.d.ts.map +1 -0
  322. package/dist/federation/relays.js +290 -0
  323. package/dist/federation/relays.js.map +1 -0
  324. package/dist/federation/replies.d.ts +80 -0
  325. package/dist/federation/replies.d.ts.map +1 -0
  326. package/dist/federation/replies.js +166 -0
  327. package/dist/federation/replies.js.map +1 -0
  328. package/dist/federation/wordpress.d.ts +131 -0
  329. package/dist/federation/wordpress.d.ts.map +1 -0
  330. package/dist/federation/wordpress.js +341 -0
  331. package/dist/federation/wordpress.js.map +1 -0
  332. package/dist/files/atomic.d.ts +103 -0
  333. package/dist/files/atomic.d.ts.map +1 -0
  334. package/dist/files/atomic.js +150 -0
  335. package/dist/files/atomic.js.map +1 -0
  336. package/dist/files/index.d.ts +3 -0
  337. package/dist/files/index.d.ts.map +1 -0
  338. package/dist/files/index.js +2 -0
  339. package/dist/files/index.js.map +1 -0
  340. package/dist/forms/protection.d.ts +85 -0
  341. package/dist/forms/protection.d.ts.map +1 -0
  342. package/dist/forms/protection.js +116 -0
  343. package/dist/forms/protection.js.map +1 -0
  344. package/dist/images/icons.d.ts +59 -0
  345. package/dist/images/icons.d.ts.map +1 -0
  346. package/dist/images/icons.js +131 -0
  347. package/dist/images/icons.js.map +1 -0
  348. package/dist/images/index.d.ts +7 -0
  349. package/dist/images/index.d.ts.map +1 -0
  350. package/dist/images/index.js +4 -0
  351. package/dist/images/index.js.map +1 -0
  352. package/dist/images/markup.d.ts +47 -0
  353. package/dist/images/markup.d.ts.map +1 -0
  354. package/dist/images/markup.js +175 -0
  355. package/dist/images/markup.js.map +1 -0
  356. package/dist/images/paths.d.ts +34 -0
  357. package/dist/images/paths.d.ts.map +1 -0
  358. package/dist/images/paths.js +41 -0
  359. package/dist/images/paths.js.map +1 -0
  360. package/dist/images/variants.d.ts +104 -0
  361. package/dist/images/variants.d.ts.map +1 -0
  362. package/dist/images/variants.js +320 -0
  363. package/dist/images/variants.js.map +1 -0
  364. package/dist/index.d.ts +123 -19
  365. package/dist/index.d.ts.map +1 -1
  366. package/dist/index.js +327 -25
  367. package/dist/index.js.map +1 -1
  368. package/dist/init.d.ts +18 -0
  369. package/dist/init.d.ts.map +1 -1
  370. package/dist/init.js +34 -3
  371. package/dist/init.js.map +1 -1
  372. package/dist/mail/brevo.d.ts +35 -0
  373. package/dist/mail/brevo.d.ts.map +1 -0
  374. package/dist/mail/brevo.js +85 -0
  375. package/dist/mail/brevo.js.map +1 -0
  376. package/dist/mail/credentials.d.ts +62 -0
  377. package/dist/mail/credentials.d.ts.map +1 -0
  378. package/dist/mail/credentials.js +86 -0
  379. package/dist/mail/credentials.js.map +1 -0
  380. package/dist/mail/index.d.ts +15 -0
  381. package/dist/mail/index.d.ts.map +1 -0
  382. package/dist/mail/index.js +8 -0
  383. package/dist/mail/index.js.map +1 -0
  384. package/dist/mail/memory.d.ts +35 -0
  385. package/dist/mail/memory.d.ts.map +1 -0
  386. package/dist/mail/memory.js +29 -0
  387. package/dist/mail/memory.js.map +1 -0
  388. package/dist/mail/provider.d.ts +80 -0
  389. package/dist/mail/provider.d.ts.map +1 -0
  390. package/dist/mail/provider.js +23 -0
  391. package/dist/mail/provider.js.map +1 -0
  392. package/dist/mail/service.d.ts +156 -0
  393. package/dist/mail/service.d.ts.map +1 -0
  394. package/dist/mail/service.js +265 -0
  395. package/dist/mail/service.js.map +1 -0
  396. package/dist/mail/smtp.d.ts +39 -0
  397. package/dist/mail/smtp.d.ts.map +1 -0
  398. package/dist/mail/smtp.js +58 -0
  399. package/dist/mail/smtp.js.map +1 -0
  400. package/dist/mail/templates.d.ts +91 -0
  401. package/dist/mail/templates.d.ts.map +1 -0
  402. package/dist/mail/templates.js +69 -0
  403. package/dist/mail/templates.js.map +1 -0
  404. package/dist/notifications/comments.d.ts +66 -0
  405. package/dist/notifications/comments.d.ts.map +1 -0
  406. package/dist/notifications/comments.js +151 -0
  407. package/dist/notifications/comments.js.map +1 -0
  408. package/dist/notifications/digest.d.ts +135 -0
  409. package/dist/notifications/digest.d.ts.map +1 -0
  410. package/dist/notifications/digest.js +287 -0
  411. package/dist/notifications/digest.js.map +1 -0
  412. package/dist/notifications/index.d.ts +13 -0
  413. package/dist/notifications/index.d.ts.map +1 -0
  414. package/dist/notifications/index.js +8 -0
  415. package/dist/notifications/index.js.map +1 -0
  416. package/dist/notifications/links.d.ts +60 -0
  417. package/dist/notifications/links.d.ts.map +1 -0
  418. package/dist/notifications/links.js +63 -0
  419. package/dist/notifications/links.js.map +1 -0
  420. package/dist/notifications/optouts.d.ts +48 -0
  421. package/dist/notifications/optouts.d.ts.map +1 -0
  422. package/dist/notifications/optouts.js +93 -0
  423. package/dist/notifications/optouts.js.map +1 -0
  424. package/dist/notifications/preferences.d.ts +158 -0
  425. package/dist/notifications/preferences.d.ts.map +1 -0
  426. package/dist/notifications/preferences.js +195 -0
  427. package/dist/notifications/preferences.js.map +1 -0
  428. package/dist/notifications/routes.d.ts +28 -0
  429. package/dist/notifications/routes.d.ts.map +1 -0
  430. package/dist/notifications/routes.js +184 -0
  431. package/dist/notifications/routes.js.map +1 -0
  432. package/dist/notifications/tokens.d.ts +60 -0
  433. package/dist/notifications/tokens.d.ts.map +1 -0
  434. package/dist/notifications/tokens.js +125 -0
  435. package/dist/notifications/tokens.js.map +1 -0
  436. package/dist/notify.d.ts +84 -0
  437. package/dist/notify.d.ts.map +1 -0
  438. package/dist/notify.js +149 -0
  439. package/dist/notify.js.map +1 -0
  440. package/dist/web/archive.d.ts +49 -0
  441. package/dist/web/archive.d.ts.map +1 -0
  442. package/dist/web/archive.js +59 -0
  443. package/dist/web/archive.js.map +1 -0
  444. package/dist/web/assets.d.ts +6 -5
  445. package/dist/web/assets.d.ts.map +1 -1
  446. package/dist/web/assets.js +5 -7
  447. package/dist/web/assets.js.map +1 -1
  448. package/dist/web/authors.d.ts +143 -0
  449. package/dist/web/authors.d.ts.map +1 -0
  450. package/dist/web/authors.js +178 -0
  451. package/dist/web/authors.js.map +1 -0
  452. package/dist/web/comments.d.ts +60 -0
  453. package/dist/web/comments.d.ts.map +1 -0
  454. package/dist/web/comments.js +207 -0
  455. package/dist/web/comments.js.map +1 -0
  456. package/dist/web/context.d.ts +162 -28
  457. package/dist/web/context.d.ts.map +1 -1
  458. package/dist/web/context.js +123 -26
  459. package/dist/web/context.js.map +1 -1
  460. package/dist/web/conversation.d.ts +209 -0
  461. package/dist/web/conversation.d.ts.map +1 -0
  462. package/dist/web/conversation.js +557 -0
  463. package/dist/web/conversation.js.map +1 -0
  464. package/dist/web/documents.d.ts +36 -6
  465. package/dist/web/documents.d.ts.map +1 -1
  466. package/dist/web/documents.js +52 -10
  467. package/dist/web/documents.js.map +1 -1
  468. package/dist/web/feed-atom.d.ts +22 -0
  469. package/dist/web/feed-atom.d.ts.map +1 -0
  470. package/dist/web/feed-atom.js +83 -0
  471. package/dist/web/feed-atom.js.map +1 -0
  472. package/dist/web/feed-item.d.ts +145 -0
  473. package/dist/web/feed-item.d.ts.map +1 -0
  474. package/dist/web/feed-item.js +120 -0
  475. package/dist/web/feed-item.js.map +1 -0
  476. package/dist/web/feed-json.d.ts +79 -0
  477. package/dist/web/feed-json.d.ts.map +1 -0
  478. package/dist/web/feed-json.js +53 -0
  479. package/dist/web/feed-json.js.map +1 -0
  480. package/dist/web/feed-rss.d.ts +26 -0
  481. package/dist/web/feed-rss.d.ts.map +1 -0
  482. package/dist/web/feed-rss.js +203 -0
  483. package/dist/web/feed-rss.js.map +1 -0
  484. package/dist/web/feed-source.d.ts +244 -0
  485. package/dist/web/feed-source.d.ts.map +1 -0
  486. package/dist/web/feed-source.js +201 -0
  487. package/dist/web/feed-source.js.map +1 -0
  488. package/dist/web/feed-xml.d.ts +63 -0
  489. package/dist/web/feed-xml.d.ts.map +1 -0
  490. package/dist/web/feed-xml.js +116 -0
  491. package/dist/web/feed-xml.js.map +1 -0
  492. package/dist/web/feeds.d.ts +31 -96
  493. package/dist/web/feeds.d.ts.map +1 -1
  494. package/dist/web/feeds.js +94 -198
  495. package/dist/web/feeds.js.map +1 -1
  496. package/dist/web/health.d.ts +31 -0
  497. package/dist/web/health.d.ts.map +1 -0
  498. package/dist/web/health.js +44 -0
  499. package/dist/web/health.js.map +1 -0
  500. package/dist/web/index.d.ts +30 -10
  501. package/dist/web/index.d.ts.map +1 -1
  502. package/dist/web/index.js +18 -7
  503. package/dist/web/index.js.map +1 -1
  504. package/dist/web/navigation.d.ts +73 -0
  505. package/dist/web/navigation.d.ts.map +1 -0
  506. package/dist/web/navigation.js +110 -0
  507. package/dist/web/navigation.js.map +1 -0
  508. package/dist/web/negotiate.d.ts +9 -0
  509. package/dist/web/negotiate.d.ts.map +1 -1
  510. package/dist/web/negotiate.js +6 -1
  511. package/dist/web/negotiate.js.map +1 -1
  512. package/dist/web/recent.d.ts +43 -0
  513. package/dist/web/recent.d.ts.map +1 -0
  514. package/dist/web/recent.js +37 -0
  515. package/dist/web/recent.js.map +1 -0
  516. package/dist/web/render.d.ts +197 -9
  517. package/dist/web/render.d.ts.map +1 -1
  518. package/dist/web/render.js +334 -17
  519. package/dist/web/render.js.map +1 -1
  520. package/dist/web/routes.d.ts +25 -8
  521. package/dist/web/routes.d.ts.map +1 -1
  522. package/dist/web/routes.js +845 -93
  523. package/dist/web/routes.js.map +1 -1
  524. package/dist/web/sanitize.d.ts +19 -0
  525. package/dist/web/sanitize.d.ts.map +1 -0
  526. package/dist/web/sanitize.js +272 -0
  527. package/dist/web/sanitize.js.map +1 -0
  528. package/dist/web/search.d.ts +79 -0
  529. package/dist/web/search.d.ts.map +1 -0
  530. package/dist/web/search.js +94 -0
  531. package/dist/web/search.js.map +1 -0
  532. package/dist/web/sitemap.d.ts +120 -0
  533. package/dist/web/sitemap.d.ts.map +1 -0
  534. package/dist/web/sitemap.js +199 -0
  535. package/dist/web/sitemap.js.map +1 -0
  536. package/dist/web/taxonomy.d.ts +152 -0
  537. package/dist/web/taxonomy.d.ts.map +1 -0
  538. package/dist/web/taxonomy.js +237 -0
  539. package/dist/web/taxonomy.js.map +1 -0
  540. package/dist/web/templates.d.ts +54 -16
  541. package/dist/web/templates.d.ts.map +1 -1
  542. package/dist/web/templates.js +109 -28
  543. package/dist/web/templates.js.map +1 -1
  544. package/dist/web/themes.d.ts +205 -0
  545. package/dist/web/themes.d.ts.map +1 -0
  546. package/dist/web/themes.js +277 -0
  547. package/dist/web/themes.js.map +1 -0
  548. package/dist/webmention/discovery.d.ts +51 -0
  549. package/dist/webmention/discovery.d.ts.map +1 -0
  550. package/dist/webmention/discovery.js +176 -0
  551. package/dist/webmention/discovery.js.map +1 -0
  552. package/dist/webmention/html.d.ts +71 -0
  553. package/dist/webmention/html.d.ts.map +1 -0
  554. package/dist/webmention/html.js +285 -0
  555. package/dist/webmention/html.js.map +1 -0
  556. package/dist/webmention/index.d.ts +12 -0
  557. package/dist/webmention/index.d.ts.map +1 -0
  558. package/dist/webmention/index.js +8 -0
  559. package/dist/webmention/index.js.map +1 -0
  560. package/dist/webmention/links.d.ts +18 -0
  561. package/dist/webmention/links.d.ts.map +1 -0
  562. package/dist/webmention/links.js +64 -0
  563. package/dist/webmention/links.js.map +1 -0
  564. package/dist/webmention/microformats.d.ts +93 -0
  565. package/dist/webmention/microformats.d.ts.map +1 -0
  566. package/dist/webmention/microformats.js +376 -0
  567. package/dist/webmention/microformats.js.map +1 -0
  568. package/dist/webmention/receive.d.ts +141 -0
  569. package/dist/webmention/receive.d.ts.map +1 -0
  570. package/dist/webmention/receive.js +264 -0
  571. package/dist/webmention/receive.js.map +1 -0
  572. package/dist/webmention/routes.d.ts +37 -0
  573. package/dist/webmention/routes.d.ts.map +1 -0
  574. package/dist/webmention/routes.js +75 -0
  575. package/dist/webmention/routes.js.map +1 -0
  576. package/dist/webmention/service.d.ts +94 -0
  577. package/dist/webmention/service.d.ts.map +1 -0
  578. package/dist/webmention/service.js +207 -0
  579. package/dist/webmention/service.js.map +1 -0
  580. package/package.json +11 -6
  581. package/templates/site/content/_data/site.json +1 -0
  582. package/templates/site/content/posts/2026-01-01-hello-world.md +5 -3
  583. package/templates/site/geekity.config.ts +11 -1
  584. package/templates/site/tsconfig.json +1 -1
  585. package/themes/default/README.md +1096 -50
  586. package/themes/default/layouts/404.njk +2 -3
  587. package/themes/default/layouts/author.njk +37 -0
  588. package/themes/default/layouts/base.njk +172 -31
  589. package/themes/default/layouts/category.njk +27 -0
  590. package/themes/default/layouts/front-page.njk +57 -0
  591. package/themes/default/layouts/home.njk +17 -1
  592. package/themes/default/layouts/page.njk +57 -5
  593. package/themes/default/layouts/post.njk +78 -19
  594. package/themes/default/layouts/search.njk +57 -0
  595. package/themes/default/layouts/tag.njk +3 -6
  596. package/themes/default/mail/comment-digest.html.njk +37 -0
  597. package/themes/default/mail/comment-digest.subject.njk +3 -0
  598. package/themes/default/mail/comment-digest.txt.njk +40 -0
  599. package/themes/default/mail/comment-pending.html.njk +27 -0
  600. package/themes/default/mail/comment-pending.subject.njk +2 -0
  601. package/themes/default/mail/comment-pending.txt.njk +24 -0
  602. package/themes/default/mail/comment-reply.html.njk +21 -0
  603. package/themes/default/mail/comment-reply.subject.njk +2 -0
  604. package/themes/default/mail/comment-reply.txt.njk +22 -0
  605. package/themes/default/mail/contact-message.html.njk +24 -0
  606. package/themes/default/mail/contact-message.subject.njk +5 -0
  607. package/themes/default/mail/contact-message.txt.njk +20 -0
  608. package/themes/default/mail/password-changed.html.njk +18 -0
  609. package/themes/default/mail/password-changed.subject.njk +2 -0
  610. package/themes/default/mail/password-changed.txt.njk +17 -0
  611. package/themes/default/mail/password-reset.html.njk +22 -0
  612. package/themes/default/mail/password-reset.subject.njk +2 -0
  613. package/themes/default/mail/password-reset.txt.njk +18 -0
  614. package/themes/default/mail/test.html.njk +14 -0
  615. package/themes/default/mail/test.subject.njk +2 -0
  616. package/themes/default/mail/test.txt.njk +12 -0
  617. package/themes/default/partials/archive.njk +27 -0
  618. package/themes/default/partials/bio.njk +77 -0
  619. package/themes/default/partials/comment-form.njk +109 -0
  620. package/themes/default/partials/contact-form.njk +88 -0
  621. package/themes/default/partials/conversation.njk +139 -0
  622. package/themes/default/partials/feeds.njk +26 -0
  623. package/themes/default/partials/jsonld.njk +120 -0
  624. package/themes/default/partials/pagination.njk +12 -6
  625. package/themes/default/partials/post-list.njk +51 -18
  626. package/themes/default/partials/search-form.njk +17 -0
  627. package/themes/default/partials/tags.njk +26 -5
  628. package/themes/default/static/highlight.js +4 -0
  629. package/themes/default/static/style.css +1119 -194
  630. package/themes/default/theme.json +5 -0
  631. package/admin/layouts/document-editor.njk +0 -167
  632. package/admin/layouts/federation.njk +0 -145
  633. package/admin/layouts/login.njk +0 -23
  634. package/admin/layouts/settings.njk +0 -116
  635. package/admin/layouts/setup.njk +0 -27
  636. package/admin/layouts/users.njk +0 -91
  637. /package/admin/{layouts/_flash.njk → components/flash.njk} +0 -0
@@ -0,0 +1,156 @@
1
+ import type { ResolvedConfig } from '../config.ts';
2
+ import type { ThemeSource } from '../web/themes.ts';
3
+ import type { MailAddress, MailProvider, MailProviderName } from './provider.ts';
4
+ import type { MailTemplates } from './templates.ts';
5
+ /**
6
+ * The one door out of this CMS for email.
7
+ *
8
+ * Every feature that emails — a password reset (TASK-54), a moderation notice
9
+ * (TASK-55), a contact form (TASK-56) — calls {@link MailService.send} and
10
+ * nothing else. What carries the message, whether the site is configured to
11
+ * send at all, how many times a refusal is worth trying again and where the
12
+ * attempts are written down are all decided here, once.
13
+ *
14
+ * Three things follow from that, and they are the whole design:
15
+ *
16
+ * - **A site with no mail configuration still works.** `send` resolves
17
+ * successfully, having sent nothing and said so in the log. A feature that
18
+ * emails is not a feature that breaks without a mail account.
19
+ * - **Nothing is decided at boot.** The settings and `data/mail.json` are read
20
+ * per send, so a key pasted into the settings screen sends the next message
21
+ * and a provider switched there takes effect at once, neither needing a
22
+ * restart — the same way every other setting behaves under decision-9.
23
+ * - **Sending is off the request.** Messages go on one queue, one at a time,
24
+ * and a refusal is tried again after a growing wait. A caller that wants the
25
+ * answer — the Send test email button — awaits the promise; a caller that
26
+ * does not simply drops it.
27
+ */
28
+ /** How many times a message is tried before the service gives up. */
29
+ export declare const DEFAULT_MAIL_ATTEMPTS = 3;
30
+ /** How long to wait before attempt `n + 1`: 2s, then 8s, then 18s. */
31
+ export declare function defaultMailBackoffMs(attempt: number): number;
32
+ /** Where the service says what it did and what it could not do. `console` will do. */
33
+ export interface MailLogger {
34
+ /** One line per message that went, and one per message that was not sent. */
35
+ info(message: string): void;
36
+ /** One line per attempt that failed, and one when the service gives up. */
37
+ warn(message: string): void;
38
+ }
39
+ /** Anything that can be addressed: an address, or one with a name on it. */
40
+ export type MailRecipient = string | MailAddress;
41
+ /** What {@link MailService.send} takes: a template, and who to send it to. */
42
+ export interface TemplateMail {
43
+ /** Who it goes to. One address or several. */
44
+ to: MailRecipient | readonly MailRecipient[];
45
+ /** Which message under `mail/` in the theme to render. */
46
+ template: string;
47
+ /**
48
+ * The subject, when the caller would rather decide it than let the
49
+ * template's `.subject.njk` do it.
50
+ */
51
+ subject?: string | undefined;
52
+ /** What the template renders against, on top of `site` and `baseUrl`. */
53
+ data?: Record<string, unknown> | undefined;
54
+ /** Where a reply goes, when it is not the site's own reply-to setting. */
55
+ replyTo?: MailRecipient | undefined;
56
+ }
57
+ /** What {@link MailService.sendRaw} takes: a message the caller built itself. */
58
+ export interface RawMail {
59
+ /** Who it goes to. */
60
+ to: MailRecipient | readonly MailRecipient[];
61
+ /** The subject line. */
62
+ subject: string;
63
+ /** The plain text body. */
64
+ text: string;
65
+ /** The HTML twin, when there is one. */
66
+ html?: string | undefined;
67
+ /** Where a reply goes, when it is not the site's own reply-to setting. */
68
+ replyTo?: MailRecipient | undefined;
69
+ }
70
+ /** What became of one message. Never throws; this is the whole answer. */
71
+ export interface MailResult {
72
+ /**
73
+ * Whether the caller may carry on. True for a message that went **and** for
74
+ * one a site with no mail configuration never sent: neither is a failure of
75
+ * the thing that asked for it.
76
+ */
77
+ readonly ok: boolean;
78
+ /** Whether it was never sent because the site sends no mail. */
79
+ readonly skipped: boolean;
80
+ /** Which provider carried it, or `none`. */
81
+ readonly provider: MailProviderName;
82
+ /** Who it was addressed to. */
83
+ readonly to: readonly string[];
84
+ /** The subject that was sent, after the template and the caller had their say. */
85
+ readonly subject: string;
86
+ /** How many attempts it took, or 0 when nothing was attempted. */
87
+ readonly attempts: number;
88
+ /** The id the provider gave it, which is what finds it in their logs. */
89
+ readonly messageId?: string | undefined;
90
+ /** Why it did not go, when it did not. */
91
+ readonly error?: string | undefined;
92
+ }
93
+ /** What {@link createMailService} needs. */
94
+ export interface CreateMailServiceOptions {
95
+ /**
96
+ * Config after defaults: the base URL, the content directory the settings
97
+ * are read from, the data directory the credentials are in, the theme
98
+ * directory the messages are looked up in, and whether templates are cached.
99
+ */
100
+ config: Pick<ResolvedConfig, 'baseUrl' | 'contentDir' | 'dataDir' | 'themesDir' | 'watch'>;
101
+ /**
102
+ * A provider named by the site, which wins outright over the settings and
103
+ * `data/mail.json` — the way a `commentChecker` in the config wins over the
104
+ * Akismet key. {@link createMemoryMailProvider} is what a test names here.
105
+ */
106
+ provider?: MailProvider | undefined;
107
+ /** The messages. Defaults to the theme's, packaged theme behind it. */
108
+ templates?: MailTemplates | undefined;
109
+ /**
110
+ * Which theme the messages come from, when this service builds its own
111
+ * templates. Defaults to one over the config's themes directory and the
112
+ * site's `theme` setting; `createCms` hands in the one the pages use, so a
113
+ * site has a single answer to which theme it is running.
114
+ */
115
+ themes?: ThemeSource | undefined;
116
+ /** Where attempts are logged. Defaults to `console`. */
117
+ logger?: MailLogger | undefined;
118
+ /** How many times one message is tried. Defaults to {@link DEFAULT_MAIL_ATTEMPTS}. */
119
+ attempts?: number | undefined;
120
+ /**
121
+ * How long to wait before attempt `n + 1`. Defaults to
122
+ * {@link defaultMailBackoffMs}; a test hands in something instant.
123
+ */
124
+ backoffMs?: ((attempt: number) => number) | undefined;
125
+ /** How the wait is taken. Defaults to a timer; a test hands in a stub. */
126
+ wait?: ((ms: number) => Promise<void>) | undefined;
127
+ }
128
+ /** Sends a site's email, or explains in the log why it did not. */
129
+ export interface MailService {
130
+ /**
131
+ * Whether anything would actually be sent: a provider named in the config,
132
+ * or a provider chosen in the settings whose credential is in
133
+ * `data/mail.json`. Read per call, so it changes the moment the file does.
134
+ */
135
+ configured(): boolean;
136
+ /** Which provider would carry the next message, or `none`. */
137
+ providerName(): MailProviderName;
138
+ /** The From line the next message would carry. */
139
+ from(): MailAddress;
140
+ /**
141
+ * Render a message from the theme and queue it.
142
+ *
143
+ * The promise resolves when the message has been sent, or given up on, or
144
+ * skipped — a caller that wants to report the outcome awaits it, and one
145
+ * that does not drops it with `void`. It never rejects: a failure is a
146
+ * result with `ok: false` and the provider's own words in `error`.
147
+ */
148
+ send(message: TemplateMail): Promise<MailResult>;
149
+ /** The same, for a caller that built the message itself and wants no template. */
150
+ sendRaw(message: RawMail): Promise<MailResult>;
151
+ /** Resolve once every queued message has finished, however it finished. */
152
+ settled(): Promise<void>;
153
+ }
154
+ /** Build the mail service for one site. */
155
+ export declare function createMailService(options: CreateMailServiceOptions): MailService;
156
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/mail/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AAG/F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,sFAAsF;AACtF,MAAM,WAAW,UAAU;IACzB,6EAA6E;IAC7E,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,2EAA2E;IAC3E,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,4EAA4E;AAC5E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,CAAC;AAEjD,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,EAAE,EAAE,aAAa,GAAG,SAAS,aAAa,EAAE,CAAC;IAC7C,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC3C,0EAA0E;IAC1E,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CACrC;AAED,iFAAiF;AACjF,MAAM,WAAW,OAAO;IACtB,sBAAsB;IACtB,EAAE,EAAE,aAAa,GAAG,SAAS,aAAa,EAAE,CAAC;IAC7C,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CACrC;AAED,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,4CAA4C;AAC5C,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC,CAAC;IAC3F;;;;OAIG;IACH,QAAQ,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACpC,uEAAuE;IACvE,SAAS,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACtC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,wDAAwD;IACxD,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;IACtD,0EAA0E;IAC1E,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;CACpD;AAED,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,UAAU,IAAI,OAAO,CAAC;IACtB,8DAA8D;IAC9D,YAAY,IAAI,gBAAgB,CAAC;IACjC,kDAAkD;IAClD,IAAI,IAAI,WAAW,CAAC;IACpB;;;;;;;OAOG;IACH,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACjD,kFAAkF;IAClF,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C,2EAA2E;IAC3E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,WAAW,CAyNhF"}
@@ -0,0 +1,265 @@
1
+ import { readSiteSettings } from '../admin/settings.js';
2
+ import { createThemeSource } from '../web/themes.js';
3
+ import { createBrevoProvider } from './brevo.js';
4
+ import { readMailCredentials } from './credentials.js';
5
+ import { createSmtpProvider } from './smtp.js';
6
+ import { createMailTemplates } from './templates.js';
7
+ /**
8
+ * The one door out of this CMS for email.
9
+ *
10
+ * Every feature that emails — a password reset (TASK-54), a moderation notice
11
+ * (TASK-55), a contact form (TASK-56) — calls {@link MailService.send} and
12
+ * nothing else. What carries the message, whether the site is configured to
13
+ * send at all, how many times a refusal is worth trying again and where the
14
+ * attempts are written down are all decided here, once.
15
+ *
16
+ * Three things follow from that, and they are the whole design:
17
+ *
18
+ * - **A site with no mail configuration still works.** `send` resolves
19
+ * successfully, having sent nothing and said so in the log. A feature that
20
+ * emails is not a feature that breaks without a mail account.
21
+ * - **Nothing is decided at boot.** The settings and `data/mail.json` are read
22
+ * per send, so a key pasted into the settings screen sends the next message
23
+ * and a provider switched there takes effect at once, neither needing a
24
+ * restart — the same way every other setting behaves under decision-9.
25
+ * - **Sending is off the request.** Messages go on one queue, one at a time,
26
+ * and a refusal is tried again after a growing wait. A caller that wants the
27
+ * answer — the Send test email button — awaits the promise; a caller that
28
+ * does not simply drops it.
29
+ */
30
+ /** How many times a message is tried before the service gives up. */
31
+ export const DEFAULT_MAIL_ATTEMPTS = 3;
32
+ /** How long to wait before attempt `n + 1`: 2s, then 8s, then 18s. */
33
+ export function defaultMailBackoffMs(attempt) {
34
+ return attempt * attempt * 2000;
35
+ }
36
+ /** Build the mail service for one site. */
37
+ export function createMailService(options) {
38
+ const { config } = options;
39
+ const logger = options.logger ?? console;
40
+ const attempts = Math.max(1, options.attempts ?? DEFAULT_MAIL_ATTEMPTS);
41
+ const backoffMs = options.backoffMs ?? defaultMailBackoffMs;
42
+ const wait = options.wait ??
43
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
44
+ const templates = options.templates ??
45
+ createMailTemplates({
46
+ themes: options.themes ??
47
+ createThemeSource({
48
+ themesDir: config.themesDir,
49
+ chosen: () => readSiteSettings(config.contentDir).theme,
50
+ }),
51
+ baseUrl: config.baseUrl,
52
+ noCache: config.watch,
53
+ });
54
+ // Messages are chained rather than sent at once, and the chain never
55
+ // rejects: one provider that hangs delays the next message and nothing else.
56
+ let chain = Promise.resolve();
57
+ function enqueue(task) {
58
+ const result = chain.then(task);
59
+ chain = result.then(ignore, ignore);
60
+ return result;
61
+ }
62
+ /**
63
+ * The provider that would carry the next message, built from the file as it
64
+ * reads now, or `undefined` when the site sends no mail.
65
+ *
66
+ * Built per send rather than held: it is how a credential saved on the
67
+ * settings screen reaches the very next message, and how one removed stops
68
+ * the one after it.
69
+ */
70
+ function provider() {
71
+ if (options.provider !== undefined)
72
+ return options.provider;
73
+ const settings = readSiteSettings(config.contentDir);
74
+ const credentials = readMailCredentials(config.dataDir);
75
+ if (settings.mailProvider === 'brevo' && credentials.brevo !== undefined) {
76
+ return createBrevoProvider({ apiKey: credentials.brevo.apiKey });
77
+ }
78
+ if (settings.mailProvider === 'smtp' && credentials.smtp !== undefined) {
79
+ return createSmtpProvider(credentials.smtp);
80
+ }
81
+ return undefined;
82
+ }
83
+ /** The From line, as the settings say it now. */
84
+ function from(settings) {
85
+ const address = settings.mailFromAddress === '' ? fallbackAddress(config.baseUrl) : settings.mailFromAddress;
86
+ const name = settings.mailFromName === '' ? settings.title : settings.mailFromName;
87
+ return name === '' ? { address } : { name, address };
88
+ }
89
+ /** Try one message until it goes or the site runs out of patience. */
90
+ async function deliver(carrier, message) {
91
+ const to = message.to.map((recipient) => recipient.address);
92
+ const where = `"${message.subject}" to ${to.join(', ')} via ${carrier.name}`;
93
+ let lastError = '';
94
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
95
+ const which = `attempt ${String(attempt)} of ${String(attempts)}`;
96
+ try {
97
+ const delivery = await carrier.deliver(message);
98
+ logger.info(`Sent ${where} (${which}): ${delivery.messageId ?? 'no message id'}`);
99
+ return {
100
+ ok: true,
101
+ skipped: false,
102
+ provider: carrier.name,
103
+ to,
104
+ subject: message.subject,
105
+ attempts: attempt,
106
+ ...(delivery.messageId === undefined ? {} : { messageId: delivery.messageId }),
107
+ };
108
+ }
109
+ catch (thrown) {
110
+ lastError = messageOf(thrown);
111
+ logger.warn(`Could not send ${where} (${which}): ${lastError}`);
112
+ if (attempt < attempts)
113
+ await wait(backoffMs(attempt));
114
+ }
115
+ }
116
+ logger.warn(`Gave up sending ${where} after ${String(attempts)} attempts: ${lastError}`);
117
+ return {
118
+ ok: false,
119
+ skipped: false,
120
+ provider: carrier.name,
121
+ to,
122
+ subject: message.subject,
123
+ attempts,
124
+ error: lastError,
125
+ };
126
+ }
127
+ /**
128
+ * The one path both `send` and `sendRaw` take: settle the provider, build
129
+ * the message, and queue it.
130
+ *
131
+ * `build` runs inside the queue rather than before it, so a template read is
132
+ * one of the things that is serialised — and so a template edited while a
133
+ * message is waiting is the one that goes out.
134
+ */
135
+ function queue(to, replyTo, build) {
136
+ const recipients = addressesOf(to);
137
+ const carrier = provider();
138
+ if (carrier === undefined) {
139
+ // Not a failure. A site with no mail account is a site whose password
140
+ // resets are done from the shell and whose moderation happens on the
141
+ // Comments screen, and neither of those should throw here.
142
+ logger.info(`No mail is configured, so a message to ${recipients
143
+ .map((recipient) => recipient.address)
144
+ .join(', ')} was not sent.`);
145
+ return Promise.resolve({
146
+ ok: true,
147
+ skipped: true,
148
+ provider: 'none',
149
+ to: recipients.map((recipient) => recipient.address),
150
+ subject: '',
151
+ attempts: 0,
152
+ });
153
+ }
154
+ return enqueue(async () => {
155
+ const settings = readSiteSettings(config.contentDir);
156
+ let body;
157
+ try {
158
+ body = build(settings);
159
+ }
160
+ catch (thrown) {
161
+ // A template that will not render is a mistake in this site's own
162
+ // files, not a provider that refused: there is nothing to retry, and
163
+ // the message names the file to fix.
164
+ const error = messageOf(thrown);
165
+ logger.warn(`Could not build a message for ${recipients.map((r) => r.address).join(', ')}: ${error}`);
166
+ return {
167
+ ok: false,
168
+ skipped: false,
169
+ provider: carrier.name,
170
+ to: recipients.map((recipient) => recipient.address),
171
+ subject: '',
172
+ attempts: 0,
173
+ error,
174
+ };
175
+ }
176
+ const answerTo = replyTo === undefined ? replyToIn(settings) : addressOf(replyTo);
177
+ return await deliver(carrier, {
178
+ from: from(settings),
179
+ to: recipients,
180
+ ...(answerTo === undefined ? {} : { replyTo: answerTo }),
181
+ subject: body.subject,
182
+ text: body.text,
183
+ ...(body.html === undefined ? {} : { html: body.html }),
184
+ });
185
+ });
186
+ }
187
+ return {
188
+ configured() {
189
+ return provider() !== undefined;
190
+ },
191
+ providerName() {
192
+ return provider()?.name ?? 'none';
193
+ },
194
+ from() {
195
+ return from(readSiteSettings(config.contentDir));
196
+ },
197
+ send(message) {
198
+ return queue(message.to, message.replyTo, (settings) => {
199
+ const rendered = templates.render(message.template, {
200
+ site: settings,
201
+ baseUrl: config.baseUrl,
202
+ ...message.data,
203
+ });
204
+ return {
205
+ // The caller's own subject wins: a reset link's subject is the
206
+ // feature's to decide, and a template that offers one is a default.
207
+ subject: message.subject ?? (rendered.subject === '' ? '(no subject)' : rendered.subject),
208
+ text: rendered.text,
209
+ ...(rendered.html === undefined ? {} : { html: rendered.html }),
210
+ };
211
+ });
212
+ },
213
+ sendRaw(message) {
214
+ return queue(message.to, message.replyTo, () => ({
215
+ subject: message.subject,
216
+ text: message.text,
217
+ ...(message.html === undefined ? {} : { html: message.html }),
218
+ }));
219
+ },
220
+ async settled() {
221
+ await chain;
222
+ },
223
+ };
224
+ }
225
+ /** Where a reply goes, as the settings say it, or nowhere in particular. */
226
+ function replyToIn(settings) {
227
+ return settings.mailReplyTo === '' ? undefined : { address: settings.mailReplyTo };
228
+ }
229
+ /**
230
+ * The address a site sends as when its settings name none: `no-reply@` at the
231
+ * base URL's host.
232
+ *
233
+ * A guess, and a documented one — a provider only accepts a sender it has
234
+ * verified, so a site that sends anything real fills the field in. It exists
235
+ * so that a site which has pasted a key but not yet typed an address gets a
236
+ * message it can read the refusal from, rather than silence.
237
+ */
238
+ function fallbackAddress(baseUrl) {
239
+ try {
240
+ return `no-reply@${new URL(baseUrl).hostname}`;
241
+ }
242
+ catch {
243
+ return 'no-reply@localhost';
244
+ }
245
+ }
246
+ /** One recipient as an address. */
247
+ function addressOf(recipient) {
248
+ return typeof recipient === 'string' ? { address: recipient } : recipient;
249
+ }
250
+ /** However the caller spelled the recipients, as a list of addresses. */
251
+ function addressesOf(to) {
252
+ if (typeof to === 'string')
253
+ return [{ address: to }];
254
+ if (Array.isArray(to))
255
+ return to.map(addressOf);
256
+ return [addressOf(to)];
257
+ }
258
+ /** What a thrown value has to say for itself. */
259
+ function messageOf(thrown) {
260
+ return thrown instanceof Error ? thrown.message : String(thrown);
261
+ }
262
+ function ignore() {
263
+ // A failed send is already logged; the queue carries on regardless.
264
+ }
265
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/mail/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { MailProvider } from './provider.ts';
2
+ /**
3
+ * Any SMTP server, as one {@link MailProvider}.
4
+ *
5
+ * The other half of the pair Brevo's API is: an account on Fastmail, a relay
6
+ * on the same machine, Brevo's own SMTP relay, or whatever the host provides.
7
+ * nodemailer does the protocol; everything here is the two lines of
8
+ * translation between this package's {@link OutgoingMail} and the shape it
9
+ * wants.
10
+ *
11
+ * A transport is built per message rather than kept open. Sending is rare — a
12
+ * password reset, a comment notification — and a pooled connection to a server
13
+ * that has since restarted is a failure that only shows up on the message that
14
+ * mattered.
15
+ */
16
+ /** How long a whole SMTP conversation is given. */
17
+ export declare const SMTP_TIMEOUT_MS = 20000;
18
+ /** What {@link createSmtpProvider} needs. Exactly what `data/mail.json` holds. */
19
+ export interface SmtpProviderOptions {
20
+ /** The server's hostname. */
21
+ host: string;
22
+ /** The port: 587 for submission with STARTTLS, 465 for implicit TLS. */
23
+ port: number;
24
+ /**
25
+ * Whether the connection is TLS from the first byte, which is what port 465
26
+ * means. On 587 this is `false` and the connection is upgraded with
27
+ * STARTTLS when the server offers it.
28
+ */
29
+ secure: boolean;
30
+ /** The username, or empty for a server that wants no credentials. */
31
+ user?: string | undefined;
32
+ /** The password. */
33
+ password?: string | undefined;
34
+ /** How long the conversation is given. */
35
+ timeoutMs?: number | undefined;
36
+ }
37
+ /** A provider that hands each message to an SMTP server. */
38
+ export declare function createSmtpProvider(options: SmtpProviderOptions): MailProvider;
39
+ //# sourceMappingURL=smtp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smtp.d.ts","sourceRoot":"","sources":["../../src/mail/smtp.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA6B,YAAY,EAAgB,MAAM,eAAe,CAAC;AAE3F;;;;;;;;;;;;;GAaG;AAEH,mDAAmD;AACnD,eAAO,MAAM,eAAe,QAAS,CAAC;AAEtC,kFAAkF;AAClF,MAAM,WAAW,mBAAmB;IAClC,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,4DAA4D;AAC5D,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAoC7E"}
@@ -0,0 +1,58 @@
1
+ import nodemailer from 'nodemailer';
2
+ /**
3
+ * Any SMTP server, as one {@link MailProvider}.
4
+ *
5
+ * The other half of the pair Brevo's API is: an account on Fastmail, a relay
6
+ * on the same machine, Brevo's own SMTP relay, or whatever the host provides.
7
+ * nodemailer does the protocol; everything here is the two lines of
8
+ * translation between this package's {@link OutgoingMail} and the shape it
9
+ * wants.
10
+ *
11
+ * A transport is built per message rather than kept open. Sending is rare — a
12
+ * password reset, a comment notification — and a pooled connection to a server
13
+ * that has since restarted is a failure that only shows up on the message that
14
+ * mattered.
15
+ */
16
+ /** How long a whole SMTP conversation is given. */
17
+ export const SMTP_TIMEOUT_MS = 20_000;
18
+ /** A provider that hands each message to an SMTP server. */
19
+ export function createSmtpProvider(options) {
20
+ return {
21
+ name: 'smtp',
22
+ async deliver(message) {
23
+ const timeout = options.timeoutMs ?? SMTP_TIMEOUT_MS;
24
+ const transport = nodemailer.createTransport({
25
+ host: options.host,
26
+ port: options.port,
27
+ secure: options.secure,
28
+ ...(options.user === undefined || options.user === ''
29
+ ? {}
30
+ : { auth: { user: options.user, pass: options.password ?? '' } }),
31
+ connectionTimeout: timeout,
32
+ greetingTimeout: timeout,
33
+ socketTimeout: timeout,
34
+ });
35
+ try {
36
+ const sent = await transport.sendMail({
37
+ from: address(message.from),
38
+ to: message.to.map(address),
39
+ ...(message.replyTo === undefined ? {} : { replyTo: address(message.replyTo) }),
40
+ subject: message.subject,
41
+ text: message.text,
42
+ ...(message.html === undefined || message.html === '' ? {} : { html: message.html }),
43
+ });
44
+ return { messageId: sent.messageId };
45
+ }
46
+ finally {
47
+ // The transport holds a socket even unpooled; closing it is what keeps
48
+ // a long-running process from accumulating them.
49
+ transport.close();
50
+ }
51
+ },
52
+ };
53
+ }
54
+ /** One address as nodemailer wants it. */
55
+ function address(value) {
56
+ return { name: value.name ?? '', address: value.address };
57
+ }
58
+ //# sourceMappingURL=smtp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smtp.js","sourceRoot":"","sources":["../../src/mail/smtp.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,YAAY,CAAC;AAIpC;;;;;;;;;;;;;GAaG;AAEH,mDAAmD;AACnD,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AAsBtC,4DAA4D;AAC5D,MAAM,UAAU,kBAAkB,CAAC,OAA4B;IAC7D,OAAO;QACL,IAAI,EAAE,MAAM;QAEZ,KAAK,CAAC,OAAO,CAAC,OAAqB;YACjC,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,IAAI,eAAe,CAAC;YACrD,MAAM,SAAS,GAAG,UAAU,CAAC,eAAe,CAAC;gBAC3C,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,EAAE;oBACnD,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE,EAAE,EAAE,CAAC;gBACnE,iBAAiB,EAAE,OAAO;gBAC1B,eAAe,EAAE,OAAO;gBACxB,aAAa,EAAE,OAAO;aACvB,CAAC,CAAC;YAEH,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC;oBACpC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;oBAC3B,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;oBAC3B,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC/E,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;iBACrF,CAAC,CAAC;gBAEH,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;YACvC,CAAC;oBAAS,CAAC;gBACT,uEAAuE;gBACvE,iDAAiD;gBACjD,SAAS,CAAC,KAAK,EAAE,CAAC;YACpB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,0CAA0C;AAC1C,SAAS,OAAO,CAAC,KAAkB;IACjC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,91 @@
1
+ import type { Environment } from 'nunjucks';
2
+ import type { ThemeSource } from '../web/themes.ts';
3
+ /**
4
+ * What a message looks like before it is addressed: a subject, a plain text
5
+ * body, and an HTML twin when the theme wrote one.
6
+ *
7
+ * Messages are Nunjucks templates under `mail/` in the theme, looked up the
8
+ * way every other template is — the theme the site has chosen first, the theme
9
+ * that ships in this package second, file by file. So a theme overrides the
10
+ * text of one message and keeps the subject and the HTML the package ships,
11
+ * and one that writes `mail/welcome.txt.njk` has a message the package never
12
+ * had.
13
+ *
14
+ * Three files rather than one, because the three parts of a message have
15
+ * nothing to do with each other: a subject is one line with no markup, a text
16
+ * body is what most mail readers show, and the HTML twin is optional
17
+ * throughout. A message with no `.html.njk` is sent as plain text, which is
18
+ * the right thing rather than a missing feature.
19
+ */
20
+ /** One rendered message, ready to be addressed and handed to a provider. */
21
+ export interface RenderedMail {
22
+ /**
23
+ * The subject, on one line. Empty when the message has no `.subject.njk`,
24
+ * in which case the caller's own subject is what is sent.
25
+ */
26
+ readonly subject: string;
27
+ /** The plain text body. Every message has one. */
28
+ readonly text: string;
29
+ /** The HTML twin, when the theme wrote one. */
30
+ readonly html?: string | undefined;
31
+ }
32
+ /** The three templates one message is built from. */
33
+ export interface MailTemplateFiles {
34
+ /** The subject line. Optional. */
35
+ readonly subject: string;
36
+ /** The plain text body. Required. */
37
+ readonly text: string;
38
+ /** The HTML twin. Optional. */
39
+ readonly html: string;
40
+ }
41
+ /** Which files a named message is built from, relative to a theme directory. */
42
+ export declare function mailTemplateFiles(name: string): MailTemplateFiles;
43
+ /** What {@link createMailTemplates} needs. */
44
+ export interface CreateMailTemplatesOptions {
45
+ /**
46
+ * Which theme the messages are read from, asked per message.
47
+ *
48
+ * A source rather than a directory because mail is part of the theme
49
+ * (decision-15): a site that changes theme changes the words its password
50
+ * resets and moderation notices are written in, and the message after the
51
+ * change should be the new theme's rather than the next boot's.
52
+ */
53
+ themes: ThemeSource;
54
+ /** Public origin, for the `url` and `absoluteUrl` filters. */
55
+ baseUrl: string;
56
+ /**
57
+ * Recompile a template on every render instead of caching it. On while the
58
+ * content watcher is on, so editing a message in development shows up
59
+ * without a restart, exactly as editing a layout does.
60
+ */
61
+ noCache?: boolean | undefined;
62
+ }
63
+ /** Turns a named message and some data into the three parts of an email. */
64
+ export interface MailTemplates {
65
+ /**
66
+ * Render one message.
67
+ *
68
+ * Throws when there is no `.txt.njk` for that name anywhere on the search
69
+ * path — a message with no body is a mistake in the calling code, and
70
+ * sending an empty one would hide it.
71
+ */
72
+ render(name: string, context?: Record<string, unknown>): RenderedMail;
73
+ /**
74
+ * The Nunjucks environment the HTML half renders through, for a site that
75
+ * wants to add its own filters.
76
+ */
77
+ readonly environment: Environment;
78
+ /**
79
+ * The twin the subject and the text body render through: the same loader and
80
+ * the same filters, with autoescaping off.
81
+ *
82
+ * A plain text body is not HTML. Escaping it turns the `&` between two query
83
+ * parameters into `&amp;` — which is what a one-click moderation link is
84
+ * made of (TASK-55) — and an apostrophe in somebody's name into `&#39;`. A
85
+ * site adding a filter that both halves should have adds it to both.
86
+ */
87
+ readonly plainEnvironment: Environment;
88
+ }
89
+ /** Build the mail templates for one site. */
90
+ export declare function createMailTemplates(options: CreateMailTemplatesOptions): MailTemplates;
91
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/mail/templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;GAgBG;AAEH,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IAChC,kCAAkC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAMjE;AAED,8CAA8C;AAC9C,MAAM,WAAW,0BAA0B;IACzC;;;;;;;OAOG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B;AAED,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAAC;IACtE;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC;;;;;;;;OAQG;IACH,QAAQ,CAAC,gBAAgB,EAAE,WAAW,CAAC;CACxC;AAED,6CAA6C;AAC7C,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,aAAa,CAkEtF"}