@geekity/cms 0.3.0 → 0.5.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 (660) hide show
  1. package/README.md +2217 -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/{layouts/document-editor.njk → pages/documents/editor.njk} +75 -59
  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/federation/settings.njk +77 -0
  19. package/admin/pages/media/library.njk +160 -0
  20. package/admin/pages/messages/all.njk +110 -0
  21. package/admin/pages/navigation/menus.njk +129 -0
  22. package/admin/{layouts → pages}/placeholder.njk +5 -1
  23. package/admin/pages/settings/discussion.njk +99 -0
  24. package/admin/pages/settings/email.njk +203 -0
  25. package/admin/pages/settings/general.njk +29 -0
  26. package/admin/pages/settings/permalinks.njk +50 -0
  27. package/admin/pages/settings/reading.njk +64 -0
  28. package/admin/pages/tools/content-index.njk +89 -0
  29. package/admin/pages/users/edit.njk +239 -0
  30. package/admin/pages/users/list.njk +51 -0
  31. package/admin/pages/users/new.njk +41 -0
  32. package/admin/static/admin.css +558 -3
  33. package/admin/static/copy.js +52 -0
  34. package/admin/static/editor.js +16 -16
  35. package/admin/static/slug.js +61 -0
  36. package/dist/access-log.d.ts +44 -0
  37. package/dist/access-log.d.ts.map +1 -0
  38. package/dist/access-log.js +71 -0
  39. package/dist/access-log.js.map +1 -0
  40. package/dist/admin/accounts.d.ts +413 -0
  41. package/dist/admin/accounts.d.ts.map +1 -0
  42. package/dist/admin/accounts.js +680 -0
  43. package/dist/admin/accounts.js.map +1 -0
  44. package/dist/admin/appearance.d.ts +80 -0
  45. package/dist/admin/appearance.d.ts.map +1 -0
  46. package/dist/admin/appearance.js +136 -0
  47. package/dist/admin/appearance.js.map +1 -0
  48. package/dist/admin/comments.d.ts +90 -0
  49. package/dist/admin/comments.d.ts.map +1 -0
  50. package/dist/admin/comments.js +244 -0
  51. package/dist/admin/comments.js.map +1 -0
  52. package/dist/admin/credentials.d.ts +26 -2
  53. package/dist/admin/credentials.d.ts.map +1 -1
  54. package/dist/admin/credentials.js +35 -2
  55. package/dist/admin/credentials.js.map +1 -1
  56. package/dist/admin/documents.d.ts +117 -10
  57. package/dist/admin/documents.d.ts.map +1 -1
  58. package/dist/admin/documents.js +328 -41
  59. package/dist/admin/documents.js.map +1 -1
  60. package/dist/admin/federation.d.ts +161 -50
  61. package/dist/admin/federation.d.ts.map +1 -1
  62. package/dist/admin/federation.js +235 -93
  63. package/dist/admin/federation.js.map +1 -1
  64. package/dist/admin/formatting.d.ts +20 -0
  65. package/dist/admin/formatting.d.ts.map +1 -0
  66. package/dist/admin/formatting.js +72 -0
  67. package/dist/admin/formatting.js.map +1 -0
  68. package/dist/admin/headers.d.ts +65 -0
  69. package/dist/admin/headers.d.ts.map +1 -0
  70. package/dist/admin/headers.js +104 -0
  71. package/dist/admin/headers.js.map +1 -0
  72. package/dist/admin/index.d.ts +38 -12
  73. package/dist/admin/index.d.ts.map +1 -1
  74. package/dist/admin/index.js +25 -7
  75. package/dist/admin/index.js.map +1 -1
  76. package/dist/admin/media.d.ts +168 -0
  77. package/dist/admin/media.d.ts.map +1 -0
  78. package/dist/admin/media.js +295 -0
  79. package/dist/admin/media.js.map +1 -0
  80. package/dist/admin/menu.d.ts +79 -0
  81. package/dist/admin/menu.d.ts.map +1 -0
  82. package/dist/admin/menu.js +143 -0
  83. package/dist/admin/menu.js.map +1 -0
  84. package/dist/admin/messages.d.ts +80 -0
  85. package/dist/admin/messages.d.ts.map +1 -0
  86. package/dist/admin/messages.js +152 -0
  87. package/dist/admin/messages.js.map +1 -0
  88. package/dist/admin/navigation.d.ts +155 -0
  89. package/dist/admin/navigation.d.ts.map +1 -0
  90. package/dist/admin/navigation.js +292 -0
  91. package/dist/admin/navigation.js.map +1 -0
  92. package/dist/admin/preview.d.ts.map +1 -1
  93. package/dist/admin/preview.js +31 -7
  94. package/dist/admin/preview.js.map +1 -1
  95. package/dist/admin/recovery.d.ts +92 -0
  96. package/dist/admin/recovery.d.ts.map +1 -0
  97. package/dist/admin/recovery.js +229 -0
  98. package/dist/admin/recovery.js.map +1 -0
  99. package/dist/admin/routes.d.ts +0 -20
  100. package/dist/admin/routes.d.ts.map +1 -1
  101. package/dist/admin/routes.js +164 -105
  102. package/dist/admin/routes.js.map +1 -1
  103. package/dist/admin/session.d.ts +17 -3
  104. package/dist/admin/session.d.ts.map +1 -1
  105. package/dist/admin/session.js +19 -5
  106. package/dist/admin/session.js.map +1 -1
  107. package/dist/admin/settings-discussion.d.ts +37 -0
  108. package/dist/admin/settings-discussion.d.ts.map +1 -0
  109. package/dist/admin/settings-discussion.js +117 -0
  110. package/dist/admin/settings-discussion.js.map +1 -0
  111. package/dist/admin/settings-email.d.ts +54 -0
  112. package/dist/admin/settings-email.d.ts.map +1 -0
  113. package/dist/admin/settings-email.js +189 -0
  114. package/dist/admin/settings-email.js.map +1 -0
  115. package/dist/admin/settings-federation.d.ts +48 -0
  116. package/dist/admin/settings-federation.d.ts.map +1 -0
  117. package/dist/admin/settings-federation.js +123 -0
  118. package/dist/admin/settings-federation.js.map +1 -0
  119. package/dist/admin/settings-general.d.ts +12 -0
  120. package/dist/admin/settings-general.d.ts.map +1 -0
  121. package/dist/admin/settings-general.js +48 -0
  122. package/dist/admin/settings-general.js.map +1 -0
  123. package/dist/admin/settings-page.d.ts +119 -0
  124. package/dist/admin/settings-page.d.ts.map +1 -0
  125. package/dist/admin/settings-page.js +132 -0
  126. package/dist/admin/settings-page.js.map +1 -0
  127. package/dist/admin/settings-pages.d.ts +30 -0
  128. package/dist/admin/settings-pages.d.ts.map +1 -0
  129. package/dist/admin/settings-pages.js +43 -0
  130. package/dist/admin/settings-pages.js.map +1 -0
  131. package/dist/admin/settings-permalinks.d.ts +16 -0
  132. package/dist/admin/settings-permalinks.d.ts.map +1 -0
  133. package/dist/admin/settings-permalinks.js +32 -0
  134. package/dist/admin/settings-permalinks.js.map +1 -0
  135. package/dist/admin/settings-reading.d.ts +34 -0
  136. package/dist/admin/settings-reading.d.ts.map +1 -0
  137. package/dist/admin/settings-reading.js +49 -0
  138. package/dist/admin/settings-reading.js.map +1 -0
  139. package/dist/admin/settings.d.ts +339 -96
  140. package/dist/admin/settings.d.ts.map +1 -1
  141. package/dist/admin/settings.js +600 -308
  142. package/dist/admin/settings.js.map +1 -1
  143. package/dist/admin/store.d.ts +606 -152
  144. package/dist/admin/store.d.ts.map +1 -1
  145. package/dist/admin/store.js +863 -268
  146. package/dist/admin/store.js.map +1 -1
  147. package/dist/admin/taxonomy.d.ts +114 -0
  148. package/dist/admin/taxonomy.d.ts.map +1 -0
  149. package/dist/admin/taxonomy.js +289 -0
  150. package/dist/admin/taxonomy.js.map +1 -0
  151. package/dist/admin/templates.d.ts +46 -14
  152. package/dist/admin/templates.d.ts.map +1 -1
  153. package/dist/admin/templates.js +46 -14
  154. package/dist/admin/templates.js.map +1 -1
  155. package/dist/admin/throttle.d.ts +84 -0
  156. package/dist/admin/throttle.d.ts.map +1 -0
  157. package/dist/admin/throttle.js +143 -0
  158. package/dist/admin/throttle.js.map +1 -0
  159. package/dist/admin/tools.d.ts +111 -0
  160. package/dist/admin/tools.d.ts.map +1 -0
  161. package/dist/admin/tools.js +182 -0
  162. package/dist/admin/tools.js.map +1 -0
  163. package/dist/admin/uploads.d.ts +28 -2
  164. package/dist/admin/uploads.d.ts.map +1 -1
  165. package/dist/admin/uploads.js +43 -4
  166. package/dist/admin/uploads.js.map +1 -1
  167. package/dist/admin/users.d.ts +100 -13
  168. package/dist/admin/users.d.ts.map +1 -1
  169. package/dist/admin/users.js +412 -39
  170. package/dist/admin/users.js.map +1 -1
  171. package/dist/cache.d.ts +141 -0
  172. package/dist/cache.d.ts.map +1 -0
  173. package/dist/cache.js +207 -0
  174. package/dist/cache.js.map +1 -0
  175. package/dist/cli.d.ts +14 -2
  176. package/dist/cli.d.ts.map +1 -1
  177. package/dist/cli.js +429 -21
  178. package/dist/cli.js.map +1 -1
  179. package/dist/comments/akismet.d.ts +130 -0
  180. package/dist/comments/akismet.d.ts.map +1 -0
  181. package/dist/comments/akismet.js +274 -0
  182. package/dist/comments/akismet.js.map +1 -0
  183. package/dist/comments/conversation.d.ts +29 -0
  184. package/dist/comments/conversation.d.ts.map +1 -0
  185. package/dist/comments/conversation.js +60 -0
  186. package/dist/comments/conversation.js.map +1 -0
  187. package/dist/comments/form.d.ts +123 -0
  188. package/dist/comments/form.d.ts.map +1 -0
  189. package/dist/comments/form.js +79 -0
  190. package/dist/comments/form.js.map +1 -0
  191. package/dist/comments/index.d.ts +16 -0
  192. package/dist/comments/index.d.ts.map +1 -0
  193. package/dist/comments/index.js +10 -0
  194. package/dist/comments/index.js.map +1 -0
  195. package/dist/comments/markdown.d.ts +8 -0
  196. package/dist/comments/markdown.d.ts.map +1 -0
  197. package/dist/comments/markdown.js +85 -0
  198. package/dist/comments/markdown.js.map +1 -0
  199. package/dist/comments/moderate.d.ts +71 -0
  200. package/dist/comments/moderate.d.ts.map +1 -0
  201. package/dist/comments/moderate.js +88 -0
  202. package/dist/comments/moderate.js.map +1 -0
  203. package/dist/comments/policy.d.ts +57 -0
  204. package/dist/comments/policy.d.ts.map +1 -0
  205. package/dist/comments/policy.js +80 -0
  206. package/dist/comments/policy.js.map +1 -0
  207. package/dist/comments/records.d.ts +266 -0
  208. package/dist/comments/records.d.ts.map +1 -0
  209. package/dist/comments/records.js +487 -0
  210. package/dist/comments/records.js.map +1 -0
  211. package/dist/comments/routes.d.ts +60 -0
  212. package/dist/comments/routes.d.ts.map +1 -0
  213. package/dist/comments/routes.js +212 -0
  214. package/dist/comments/routes.js.map +1 -0
  215. package/dist/comments/submission.d.ts +297 -0
  216. package/dist/comments/submission.d.ts.map +1 -0
  217. package/dist/comments/submission.js +280 -0
  218. package/dist/comments/submission.js.map +1 -0
  219. package/dist/comments/viewer.d.ts +21 -0
  220. package/dist/comments/viewer.d.ts.map +1 -0
  221. package/dist/comments/viewer.js +45 -0
  222. package/dist/comments/viewer.js.map +1 -0
  223. package/dist/config.d.ts +225 -5
  224. package/dist/config.d.ts.map +1 -1
  225. package/dist/config.js +121 -6
  226. package/dist/config.js.map +1 -1
  227. package/dist/contact/delivery.d.ts +60 -0
  228. package/dist/contact/delivery.d.ts.map +1 -0
  229. package/dist/contact/delivery.js +87 -0
  230. package/dist/contact/delivery.js.map +1 -0
  231. package/dist/contact/form.d.ts +130 -0
  232. package/dist/contact/form.d.ts.map +1 -0
  233. package/dist/contact/form.js +163 -0
  234. package/dist/contact/form.js.map +1 -0
  235. package/dist/contact/index.d.ts +17 -0
  236. package/dist/contact/index.d.ts.map +1 -0
  237. package/dist/contact/index.js +13 -0
  238. package/dist/contact/index.js.map +1 -0
  239. package/dist/contact/records.d.ts +124 -0
  240. package/dist/contact/records.d.ts.map +1 -0
  241. package/dist/contact/records.js +218 -0
  242. package/dist/contact/records.js.map +1 -0
  243. package/dist/contact/routes.d.ts +17 -0
  244. package/dist/contact/routes.d.ts.map +1 -0
  245. package/dist/contact/routes.js +127 -0
  246. package/dist/contact/routes.js.map +1 -0
  247. package/dist/contact/submission.d.ts +103 -0
  248. package/dist/contact/submission.d.ts.map +1 -0
  249. package/dist/contact/submission.js +166 -0
  250. package/dist/contact/submission.js.map +1 -0
  251. package/dist/content/document.d.ts +7 -2
  252. package/dist/content/document.d.ts.map +1 -1
  253. package/dist/content/document.js +1 -0
  254. package/dist/content/document.js.map +1 -1
  255. package/dist/content/index.d.ts +7 -2
  256. package/dist/content/index.d.ts.map +1 -1
  257. package/dist/content/index.js +4 -1
  258. package/dist/content/index.js.map +1 -1
  259. package/dist/content/markdown.d.ts.map +1 -1
  260. package/dist/content/markdown.js +24 -2
  261. package/dist/content/markdown.js.map +1 -1
  262. package/dist/content/parser.d.ts.map +1 -1
  263. package/dist/content/parser.js +11 -4
  264. package/dist/content/parser.js.map +1 -1
  265. package/dist/content/save.d.ts +5 -0
  266. package/dist/content/save.d.ts.map +1 -1
  267. package/dist/content/save.js +44 -1
  268. package/dist/content/save.js.map +1 -1
  269. package/dist/content/schedule.d.ts +125 -0
  270. package/dist/content/schedule.d.ts.map +1 -0
  271. package/dist/content/schedule.js +207 -0
  272. package/dist/content/schedule.js.map +1 -0
  273. package/dist/content/search.d.ts +64 -0
  274. package/dist/content/search.d.ts.map +1 -0
  275. package/dist/content/search.js +136 -0
  276. package/dist/content/search.js.map +1 -0
  277. package/dist/content/store.d.ts +220 -7
  278. package/dist/content/store.d.ts.map +1 -1
  279. package/dist/content/store.js +392 -67
  280. package/dist/content/store.js.map +1 -1
  281. package/dist/content/sync.d.ts +7 -1
  282. package/dist/content/sync.d.ts.map +1 -1
  283. package/dist/content/sync.js +18 -10
  284. package/dist/content/sync.js.map +1 -1
  285. package/dist/content/time.d.ts +56 -0
  286. package/dist/content/time.d.ts.map +1 -0
  287. package/dist/content/time.js +141 -0
  288. package/dist/content/time.js.map +1 -0
  289. package/dist/content/writer.d.ts.map +1 -1
  290. package/dist/content/writer.js +3 -0
  291. package/dist/content/writer.js.map +1 -1
  292. package/dist/env.d.ts +70 -5
  293. package/dist/env.d.ts.map +1 -1
  294. package/dist/federation/actor.d.ts +95 -41
  295. package/dist/federation/actor.d.ts.map +1 -1
  296. package/dist/federation/actor.js +180 -53
  297. package/dist/federation/actor.js.map +1 -1
  298. package/dist/federation/article.d.ts +44 -17
  299. package/dist/federation/article.d.ts.map +1 -1
  300. package/dist/federation/article.js +98 -46
  301. package/dist/federation/article.js.map +1 -1
  302. package/dist/federation/delivery.d.ts +63 -15
  303. package/dist/federation/delivery.d.ts.map +1 -1
  304. package/dist/federation/delivery.js +154 -80
  305. package/dist/federation/delivery.js.map +1 -1
  306. package/dist/federation/federation.d.ts +22 -17
  307. package/dist/federation/federation.d.ts.map +1 -1
  308. package/dist/federation/federation.js +98 -77
  309. package/dist/federation/federation.js.map +1 -1
  310. package/dist/federation/followers.d.ts +3 -2
  311. package/dist/federation/followers.d.ts.map +1 -1
  312. package/dist/federation/followers.js +6 -5
  313. package/dist/federation/followers.js.map +1 -1
  314. package/dist/federation/import-wordpress.d.ts +128 -0
  315. package/dist/federation/import-wordpress.d.ts.map +1 -0
  316. package/dist/federation/import-wordpress.js +426 -0
  317. package/dist/federation/import-wordpress.js.map +1 -0
  318. package/dist/federation/inbox.d.ts +36 -13
  319. package/dist/federation/inbox.d.ts.map +1 -1
  320. package/dist/federation/inbox.js +120 -49
  321. package/dist/federation/inbox.js.map +1 -1
  322. package/dist/federation/index.d.ts +22 -10
  323. package/dist/federation/index.d.ts.map +1 -1
  324. package/dist/federation/index.js +13 -8
  325. package/dist/federation/index.js.map +1 -1
  326. package/dist/federation/keys.d.ts +98 -20
  327. package/dist/federation/keys.d.ts.map +1 -1
  328. package/dist/federation/keys.js +269 -39
  329. package/dist/federation/keys.js.map +1 -1
  330. package/dist/federation/mount.d.ts +46 -10
  331. package/dist/federation/mount.d.ts.map +1 -1
  332. package/dist/federation/mount.js +300 -28
  333. package/dist/federation/mount.js.map +1 -1
  334. package/dist/federation/paths.d.ts +23 -35
  335. package/dist/federation/paths.d.ts.map +1 -1
  336. package/dist/federation/paths.js +26 -39
  337. package/dist/federation/paths.js.map +1 -1
  338. package/dist/federation/records.d.ts +223 -0
  339. package/dist/federation/records.d.ts.map +1 -0
  340. package/dist/federation/records.js +506 -0
  341. package/dist/federation/records.js.map +1 -0
  342. package/dist/federation/relays.d.ts +114 -0
  343. package/dist/federation/relays.d.ts.map +1 -0
  344. package/dist/federation/relays.js +290 -0
  345. package/dist/federation/relays.js.map +1 -0
  346. package/dist/federation/replies.d.ts +80 -0
  347. package/dist/federation/replies.d.ts.map +1 -0
  348. package/dist/federation/replies.js +166 -0
  349. package/dist/federation/replies.js.map +1 -0
  350. package/dist/federation/wordpress.d.ts +131 -0
  351. package/dist/federation/wordpress.d.ts.map +1 -0
  352. package/dist/federation/wordpress.js +341 -0
  353. package/dist/federation/wordpress.js.map +1 -0
  354. package/dist/files/atomic.d.ts +103 -0
  355. package/dist/files/atomic.d.ts.map +1 -0
  356. package/dist/files/atomic.js +150 -0
  357. package/dist/files/atomic.js.map +1 -0
  358. package/dist/files/index.d.ts +3 -0
  359. package/dist/files/index.d.ts.map +1 -0
  360. package/dist/files/index.js +2 -0
  361. package/dist/files/index.js.map +1 -0
  362. package/dist/forms/protection.d.ts +85 -0
  363. package/dist/forms/protection.d.ts.map +1 -0
  364. package/dist/forms/protection.js +116 -0
  365. package/dist/forms/protection.js.map +1 -0
  366. package/dist/images/icons.d.ts +59 -0
  367. package/dist/images/icons.d.ts.map +1 -0
  368. package/dist/images/icons.js +131 -0
  369. package/dist/images/icons.js.map +1 -0
  370. package/dist/images/index.d.ts +7 -0
  371. package/dist/images/index.d.ts.map +1 -0
  372. package/dist/images/index.js +4 -0
  373. package/dist/images/index.js.map +1 -0
  374. package/dist/images/markup.d.ts +47 -0
  375. package/dist/images/markup.d.ts.map +1 -0
  376. package/dist/images/markup.js +175 -0
  377. package/dist/images/markup.js.map +1 -0
  378. package/dist/images/paths.d.ts +34 -0
  379. package/dist/images/paths.d.ts.map +1 -0
  380. package/dist/images/paths.js +41 -0
  381. package/dist/images/paths.js.map +1 -0
  382. package/dist/images/variants.d.ts +104 -0
  383. package/dist/images/variants.d.ts.map +1 -0
  384. package/dist/images/variants.js +320 -0
  385. package/dist/images/variants.js.map +1 -0
  386. package/dist/index.d.ts +123 -19
  387. package/dist/index.d.ts.map +1 -1
  388. package/dist/index.js +344 -25
  389. package/dist/index.js.map +1 -1
  390. package/dist/init.d.ts +18 -0
  391. package/dist/init.d.ts.map +1 -1
  392. package/dist/init.js +34 -3
  393. package/dist/init.js.map +1 -1
  394. package/dist/mail/brevo.d.ts +35 -0
  395. package/dist/mail/brevo.d.ts.map +1 -0
  396. package/dist/mail/brevo.js +85 -0
  397. package/dist/mail/brevo.js.map +1 -0
  398. package/dist/mail/credentials.d.ts +62 -0
  399. package/dist/mail/credentials.d.ts.map +1 -0
  400. package/dist/mail/credentials.js +86 -0
  401. package/dist/mail/credentials.js.map +1 -0
  402. package/dist/mail/index.d.ts +15 -0
  403. package/dist/mail/index.d.ts.map +1 -0
  404. package/dist/mail/index.js +8 -0
  405. package/dist/mail/index.js.map +1 -0
  406. package/dist/mail/memory.d.ts +35 -0
  407. package/dist/mail/memory.d.ts.map +1 -0
  408. package/dist/mail/memory.js +29 -0
  409. package/dist/mail/memory.js.map +1 -0
  410. package/dist/mail/provider.d.ts +80 -0
  411. package/dist/mail/provider.d.ts.map +1 -0
  412. package/dist/mail/provider.js +23 -0
  413. package/dist/mail/provider.js.map +1 -0
  414. package/dist/mail/service.d.ts +156 -0
  415. package/dist/mail/service.d.ts.map +1 -0
  416. package/dist/mail/service.js +265 -0
  417. package/dist/mail/service.js.map +1 -0
  418. package/dist/mail/smtp.d.ts +39 -0
  419. package/dist/mail/smtp.d.ts.map +1 -0
  420. package/dist/mail/smtp.js +58 -0
  421. package/dist/mail/smtp.js.map +1 -0
  422. package/dist/mail/templates.d.ts +91 -0
  423. package/dist/mail/templates.d.ts.map +1 -0
  424. package/dist/mail/templates.js +69 -0
  425. package/dist/mail/templates.js.map +1 -0
  426. package/dist/notifications/comments.d.ts +66 -0
  427. package/dist/notifications/comments.d.ts.map +1 -0
  428. package/dist/notifications/comments.js +151 -0
  429. package/dist/notifications/comments.js.map +1 -0
  430. package/dist/notifications/digest.d.ts +135 -0
  431. package/dist/notifications/digest.d.ts.map +1 -0
  432. package/dist/notifications/digest.js +287 -0
  433. package/dist/notifications/digest.js.map +1 -0
  434. package/dist/notifications/index.d.ts +13 -0
  435. package/dist/notifications/index.d.ts.map +1 -0
  436. package/dist/notifications/index.js +8 -0
  437. package/dist/notifications/index.js.map +1 -0
  438. package/dist/notifications/links.d.ts +60 -0
  439. package/dist/notifications/links.d.ts.map +1 -0
  440. package/dist/notifications/links.js +63 -0
  441. package/dist/notifications/links.js.map +1 -0
  442. package/dist/notifications/optouts.d.ts +48 -0
  443. package/dist/notifications/optouts.d.ts.map +1 -0
  444. package/dist/notifications/optouts.js +93 -0
  445. package/dist/notifications/optouts.js.map +1 -0
  446. package/dist/notifications/preferences.d.ts +158 -0
  447. package/dist/notifications/preferences.d.ts.map +1 -0
  448. package/dist/notifications/preferences.js +195 -0
  449. package/dist/notifications/preferences.js.map +1 -0
  450. package/dist/notifications/routes.d.ts +28 -0
  451. package/dist/notifications/routes.d.ts.map +1 -0
  452. package/dist/notifications/routes.js +184 -0
  453. package/dist/notifications/routes.js.map +1 -0
  454. package/dist/notifications/tokens.d.ts +60 -0
  455. package/dist/notifications/tokens.d.ts.map +1 -0
  456. package/dist/notifications/tokens.js +125 -0
  457. package/dist/notifications/tokens.js.map +1 -0
  458. package/dist/notify.d.ts +84 -0
  459. package/dist/notify.d.ts.map +1 -0
  460. package/dist/notify.js +149 -0
  461. package/dist/notify.js.map +1 -0
  462. package/dist/web/archive.d.ts +49 -0
  463. package/dist/web/archive.d.ts.map +1 -0
  464. package/dist/web/archive.js +59 -0
  465. package/dist/web/archive.js.map +1 -0
  466. package/dist/web/assets.d.ts +6 -5
  467. package/dist/web/assets.d.ts.map +1 -1
  468. package/dist/web/assets.js +5 -7
  469. package/dist/web/assets.js.map +1 -1
  470. package/dist/web/authors.d.ts +143 -0
  471. package/dist/web/authors.d.ts.map +1 -0
  472. package/dist/web/authors.js +178 -0
  473. package/dist/web/authors.js.map +1 -0
  474. package/dist/web/comments.d.ts +60 -0
  475. package/dist/web/comments.d.ts.map +1 -0
  476. package/dist/web/comments.js +207 -0
  477. package/dist/web/comments.js.map +1 -0
  478. package/dist/web/context.d.ts +162 -28
  479. package/dist/web/context.d.ts.map +1 -1
  480. package/dist/web/context.js +123 -26
  481. package/dist/web/context.js.map +1 -1
  482. package/dist/web/conversation.d.ts +209 -0
  483. package/dist/web/conversation.d.ts.map +1 -0
  484. package/dist/web/conversation.js +557 -0
  485. package/dist/web/conversation.js.map +1 -0
  486. package/dist/web/documents.d.ts +36 -6
  487. package/dist/web/documents.d.ts.map +1 -1
  488. package/dist/web/documents.js +52 -10
  489. package/dist/web/documents.js.map +1 -1
  490. package/dist/web/feed-atom.d.ts +22 -0
  491. package/dist/web/feed-atom.d.ts.map +1 -0
  492. package/dist/web/feed-atom.js +83 -0
  493. package/dist/web/feed-atom.js.map +1 -0
  494. package/dist/web/feed-item.d.ts +145 -0
  495. package/dist/web/feed-item.d.ts.map +1 -0
  496. package/dist/web/feed-item.js +120 -0
  497. package/dist/web/feed-item.js.map +1 -0
  498. package/dist/web/feed-json.d.ts +79 -0
  499. package/dist/web/feed-json.d.ts.map +1 -0
  500. package/dist/web/feed-json.js +53 -0
  501. package/dist/web/feed-json.js.map +1 -0
  502. package/dist/web/feed-rss.d.ts +26 -0
  503. package/dist/web/feed-rss.d.ts.map +1 -0
  504. package/dist/web/feed-rss.js +203 -0
  505. package/dist/web/feed-rss.js.map +1 -0
  506. package/dist/web/feed-source.d.ts +244 -0
  507. package/dist/web/feed-source.d.ts.map +1 -0
  508. package/dist/web/feed-source.js +201 -0
  509. package/dist/web/feed-source.js.map +1 -0
  510. package/dist/web/feed-xml.d.ts +63 -0
  511. package/dist/web/feed-xml.d.ts.map +1 -0
  512. package/dist/web/feed-xml.js +116 -0
  513. package/dist/web/feed-xml.js.map +1 -0
  514. package/dist/web/feeds.d.ts +31 -96
  515. package/dist/web/feeds.d.ts.map +1 -1
  516. package/dist/web/feeds.js +94 -198
  517. package/dist/web/feeds.js.map +1 -1
  518. package/dist/web/health.d.ts +31 -0
  519. package/dist/web/health.d.ts.map +1 -0
  520. package/dist/web/health.js +44 -0
  521. package/dist/web/health.js.map +1 -0
  522. package/dist/web/index.d.ts +30 -10
  523. package/dist/web/index.d.ts.map +1 -1
  524. package/dist/web/index.js +18 -7
  525. package/dist/web/index.js.map +1 -1
  526. package/dist/web/navigation.d.ts +195 -0
  527. package/dist/web/navigation.d.ts.map +1 -0
  528. package/dist/web/navigation.js +300 -0
  529. package/dist/web/navigation.js.map +1 -0
  530. package/dist/web/negotiate.d.ts +31 -0
  531. package/dist/web/negotiate.d.ts.map +1 -1
  532. package/dist/web/negotiate.js +31 -11
  533. package/dist/web/negotiate.js.map +1 -1
  534. package/dist/web/recent.d.ts +43 -0
  535. package/dist/web/recent.d.ts.map +1 -0
  536. package/dist/web/recent.js +37 -0
  537. package/dist/web/recent.js.map +1 -0
  538. package/dist/web/render.d.ts +203 -9
  539. package/dist/web/render.d.ts.map +1 -1
  540. package/dist/web/render.js +341 -17
  541. package/dist/web/render.js.map +1 -1
  542. package/dist/web/routes.d.ts +25 -8
  543. package/dist/web/routes.d.ts.map +1 -1
  544. package/dist/web/routes.js +854 -93
  545. package/dist/web/routes.js.map +1 -1
  546. package/dist/web/sanitize.d.ts +19 -0
  547. package/dist/web/sanitize.d.ts.map +1 -0
  548. package/dist/web/sanitize.js +272 -0
  549. package/dist/web/sanitize.js.map +1 -0
  550. package/dist/web/search.d.ts +79 -0
  551. package/dist/web/search.d.ts.map +1 -0
  552. package/dist/web/search.js +94 -0
  553. package/dist/web/search.js.map +1 -0
  554. package/dist/web/sitemap.d.ts +120 -0
  555. package/dist/web/sitemap.d.ts.map +1 -0
  556. package/dist/web/sitemap.js +199 -0
  557. package/dist/web/sitemap.js.map +1 -0
  558. package/dist/web/taxonomy.d.ts +152 -0
  559. package/dist/web/taxonomy.d.ts.map +1 -0
  560. package/dist/web/taxonomy.js +237 -0
  561. package/dist/web/taxonomy.js.map +1 -0
  562. package/dist/web/templates.d.ts +54 -16
  563. package/dist/web/templates.d.ts.map +1 -1
  564. package/dist/web/templates.js +109 -28
  565. package/dist/web/templates.js.map +1 -1
  566. package/dist/web/themes.d.ts +226 -0
  567. package/dist/web/themes.d.ts.map +1 -0
  568. package/dist/web/themes.js +314 -0
  569. package/dist/web/themes.js.map +1 -0
  570. package/dist/webmention/discovery.d.ts +51 -0
  571. package/dist/webmention/discovery.d.ts.map +1 -0
  572. package/dist/webmention/discovery.js +176 -0
  573. package/dist/webmention/discovery.js.map +1 -0
  574. package/dist/webmention/html.d.ts +71 -0
  575. package/dist/webmention/html.d.ts.map +1 -0
  576. package/dist/webmention/html.js +285 -0
  577. package/dist/webmention/html.js.map +1 -0
  578. package/dist/webmention/index.d.ts +12 -0
  579. package/dist/webmention/index.d.ts.map +1 -0
  580. package/dist/webmention/index.js +8 -0
  581. package/dist/webmention/index.js.map +1 -0
  582. package/dist/webmention/links.d.ts +18 -0
  583. package/dist/webmention/links.d.ts.map +1 -0
  584. package/dist/webmention/links.js +64 -0
  585. package/dist/webmention/links.js.map +1 -0
  586. package/dist/webmention/microformats.d.ts +93 -0
  587. package/dist/webmention/microformats.d.ts.map +1 -0
  588. package/dist/webmention/microformats.js +376 -0
  589. package/dist/webmention/microformats.js.map +1 -0
  590. package/dist/webmention/receive.d.ts +141 -0
  591. package/dist/webmention/receive.d.ts.map +1 -0
  592. package/dist/webmention/receive.js +264 -0
  593. package/dist/webmention/receive.js.map +1 -0
  594. package/dist/webmention/routes.d.ts +37 -0
  595. package/dist/webmention/routes.d.ts.map +1 -0
  596. package/dist/webmention/routes.js +75 -0
  597. package/dist/webmention/routes.js.map +1 -0
  598. package/dist/webmention/service.d.ts +94 -0
  599. package/dist/webmention/service.d.ts.map +1 -0
  600. package/dist/webmention/service.js +207 -0
  601. package/dist/webmention/service.js.map +1 -0
  602. package/package.json +11 -6
  603. package/templates/site/content/_data/site.json +8 -0
  604. package/templates/site/content/pages/about.md +7 -0
  605. package/templates/site/content/posts/2026-01-01-hello-world.md +5 -3
  606. package/templates/site/geekity.config.ts +11 -1
  607. package/templates/site/tsconfig.json +1 -1
  608. package/themes/default/README.md +1216 -50
  609. package/themes/default/layouts/404.njk +2 -3
  610. package/themes/default/layouts/author.njk +36 -0
  611. package/themes/default/layouts/base.njk +161 -31
  612. package/themes/default/layouts/category.njk +27 -0
  613. package/themes/default/layouts/front-page.njk +56 -0
  614. package/themes/default/layouts/home.njk +17 -1
  615. package/themes/default/layouts/page.njk +57 -5
  616. package/themes/default/layouts/post.njk +78 -19
  617. package/themes/default/layouts/search.njk +57 -0
  618. package/themes/default/layouts/tag.njk +3 -6
  619. package/themes/default/mail/comment-digest.html.njk +37 -0
  620. package/themes/default/mail/comment-digest.subject.njk +3 -0
  621. package/themes/default/mail/comment-digest.txt.njk +40 -0
  622. package/themes/default/mail/comment-pending.html.njk +27 -0
  623. package/themes/default/mail/comment-pending.subject.njk +2 -0
  624. package/themes/default/mail/comment-pending.txt.njk +24 -0
  625. package/themes/default/mail/comment-reply.html.njk +21 -0
  626. package/themes/default/mail/comment-reply.subject.njk +2 -0
  627. package/themes/default/mail/comment-reply.txt.njk +22 -0
  628. package/themes/default/mail/contact-message.html.njk +24 -0
  629. package/themes/default/mail/contact-message.subject.njk +5 -0
  630. package/themes/default/mail/contact-message.txt.njk +20 -0
  631. package/themes/default/mail/password-changed.html.njk +18 -0
  632. package/themes/default/mail/password-changed.subject.njk +2 -0
  633. package/themes/default/mail/password-changed.txt.njk +17 -0
  634. package/themes/default/mail/password-reset.html.njk +22 -0
  635. package/themes/default/mail/password-reset.subject.njk +2 -0
  636. package/themes/default/mail/password-reset.txt.njk +18 -0
  637. package/themes/default/mail/test.html.njk +14 -0
  638. package/themes/default/mail/test.subject.njk +2 -0
  639. package/themes/default/mail/test.txt.njk +12 -0
  640. package/themes/default/partials/archive.njk +27 -0
  641. package/themes/default/partials/bio.njk +71 -0
  642. package/themes/default/partials/comment-form.njk +137 -0
  643. package/themes/default/partials/contact-form.njk +88 -0
  644. package/themes/default/partials/conversation.njk +139 -0
  645. package/themes/default/partials/feeds.njk +26 -0
  646. package/themes/default/partials/jsonld.njk +120 -0
  647. package/themes/default/partials/menu.njk +32 -0
  648. package/themes/default/partials/pagination.njk +12 -6
  649. package/themes/default/partials/post-list.njk +51 -18
  650. package/themes/default/partials/search-form.njk +22 -0
  651. package/themes/default/partials/tags.njk +26 -5
  652. package/themes/default/static/highlight.js +4 -0
  653. package/themes/default/static/style.css +1142 -194
  654. package/themes/default/theme.json +9 -0
  655. package/admin/layouts/federation.njk +0 -145
  656. package/admin/layouts/login.njk +0 -23
  657. package/admin/layouts/settings.njk +0 -116
  658. package/admin/layouts/setup.njk +0 -27
  659. package/admin/layouts/users.njk +0 -91
  660. /package/admin/{layouts/_flash.njk → components/flash.njk} +0 -0
