@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,280 @@
1
+ import { CSRF_FIELD } from '../admin/session.js';
2
+ import { ADDRESS_SALT_FILE, FORM_LOADED_FIELD, FORM_TRAP_FIELD, formTimingRefusal, trapped, } from '../forms/protection.js';
3
+ import { renderCommentMarkdown } from './markdown.js';
4
+ import { intakeComment } from './records.js';
5
+ /**
6
+ * What happens between somebody pressing Post and a proposed comment.
7
+ *
8
+ * Three defences run before anything is proposed, in the order that costs least
9
+ * and gives away least — the honeypot, the age of the form and the per-address
10
+ * rate limit. The first two are `forms/protection.ts`, shared with the contact
11
+ * form (TASK-56) so the two public forms cannot drift into different rules; the
12
+ * third is the same limiter the login form uses, over the commenter's address,
13
+ * so one machine cannot post a hundred comments while a moderator sleeps.
14
+ *
15
+ * What survives all three is handed to {@link intakeComment}, which is where
16
+ * every comment on this site is written whatever proposed it: the checker, the
17
+ * verdict-to-status rule, the file, the index and the notice all live there, so
18
+ * a form submission and a webmention cannot be treated by two sets of rules.
19
+ *
20
+ * The {@link CommentChecker} interface stays here because this is where it
21
+ * reads: it is the one seam a third-party service plugs into, everything
22
+ * Akismet's API asks for is on the submission it is handed, and its four
23
+ * answers are the four the intake understands. Nothing else in the CMS has to
24
+ * know such a service exists.
25
+ */
26
+ // Re-exported where they have always been, so a site that imported them from
27
+ // the package, and the modules in here that did, are unaffected by the move.
28
+ export { hashClientAddress, MAXIMUM_FORM_AGE_SECONDS, MINIMUM_SUBMIT_SECONDS, } from '../forms/protection.js';
29
+ /** Where the salt that hides commenters' addresses lives, under `dataDir`. */
30
+ export const COMMENT_SALT_FILE = ADDRESS_SALT_FILE;
31
+ /** The fields the comment form submits. */
32
+ export const COMMENT_FIELDS = {
33
+ /** The post being commented on, as its slug. */
34
+ post: 'post',
35
+ /** The commenter's name. Required. */
36
+ name: 'name',
37
+ /** Their email. Optional, and never shown. */
38
+ email: 'email',
39
+ /** Their website. Optional. */
40
+ url: 'url',
41
+ /** The comment itself, as Markdown. Required. */
42
+ body: 'body',
43
+ /** The comment being answered, or empty for one answering the post. */
44
+ inReplyTo: 'in_reply_to',
45
+ /** The honeypot, which every public form here spells the same way. */
46
+ trap: FORM_TRAP_FIELD,
47
+ /** When the form was rendered, in epoch milliseconds. */
48
+ loaded: FORM_LOADED_FIELD,
49
+ /**
50
+ * The CSRF token, which only the signed-in form carries (TASK-103).
51
+ *
52
+ * Spelled exactly as every admin form spells it, because it is the same
53
+ * per-session token and is checked by the same comparison.
54
+ */
55
+ csrf: CSRF_FIELD,
56
+ /**
57
+ * "Tell me when somebody answers this."
58
+ *
59
+ * A checkbox, so any non-empty value is a yes. It only ever means anything
60
+ * alongside an email address, and the form only offers it when the site can
61
+ * actually send mail (TASK-55).
62
+ */
63
+ notify: 'notify',
64
+ };
65
+ /** How many comments one address may post before it has to wait. */
66
+ export const COMMENT_RATE_LIMIT = 5;
67
+ /** How long that window is, and how long the first wait after it lasts. */
68
+ export const COMMENT_RATE_WINDOW_SECONDS = 600;
69
+ /** The longest a name may be. */
70
+ export const MAXIMUM_NAME_LENGTH = 80;
71
+ /** The longest an email or a website may be. */
72
+ export const MAXIMUM_URL_LENGTH = 500;
73
+ /** The longest a comment may be, in characters. */
74
+ export const MAXIMUM_BODY_LENGTH = 10_000;
75
+ /**
76
+ * What is wrong with a submitted comment, as a person would be told it.
77
+ *
78
+ * Only the things a person can fix. The honeypot, the form's age and the rate
79
+ * limit are not here: none of them is the commenter's mistake, and two of them
80
+ * should not be explained to whoever tripped them.
81
+ *
82
+ * A signed-in commenter is asked for nothing but the comment (TASK-103), so
83
+ * only the comment is checked: the name, the email and the website come off
84
+ * the account and there are no boxes on the form to put anything right in.
85
+ */
86
+ export function commentProblems(form, signedIn = false) {
87
+ const problems = {};
88
+ const body = form.body.trim();
89
+ if (body === '')
90
+ problems.body = 'A comment needs something in it.';
91
+ else if (body.length > MAXIMUM_BODY_LENGTH) {
92
+ problems.body = `That comment is longer than ${String(MAXIMUM_BODY_LENGTH)} characters.`;
93
+ }
94
+ if (signedIn)
95
+ return problems;
96
+ const name = form.name.trim();
97
+ if (name === '')
98
+ problems.name = 'A comment needs a name to go under.';
99
+ else if (name.length > MAXIMUM_NAME_LENGTH) {
100
+ problems.name = `That name is longer than ${String(MAXIMUM_NAME_LENGTH)} characters.`;
101
+ }
102
+ const email = form.email.trim();
103
+ if (email !== '' && !isEmail(email)) {
104
+ problems.email =
105
+ 'That does not look like an email address. Leave it empty if you would rather.';
106
+ }
107
+ const url = form.url.trim();
108
+ if (url !== '' && normalizeWebsite(url) === undefined) {
109
+ problems.url = 'A website is an http:// or https:// address.';
110
+ }
111
+ return problems;
112
+ }
113
+ /**
114
+ * Take a submitted form and, if it survives everything, store the comment.
115
+ *
116
+ * The caller has already decided that the post is open: whether a form should
117
+ * exist at all is {@link commentsOpen}'s question, and asking it here as well
118
+ * would let the two answers differ.
119
+ */
120
+ export async function submitComment(options) {
121
+ const { records, document, form, throttle } = options;
122
+ const now = options.now ?? new Date();
123
+ // Whether the site knows who this is. It decides the attribution, the two
124
+ // cheap defences and where the comment lands, so it is read once.
125
+ const signedIn = options.author;
126
+ // The honeypot first, because it costs one comparison and because a
127
+ // submission that tripped it should not reach anything that could tell it so.
128
+ // Not for a signed-in commenter: their form carries no honeypot, and a
129
+ // session is better evidence than an empty box.
130
+ if (signedIn === undefined && trapped(form.trap))
131
+ return refused({ kind: 'discarded' });
132
+ const problems = commentProblems(form, signedIn !== undefined);
133
+ if (Object.keys(problems).length > 0)
134
+ return refused({ kind: 'invalid', problems });
135
+ // The form's age, for the same reason and with the same exception: a
136
+ // signed-in form carries no loaded-at stamp to measure.
137
+ if (signedIn === undefined) {
138
+ const timing = formTimingRefusal(form.loaded, now);
139
+ if (timing !== undefined)
140
+ return refused({ kind: timing });
141
+ }
142
+ // The rate limit stays either way. It is not about proving somebody is a
143
+ // person; it is about what one address may do in ten minutes, and a stolen
144
+ // session is exactly the case it would be wanted for.
145
+ const keys = commentKeys(options.address);
146
+ const wait = throttle.retryAfter(keys);
147
+ if (wait !== undefined)
148
+ return refused({ kind: 'rate-limited', retryAfter: wait });
149
+ const markdown = form.body.trim();
150
+ const author = signedIn === undefined
151
+ ? {
152
+ name: form.name.trim(),
153
+ url: normalizeWebsite(form.url.trim()) ?? null,
154
+ email: form.email.trim() === '' ? null : form.email.trim(),
155
+ // A form asks for no picture: only a webmention brings one (TASK-51).
156
+ avatar: null,
157
+ }
158
+ : {
159
+ name: signedIn.name,
160
+ url: signedIn.url,
161
+ email: signedIn.email,
162
+ avatar: null,
163
+ };
164
+ const proposed = {
165
+ slug: document.slug,
166
+ permalink: document.permalink,
167
+ source: 'comment',
168
+ kind: 'reply',
169
+ author,
170
+ content: { markdown, html: renderCommentMarkdown(markdown) },
171
+ submitted: now.toISOString(),
172
+ inReplyTo: parentOf(form.inReplyTo, document.slug, records),
173
+ // A comment written here lives here: only a webmention has a page of its
174
+ // own somewhere else (TASK-51).
175
+ url: null,
176
+ // Only ever true when there is an address to send to. A box ticked with
177
+ // the email field empty is somebody asking to be told at nowhere.
178
+ notify: options.notifiable === true && form.notify.trim() !== '' && author.email !== null,
179
+ };
180
+ const outcome = await intakeComment({
181
+ records,
182
+ // Geekity has one role, so anybody who can sign in can already publish a
183
+ // post and approve any comment. A comment of theirs waiting in a queue for
184
+ // them to approve would be theatre, and a spam service has no say in what
185
+ // the owner of the site says — so a signed-in comment arrives at the
186
+ // intake as exactly what it is, the site's own author writing, and is
187
+ // approved on arrival with no call to the checker (TASK-103).
188
+ origin: signedIn === undefined ? 'form' : 'moderator',
189
+ comment: proposed,
190
+ post: {
191
+ title: document.title,
192
+ url: absolute(document.permalink, options.baseUrl),
193
+ },
194
+ dataDir: options.dataDir,
195
+ baseUrl: options.baseUrl,
196
+ checker: options.checker,
197
+ notices: options.notices,
198
+ address: options.address,
199
+ userAgent: options.userAgent,
200
+ referrer: options.referrer,
201
+ });
202
+ // Counted against the address whatever became of it: a machine posting
203
+ // rubbish should not get unlimited free attempts because the rubbish was
204
+ // recognised.
205
+ throttle.fail(keys);
206
+ return outcome.kind === 'stored'
207
+ ? { kind: 'stored', comment: outcome.comment }
208
+ : refused({ kind: 'discarded' });
209
+ }
210
+ /**
211
+ * The comment a submission answers, or `null`.
212
+ *
213
+ * A named parent that is not a comment on this very post is dropped rather
214
+ * than stored: threading a reply under something on another page would put it
215
+ * somewhere nobody can see it, and an id somebody made up is not a parent.
216
+ */
217
+ function parentOf(submitted, slug, records) {
218
+ const id = submitted.trim();
219
+ if (id === '')
220
+ return null;
221
+ const parent = records.admin.getComment(id);
222
+ return parent !== undefined && parent.slug === slug ? parent.id : null;
223
+ }
224
+ /** The keys a submission is rate limited against. */
225
+ export function commentKeys(address) {
226
+ return address === undefined ? [] : [`comment:${address}`];
227
+ }
228
+ /**
229
+ * A submitted website as the absolute URL it means, or `undefined` when it
230
+ * means none.
231
+ *
232
+ * A bare `example.com` becomes `https://example.com/`, because that is what
233
+ * somebody typing it meant and refusing it would be pedantry; anything whose
234
+ * scheme is not http or https is refused, because it is going on a page as a
235
+ * link.
236
+ */
237
+ export function normalizeWebsite(value) {
238
+ const trimmed = value.trim();
239
+ if (trimmed === '' || trimmed.length > MAXIMUM_URL_LENGTH)
240
+ return undefined;
241
+ const candidate = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed) ? trimmed : `https://${trimmed}`;
242
+ let url;
243
+ try {
244
+ url = new URL(candidate);
245
+ }
246
+ catch {
247
+ return undefined;
248
+ }
249
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
250
+ return undefined;
251
+ if (url.hostname === '')
252
+ return undefined;
253
+ return url.href;
254
+ }
255
+ /**
256
+ * Whether something looks like an email address.
257
+ *
258
+ * Deliberately loose. The address is never shown and nothing is ever sent to
259
+ * it (TASK-50 has no email at all), so the only thing checking it buys is
260
+ * catching a typo; a stricter rule would refuse valid addresses and buy
261
+ * nothing.
262
+ */
263
+ function isEmail(value) {
264
+ if (value.length > MAXIMUM_URL_LENGTH)
265
+ return false;
266
+ return /^[^\s@]+@[^\s@.]+(\.[^\s@.]+)+$/.test(value);
267
+ }
268
+ /** A site-root path as an absolute URL. */
269
+ function absolute(pathname, baseUrl) {
270
+ try {
271
+ return new URL(pathname, baseUrl).href;
272
+ }
273
+ catch {
274
+ return pathname;
275
+ }
276
+ }
277
+ function refused(refusal) {
278
+ return { kind: 'refused', refusal };
279
+ }
280
+ //# sourceMappingURL=submission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submission.js","sourceRoot":"","sources":["../../src/comments/submission.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { Context } from 'hono';
2
+ import type { GeekityEnv } from '../env.ts';
3
+ import type { CommentViewer } from './form.ts';
4
+ /**
5
+ * Who the public site knows is reading it, when the request carries a login.
6
+ *
7
+ * The one place outside `/admin` that turns a session cookie into a person.
8
+ * Everything a comment needs about them is answered here — the name it goes
9
+ * under, the archive it links to, the email it is stored with and the token
10
+ * the form has to carry — so the page that draws the form and the endpoint
11
+ * that takes the submission are looking at one reading of one session.
12
+ *
13
+ * Deliberately total about the ways a cookie can be worth nothing. A forged or
14
+ * expired id, a session belonging to a user who has since been deleted, and
15
+ * the anonymous session that holds a CSRF token before anybody has logged in
16
+ * all answer `undefined`, which is the stranger's form: the only thing that
17
+ * gets the short form is a live session naming a user `users.json` still
18
+ * holds, which is the same test the admin guard applies at its own door.
19
+ */
20
+ export declare function signedInCommenter(c: Context<GeekityEnv>): CommentViewer | undefined;
21
+ //# sourceMappingURL=viewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewer.d.ts","sourceRoot":"","sources":["../../src/comments/viewer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAIpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,aAAa,GAAG,SAAS,CAwBnF"}
@@ -0,0 +1,45 @@
1
+ import { findUserById } from '../admin/accounts.js';
2
+ import { sessionIdFrom } from '../admin/session.js';
3
+ import { authorHref } from '../web/authors.js';
4
+ /**
5
+ * Who the public site knows is reading it, when the request carries a login.
6
+ *
7
+ * The one place outside `/admin` that turns a session cookie into a person.
8
+ * Everything a comment needs about them is answered here — the name it goes
9
+ * under, the archive it links to, the email it is stored with and the token
10
+ * the form has to carry — so the page that draws the form and the endpoint
11
+ * that takes the submission are looking at one reading of one session.
12
+ *
13
+ * Deliberately total about the ways a cookie can be worth nothing. A forged or
14
+ * expired id, a session belonging to a user who has since been deleted, and
15
+ * the anonymous session that holds a CSRF token before anybody has logged in
16
+ * all answer `undefined`, which is the stranger's form: the only thing that
17
+ * gets the short form is a live session naming a user `users.json` still
18
+ * holds, which is the same test the admin guard applies at its own door.
19
+ */
20
+ export function signedInCommenter(c) {
21
+ const id = sessionIdFrom(c);
22
+ if (id === undefined)
23
+ return undefined;
24
+ // Reading a session prunes an expired one, so this is a live session or
25
+ // nothing at all. Measured against the wall clock rather than the site's
26
+ // configured one, because that is the clock a session was minted against and
27
+ // the one the admin guard reads: a login cannot last longer on the public
28
+ // site than it does in the admin.
29
+ const session = c.var.admin.getSession(id);
30
+ if (session?.userId == null)
31
+ return undefined;
32
+ const user = findUserById(c.var.config.dataDir, session.userId);
33
+ if (user === undefined)
34
+ return undefined;
35
+ return {
36
+ // What a byline would print for them, on the same rule: a user who has
37
+ // written no display name is called by their username, which is what
38
+ // their archive is under anyway.
39
+ name: user.profile?.displayName ?? user.username,
40
+ url: authorHref(user.username),
41
+ email: user.email ?? null,
42
+ csrfToken: session.csrfToken,
43
+ };
44
+ }
45
+ //# sourceMappingURL=viewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewer.js","sourceRoot":"","sources":["../../src/comments/viewer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG/C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAAsB;IACtD,MAAM,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAEvC,wEAAwE;IACxE,yEAAyE;IACzE,6EAA6E;IAC7E,0EAA0E;IAC1E,kCAAkC;IAClC,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAC3C,IAAI,OAAO,EAAE,MAAM,IAAI,IAAI;QAAE,OAAO,SAAS,CAAC;IAE9C,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAChE,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAEzC,OAAO;QACL,uEAAuE;QACvE,qEAAqE;QACrE,iCAAiC;QACjC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,WAAW,IAAI,IAAI,CAAC,QAAQ;QAChD,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC9B,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI;QACzB,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC;AACJ,CAAC"}
package/dist/config.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import type { KvStore, MessageQueue } from '@fedify/fedify';
2
+ import type { AccessLogWriter } from './access-log.ts';
3
+ import type { CommentChecker } from './comments/submission.ts';
4
+ import type { Clock } from './content/store.ts';
2
5
  import type { DocumentChange } from './content/sync.ts';
