@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,130 @@
1
+ import type { CommentChecker } from './submission.ts';
2
+ /**
3
+ * Akismet, as the one {@link CommentChecker} this package ships.
4
+ *
5
+ * The CMS knows about spam services through a seam and nothing else (doc-6):
6
+ * everything below implements that interface and reaches one HTTP API. Take
7
+ * this file away and comments still work, still queue, and still moderate.
8
+ *
9
+ * Two things make it optional at runtime rather than at build time:
10
+ *
11
+ * - **The key is a credential**, so it lives in `data/akismet.json` at mode
12
+ * `0600` beside the password hashes and the actor's private keys, and never
13
+ * in `content/_data/site.json`, which is public, in git and published with
14
+ * the site.
15
+ * - **The key file is read on every call**, so a key pasted into the settings
16
+ * screen starts filtering the next comment and a key removed stops filtering
17
+ * at once. Without one, nothing is sent anywhere and the verdict is
18
+ * `unknown` — the site's own rules decide, exactly as they did before
19
+ * anybody had heard of Akismet.
20
+ */
21
+ /** Where the key lives, under `dataDir`. */
22
+ export declare const AKISMET_KEY_FILE = "akismet.json";
23
+ /** The API this speaks. */
24
+ export declare const AKISMET_ENDPOINT = "https://rest.akismet.com/1.1";
25
+ /**
26
+ * What Akismet is told this is.
27
+ *
28
+ * Its docs ask for `Application/Version | Plugin/Version`, and the pair is how
29
+ * a report in their dashboard says which piece of software sent a call.
30
+ */
31
+ export declare const AKISMET_USER_AGENT = "Geekity CMS/1.0 | Akismet/1.1";
32
+ /**
33
+ * How long Akismet has to answer before the call is abandoned.
34
+ *
35
+ * A comment must not wait on somebody else's service: an abandoned call is a
36
+ * logged failure and no opinion, and the comment goes to the queue.
37
+ */
38
+ export declare const AKISMET_TIMEOUT_MS = 10000;
39
+ /** What the last {@link verifyAkismetKey} of a key came to. */
40
+ export type AkismetKeyStatus =
41
+ /** Akismet said `valid`. */
42
+ 'valid'
43
+ /** Akismet said `invalid`: the key is not one of theirs, or not any more. */
44
+ | 'invalid'
45
+ /** Akismet could not be reached, so nothing is known either way. */
46
+ | 'unchecked';
47
+ /** The key as `data/akismet.json` holds it. */
48
+ export interface AkismetKeyRecord {
49
+ /** The key itself. */
50
+ key: string;
51
+ /** What Akismet said about it when it was last checked. */
52
+ status: AkismetKeyStatus;
53
+ /** When that was, as an instant. */
54
+ checkedAt: string;
55
+ }
56
+ /**
57
+ * The stored key, or `undefined` when the site has none.
58
+ *
59
+ * Read tolerantly and synchronously, like every other file this CMS treats as
60
+ * truth: a file that will not parse, or one with no key in it, is a site with
61
+ * no Akismet rather than a site that will not boot.
62
+ */
63
+ export declare function readAkismetKey(dataDir: string): AkismetKeyRecord | undefined;
64
+ /** Write the key, private to the account the site runs as. */
65
+ export declare function writeAkismetKey(dataDir: string, record: AkismetKeyRecord): Promise<void>;
66
+ /** Forget the key, which is how a site turns Akismet off. */
67
+ export declare function removeAkismetKey(dataDir: string): Promise<void>;
68
+ /** Where one site's key file is. */
69
+ export declare function akismetKeyPath(dataDir: string): string;
70
+ /** What {@link verifyAkismetKey} needs. */
71
+ export interface VerifyAkismetKeyOptions {
72
+ /** The key to check. */
73
+ key: string;
74
+ /** The site's public origin, which Akismet calls the blog. */
75
+ blog: string;
76
+ /** The HTTP client. Defaults to the global `fetch`; a test hands in its own. */
77
+ fetch?: typeof fetch | undefined;
78
+ /** How long to wait. */
79
+ timeoutMs?: number | undefined;
80
+ }
81
+ /**
82
+ * Ask Akismet whether a key is one of theirs.
83
+ *
84
+ * Three answers rather than two, because "we could not ask" is not "no": a key
85
+ * typed while the machine is offline should not be reported as a bad key, and
86
+ * the settings screen says which of the two happened.
87
+ */
88
+ export declare function verifyAkismetKey(options: VerifyAkismetKeyOptions): Promise<AkismetKeyStatus>;
89
+ /** What {@link createAkismetChecker} needs around it. */
90
+ export interface AkismetCheckerOptions {
91
+ /** Where the key file lives. Read on every call, so a new key needs no restart. */
92
+ dataDir: string;
93
+ /**
94
+ * The site's language, as `blog_lang` wants it.
95
+ *
96
+ * A function rather than a string because the setting is a file the admin
97
+ * rewrites: reading it when a comment arrives is what makes a change to it
98
+ * take effect at once, the way every other setting does (decision-9).
99
+ */
100
+ language: () => string;
101
+ /** The HTTP client. Defaults to the global `fetch`; a test hands in its own. */
102
+ fetch?: typeof fetch | undefined;
103
+ /** Where a failure is reported. Defaults to the console. */
104
+ logger?: {
105
+ warn(message: string): void;
106
+ } | undefined;
107
+ /** How long Akismet has to answer. */
108
+ timeoutMs?: number | undefined;
109
+ /**
110
+ * Send `is_test`, so nothing said here trains the classifier or counts
111
+ * against the site's plan. Off in production and on wherever a real key
112
+ * could conceivably meet a real endpoint from a test.
113
+ */
114
+ isTest?: boolean | undefined;
115
+ }
116
+ /**
117
+ * A {@link CommentChecker} that asks Akismet.
118
+ *
119
+ * Built whether or not the site has a key: with none, every method returns
120
+ * without sending anything, which is what lets a key pasted into the settings
121
+ * screen start working on the very next comment.
122
+ *
123
+ * Nothing here throws. The seam would catch it and call it `unknown` anyway
124
+ * (doc-6), but then the log would say only that a checker refused to answer;
125
+ * catching it here is what puts Akismet's own words — a 503, a
126
+ * `X-akismet-debug-help` — in front of whoever has to work out why the queue
127
+ * has stopped being filtered.
128
+ */
129
+ export declare function createAkismetChecker(options: AkismetCheckerOptions): CommentChecker;
130
+ //# sourceMappingURL=akismet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"akismet.d.ts","sourceRoot":"","sources":["../../src/comments/akismet.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAoC,MAAM,iBAAiB,CAAC;AAExF;;;;;;;;;;;;;;;;;;GAkBG;AAEH,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,iBAAiB,CAAC;AAE/C,2BAA2B;AAC3B,eAAO,MAAM,gBAAgB,iCAAiC,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,kCAAkC,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAEzC,+DAA+D;AAC/D,MAAM,MAAM,gBAAgB;AAC1B,4BAA4B;AAC1B,OAAO;AACT,6EAA6E;GAC3E,SAAS;AACX,oEAAoE;GAClE,WAAW,CAAC;AAEhB,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,sBAAsB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,MAAM,EAAE,gBAAgB,CAAC;IACzB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAsB5E;AAED,8DAA8D;AAC9D,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9F;AAED,6DAA6D;AAC7D,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAErE;AAED,oCAAoC;AACpC,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,2CAA2C;AAC3C,MAAM,WAAW,uBAAuB;IACtC,wBAAwB;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,KAAK,GAAG,SAAS,CAAC;IACjC,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAY3B;AAED,yDAAyD;AACzD,MAAM,WAAW,qBAAqB;IACpC,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,MAAM,CAAC;IACvB,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,KAAK,GAAG,SAAS,CAAC;IACjC,4DAA4D;IAC5D,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC;IACrD,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CA+DnF"}
@@ -0,0 +1,274 @@
1
+ import { rm } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { readFileIfPresentSync, writeFileAtomically } from '../files/atomic.js';
4
+ import { COMMENT_FIELDS } from './submission.js';
5
+ /**
6
+ * Akismet, as the one {@link CommentChecker} this package ships.
7
+ *
8
+ * The CMS knows about spam services through a seam and nothing else (doc-6):
9
+ * everything below implements that interface and reaches one HTTP API. Take
10
+ * this file away and comments still work, still queue, and still moderate.
11
+ *
12
+ * Two things make it optional at runtime rather than at build time:
13
+ *
14
+ * - **The key is a credential**, so it lives in `data/akismet.json` at mode
15
+ * `0600` beside the password hashes and the actor's private keys, and never
16
+ * in `content/_data/site.json`, which is public, in git and published with
17
+ * the site.
18
+ * - **The key file is read on every call**, so a key pasted into the settings
19
+ * screen starts filtering the next comment and a key removed stops filtering
20
+ * at once. Without one, nothing is sent anywhere and the verdict is
21
+ * `unknown` — the site's own rules decide, exactly as they did before
22
+ * anybody had heard of Akismet.
23
+ */
24
+ /** Where the key lives, under `dataDir`. */
25
+ export const AKISMET_KEY_FILE = 'akismet.json';
26
+ /** The API this speaks. */
27
+ export const AKISMET_ENDPOINT = 'https://rest.akismet.com/1.1';
28
+ /**
29
+ * What Akismet is told this is.
30
+ *
31
+ * Its docs ask for `Application/Version | Plugin/Version`, and the pair is how
32
+ * a report in their dashboard says which piece of software sent a call.
33
+ */
34
+ export const AKISMET_USER_AGENT = 'Geekity CMS/1.0 | Akismet/1.1';
35
+ /**
36
+ * How long Akismet has to answer before the call is abandoned.
37
+ *
38
+ * A comment must not wait on somebody else's service: an abandoned call is a
39
+ * logged failure and no opinion, and the comment goes to the queue.
40
+ */
41
+ export const AKISMET_TIMEOUT_MS = 10_000;
42
+ /**
43
+ * The stored key, or `undefined` when the site has none.
44
+ *
45
+ * Read tolerantly and synchronously, like every other file this CMS treats as
46
+ * truth: a file that will not parse, or one with no key in it, is a site with
47
+ * no Akismet rather than a site that will not boot.
48
+ */
49
+ export function readAkismetKey(dataDir) {
50
+ const raw = readFileIfPresentSync(akismetKeyPath(dataDir));
51
+ if (raw === undefined)
52
+ return undefined;
53
+ let parsed;
54
+ try {
55
+ parsed = JSON.parse(raw);
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ if (typeof parsed !== 'object' || parsed === null)
61
+ return undefined;
62
+ const file = parsed;
63
+ const key = typeof file['key'] === 'string' ? file['key'].trim() : '';
64
+ if (key === '')
65
+ return undefined;
66
+ const status = file['status'];
67
+ return {
68
+ key,
69
+ status: status === 'valid' || status === 'invalid' ? status : 'unchecked',
70
+ checkedAt: typeof file['checkedAt'] === 'string' ? file['checkedAt'] : '',
71
+ };
72
+ }
73
+ /** Write the key, private to the account the site runs as. */
74
+ export async function writeAkismetKey(dataDir, record) {
75
+ await writeFileAtomically(akismetKeyPath(dataDir), `${JSON.stringify(record, null, 2)}\n`, {
76
+ mode: 0o600,
77
+ });
78
+ }
79
+ /** Forget the key, which is how a site turns Akismet off. */
80
+ export async function removeAkismetKey(dataDir) {
81
+ await rm(akismetKeyPath(dataDir), { force: true });
82
+ }
83
+ /** Where one site's key file is. */
84
+ export function akismetKeyPath(dataDir) {
85
+ return path.join(dataDir, AKISMET_KEY_FILE);
86
+ }
87
+ /**
88
+ * Ask Akismet whether a key is one of theirs.
89
+ *
90
+ * Three answers rather than two, because "we could not ask" is not "no": a key
91
+ * typed while the machine is offline should not be reported as a bad key, and
92
+ * the settings screen says which of the two happened.
93
+ */
94
+ export async function verifyAkismetKey(options) {
95
+ const body = new URLSearchParams({ api_key: options.key, blog: options.blog });
96
+ let answer;
97
+ try {
98
+ answer = await post(`${AKISMET_ENDPOINT}/verify-key`, body, options.fetch, options.timeoutMs);
99
+ }
100
+ catch {
101
+ return 'unchecked';
102
+ }
103
+ if (!answer.ok)
104
+ return 'unchecked';
105
+ return answer.body.trim() === 'valid' ? 'valid' : 'invalid';
106
+ }
107
+ /**
108
+ * A {@link CommentChecker} that asks Akismet.
109
+ *
110
+ * Built whether or not the site has a key: with none, every method returns
111
+ * without sending anything, which is what lets a key pasted into the settings
112
+ * screen start working on the very next comment.
113
+ *
114
+ * Nothing here throws. The seam would catch it and call it `unknown` anyway
115
+ * (doc-6), but then the log would say only that a checker refused to answer;
116
+ * catching it here is what puts Akismet's own words — a 503, a
117
+ * `X-akismet-debug-help` — in front of whoever has to work out why the queue
118
+ * has stopped being filtered.
119
+ */
120
+ export function createAkismetChecker(options) {
121
+ const warn = (message) => {
122
+ (options.logger ?? console).warn(message);
123
+ };
124
+ /** Send one call, or nothing at all when the site has no key. */
125
+ async function send(method, fields) {
126
+ const stored = readAkismetKey(options.dataDir);
127
+ if (stored === undefined)
128
+ return undefined;
129
+ try {
130
+ const answer = await post(`${AKISMET_ENDPOINT}/${method}`, fields(stored.key), options.fetch, options.timeoutMs);
131
+ if (!answer.ok) {
132
+ warn(`Akismet answered ${method} with ${String(answer.status)}.${help(answer)}`);
133
+ return undefined;
134
+ }
135
+ return answer;
136
+ }
137
+ catch (error) {
138
+ warn(`Akismet could not be asked about ${method}: ${messageOf(error)}`);
139
+ return undefined;
140
+ }
141
+ }
142
+ return {
143
+ async check(submission) {
144
+ const answer = await send('comment-check', (key) => checkFields(key, submission, options));
145
+ if (answer === undefined)
146
+ return 'unknown';
147
+ const said = answer.body.trim();
148
+ if (said === 'true')
149
+ return answer.proTip?.trim() === 'discard' ? 'discard' : 'spam';
150
+ // Anything that is not `true` or `false` is Akismet telling us the call
151
+ // was wrong — a key it does not know, a blog it cannot parse — and the
152
+ // header is where it says which.
153
+ if (said !== 'false') {
154
+ warn(`Akismet answered comment-check with ${JSON.stringify(said)}.${help(answer)}`);
155
+ return 'unknown';
156
+ }
157
+ // `false` is not `ham`. Akismet saw nothing wrong; whether that means
158
+ // approved or pending is TASK-50's rule about who has commented before,
159
+ // and this has no business overruling it.
160
+ return 'unknown';
161
+ },
162
+ async reportSpam(report) {
163
+ await send('submit-spam', (key) => reportFields(key, report, options));
164
+ },
165
+ async reportHam(report) {
166
+ await send('submit-ham', (key) => reportFields(key, report, options));
167
+ },
168
+ };
169
+ }
170
+ /** The fields one `comment-check` carries. */
171
+ function checkFields(key, submission, options) {
172
+ const { comment } = submission;
173
+ // A webmention is a page linking here rather than a form somebody filled in,
174
+ // so it has no honeypot to name; a contact message is a form like a
175
+ // comment's, so it does.
176
+ const formless = (submission.type ?? comment.source) === 'webmention';
177
+ return fields({
178
+ api_key: key,
179
+ blog: submission.baseUrl,
180
+ blog_charset: 'UTF-8',
181
+ blog_lang: blogLang(options.language()),
182
+ user_ip: submission.address,
183
+ user_agent: submission.userAgent,
184
+ referrer: submission.referrer,
185
+ permalink: submission.post.url,
186
+ // What the caller says this is, or what the comment says it is: a
187
+ // webmention is a page linking here rather than a form somebody filled in,
188
+ // and Akismet takes a type it does not know as a type of its own.
189
+ comment_type: submission.type ?? (comment.source === 'webmention' ? 'webmention' : 'comment'),
190
+ comment_author: comment.author.name,
191
+ comment_author_email: comment.author.email ?? undefined,
192
+ comment_author_url: comment.author.url ?? undefined,
193
+ comment_content: comment.content.markdown,
194
+ comment_date_gmt: comment.submitted,
195
+ // Naming the honeypot lets Akismet see that a submission left it empty,
196
+ // which is one more thing it knows about a submission it is judging.
197
+ honeypot_field_name: formless ? undefined : COMMENT_FIELDS.trap,
198
+ is_test: options.isTest === true ? '1' : undefined,
199
+ });
200
+ }
201
+ /**
202
+ * The fields a `submit-spam` or `submit-ham` carries.
203
+ *
204
+ * As close to the original `comment-check` as this site can get, which is what
205
+ * Akismet asks for — but not all the way there. `user_ip`, `user_agent` and
206
+ * `referrer` are gone, because a stored comment keeps a salted hash of the
207
+ * address and nothing else (doc-6): the file is published with the site, and
208
+ * an address in it would be a reader's home written into a public repository.
209
+ * The content, the author and the permalink are what is left, and they are
210
+ * what a correction is mostly about.
211
+ */
212
+ function reportFields(key, report, options) {
213
+ const { comment } = report;
214
+ return fields({
215
+ api_key: key,
216
+ blog: report.baseUrl,
217
+ blog_charset: 'UTF-8',
218
+ blog_lang: blogLang(options.language()),
219
+ permalink: report.url,
220
+ comment_type: comment.source === 'webmention' ? 'webmention' : 'comment',
221
+ comment_author: comment.author.name,
222
+ comment_author_email: comment.author.email ?? undefined,
223
+ comment_author_url: comment.author.url ?? undefined,
224
+ comment_content: comment.content.markdown,
225
+ comment_date_gmt: comment.submitted,
226
+ is_test: options.isTest === true ? '1' : undefined,
227
+ });
228
+ }
229
+ /** A body, with everything the site could not fill in left out entirely. */
230
+ function fields(values) {
231
+ const body = new URLSearchParams();
232
+ for (const [name, value] of Object.entries(values)) {
233
+ if (value !== undefined && value !== '')
234
+ body.set(name, value);
235
+ }
236
+ return body;
237
+ }
238
+ /**
239
+ * A language setting as `blog_lang` wants it: ISO 639-1, so `en-GB` is `en`.
240
+ *
241
+ * The setting is a BCP 47 tag because that is what an `<html lang>` and a feed
242
+ * want; Akismet documents the shorter thing, and the primary subtag is it.
243
+ */
244
+ function blogLang(language) {
245
+ return (language.split('-')[0] ?? '').trim().toLowerCase();
246
+ }
247
+ /** Whatever Akismet said was wrong, as a sentence to append to a log line. */
248
+ function help(answer) {
249
+ return answer.debugHelp === undefined ? '' : ` ${answer.debugHelp}`;
250
+ }
251
+ function messageOf(error) {
252
+ return error instanceof Error ? error.message : String(error);
253
+ }
254
+ /** POST a urlencoded body to Akismet and read the answer. */
255
+ async function post(url, body, client, timeoutMs) {
256
+ const call = client ?? globalThis.fetch;
257
+ const response = await call(url, {
258
+ method: 'POST',
259
+ headers: {
260
+ 'content-type': 'application/x-www-form-urlencoded; charset=utf-8',
261
+ 'user-agent': AKISMET_USER_AGENT,
262
+ },
263
+ body: body.toString(),
264
+ signal: AbortSignal.timeout(timeoutMs ?? AKISMET_TIMEOUT_MS),
265
+ });
266
+ return {
267
+ ok: response.ok,
268
+ status: response.status,
269
+ body: await response.text(),
270
+ proTip: response.headers.get('x-akismet-pro-tip') ?? undefined,
271
+ debugHelp: response.headers.get('x-akismet-debug-help') ?? undefined,
272
+ };
273
+ }
274
+ //# sourceMappingURL=akismet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"akismet.js","sourceRoot":"","sources":["../../src/comments/akismet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD;;;;;;;;;;;;;;;;;;GAkBG;AAEH,4CAA4C;AAC5C,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAE/C,2BAA2B;AAC3B,MAAM,CAAC,MAAM,gBAAgB,GAAG,8BAA8B,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,+BAA+B,CAAC;AAElE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAqBzC;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,GAAG,GAAG,qBAAqB,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3D,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAExC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACpE,MAAM,IAAI,GAAG,MAAiC,CAAC;IAC/C,MAAM,GAAG,GAAG,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAEjC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,OAAO;QACL,GAAG;QACH,MAAM,EAAE,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW;QACzE,SAAS,EAAE,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE;KAC1E,CAAC;AACJ,CAAC;AAED,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAe,EAAE,MAAwB;IAC7E,MAAM,mBAAmB,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QACzF,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAAe;IACpD,MAAM,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;AAC9C,CAAC;AAcD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAgC;IAEhC,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAE/E,IAAI,MAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,gBAAgB,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAChG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC;IACnC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9D,CAAC;AA4BD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA8B;IACjE,MAAM,IAAI,GAAG,CAAC,OAAe,EAAQ,EAAE;QACrC,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC,CAAC;IAEF,iEAAiE;IACjE,KAAK,UAAU,IAAI,CACjB,MAAsD,EACtD,MAAwC;QAExC,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAE3C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CACvB,GAAG,gBAAgB,IAAI,MAAM,EAAE,EAC/B,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAClB,OAAO,CAAC,KAAK,EACb,OAAO,CAAC,SAAS,CAClB,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,IAAI,CAAC,oBAAoB,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACjF,OAAO,SAAS,CAAC;YACnB,CAAC;YAED,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,oCAAoC,MAAM,KAAK,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACxE,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,UAAU;YACpB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;YAC3F,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YAE3C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAChC,IAAI,IAAI,KAAK,MAAM;gBAAE,OAAO,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;YAErF,wEAAwE;YACxE,uEAAuE;YACvE,iCAAiC;YACjC,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACrB,IAAI,CAAC,uCAAuC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACpF,OAAO,SAAS,CAAC;YACnB,CAAC;YAED,sEAAsE;YACtE,wEAAwE;YACxE,0CAA0C;YAC1C,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,MAAM;YACrB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACzE,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,MAAM;YACpB,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACxE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,8CAA8C;AAC9C,SAAS,WAAW,CAClB,GAAW,EACX,UAA6B,EAC7B,OAA8B;IAE9B,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IAC/B,6EAA6E;IAC7E,oEAAoE;IACpE,yBAAyB;IACzB,MAAM,QAAQ,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,YAAY,CAAC;IAEtE,OAAO,MAAM,CAAC;QACZ,OAAO,EAAE,GAAG;QACZ,IAAI,EAAE,UAAU,CAAC,OAAO;QACxB,YAAY,EAAE,OAAO;QACrB,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACvC,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,UAAU,EAAE,UAAU,CAAC,SAAS;QAChC,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG;QAC9B,kEAAkE;QAClE,2EAA2E;QAC3E,kEAAkE;QAClE,YAAY,EAAE,UAAU,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7F,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;QACnC,oBAAoB,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,SAAS;QACvD,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,SAAS;QACnD,eAAe,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ;QACzC,gBAAgB,EAAE,OAAO,CAAC,SAAS;QACnC,wEAAwE;QACxE,qEAAqE;QACrE,mBAAmB,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI;QAC/D,OAAO,EAAE,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;KACnD,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,YAAY,CACnB,GAAW,EACX,MAAqB,EACrB,OAA8B;IAE9B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAE3B,OAAO,MAAM,CAAC;QACZ,OAAO,EAAE,GAAG;QACZ,IAAI,EAAE,MAAM,CAAC,OAAO;QACpB,YAAY,EAAE,OAAO;QACrB,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACvC,SAAS,EAAE,MAAM,CAAC,GAAG;QACrB,YAAY,EAAE,OAAO,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;QACxE,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;QACnC,oBAAoB,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,SAAS;QACvD,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,SAAS;QACnD,eAAe,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ;QACzC,gBAAgB,EAAE,OAAO,CAAC,SAAS;QACnC,OAAO,EAAE,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;KACnD,CAAC,CAAC;AACL,CAAC;AAED,4EAA4E;AAC5E,SAAS,MAAM,CAAC,MAA0C;IACxD,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;YAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,QAAgB;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAC7D,CAAC;AAED,8EAA8E;AAC9E,SAAS,IAAI,CAAC,MAAqB;IACjC,OAAO,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;AACtE,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAgBD,6DAA6D;AAC7D,KAAK,UAAU,IAAI,CACjB,GAAW,EACX,IAAqB,EACrB,MAAgC,EAChC,SAA6B;IAE7B,MAAM,IAAI,GAAG,MAAM,IAAI,UAAU,CAAC,KAAK,CAAC;IACxC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE;QAC/B,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kDAAkD;YAClE,YAAY,EAAE,kBAAkB;SACjC;QACD,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE;QACrB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;KAC7D,CAAC,CAAC;IAEH,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE;QAC3B,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,SAAS;QAC9D,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,SAAS;KACrE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { AdminStore, PostComment } from '../admin/store.ts';
2
+ import type { Document } from '../content/document.ts';
3
+ import type { Interaction } from '../web/conversation.ts';
4
+ /**
5
+ * Native comments as the conversation sees them.
6
+ *
7
+ * `web/conversation.ts` deliberately knows nothing about where an answer came
8
+ * from, so this is the translation: a stored comment becomes an
9
+ * {@link Interaction} and joins the fediverse replies in the same thread,
10
+ * nested by the same rule. Nothing about a comment survives the trip that a
11
+ * theme would have to special-case — the source is on the record so a theme
12
+ * *may*, not so it must.
13
+ *
14
+ * Only approved comments come through. A comment waiting for a moderator and
15
+ * one filed as spam are both in the file and both in the index, and neither is
16
+ * anything a reader should be shown.
17
+ */
18
+ /** The approved comments on a post, as interactions, oldest first. */
19
+ export declare function commentInteractions(admin: AdminStore, document: Document): Interaction[];
20
+ /** One stored comment as the thread's own shape. */
21
+ export declare function interactionOf(comment: PostComment, document: Document): Interaction;
22
+ /**
23
+ * The `id` a comment is anchored at on the page.
24
+ *
25
+ * Prefixed rather than bare so a comment can never collide with a heading
26
+ * anchor the post's own Markdown produced.
27
+ */
28
+ export declare function commentAnchor(id: string): string;
29
+ //# sourceMappingURL=conversation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/comments/conversation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AAEH,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,GAAG,WAAW,EAAE,CAKxF;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,GAAG,WAAW,CA2BnF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEhD"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Native comments as the conversation sees them.
3
+ *
4
+ * `web/conversation.ts` deliberately knows nothing about where an answer came
5
+ * from, so this is the translation: a stored comment becomes an
6
+ * {@link Interaction} and joins the fediverse replies in the same thread,
7
+ * nested by the same rule. Nothing about a comment survives the trip that a
8
+ * theme would have to special-case — the source is on the record so a theme
9
+ * *may*, not so it must.
10
+ *
11
+ * Only approved comments come through. A comment waiting for a moderator and
12
+ * one filed as spam are both in the file and both in the index, and neither is
13
+ * anything a reader should be shown.
14
+ */
15
+ /** The approved comments on a post, as interactions, oldest first. */
16
+ export function commentInteractions(admin, document) {
17
+ return admin
18
+ .listCommentsFor(document.slug)
19
+ .filter((comment) => comment.status === 'approved')
20
+ .map((comment) => interactionOf(comment, document));
21
+ }
22
+ /** One stored comment as the thread's own shape. */
23
+ export function interactionOf(comment, document) {
24
+ return {
25
+ id: comment.id,
26
+ source: comment.source,
27
+ kind: comment.kind,
28
+ author: {
29
+ name: comment.author.name,
30
+ // A commenter has no fediverse identity: they gave a name, maybe a
31
+ // website, and nothing that names them anywhere else.
32
+ handle: null,
33
+ url: comment.author.url,
34
+ // Only a webmention has one: it came out of the source page's `h-card`,
35
+ // and a form asks nobody for a picture.
36
+ avatar: comment.author.avatar,
37
+ actorId: null,
38
+ },
39
+ // Where it can be read. A comment written here lives here, at its own
40
+ // anchor; a webmention lives on the page it was sent from, and its `url`
41
+ // says so. A theme that prints `url` for a fediverse reply prints a
42
+ // working link for either.
43
+ url: comment.url ?? `${document.permalink}#${commentAnchor(comment.id)}`,
44
+ content: comment.content.html,
45
+ published: new Date(comment.submitted),
46
+ inReplyTo: comment.inReplyTo,
47
+ status: comment.status,
48
+ replies: [],
49
+ };
50
+ }
51
+ /**
52
+ * The `id` a comment is anchored at on the page.
53
+ *
54
+ * Prefixed rather than bare so a comment can never collide with a heading
55
+ * anchor the post's own Markdown produced.
56
+ */
57
+ export function commentAnchor(id) {
58
+ return `comment-${id}`;
59
+ }
60
+ //# sourceMappingURL=conversation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation.js","sourceRoot":"","sources":["../../src/comments/conversation.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,sEAAsE;AACtE,MAAM,UAAU,mBAAmB,CAAC,KAAiB,EAAE,QAAkB;IACvE,OAAO,KAAK;SACT,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC;SAC9B,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC;SAClD,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,OAAoB,EAAE,QAAkB;IACpE,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM,EAAE;YACN,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;YACzB,mEAAmE;YACnE,sDAAsD;YACtD,MAAM,EAAE,IAAI;YACZ,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG;YACvB,wEAAwE;YACxE,wCAAwC;YACxC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM;YAC7B,OAAO,EAAE,IAAI;SACd;QACD,sEAAsE;QACtE,yEAAyE;QACzE,oEAAoE;QACpE,2BAA2B;QAC3B,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,QAAQ,CAAC,SAAS,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;QACxE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI;QAC7B,SAAS,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;QACtC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,EAAE;KACZ,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,OAAO,WAAW,EAAE,EAAE,CAAC;AACzB,CAAC"}
@@ -0,0 +1,123 @@
1
+ import type { Document } from '../content/document.ts';
2
+ import { COMMENT_FIELDS } from './submission.ts';
3
+ import type { CommentForm, CommentProblems } from './submission.ts';
4
+ /**
5
+ * The comment form as a template receives it.
6
+ *
7
+ * The theme renders `partials/comment-form.njk` from this, and it is on the
8
+ * context only when the post is actually taking comments — so a closed post
9
+ * shows the thread with no form, and a layout asks `{% if commentForm %}`
10
+ * rather than working out the rules for itself.
11
+ */
12
+ export interface CommentFormContext {
13
+ /** Where the form posts. */
14
+ action: string;
15
+ /** The name each field is submitted under. */
16
+ fields: typeof COMMENT_FIELDS;
17
+ /** Which post is being commented on, as the hidden field carries it. */
18
+ post: string;
19
+ /**
20
+ * When this form was rendered, in epoch milliseconds, as the hidden field
21
+ * carries it. What the minimum submit time is measured against.
22
+ */
23
+ loaded: string;
24
+ /** What is in the fields: empty on a fresh form, what was typed on a refused one. */
25
+ values: Record<'name' | 'email' | 'url' | 'body' | 'inReplyTo' | 'notify', string>;
26
+ /**
27
+ * Whether to offer "tell me about replies" at all (TASK-55).
28
+ *
29
+ * False on a site that sends no mail, where the box would be a promise
30
+ * nothing could keep. A theme asks `{% if commentForm.notifiable %}`.
31
+ */
32
+ notifiable: boolean;
33
+ /** One message per field a person has to put right. Empty on a fresh form. */
34
+ problems: CommentProblems;
35
+ /** A message about the submission as a whole, when there is one. */
36
+ error?: string | undefined;
37
+ /** The longest a name may be, for the field's `maxlength`. */
38
+ nameLength: number;
39
+ /** The longest a comment may be, for the textarea's `maxlength`. */
40
+ bodyLength: number;
41
+ /**
42
+ * Who the site knows is filling this in, when a session says so (TASK-103).
43
+ *
44
+ * Absent for a stranger, so a theme asks `{% if commentForm.signedInAs %}`
45
+ * and draws the short form — the comment box and a sentence naming the
46
+ * person — instead of the name, email and website boxes and the honeypot.
47
+ * Nothing about the account is here but the two things the sentence prints:
48
+ * the email is never handed to a template, exactly as a stranger's never is.
49
+ */
50
+ signedInAs?: {
51
+ name: string;
52
+ url: string;
53
+ } | undefined;
54
+ /**
55
+ * The token the signed-in form carries, and absent on a stranger's.
56
+ *
57
+ * A form that acts on nobody's behalf needs none; the moment this one posts
58
+ * under somebody's name, a page on another site could make them post without
59
+ * knowing it, so it carries the same per-session token every admin form
60
+ * does and the endpoint checks it the way the admin guard does.
61
+ */
62
+ csrfToken?: string | undefined;
63
+ }
64
+ /**
65
+ * Who the site knows is commenting, when a valid session says so.
66
+ *
67
+ * Read from the session on the request rather than from anything the browser
68
+ * typed: `email` is the account's, which the comment is stored with and which
69
+ * no template is ever handed, and `csrfToken` is that session's own.
70
+ */
71
+ export interface CommentViewer {
72
+ /** What the comment goes under: their display name, else their username. */
73
+ name: string;
74
+ /** Their author archive, which is the comment's website. */
75
+ url: string;
76
+ /** The account's email, which is stored and never shown. */
77
+ email: string | null;
78
+ /** This session's CSRF token, which the form carries. */
79
+ csrfToken: string;
80
+ }
81
+ /**
82
+ * The same form drawn for somebody the site knows rather than for a stranger.
83
+ *
84
+ * A function over a built form rather than another parameter on the two
85
+ * builders, so `commentForm` and `refilledCommentForm` keep the signatures the
86
+ * package publishes and there is one place that decides what "signed in"
87
+ * changes about a form.
88
+ */
89
+ export declare function signedInCommentForm(form: CommentFormContext, viewer: CommentViewer): CommentFormContext;
90
+ /** Where the comment form posts. */
91
+ export declare const COMMENT_POST_PATH = "/_geekity/comments";
92
+ /** The query the redirect after a submission carries, and what it can say. */
93
+ export declare const COMMENT_NOTICE_PARAM = "comment";
94
+ /**
95
+ * The query a Reply link on a comment carries.
96
+ *
97
+ * Threading without a line of JavaScript: the link goes to the form with the
98
+ * comment it answers named in the URL, the form puts that in its hidden field,
99
+ * and Cancel is a link back to the page without the query.
100
+ */
101
+ export declare const COMMENT_REPLY_PARAM = "reply_to";
102
+ /**
103
+ * What that query means, and what the reader is told.
104
+ *
105
+ * A redirect rather than a rendered page, so a refresh does not post the
106
+ * comment again; a query rather than a flash, because a reader has no session
107
+ * to hang one on.
108
+ */
109
+ export declare const COMMENT_NOTICES: Readonly<Record<string, string>>;
110
+ /** A fresh, empty form for a post that is taking comments. */
111
+ export declare function commentForm(document: Document, now?: Date, notifiable?: boolean): CommentFormContext;
112
+ /**
113
+ * The same form with what somebody typed still in it.
114
+ *
115
+ * What a refused submission is answered with: losing a paragraph because an
116
+ * email address had a typo in it is the fastest way to lose a commenter.
117
+ */
118
+ export declare function refilledCommentForm(document: Document, values: CommentFormContext['values'], problems: CommentProblems, now?: Date, error?: string, notifiable?: boolean): CommentFormContext;
119
+ /** What a form holds before anybody has typed in it. */
120
+ export declare function blankValues(): CommentFormContext['values'];
121
+ /** What a submitted form typed, for putting back in a refused one. */
122
+ export declare function valuesOf(form: CommentForm): CommentFormContext['values'];
123
+ //# sourceMappingURL=form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/comments/form.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,cAAc,EAA4C,MAAM,iBAAiB,CAAC;AAC3F,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEpE;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IACjC,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,MAAM,EAAE,OAAO,cAAc,CAAC;IAC9B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnF;;;;;OAKG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,8EAA8E;IAC9E,QAAQ,EAAE,eAAe,CAAC;IAC1B,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACvD;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,kBAAkB,EACxB,MAAM,EAAE,aAAa,GACpB,kBAAkB,CAMpB;AAED,oCAAoC;AACpC,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAG5D,CAAC;AAEF,8DAA8D;AAC9D,wBAAgB,WAAW,CACzB,QAAQ,EAAE,QAAQ,EAClB,GAAG,GAAE,IAAiB,EACtB,UAAU,UAAQ,GACjB,kBAAkB,CAEpB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,kBAAkB,CAAC,QAAQ,CAAC,EACpC,QAAQ,EAAE,eAAe,EACzB,GAAG,GAAE,IAAiB,EACtB,KAAK,CAAC,EAAE,MAAM,EACd,UAAU,UAAQ,GACjB,kBAAkB,CAapB;AAED,wDAAwD;AACxD,wBAAgB,WAAW,IAAI,kBAAkB,CAAC,QAAQ,CAAC,CAE1D;AAED,sEAAsE;AACtE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CASxE"}