@@ -0,0 +1,287 @@
1
+ import path from 'node:path';
2
+ import { listUsers } from '../admin/accounts.js';
3
+ import { MODERATION_ACTIONS } from '../comments/moderate.js';
4
+ import { readFileIfPresentSync, updateFileAtomically } from '../files/atomic.js';
5
+ import { COMMENTS_NOTIFICATION } from './comments.js';
6
+ import { moderationLink } from './links.js';
7
+ import { DELIVERY_WINDOW_MS, isBatchedMode, notificationMode, notificationRecipients, } from './preferences.js';
8
+ /**
9
+ * One message a window instead of one message a comment.
10
+ *
11
+ * TASK-55 emails every moderator the moment something enters the queue, which
12
+ * is right for a quiet site and wrong for a spam wave that Akismet let
13
+ * through. A user who has chosen `hourly` or `daily` on `/admin/users` is sent
14
+ * nothing when a comment arrives; instead this sends them one message per
15
+ * window listing everything still waiting, each item with its own approve,
16
+ * spam and delete links.
17
+ *
18
+ * Three decisions hold the whole thing up:
19
+ *
20
+ * - **A digest is derived, not queued.** It is built from what
21
+ * `admin.listComments({ status: 'pending' })` says right now, so an item
22
+ * somebody approved before the run is simply absent and there is no queue to
23
+ * keep true across a restart, a crash or a `geekity rebuild`. Nothing has to
24
+ * remember what it meant to send.
25
+ * - **The only new state is when each user was last written to**, and it lives
26
+ * in `data/notification-digests.json` rather than in the database, because
27
+ * decision-9 says the database may be deleted whenever the site is stopped —
28
+ * and a forgotten timestamp would mean either a window silently skipped or a
29
+ * digest sent twice. It is its own file rather than a field in
30
+ * `data/users.json` because that file is the answer to "who may sign in",
31
+ * and a timestamp the sender rewrites every hour is bookkeeping rather than
32
+ * anything about the person.
33
+ * - **Nothing is sent for an empty queue, and an empty run records nothing.**
34
+ * A user with nothing waiting is never emailed, and because their timestamp
35
+ * does not move, the first thing that does arrive goes out on the very next
36
+ * tick rather than waiting out a window that had nothing in it. The promise
37
+ * a mode makes is "at most one message per window", and that is kept.
38
+ */
39
+ /** The message a batched user gets: everything still waiting, in one. */
40
+ export const COMMENT_DIGEST_TEMPLATE = 'comment-digest';
41
+ /** The file, relative to `dataDir`. */
42
+ export const DIGEST_TIMES_FILE = 'notification-digests.json';
43
+ /** Nobody but the site's own user reads it. */
44
+ export const DIGEST_TIMES_FILE_MODE = 0o600;
45
+ /**
46
+ * How often the timer looks.
47
+ *
48
+ * A minute. The tick is not the window — each user's own mode decides whether
49
+ * they are due — so this only sets how promptly a window that has come up is
50
+ * noticed, and a minute's lag on an hourly digest is nothing while a busier
51
+ * tick would be a query a minute for no one.
52
+ */
53
+ export const DIGEST_TICK_MS = 60 * 1000;
54
+ /**
55
+ * The most items one digest lists.
56
+ *
57
+ * A cap rather than "everything", because "everything" during a spam wave is a
58
+ * message with thousands of entries and three signed links each, which no mail
59
+ * provider would take and nobody could read. What is over the cap is counted
60
+ * in a line at the bottom pointing at the moderation queue, and the next
61
+ * window lists the next hundred.
62
+ */
63
+ export const DIGEST_MAX_ITEMS = 100;
64
+ /** Where the digest times live for a given data directory. */
65
+ export function digestTimesFile(dataDir) {
66
+ return path.join(dataDir, DIGEST_TIMES_FILE);
67
+ }
68
+ /**
69
+ * When each user last had a digest of each event, as
70
+ * `{ [event]: { [userId]: instant } }`.
71
+ *
72
+ * Keyed by event as well as by user because the registry may grow a second
73
+ * batched notice, and the two would then be on windows of their own. A file
74
+ * that is missing, damaged or the wrong shape reads as nobody having had
75
+ * anything — the same direction {@link readCommentOptOuts} fails in, and for
76
+ * the same reason: the cost is a duplicate digest, and the alternative is a
77
+ * site whose timer throws over a stray comma.
78
+ */
79
+ export function readDigestTimes(dataDir) {
80
+ return digestTimesIn(readFileIfPresentSync(digestTimesFile(dataDir)));
81
+ }
82
+ /**
83
+ * Write down that these users have just been sent a digest of this event.
84
+ *
85
+ * The read is inside the write, so two runs at once cannot lose one another's
86
+ * timestamps.
87
+ */
88
+ export async function recordDigestTimes(dataDir, event, users, at) {
89
+ if (users.length === 0)
90
+ return;
91
+ await updateFileAtomically(digestTimesFile(dataDir), (current) => {
92
+ const held = digestTimesIn(current);
93
+ const forEvent = { ...held[event] };
94
+ for (const id of users)
95
+ forEvent[String(id)] = at.toISOString();
96
+ return `${JSON.stringify({ ...held, [event]: forEvent }, null, 2)}\n`;
97
+ }, { mode: DIGEST_TIMES_FILE_MODE });
98
+ }
99
+ /** The real one. */
100
+ export const systemNotificationTimers = {
101
+ set(callback, everyMs) {
102
+ const handle = setInterval(callback, everyMs);
103
+ handle.unref?.();
104
+ return handle;
105
+ },
106
+ clear(handle) {
107
+ clearInterval(handle);
108
+ },
109
+ };
110
+ /** Build the digest sender for one site. */
111
+ export function createCommentDigest(options) {
112
+ const { admin, store, mail, config } = options;
113
+ const timers = options.timers ?? systemNotificationTimers;
114
+ const tickMs = options.tickMs ?? DIGEST_TICK_MS;
115
+ const maxItems = options.maxItems ?? DIGEST_MAX_ITEMS;
116
+ const logger = options.logger ?? console;
117
+ let handle;
118
+ let running = Promise.resolve();
119
+ /** The post an item is on, as a message names it. */
120
+ function postOf(comment) {
121
+ const document = store.getBySlug(comment.slug);
122
+ return {
123
+ title: document?.title ?? comment.slug,
124
+ url: absolute(comment.permalink, config.baseUrl),
125
+ };
126
+ }
127
+ /**
128
+ * One item of the digest: the comment as a message prints it, the post it is
129
+ * on, and its three links.
130
+ *
131
+ * The links are minted per recipient rather than once for the run, because a
132
+ * moderation link is spent the first time it is used: two moderators sharing
133
+ * one link would mean the second of them clicking a dead one.
134
+ */
135
+ function itemFor(comment, now) {
136
+ const context = { dataDir: config.dataDir, baseUrl: config.baseUrl, now };
137
+ return {
138
+ comment: {
139
+ author: comment.author.name,
140
+ // The website they gave, never their email: the message goes to a
141
+ // moderator, but it also goes through a third party's servers.
142
+ website: comment.author.url,
143
+ source: comment.source,
144
+ kind: comment.kind,
145
+ text: comment.content.markdown,
146
+ html: comment.content.html,
147
+ submitted: comment.submitted,
148
+ url: comment.url,
149
+ },
150
+ post: postOf(comment),
151
+ actions: MODERATION_ACTIONS.map((action) => ({
152
+ action,
153
+ label: action === 'approve' ? 'Approve' : action === 'spam' ? 'Spam' : 'Delete',
154
+ url: moderationLink(context, comment.id, action),
155
+ })),
156
+ };
157
+ }
158
+ async function send() {
159
+ // Asked before anything else, so a site that sends no mail never mints a
160
+ // signing secret it would have no use for and never writes a record of
161
+ // digests it did not send.
162
+ if (!mail.configured())
163
+ return 0;
164
+ const now = config.now();
165
+ // Read per run rather than held, so a record written by another process —
166
+ // or one restored with the rest of `data/` — is what this run goes on.
167
+ const sentAt = readDigestTimes(config.dataDir)[COMMENTS_NOTIFICATION] ?? {};
168
+ const due = notificationRecipients(listUsers(config.dataDir), COMMENTS_NOTIFICATION).filter((recipient) => {
169
+ const mode = notificationMode(recipient.user, COMMENTS_NOTIFICATION);
170
+ if (!isBatchedMode(mode))
171
+ return false;
172
+ return elapsed(sentAt[String(recipient.user.id)], now) >= DELIVERY_WINDOW_MS[mode];
173
+ });
174
+ if (due.length === 0)
175
+ return 0;
176
+ // One query for the whole run: everybody due is due the same list, because
177
+ // a digest is what is waiting rather than what has happened since.
178
+ const pending = admin.listComments({ status: 'pending' });
179
+ if (pending.length === 0)
180
+ return 0;
181
+ const listed = pending.slice(0, maxItems);
182
+ const queueUrl = absolute('/admin/comments', config.baseUrl);
183
+ for (const recipient of due) {
184
+ void mail.send({
185
+ to: recipient.email,
186
+ template: COMMENT_DIGEST_TEMPLATE,
187
+ data: {
188
+ items: listed.map((comment) => itemFor(comment, now)),
189
+ total: pending.length,
190
+ more: pending.length - listed.length,
191
+ mode: notificationMode(recipient.user, COMMENTS_NOTIFICATION),
192
+ queueUrl,
193
+ },
194
+ });
195
+ }
196
+ // After the sends rather than before, because `send` never rejects: a
197
+ // message the provider refuses is retried by the mail service itself, and
198
+ // a run that recorded nothing would send the whole digest again next tick.
199
+ await recordDigestTimes(config.dataDir, COMMENTS_NOTIFICATION, due.map((recipient) => recipient.user.id), now);
200
+ return due.length;
201
+ }
202
+ /** One run, and the thing `settled()` waits on. */
203
+ function runOnce() {
204
+ const started = send();
205
+ running = started;
206
+ return started;
207
+ }
208
+ return {
209
+ async run() {
210
+ return await runOnce();
211
+ },
212
+ start() {
213
+ if (handle !== undefined)
214
+ return;
215
+ handle = timers.set(() => {
216
+ running = runOnce().catch((error) => {
217
+ // A tick that fails is a line in the log and nothing else: the
218
+ // record was not written, so the next tick tries the same window
219
+ // again.
220
+ logger.warn(`A comment digest could not be sent: ${messageOf(error)}`);
221
+ });
222
+ }, tickMs);
223
+ },
224
+ stop() {
225
+ if (handle !== undefined)
226
+ timers.clear(handle);
227
+ handle = undefined;
228
+ },
229
+ async settled() {
230
+ await running.catch(ignore);
231
+ },
232
+ };
233
+ }
234
+ /** How long ago that instant was, or `Infinity` for one that was never. */
235
+ function elapsed(instant, now) {
236
+ if (instant === undefined)
237
+ return Number.POSITIVE_INFINITY;
238
+ const at = Date.parse(instant);
239
+ // A timestamp nobody can read is a timestamp there is no reason to trust,
240
+ // and treating it as "never" costs one duplicate digest at most.
241
+ if (Number.isNaN(at))
242
+ return Number.POSITIVE_INFINITY;
243
+ return now.getTime() - at;
244
+ }
245
+ /** The record a file's bytes hold. */
246
+ function digestTimesIn(source) {
247
+ if (source === undefined)
248
+ return {};
249
+ let parsed;
250
+ try {
251
+ parsed = JSON.parse(source);
252
+ }
253
+ catch {
254
+ return {};
255
+ }
256
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
257
+ return {};
258
+ const found = {};
259
+ for (const [event, times] of Object.entries(parsed)) {
260
+ if (typeof times !== 'object' || times === null || Array.isArray(times))
261
+ continue;
262
+ const forEvent = {};
263
+ for (const [id, at] of Object.entries(times)) {
264
+ if (typeof at === 'string' && at !== '')
265
+ forEvent[id] = at;
266
+ }
267
+ if (Object.keys(forEvent).length > 0)
268
+ found[event] = forEvent;
269
+ }
270
+ return found;
271
+ }
272
+ /** A site-root path as an absolute URL. */
273
+ function absolute(pathname, baseUrl) {
274
+ try {
275
+ return new URL(pathname, baseUrl).href;
276
+ }
277
+ catch {
278
+ return pathname;
279
+ }
280
+ }
281
+ function messageOf(error) {
282
+ return error instanceof Error ? error.message : String(error);
283
+ }
284
+ function ignore() {
285
+ // A failed run is already logged; waiting for it must not throw.
286
+ }
287
+ //# sourceMappingURL=digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/notifications/digest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export { COMMENT_PENDING_TEMPLATE, COMMENT_REPLY_TEMPLATE, COMMENTS_NOTIFICATION, createCommentNotifier, } from './comments.ts';
2
+ export type { CommentNotifier, CreateCommentNotifierOptions } from './comments.ts';
3
+ export { COMMENT_DIGEST_TEMPLATE, createCommentDigest, DIGEST_MAX_ITEMS, DIGEST_TICK_MS, DIGEST_TIMES_FILE, digestTimesFile, readDigestTimes, recordDigestTimes, systemNotificationTimers, } from './digest.ts';
4
+ export type { CommentDigest, CreateCommentDigestOptions, DigestLogger, NotificationTimers, } from './digest.ts';
5
+ export { MODERATE_PATH, MODERATION_TOKEN_LIFETIME_SECONDS, moderationLink, NOTIFICATION_FIELDS, UNSUBSCRIBE_ACTION, UNSUBSCRIBE_PATH, UNSUBSCRIBE_TOKEN_LIFETIME_SECONDS, unsubscribeLink, } from './links.ts';
6
+ export type { LinkContext } from './links.ts';
7
+ export { addCommentOptOut, COMMENT_OPTOUTS_FILE, commentOptOutsFile, hasOptedOut, readCommentOptOuts, } from './optouts.ts';
8
+ export { DEFAULT_DELIVERY_MODE, DELIVERY_MODE_LABELS, DELIVERY_WINDOW_MS, deliveryMode, isBatchedMode, NOTIFICATION_DELIVERY_MODES, NOTIFICATION_EVENTS, notificationEvent, notificationMode, notificationRecipients, notificationSwitches, notificationWanted, withNotification, withNotificationMode, } from './preferences.ts';
9
+ export type { NotificationDeliveryMode, NotificationEvent, NotificationSwitch, } from './preferences.ts';
10
+ export { mountNotificationLinks } from './routes.ts';
11
+ export { NOTIFICATION_SECRET_FILE, notificationTokenExpiry, readNotificationToken, signNotificationToken, } from './tokens.ts';
12
+ export type { NewNotificationToken, NotificationClaim } from './tokens.ts';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/notifications/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,eAAe,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,aAAa,EACb,0BAA0B,EAC1B,YAAY,EACZ,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,EACb,iCAAiC,EACjC,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kCAAkC,EAClC,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,WAAW,EACX,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,2BAA2B,EAC3B,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { COMMENT_PENDING_TEMPLATE, COMMENT_REPLY_TEMPLATE, COMMENTS_NOTIFICATION, createCommentNotifier, } from './comments.js';
2
+ export { COMMENT_DIGEST_TEMPLATE, createCommentDigest, DIGEST_MAX_ITEMS, DIGEST_TICK_MS, DIGEST_TIMES_FILE, digestTimesFile, readDigestTimes, recordDigestTimes, systemNotificationTimers, } from './digest.js';
3
+ export { MODERATE_PATH, MODERATION_TOKEN_LIFETIME_SECONDS, moderationLink, NOTIFICATION_FIELDS, UNSUBSCRIBE_ACTION, UNSUBSCRIBE_PATH, UNSUBSCRIBE_TOKEN_LIFETIME_SECONDS, unsubscribeLink, } from './links.js';
4
+ export { addCommentOptOut, COMMENT_OPTOUTS_FILE, commentOptOutsFile, hasOptedOut, readCommentOptOuts, } from './optouts.js';
5
+ export { DEFAULT_DELIVERY_MODE, DELIVERY_MODE_LABELS, DELIVERY_WINDOW_MS, deliveryMode, isBatchedMode, NOTIFICATION_DELIVERY_MODES, NOTIFICATION_EVENTS, notificationEvent, notificationMode, notificationRecipients, notificationSwitches, notificationWanted, withNotification, withNotificationMode, } from './preferences.js';
6
+ export { mountNotificationLinks } from './routes.js';
7
+ export { NOTIFICATION_SECRET_FILE, notificationTokenExpiry, readNotificationToken, signNotificationToken, } from './tokens.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/notifications/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AAOrB,OAAO,EACL,aAAa,EACb,iCAAiC,EACjC,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kCAAkC,EAClC,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,WAAW,EACX,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,2BAA2B,EAC3B,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,60 @@
1
+ import type { ModerationAction } from '../comments/moderate.ts';
2
+ /**
3
+ * The URLs a notification puts in front of somebody, and what they cost.
4
+ *
5
+ * Both live under `/_geekity/`, like the comment endpoint and the webmention
6
+ * one, so no permalink a site ever mints can shadow them and the route table
7
+ * does not grow with the site.
8
+ *
9
+ * Each carries two things: the token, which is the whole of its authority, and
10
+ * a plain `action=` that says what it does. The action is in the token too and
11
+ * the handler refuses a link whose two halves disagree — it is in the query so
12
+ * that a person reading a wall of links in a message can see which is which,
13
+ * and so that a browser's address bar says what is about to happen.
14
+ */
15
+ /** Where a moderation link lands. */
16
+ export declare const MODERATE_PATH = "/_geekity/moderate";
17
+ /** Where an unsubscribe link lands. */
18
+ export declare const UNSUBSCRIBE_PATH = "/_geekity/unsubscribe";
19
+ /** The fields both landing pages read, from the query and from the form. */
20
+ export declare const NOTIFICATION_FIELDS: {
21
+ /** The signed claim. */
22
+ readonly token: 'token';
23
+ /** What the link says it does, checked against the token. */
24
+ readonly action: 'action';
25
+ };
26
+ /** The one thing an unsubscribe token is for. */
27
+ export declare const UNSUBSCRIBE_ACTION = "unsubscribe";
28
+ /**
29
+ * How long a moderation link works for.
30
+ *
31
+ * A week. Long enough that a message read on Monday is still actionable on
32
+ * Friday, short enough that a mailbox somebody stopped reading a year ago is
33
+ * not a drawer of keys to this site's comment queue. After it, the link says
34
+ * so and the moderation screen is one sign-in away.
35
+ */
36
+ export declare const MODERATION_TOKEN_LIFETIME_SECONDS: number;
37
+ /**
38
+ * How long an unsubscribe link works for.
39
+ *
40
+ * A year, which is much longer, because the two links are asking opposite
41
+ * questions. A stale moderation link is a small power somebody should not
42
+ * still have; a stale unsubscribe link is a person who pressed Stop and was
43
+ * told no. The second failure is worse, and the link grants nothing but the
44
+ * right to be left alone.
45
+ */
46
+ export declare const UNSUBSCRIBE_TOKEN_LIFETIME_SECONDS: number;
47
+ /** What every link builder needs: the secret's home and the site's origin. */
48
+ export interface LinkContext {
49
+ /** Where the signing secret lives. */
50
+ dataDir: string;
51
+ /** The site's public origin; the link is read somewhere else, so it is absolute. */
52
+ baseUrl: string;
53
+ /** The clock. */
54
+ now: Date;
55
+ }
56
+ /** The link that does one thing to one comment. */
57
+ export declare function moderationLink(context: LinkContext, commentId: string, action: ModerationAction): string;
58
+ /** The link that stops this site writing to an address. */
59
+ export declare function unsubscribeLink(context: LinkContext, email: string): string;
60
+ //# sourceMappingURL=links.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../src/notifications/links.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE;;;;;;;;;;;;GAYG;AAEH,qCAAqC;AACrC,eAAO,MAAM,aAAa,uBAAuB,CAAC;AAElD,uCAAuC;AACvC,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD,4EAA4E;AAC5E,eAAO,MAAM,mBAAmB;IAC9B,wBAAwB;aACxB,KAAK,EAAE,OAAO;IACd,6DAA6D;aAC7D,MAAM,EAAE,QAAQ;CACR,CAAC;AAEX,iDAAiD;AACjD,eAAO,MAAM,kBAAkB,gBAAgB,CAAC;AAEhD;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,QAAmB,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,kCAAkC,QAAqB,CAAC;AAErE,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB;IACjB,GAAG,EAAE,IAAI,CAAC;CACX;AAED,mDAAmD;AACnD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,GACvB,MAAM,CAER;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ3E"}
@@ -0,0 +1,63 @@
1
+ import { signNotificationToken } from './tokens.js';
2
+ /**
3
+ * The URLs a notification puts in front of somebody, and what they cost.
4
+ *
5
+ * Both live under `/_geekity/`, like the comment endpoint and the webmention
6
+ * one, so no permalink a site ever mints can shadow them and the route table
7
+ * does not grow with the site.
8
+ *
9
+ * Each carries two things: the token, which is the whole of its authority, and
10
+ * a plain `action=` that says what it does. The action is in the token too and
11
+ * the handler refuses a link whose two halves disagree — it is in the query so
12
+ * that a person reading a wall of links in a message can see which is which,
13
+ * and so that a browser's address bar says what is about to happen.
14
+ */
15
+ /** Where a moderation link lands. */
16
+ export const MODERATE_PATH = '/_geekity/moderate';
17
+ /** Where an unsubscribe link lands. */
18
+ export const UNSUBSCRIBE_PATH = '/_geekity/unsubscribe';
19
+ /** The fields both landing pages read, from the query and from the form. */
20
+ export const NOTIFICATION_FIELDS = {
21
+ /** The signed claim. */
22
+ token: 'token',
23
+ /** What the link says it does, checked against the token. */
24
+ action: 'action',
25
+ };
26
+ /** The one thing an unsubscribe token is for. */
27
+ export const UNSUBSCRIBE_ACTION = 'unsubscribe';
28
+ /**
29
+ * How long a moderation link works for.
30
+ *
31
+ * A week. Long enough that a message read on Monday is still actionable on
32
+ * Friday, short enough that a mailbox somebody stopped reading a year ago is
33
+ * not a drawer of keys to this site's comment queue. After it, the link says
34
+ * so and the moderation screen is one sign-in away.
35
+ */
36
+ export const MODERATION_TOKEN_LIFETIME_SECONDS = 7 * 24 * 60 * 60;
37
+ /**
38
+ * How long an unsubscribe link works for.
39
+ *
40
+ * A year, which is much longer, because the two links are asking opposite
41
+ * questions. A stale moderation link is a small power somebody should not
42
+ * still have; a stale unsubscribe link is a person who pressed Stop and was
43
+ * told no. The second failure is worse, and the link grants nothing but the
44
+ * right to be left alone.
45
+ */
46
+ export const UNSUBSCRIBE_TOKEN_LIFETIME_SECONDS = 365 * 24 * 60 * 60;
47
+ /** The link that does one thing to one comment. */
48
+ export function moderationLink(context, commentId, action) {
49
+ return linkTo(context, MODERATE_PATH, action, commentId, MODERATION_TOKEN_LIFETIME_SECONDS);
50
+ }
51
+ /** The link that stops this site writing to an address. */
52
+ export function unsubscribeLink(context, email) {
53
+ return linkTo(context, UNSUBSCRIBE_PATH, UNSUBSCRIBE_ACTION, email, UNSUBSCRIBE_TOKEN_LIFETIME_SECONDS);
54
+ }
55
+ /** One signed link, absolute. */
56
+ function linkTo(context, pathname, action, subject, lifetimeSeconds) {
57
+ const base = context.baseUrl.endsWith('/') ? context.baseUrl : `${context.baseUrl}/`;
58
+ const url = new URL(pathname, base);
59
+ url.searchParams.set(NOTIFICATION_FIELDS.action, action);
60
+ url.searchParams.set(NOTIFICATION_FIELDS.token, signNotificationToken(context.dataDir, { action, subject, lifetimeSeconds }, context.now));
61
+ return url.href;
62
+ }
63
+ //# sourceMappingURL=links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/notifications/links.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;;;;;;;;GAYG;AAEH,qCAAqC;AACrC,MAAM,CAAC,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAElD,uCAAuC;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAExD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,wBAAwB;IACxB,KAAK,EAAE,OAAO;IACd,6DAA6D;IAC7D,MAAM,EAAE,QAAQ;CACR,CAAC;AAEX,iDAAiD;AACjD,MAAM,CAAC,MAAM,kBAAkB,GAAG,aAAa,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAElE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAYrE,mDAAmD;AACnD,MAAM,UAAU,cAAc,CAC5B,OAAoB,EACpB,SAAiB,EACjB,MAAwB;IAExB,OAAO,MAAM,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,iCAAiC,CAAC,CAAC;AAC9F,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,eAAe,CAAC,OAAoB,EAAE,KAAa;IACjE,OAAO,MAAM,CACX,OAAO,EACP,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,EACL,kCAAkC,CACnC,CAAC;AACJ,CAAC;AAED,iCAAiC;AACjC,SAAS,MAAM,CACb,OAAoB,EACpB,QAAgB,EAChB,MAAc,EACd,OAAe,EACf,eAAuB;IAEvB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,GAAG,CAAC;IACrF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACpC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzD,GAAG,CAAC,YAAY,CAAC,GAAG,CAClB,mBAAmB,CAAC,KAAK,EACzB,qBAAqB,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,CAC1F,CAAC;IACF,OAAO,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The addresses this site has been told to stop writing to.
3
+ *
4
+ * A commenter who ticked "tell me about replies" and later changed their mind
5
+ * clicks one link and is done, without a login and without answering anything.
6
+ * That is the whole of what an unsubscribe has to be, and everything else here
7
+ * follows from it:
8
+ *
9
+ * - **Site-wide, by address.** Not per post and not per thread. Somebody who
10
+ * presses Unsubscribe means "stop", and a site that then wrote to them about
11
+ * a different post would have been reading the button as "stop, on this page
12
+ * only". It also means one click settles every thread they are in, including
13
+ * the ones they have forgotten about.
14
+ * - **In `data/`, not `content/`.** The list is a list of email addresses.
15
+ * `content/` is published with the site and goes into git; this is written
16
+ * 0600 beside the password hashes, and no screen ever renders it.
17
+ * - **Folded to lower case.** An address is case-insensitive in practice, and
18
+ * somebody who unsubscribed as `Grace@example.com` has unsubscribed.
19
+ *
20
+ * A comment keeps its `notify` flag either way. The opt-out is checked at the
21
+ * moment of sending, so the list is the one thing that has to be right, and
22
+ * nothing has to go back and rewrite comment files somebody may have in git.
23
+ */
24
+ /** The file, relative to `dataDir`. */
25
+ export declare const COMMENT_OPTOUTS_FILE = "comment-optouts.json";
26
+ /** Nobody but the site's own user reads it. */
27
+ export declare const COMMENT_OPTOUTS_FILE_MODE = 384;
28
+ /** Where the list lives for a given data directory. */
29
+ export declare function commentOptOutsFile(dataDir: string): string;
30
+ /**
31
+ * Every address that has asked not to hear any more, folded to lower case.
32
+ *
33
+ * A file that is missing, damaged or not the shape it should be reads as an
34
+ * empty list rather than throwing. That is the wrong direction to fail in and
35
+ * it is chosen deliberately: the alternative is a site whose comment endpoint
36
+ * throws because a JSON file has a stray comma in it.
37
+ */
38
+ export declare function readCommentOptOuts(dataDir: string): Set<string>;
39
+ /** Whether this site has been told to stop writing to that address. */
40
+ export declare function hasOptedOut(dataDir: string, email: string | null | undefined): boolean;
41
+ /**
42
+ * Stop writing to an address. Returns whether it was not already on the list.
43
+ *
44
+ * The read is inside the write, so two links clicked at once cannot lose one
45
+ * another's address.
46
+ */
47
+ export declare function addCommentOptOut(dataDir: string, email: string): Promise<boolean>;
48
+ //# sourceMappingURL=optouts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optouts.d.ts","sourceRoot":"","sources":["../../src/notifications/optouts.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,uCAAuC;AACvC,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D,+CAA+C;AAC/C,eAAO,MAAM,yBAAyB,MAAQ,CAAC;AAE/C,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAE/D;AAED,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAGtF;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBvF"}
@@ -0,0 +1,93 @@
1
+ import path from 'node:path';
2
+ import { readFileIfPresentSync, updateFileAtomically } from '../files/atomic.js';
3
+ /**
4
+ * The addresses this site has been told to stop writing to.
5
+ *
6
+ * A commenter who ticked "tell me about replies" and later changed their mind
7
+ * clicks one link and is done, without a login and without answering anything.
8
+ * That is the whole of what an unsubscribe has to be, and everything else here
9
+ * follows from it:
10
+ *
11
+ * - **Site-wide, by address.** Not per post and not per thread. Somebody who
12
+ * presses Unsubscribe means "stop", and a site that then wrote to them about
13
+ * a different post would have been reading the button as "stop, on this page
14
+ * only". It also means one click settles every thread they are in, including
15
+ * the ones they have forgotten about.
16
+ * - **In `data/`, not `content/`.** The list is a list of email addresses.
17
+ * `content/` is published with the site and goes into git; this is written
18
+ * 0600 beside the password hashes, and no screen ever renders it.
19
+ * - **Folded to lower case.** An address is case-insensitive in practice, and
20
+ * somebody who unsubscribed as `Grace@example.com` has unsubscribed.
21
+ *
22
+ * A comment keeps its `notify` flag either way. The opt-out is checked at the
23
+ * moment of sending, so the list is the one thing that has to be right, and
24
+ * nothing has to go back and rewrite comment files somebody may have in git.
25
+ */
26
+ /** The file, relative to `dataDir`. */
27
+ export const COMMENT_OPTOUTS_FILE = 'comment-optouts.json';
28
+ /** Nobody but the site's own user reads it. */
29
+ export const COMMENT_OPTOUTS_FILE_MODE = 0o600;
30
+ /** Where the list lives for a given data directory. */
31
+ export function commentOptOutsFile(dataDir) {
32
+ return path.join(dataDir, COMMENT_OPTOUTS_FILE);
33
+ }
34
+ /**
35
+ * Every address that has asked not to hear any more, folded to lower case.
36
+ *
37
+ * A file that is missing, damaged or not the shape it should be reads as an
38
+ * empty list rather than throwing. That is the wrong direction to fail in and
39
+ * it is chosen deliberately: the alternative is a site whose comment endpoint
40
+ * throws because a JSON file has a stray comma in it.
41
+ */
42
+ export function readCommentOptOuts(dataDir) {
43
+ return optOutsIn(readFileIfPresentSync(commentOptOutsFile(dataDir)));
44
+ }
45
+ /** Whether this site has been told to stop writing to that address. */
46
+ export function hasOptedOut(dataDir, email) {
47
+ if (email === null || email === undefined || email === '')
48
+ return false;
49
+ return readCommentOptOuts(dataDir).has(email.trim().toLowerCase());
50
+ }
51
+ /**
52
+ * Stop writing to an address. Returns whether it was not already on the list.
53
+ *
54
+ * The read is inside the write, so two links clicked at once cannot lose one
55
+ * another's address.
56
+ */
57
+ export async function addCommentOptOut(dataDir, email) {
58
+ const wanted = email.trim().toLowerCase();
59
+ if (wanted === '')
60
+ return false;
61
+ let added = false;
62
+ await updateFileAtomically(commentOptOutsFile(dataDir), (current) => {
63
+ const held = optOutsIn(current);
64
+ added = !held.has(wanted);
65
+ held.add(wanted);
66
+ return `${JSON.stringify({ addresses: [...held].sort() }, null, 2)}\n`;
67
+ }, { mode: COMMENT_OPTOUTS_FILE_MODE });
68
+ return added;
69
+ }
70
+ /** The addresses a file's bytes hold. */
71
+ function optOutsIn(source) {
72
+ if (source === undefined)
73
+ return new Set();
74
+ let parsed;
75
+ try {
76
+ parsed = JSON.parse(source);
77
+ }
78
+ catch {
79
+ return new Set();
80
+ }
81
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
82
+ return new Set();
83
+ const listed = parsed['addresses'];
84
+ if (!Array.isArray(listed))
85
+ return new Set();
86
+ const found = new Set();
87
+ for (const entry of listed) {
88
+ if (typeof entry === 'string' && entry.trim() !== '')
89
+ found.add(entry.trim().toLowerCase());
90
+ }
91
+ return found;
92
+ }
93
+ //# sourceMappingURL=optouts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optouts.js","sourceRoot":"","sources":["../../src/notifications/optouts.ts"],"names":[],"mappings":"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"}