6
+ import type { MailProvider } from './mail/provider.ts';
7
+ import type { MailLogger } from './mail/service.ts';
3
8
  /**
4
9
  * Something a site wants to happen when the index changes.
5
10
  *
@@ -44,6 +49,36 @@ export interface FederationOverrides {
44
49
  */
45
50
  allowPrivateAddress?: boolean | undefined;
46
51
  }
52
+ /**
53
+ * The mail pieces a site may swap (TASK-53).
54
+ *
55
+ * Everything here has a default that is right for a personal blog: the
56
+ * provider comes from the settings screen and `data/mail.json`, a refused
57
+ * message is tried three times with a growing wait, and attempts go to the
58
+ * console. A site names one of these when it has outgrown that, and a test
59
+ * names them to read back the mail and to not wait for the backoff.
60
+ */
61
+ export interface MailOverrides {
62
+ /**
63
+ * What carries the site's email.
64
+ *
65
+ * Naming one wins outright over the provider the settings screen chose and
66
+ * the credential in `data/mail.json`, exactly as
67
+ * {@link GeekityConfig.commentChecker} wins over the Akismet key: a site
68
+ * that wrote a provider meant it. `createMemoryMailProvider()` is what a
69
+ * test names here to read back the mail a feature would have sent.
70
+ */
71
+ provider?: MailProvider | undefined;
72
+ /** How many times one message is tried. Defaults to three. */
73
+ attempts?: number | undefined;
74
+ /**
75
+ * How long to wait before attempt `n + 1`, in milliseconds. Defaults to two
76
+ * seconds, then eight, then eighteen. A test hands in `() => 0`.
77
+ */
78
+ backoffMs?: ((attempt: number) => number) | undefined;
79
+ /** Where attempts are logged. Defaults to `console`. */
80
+ logger?: MailLogger | undefined;
81
+ }
47
82
  /**
48
83
  * What a site writes in `geekity.config.ts`. Every field is optional; see
49
84
  * {@link DEFAULT_PORT} and friends for what each one falls back to.
@@ -55,8 +90,17 @@ export interface GeekityConfig {
55
90
  contentDir?: string;
56
91
  /** Directory for derived state such as the SQLite index. Default `<cwd>/data`. Overridden by `GEEKITY_DATA_DIR`. */
57
92
  dataDir?: string;
58
- /** Site template overrides, resolved before the packaged default theme. Default `<cwd>/theme`. Overridden by `GEEKITY_THEME_DIR`. */
59
- themeDir?: string;
93
+ /**
94
+ * Where this site's themes are, one directory per theme, each with a
95
+ * `theme.json` (decision-15). Default `<cwd>/themes`. Overridden by
96
+ * `GEEKITY_THEMES_DIR`. It need not exist: a site using the packaged theme
97
+ * has no themes of its own.
98
+ *
99
+ * Which one is in use is a setting rather than a deployment fact — `theme`
100
+ * in `content/_data/site.json`, chosen on the Appearance screen — because it
101
+ * is a look a site changes, not a path a host decides.
102
+ */
103
+ themesDir?: string;
60
104
  /** Public origin, used for canonical URLs, feeds and ActivityPub ids. Default `http://localhost:<port>`. Overridden by `GEEKITY_BASE_URL`. */
61
105
  baseUrl?: string;
62
106
  /**
@@ -84,6 +128,101 @@ export interface GeekityConfig {
84
128
  * `KNOWN_UPLOAD_TYPES`.
85
129
  */
86
130
  uploadTypes?: string[];
131
+ /**
132
+ * Derive smaller and more modern copies of an uploaded raster image, and
133
+ * serve the site's pages a `<picture>` that offers them. Default `true`.
134
+ * Overridden by `GEEKITY_IMAGE_OPTIMIZATION`.
135
+ *
136
+ * Turning it off changes nothing on disk under `content/uploads/` — the
137
+ * original was always the only source of truth (decision-10) — and nothing
138
+ * about the feeds. The pages go back to the plain `<img>` the Markdown
139
+ * asked for, and no encoding happens on upload.
140
+ */
141
+ imageOptimization?: boolean;
142
+ /**
143
+ * The widths variants are made at, in pixels. Default 320, 640, 960, 1280
144
+ * and 1920, which is 11ty/image's own set. Overridden by
145
+ * `GEEKITY_IMAGE_WIDTHS`, a comma-separated list.
146
+ *
147
+ * Nothing is ever upscaled: a width wider than the original is skipped, and
148
+ * the original's own width is always added, so the `srcset` has a full-size
149
+ * entry whatever the list says.
150
+ */
151
+ imageWidths?: number[];
152
+ /**
153
+ * The formats variants are made in, besides the original's own. Default
154
+ * `['webp']`. Overridden by `GEEKITY_IMAGE_FORMATS`, a comma-separated list.
155
+ *
156
+ * The original's format is always generated too, because it is what the
157
+ * `<img>` inside the `<picture>` falls back to; these are the `<source>`
158
+ * elements in front of it, in the order they are given. AVIF is opt-in —
159
+ * `['avif', 'webp']` — because its encoder is an order of magnitude slower
160
+ * than WebP's and the encoding happens in the request that uploads the file.
161
+ */
162
+ imageFormats?: string[];
163
+ /**
164
+ * How many failed sign-ins a username or a client address may make before
165
+ * the admin locks it out. Default 5. Overridden by `GEEKITY_LOGIN_ATTEMPTS`.
166
+ */
167
+ loginAttempts?: number;
168
+ /**
169
+ * How long the first lockout lasts, in seconds. Default 15 minutes. Each
170
+ * further failure doubles the wait, up to sixteen times the first one.
171
+ * Overridden by `GEEKITY_LOGIN_LOCKOUT`.
172
+ */
173
+ loginLockout?: number;
174
+ /**
175
+ * Whether an `X-Forwarded-For` header may be believed when the admin decides
176
+ * which client address a failed login belongs to. Default `false`.
177
+ * Overridden by `GEEKITY_TRUST_PROXY`.
178
+ *
179
+ * Off by default because anyone may send that header: believing it on a site
180
+ * reached directly would let an attacker put every guess on a different
181
+ * make-believe address and never be locked out at all. Turn it on only when
182
+ * a reverse proxy in front of the site sets it.
183
+ */
184
+ trustProxy?: boolean;
185
+ /**
186
+ * Write one line per request — method, path, status, duration — to stdout.
187
+ * Overridden by `GEEKITY_ACCESS_LOG`.
188
+ *
189
+ * Default `false`, which is what `createCms` gets: a CMS embedded in
190
+ * somebody else's server has no business writing to their stdout uninvited.
191
+ * `geekity serve` turns it on, because a server that answers the internet
192
+ * should be able to say what it answered; a config that names this either
193
+ * way is obeyed, and the environment variable wins over both.
194
+ */
195
+ accessLog?: boolean;
196
+ /**
197
+ * Put the client address on each access-log line. Default `false`.
198
+ * Overridden by `GEEKITY_ACCESS_LOG_ADDRESS`.
199
+ *
200
+ * Off unless the site asks, because an address is personal data and a
201
+ * self-hosted blog has no retention policy for it. Which address is right is
202
+ * {@link GeekityConfig.trustProxy}'s answer, the same one a failed sign-in
203
+ * is counted against.
204
+ */
205
+ accessLogAddress?: boolean;
206
+ /**
207
+ * Where the access log's lines go. Defaults to stdout, written one line at a
208
+ * time exactly as the CLI writes.
209
+ *
210
+ * Config only, with no environment override: a sink is a function, and the
211
+ * only callers with one are a test reading the lines back and a site that
212
+ * has somewhere of its own to put them.
213
+ */
214
+ accessLogWriter?: AccessLogWriter;
215
+ /**
216
+ * Fill a missing or empty content directory with the starter site
217
+ * `geekity init` writes, when `geekity serve` starts. Default `false`.
218
+ * Overridden by `GEEKITY_SEED_CONTENT`.
219
+ *
220
+ * Off by default so a site run from npm never has content written that it
221
+ * did not ask for. The Docker image turns it on, because its content volume
222
+ * is empty on a new box. A directory with anything in it at all, even a lone
223
+ * dotfile, is never touched.
224
+ */
225
+ seedContent?: boolean;
87
226
  /**
88
227
  * Called for every `created`, `updated` and `deleted` the index records.
89
228
  *
@@ -98,6 +237,37 @@ export interface GeekityConfig {
98
237
  * same `origin` caveat as {@link GeekityConfig.onDocumentChange}.
99
238
  */
100
239
  onPublish?: DocumentChangeHook;
240
+ /**
241
+ * A third-party opinion on native comments: Akismet, or anything else that
242
+ * answers the same three questions (TASK-50).
243
+ *
244
+ * The whole of the CMS's knowledge of such a service. It is asked about
245
+ * every comment that gets past the honeypot, the form's age and the rate
246
+ * limit, and its `spam`, `discard` and `ham` are the three things it can
247
+ * say; a checker that throws is treated as having no opinion, so a service
248
+ * that is down never stops a site taking comments. The moderation screen
249
+ * tells it when a human disagrees.
250
+ *
251
+ * Naming one here wins outright over the Akismet checker the package ships
252
+ * (TASK-52): `createCms` builds that only when this is not set, so a site
253
+ * that wrote a checker gets the checker it wrote whether or not there is a
254
+ * key in `data/akismet.json`.
255
+ */
256
+ commentChecker?: CommentChecker;
257
+ /**
258
+ * How the site sends email (TASK-53). Every field has a default; see
259
+ * {@link MailOverrides}.
260
+ */
261
+ mail?: MailOverrides;
262
+ /**
263
+ * What the CMS reads the time from.
264
+ *
265
+ * A post's date decides whether it is public yet (TASK-44), so the index and
266
+ * the scheduler both need a clock, and it is one clock so they cannot
267
+ * disagree. Defaults to {@link systemClock}. A site has no reason to name
268
+ * one; a test that wants to be somewhere else in time does.
269
+ */
270
+ now?: Clock;
101
271
  /**
102
272
  * Federation stores and guards. Every field has a default; see
103
273
  * {@link FederationOverrides}.
@@ -114,7 +284,7 @@ export interface ResolvedConfig {
114
284
  port: number;
115
285
  contentDir: string;
116
286
  dataDir: string;
117
- themeDir: string;
287
+ themesDir: string;
118
288
  baseUrl: string;
119
289
  /**
120
290
  * Where {@link ResolvedConfig.baseUrl} came from.
@@ -122,7 +292,7 @@ export interface ResolvedConfig {
122
292
  * `default` means nobody named one and it is the `http://localhost:<port>`
123
293
  * fallback, which is what lets the admin's settings screen offer a base URL
124
294
  * of its own: an `environment` or `config` value is a deployment fact and
125
- * wins over anything stored in SQLite.
295
+ * wins over the `url` in `content/_data/site.json`.
126
296
  */
127
297
  baseUrlSource: 'environment' | 'config' | 'default';
128
298
  watch: boolean;
@@ -130,8 +300,34 @@ export interface ResolvedConfig {
130
300
  uploadMaxBytes: number;
131
301
  /** Normalised: lower case, each with its leading dot. */
132
302
  uploadTypes: string[];
303
+ imageOptimization: boolean;
304
+ /** Ascending and without repeats. */
305
+ imageWidths: number[];
306
+ /** Normalised: lower case, in the order the `<source>` elements go. */
307
+ imageFormats: string[];
308
+ loginAttempts: number;
309
+ loginLockout: number;
310
+ trustProxy: boolean;
311
+ accessLog: boolean;
312
+ accessLogAddress: boolean;
313
+ /** The sink, when the site named one; otherwise the log writes to stdout. */
314
+ accessLogWriter: AccessLogWriter | undefined;
315
+ seedContent: boolean;
133
316
  onDocumentChange: DocumentChangeHook | undefined;
134
317
  onPublish: DocumentChangeHook | undefined;
318
+ /**
319
+ * The comment checker, when the site named one.
320
+ *
321
+ * `undefined` as this leaves {@link resolveConfig}. `createCms` fills it in
322
+ * with the Akismet checker (TASK-52) when the site named none, so by the
323
+ * time a request reads it off the context there is one — and with no key in
324
+ * `data/akismet.json` that one sends nothing anywhere.
325
+ */
326
+ commentChecker: CommentChecker | undefined;
327
+ /** Mail overrides, empty when the site named none. */
328
+ mail: MailOverrides;
329
+ /** The clock the index and the scheduler read. */
330
+ now: Clock;
135
331
  /** Federation stores and guards, empty when the site named none. */
136
332
  federation: FederationOverrides;
137
333
  }
@@ -145,7 +341,7 @@ export interface ResolveConfigContext {
145
341
  export declare const DEFAULT_PORT = 3000;
146
342
  export declare const DEFAULT_CONTENT_DIR = "content";
147
343
  export declare const DEFAULT_DATA_DIR = "data";
148
- export declare const DEFAULT_THEME_DIR = "theme";
344
+ export declare const DEFAULT_THEMES_DIR = "themes";
149
345
  /** How long an admin login lasts by default: fourteen days, in seconds. */
150
346
  export declare const DEFAULT_SESSION_LIFETIME: number;
151
347
  /** Largest upload a site accepts by default: ten mebibytes. */
@@ -157,6 +353,30 @@ export declare const DEFAULT_UPLOAD_MAX_BYTES: number;
157
353
  * and so is opt-in.
158
354
  */
159
355
  export declare const DEFAULT_UPLOAD_TYPES: readonly string[];
356
+ /**
357
+ * The widths an image is offered at by default.
358
+ *
359
+ * These are 11ty/image's own defaults, and deliberately so: decision-10 makes
360
+ * that plugin the reference for the markup and the width set, so a site that
361
+ * builds the same `content/` with Eleventy and runs the plugin over the same
362
+ * originals gets an equivalent `srcset` rather than a different one.
363
+ */
364
+ export declare const DEFAULT_IMAGE_WIDTHS: readonly number[];
365
+ /** The derived formats a site gets without asking. AVIF is not one; see {@link GeekityConfig.imageFormats}. */
366
+ export declare const DEFAULT_IMAGE_FORMATS: readonly string[];
367
+ /**
368
+ * The formats a site may ask for.
369
+ *
370
+ * Every one of them is something sharp can write and every browser that
371
+ * matters can read. There is no `gif` and no `svg`: an animated GIF cannot
372
+ * survive being resized frame by frame into a still, and an SVG has no
373
+ * intrinsic pixels to resize at all, so neither is ever varianted.
374
+ */
375
+ export declare const KNOWN_IMAGE_FORMATS: readonly string[];
376
+ /** Failed sign-ins allowed before the admin locks a username or an address out. */
377
+ export declare const DEFAULT_LOGIN_ATTEMPTS = 5;
378
+ /** How long the first lockout lasts by default: a quarter of an hour, in seconds. */
379
+ export declare const DEFAULT_LOGIN_LOCKOUT: number;
160
380
  /**
161
381
  * Identity helper that gives a `geekity.config.ts` file type checking and
162
382
  * completion without the site importing the type by hand.