@geekity/cms 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (637) hide show
  1. package/README.md +2091 -214
  2. package/admin/components/fields.njk +165 -0
  3. package/admin/layouts/base.njk +1 -1
  4. package/admin/layouts/settings-page.njk +26 -0
  5. package/admin/layouts/shell.njk +17 -3
  6. package/admin/pages/account/forgot.njk +47 -0
  7. package/admin/pages/account/login.njk +26 -0
  8. package/admin/pages/account/reset.njk +38 -0
  9. package/admin/pages/account/setup.njk +28 -0
  10. package/admin/pages/appearance/themes.njk +69 -0
  11. package/admin/pages/comments/all.njk +133 -0
  12. package/admin/{layouts/dashboard.njk → pages/dashboard/home.njk} +8 -15
  13. package/admin/{layouts/document-conflict.njk → pages/documents/conflict.njk} +1 -0
  14. package/admin/pages/documents/editor.njk +196 -0
  15. package/admin/{layouts/document-list.njk → pages/documents/list.njk} +19 -2
  16. package/admin/pages/documents/taxonomy.njk +95 -0
  17. package/admin/pages/federation/followers.njk +238 -0
  18. package/admin/pages/media/library.njk +160 -0
  19. package/admin/pages/messages/all.njk +110 -0
  20. package/admin/{layouts → pages}/placeholder.njk +5 -1
  21. package/admin/pages/settings/discussion.njk +99 -0
  22. package/admin/pages/settings/email.njk +203 -0
  23. package/admin/pages/settings/federation.njk +75 -0
  24. package/admin/pages/settings/general.njk +29 -0
  25. package/admin/pages/settings/permalinks.njk +50 -0
  26. package/admin/pages/settings/reading.njk +72 -0
  27. package/admin/pages/users/edit.njk +239 -0
  28. package/admin/pages/users/list.njk +48 -0
  29. package/admin/pages/users/new.njk +41 -0
  30. package/admin/static/admin.css +498 -3
  31. package/admin/static/copy.js +52 -0
  32. package/admin/static/editor.js +16 -16
  33. package/admin/static/slug.js +61 -0
  34. package/dist/admin/accounts.d.ts +413 -0
  35. package/dist/admin/accounts.d.ts.map +1 -0
  36. package/dist/admin/accounts.js +680 -0
  37. package/dist/admin/accounts.js.map +1 -0
  38. package/dist/admin/appearance.d.ts +80 -0
  39. package/dist/admin/appearance.d.ts.map +1 -0
  40. package/dist/admin/appearance.js +136 -0
  41. package/dist/admin/appearance.js.map +1 -0
  42. package/dist/admin/comments.d.ts +90 -0
  43. package/dist/admin/comments.d.ts.map +1 -0
  44. package/dist/admin/comments.js +244 -0
  45. package/dist/admin/comments.js.map +1 -0
  46. package/dist/admin/credentials.d.ts +26 -2
  47. package/dist/admin/credentials.d.ts.map +1 -1
  48. package/dist/admin/credentials.js +35 -2
  49. package/dist/admin/credentials.js.map +1 -1
  50. package/dist/admin/documents.d.ts +127 -10
  51. package/dist/admin/documents.d.ts.map +1 -1
  52. package/dist/admin/documents.js +360 -41
  53. package/dist/admin/documents.js.map +1 -1
  54. package/dist/admin/federation.d.ts +151 -50
  55. package/dist/admin/federation.d.ts.map +1 -1
  56. package/dist/admin/federation.js +225 -93
  57. package/dist/admin/federation.js.map +1 -1
  58. package/dist/admin/formatting.d.ts +20 -0
  59. package/dist/admin/formatting.d.ts.map +1 -0
  60. package/dist/admin/formatting.js +72 -0
  61. package/dist/admin/formatting.js.map +1 -0
  62. package/dist/admin/headers.d.ts +65 -0
  63. package/dist/admin/headers.d.ts.map +1 -0
  64. package/dist/admin/headers.js +104 -0
  65. package/dist/admin/headers.js.map +1 -0
  66. package/dist/admin/index.d.ts +38 -12
  67. package/dist/admin/index.d.ts.map +1 -1
  68. package/dist/admin/index.js +25 -7
  69. package/dist/admin/index.js.map +1 -1
  70. package/dist/admin/media.d.ts +168 -0
  71. package/dist/admin/media.d.ts.map +1 -0
  72. package/dist/admin/media.js +295 -0
  73. package/dist/admin/media.js.map +1 -0
  74. package/dist/admin/menu.d.ts +79 -0
  75. package/dist/admin/menu.d.ts.map +1 -0
  76. package/dist/admin/menu.js +126 -0
  77. package/dist/admin/menu.js.map +1 -0
  78. package/dist/admin/messages.d.ts +80 -0
  79. package/dist/admin/messages.d.ts.map +1 -0
  80. package/dist/admin/messages.js +152 -0
  81. package/dist/admin/messages.js.map +1 -0
  82. package/dist/admin/preview.d.ts.map +1 -1
  83. package/dist/admin/preview.js +31 -7
  84. package/dist/admin/preview.js.map +1 -1
  85. package/dist/admin/recovery.d.ts +92 -0
  86. package/dist/admin/recovery.d.ts.map +1 -0
  87. package/dist/admin/recovery.js +229 -0
  88. package/dist/admin/recovery.js.map +1 -0
  89. package/dist/admin/routes.d.ts +0 -20
  90. package/dist/admin/routes.d.ts.map +1 -1
  91. package/dist/admin/routes.js +155 -105
  92. package/dist/admin/routes.js.map +1 -1
  93. package/dist/admin/settings-discussion.d.ts +37 -0
  94. package/dist/admin/settings-discussion.d.ts.map +1 -0
  95. package/dist/admin/settings-discussion.js +117 -0
  96. package/dist/admin/settings-discussion.js.map +1 -0
  97. package/dist/admin/settings-email.d.ts +54 -0
  98. package/dist/admin/settings-email.d.ts.map +1 -0
  99. package/dist/admin/settings-email.js +189 -0
  100. package/dist/admin/settings-email.js.map +1 -0
  101. package/dist/admin/settings-federation.d.ts +39 -0
  102. package/dist/admin/settings-federation.d.ts.map +1 -0
  103. package/dist/admin/settings-federation.js +103 -0
  104. package/dist/admin/settings-federation.js.map +1 -0
  105. package/dist/admin/settings-general.d.ts +12 -0
  106. package/dist/admin/settings-general.d.ts.map +1 -0
  107. package/dist/admin/settings-general.js +48 -0
  108. package/dist/admin/settings-general.js.map +1 -0
  109. package/dist/admin/settings-page.d.ts +102 -0
  110. package/dist/admin/settings-page.d.ts.map +1 -0
  111. package/dist/admin/settings-page.js +126 -0
  112. package/dist/admin/settings-page.js.map +1 -0
  113. package/dist/admin/settings-pages.d.ts +19 -0
  114. package/dist/admin/settings-pages.d.ts.map +1 -0
  115. package/dist/admin/settings-pages.js +33 -0
  116. package/dist/admin/settings-pages.js.map +1 -0
  117. package/dist/admin/settings-permalinks.d.ts +16 -0
  118. package/dist/admin/settings-permalinks.d.ts.map +1 -0
  119. package/dist/admin/settings-permalinks.js +32 -0
  120. package/dist/admin/settings-permalinks.js.map +1 -0
  121. package/dist/admin/settings-reading.d.ts +33 -0
  122. package/dist/admin/settings-reading.d.ts.map +1 -0
  123. package/dist/admin/settings-reading.js +48 -0
  124. package/dist/admin/settings-reading.js.map +1 -0
  125. package/dist/admin/settings.d.ts +325 -96
  126. package/dist/admin/settings.d.ts.map +1 -1
  127. package/dist/admin/settings.js +650 -308
  128. package/dist/admin/settings.js.map +1 -1
  129. package/dist/admin/store.d.ts +606 -152
  130. package/dist/admin/store.d.ts.map +1 -1
  131. package/dist/admin/store.js +863 -268
  132. package/dist/admin/store.js.map +1 -1
  133. package/dist/admin/taxonomy.d.ts +114 -0
  134. package/dist/admin/taxonomy.d.ts.map +1 -0
  135. package/dist/admin/taxonomy.js +289 -0
  136. package/dist/admin/taxonomy.js.map +1 -0
  137. package/dist/admin/templates.d.ts +44 -14
  138. package/dist/admin/templates.d.ts.map +1 -1
  139. package/dist/admin/templates.js +44 -14
  140. package/dist/admin/templates.js.map +1 -1
  141. package/dist/admin/throttle.d.ts +84 -0
  142. package/dist/admin/throttle.d.ts.map +1 -0
  143. package/dist/admin/throttle.js +143 -0
  144. package/dist/admin/throttle.js.map +1 -0
  145. package/dist/admin/uploads.d.ts +28 -2
  146. package/dist/admin/uploads.d.ts.map +1 -1
  147. package/dist/admin/uploads.js +43 -4
  148. package/dist/admin/uploads.js.map +1 -1
  149. package/dist/admin/users.d.ts +100 -13
  150. package/dist/admin/users.d.ts.map +1 -1
  151. package/dist/admin/users.js +412 -39
  152. package/dist/admin/users.js.map +1 -1
  153. package/dist/cache.d.ts +141 -0
  154. package/dist/cache.d.ts.map +1 -0
  155. package/dist/cache.js +207 -0
  156. package/dist/cache.js.map +1 -0
  157. package/dist/cli.d.ts +14 -2
  158. package/dist/cli.d.ts.map +1 -1
  159. package/dist/cli.js +424 -21
  160. package/dist/cli.js.map +1 -1
  161. package/dist/comments/akismet.d.ts +130 -0
  162. package/dist/comments/akismet.d.ts.map +1 -0
  163. package/dist/comments/akismet.js +274 -0
  164. package/dist/comments/akismet.js.map +1 -0
  165. package/dist/comments/conversation.d.ts +29 -0
  166. package/dist/comments/conversation.d.ts.map +1 -0
  167. package/dist/comments/conversation.js +60 -0
  168. package/dist/comments/conversation.js.map +1 -0
  169. package/dist/comments/form.d.ts +75 -0
  170. package/dist/comments/form.d.ts.map +1 -0
  171. package/dist/comments/form.js +64 -0
  172. package/dist/comments/form.js.map +1 -0
  173. package/dist/comments/index.d.ts +15 -0
  174. package/dist/comments/index.d.ts.map +1 -0
  175. package/dist/comments/index.js +9 -0
  176. package/dist/comments/index.js.map +1 -0
  177. package/dist/comments/markdown.d.ts +8 -0
  178. package/dist/comments/markdown.d.ts.map +1 -0
  179. package/dist/comments/markdown.js +85 -0
  180. package/dist/comments/markdown.js.map +1 -0
  181. package/dist/comments/moderate.d.ts +71 -0
  182. package/dist/comments/moderate.d.ts.map +1 -0
  183. package/dist/comments/moderate.js +88 -0
  184. package/dist/comments/moderate.js.map +1 -0
  185. package/dist/comments/policy.d.ts +57 -0
  186. package/dist/comments/policy.d.ts.map +1 -0
  187. package/dist/comments/policy.js +80 -0
  188. package/dist/comments/policy.js.map +1 -0
  189. package/dist/comments/records.d.ts +266 -0
  190. package/dist/comments/records.d.ts.map +1 -0
  191. package/dist/comments/records.js +487 -0
  192. package/dist/comments/records.js.map +1 -0
  193. package/dist/comments/routes.d.ts +58 -0
  194. package/dist/comments/routes.d.ts.map +1 -0
  195. package/dist/comments/routes.js +177 -0
  196. package/dist/comments/routes.js.map +1 -0
  197. package/dist/comments/submission.d.ts +263 -0
  198. package/dist/comments/submission.d.ts.map +1 -0
  199. package/dist/comments/submission.js +241 -0
  200. package/dist/comments/submission.js.map +1 -0
  201. package/dist/config.d.ts +190 -5
  202. package/dist/config.d.ts.map +1 -1
  203. package/dist/config.js +118 -6
  204. package/dist/config.js.map +1 -1
  205. package/dist/contact/delivery.d.ts +60 -0
  206. package/dist/contact/delivery.d.ts.map +1 -0
  207. package/dist/contact/delivery.js +87 -0
  208. package/dist/contact/delivery.js.map +1 -0
  209. package/dist/contact/form.d.ts +130 -0
  210. package/dist/contact/form.d.ts.map +1 -0
  211. package/dist/contact/form.js +163 -0
  212. package/dist/contact/form.js.map +1 -0
  213. package/dist/contact/index.d.ts +17 -0
  214. package/dist/contact/index.d.ts.map +1 -0
  215. package/dist/contact/index.js +13 -0
  216. package/dist/contact/index.js.map +1 -0
  217. package/dist/contact/records.d.ts +124 -0
  218. package/dist/contact/records.d.ts.map +1 -0
  219. package/dist/contact/records.js +218 -0
  220. package/dist/contact/records.js.map +1 -0
  221. package/dist/contact/routes.d.ts +17 -0
  222. package/dist/contact/routes.d.ts.map +1 -0
  223. package/dist/contact/routes.js +127 -0
  224. package/dist/contact/routes.js.map +1 -0
  225. package/dist/contact/submission.d.ts +103 -0
  226. package/dist/contact/submission.d.ts.map +1 -0
  227. package/dist/contact/submission.js +166 -0
  228. package/dist/contact/submission.js.map +1 -0
  229. package/dist/content/document.d.ts +7 -2
  230. package/dist/content/document.d.ts.map +1 -1
  231. package/dist/content/document.js +1 -0
  232. package/dist/content/document.js.map +1 -1
  233. package/dist/content/index.d.ts +7 -2
  234. package/dist/content/index.d.ts.map +1 -1
  235. package/dist/content/index.js +4 -1
  236. package/dist/content/index.js.map +1 -1
  237. package/dist/content/markdown.d.ts.map +1 -1
  238. package/dist/content/markdown.js +24 -2
  239. package/dist/content/markdown.js.map +1 -1
  240. package/dist/content/parser.d.ts.map +1 -1
  241. package/dist/content/parser.js +11 -4
  242. package/dist/content/parser.js.map +1 -1
  243. package/dist/content/save.d.ts +5 -0
  244. package/dist/content/save.d.ts.map +1 -1
  245. package/dist/content/save.js +44 -1
  246. package/dist/content/save.js.map +1 -1
  247. package/dist/content/schedule.d.ts +125 -0
  248. package/dist/content/schedule.d.ts.map +1 -0
  249. package/dist/content/schedule.js +207 -0
  250. package/dist/content/schedule.js.map +1 -0
  251. package/dist/content/search.d.ts +64 -0
  252. package/dist/content/search.d.ts.map +1 -0
  253. package/dist/content/search.js +136 -0
  254. package/dist/content/search.js.map +1 -0
  255. package/dist/content/store.d.ts +202 -7
  256. package/dist/content/store.d.ts.map +1 -1
  257. package/dist/content/store.js +374 -67
  258. package/dist/content/store.js.map +1 -1
  259. package/dist/content/sync.d.ts +7 -1
  260. package/dist/content/sync.d.ts.map +1 -1
  261. package/dist/content/sync.js +18 -10
  262. package/dist/content/sync.js.map +1 -1
  263. package/dist/content/time.d.ts +56 -0
  264. package/dist/content/time.d.ts.map +1 -0
  265. package/dist/content/time.js +141 -0
  266. package/dist/content/time.js.map +1 -0
  267. package/dist/content/writer.d.ts.map +1 -1
  268. package/dist/content/writer.js +3 -0
  269. package/dist/content/writer.js.map +1 -1
  270. package/dist/env.d.ts +58 -4
  271. package/dist/env.d.ts.map +1 -1
  272. package/dist/federation/actor.d.ts +95 -41
  273. package/dist/federation/actor.d.ts.map +1 -1
  274. package/dist/federation/actor.js +180 -53
  275. package/dist/federation/actor.js.map +1 -1
  276. package/dist/federation/article.d.ts +44 -17
  277. package/dist/federation/article.d.ts.map +1 -1
  278. package/dist/federation/article.js +98 -46
  279. package/dist/federation/article.js.map +1 -1
  280. package/dist/federation/delivery.d.ts +63 -15
  281. package/dist/federation/delivery.d.ts.map +1 -1
  282. package/dist/federation/delivery.js +154 -80
  283. package/dist/federation/delivery.js.map +1 -1
  284. package/dist/federation/federation.d.ts +22 -17
  285. package/dist/federation/federation.d.ts.map +1 -1
  286. package/dist/federation/federation.js +98 -77
  287. package/dist/federation/federation.js.map +1 -1
  288. package/dist/federation/followers.d.ts +3 -2
  289. package/dist/federation/followers.d.ts.map +1 -1
  290. package/dist/federation/followers.js +6 -5
  291. package/dist/federation/followers.js.map +1 -1
  292. package/dist/federation/import-wordpress.d.ts +128 -0
  293. package/dist/federation/import-wordpress.d.ts.map +1 -0
  294. package/dist/federation/import-wordpress.js +426 -0
  295. package/dist/federation/import-wordpress.js.map +1 -0
  296. package/dist/federation/inbox.d.ts +36 -13
  297. package/dist/federation/inbox.d.ts.map +1 -1
  298. package/dist/federation/inbox.js +120 -49
  299. package/dist/federation/inbox.js.map +1 -1
  300. package/dist/federation/index.d.ts +22 -10
  301. package/dist/federation/index.d.ts.map +1 -1
  302. package/dist/federation/index.js +13 -8
  303. package/dist/federation/index.js.map +1 -1
  304. package/dist/federation/keys.d.ts +98 -20
  305. package/dist/federation/keys.d.ts.map +1 -1
  306. package/dist/federation/keys.js +269 -39
  307. package/dist/federation/keys.js.map +1 -1
  308. package/dist/federation/mount.d.ts +42 -10
  309. package/dist/federation/mount.d.ts.map +1 -1
  310. package/dist/federation/mount.js +266 -26
  311. package/dist/federation/mount.js.map +1 -1
  312. package/dist/federation/paths.d.ts +23 -35
  313. package/dist/federation/paths.d.ts.map +1 -1
  314. package/dist/federation/paths.js +26 -39
  315. package/dist/federation/paths.js.map +1 -1
  316. package/dist/federation/records.d.ts +223 -0
  317. package/dist/federation/records.d.ts.map +1 -0
  318. package/dist/federation/records.js +506 -0
  319. package/dist/federation/records.js.map +1 -0
  320. package/dist/federation/relays.d.ts +114 -0
  321. package/dist/federation/relays.d.ts.map +1 -0
  322. package/dist/federation/relays.js +290 -0
  323. package/dist/federation/relays.js.map +1 -0
  324. package/dist/federation/replies.d.ts +80 -0
  325. package/dist/federation/replies.d.ts.map +1 -0
  326. package/dist/federation/replies.js +166 -0
  327. package/dist/federation/replies.js.map +1 -0
  328. package/dist/federation/wordpress.d.ts +131 -0
  329. package/dist/federation/wordpress.d.ts.map +1 -0
  330. package/dist/federation/wordpress.js +341 -0
  331. package/dist/federation/wordpress.js.map +1 -0
  332. package/dist/files/atomic.d.ts +103 -0
  333. package/dist/files/atomic.d.ts.map +1 -0
  334. package/dist/files/atomic.js +150 -0
  335. package/dist/files/atomic.js.map +1 -0
  336. package/dist/files/index.d.ts +3 -0
  337. package/dist/files/index.d.ts.map +1 -0
  338. package/dist/files/index.js +2 -0
  339. package/dist/files/index.js.map +1 -0
  340. package/dist/forms/protection.d.ts +85 -0
  341. package/dist/forms/protection.d.ts.map +1 -0
  342. package/dist/forms/protection.js +116 -0
  343. package/dist/forms/protection.js.map +1 -0
  344. package/dist/images/icons.d.ts +59 -0
  345. package/dist/images/icons.d.ts.map +1 -0
  346. package/dist/images/icons.js +131 -0
  347. package/dist/images/icons.js.map +1 -0
  348. package/dist/images/index.d.ts +7 -0
  349. package/dist/images/index.d.ts.map +1 -0
  350. package/dist/images/index.js +4 -0
  351. package/dist/images/index.js.map +1 -0
  352. package/dist/images/markup.d.ts +47 -0
  353. package/dist/images/markup.d.ts.map +1 -0
  354. package/dist/images/markup.js +175 -0
  355. package/dist/images/markup.js.map +1 -0
  356. package/dist/images/paths.d.ts +34 -0
  357. package/dist/images/paths.d.ts.map +1 -0
  358. package/dist/images/paths.js +41 -0
  359. package/dist/images/paths.js.map +1 -0
  360. package/dist/images/variants.d.ts +104 -0
  361. package/dist/images/variants.d.ts.map +1 -0
  362. package/dist/images/variants.js +320 -0
  363. package/dist/images/variants.js.map +1 -0
  364. package/dist/index.d.ts +123 -19
  365. package/dist/index.d.ts.map +1 -1
  366. package/dist/index.js +327 -25
  367. package/dist/index.js.map +1 -1
  368. package/dist/init.d.ts +18 -0
  369. package/dist/init.d.ts.map +1 -1
  370. package/dist/init.js +34 -3
  371. package/dist/init.js.map +1 -1
  372. package/dist/mail/brevo.d.ts +35 -0
  373. package/dist/mail/brevo.d.ts.map +1 -0
  374. package/dist/mail/brevo.js +85 -0
  375. package/dist/mail/brevo.js.map +1 -0
  376. package/dist/mail/credentials.d.ts +62 -0
  377. package/dist/mail/credentials.d.ts.map +1 -0
  378. package/dist/mail/credentials.js +86 -0
  379. package/dist/mail/credentials.js.map +1 -0
  380. package/dist/mail/index.d.ts +15 -0
  381. package/dist/mail/index.d.ts.map +1 -0
  382. package/dist/mail/index.js +8 -0
  383. package/dist/mail/index.js.map +1 -0
  384. package/dist/mail/memory.d.ts +35 -0
  385. package/dist/mail/memory.d.ts.map +1 -0
  386. package/dist/mail/memory.js +29 -0
  387. package/dist/mail/memory.js.map +1 -0
  388. package/dist/mail/provider.d.ts +80 -0
  389. package/dist/mail/provider.d.ts.map +1 -0
  390. package/dist/mail/provider.js +23 -0
  391. package/dist/mail/provider.js.map +1 -0
  392. package/dist/mail/service.d.ts +156 -0
  393. package/dist/mail/service.d.ts.map +1 -0
  394. package/dist/mail/service.js +265 -0
  395. package/dist/mail/service.js.map +1 -0
  396. package/dist/mail/smtp.d.ts +39 -0
  397. package/dist/mail/smtp.d.ts.map +1 -0
  398. package/dist/mail/smtp.js +58 -0
  399. package/dist/mail/smtp.js.map +1 -0
  400. package/dist/mail/templates.d.ts +91 -0
  401. package/dist/mail/templates.d.ts.map +1 -0
  402. package/dist/mail/templates.js +69 -0
  403. package/dist/mail/templates.js.map +1 -0
  404. package/dist/notifications/comments.d.ts +66 -0
  405. package/dist/notifications/comments.d.ts.map +1 -0
  406. package/dist/notifications/comments.js +151 -0
  407. package/dist/notifications/comments.js.map +1 -0
  408. package/dist/notifications/digest.d.ts +135 -0
  409. package/dist/notifications/digest.d.ts.map +1 -0
  410. package/dist/notifications/digest.js +287 -0
  411. package/dist/notifications/digest.js.map +1 -0
  412. package/dist/notifications/index.d.ts +13 -0
  413. package/dist/notifications/index.d.ts.map +1 -0
  414. package/dist/notifications/index.js +8 -0
  415. package/dist/notifications/index.js.map +1 -0
  416. package/dist/notifications/links.d.ts +60 -0
  417. package/dist/notifications/links.d.ts.map +1 -0
  418. package/dist/notifications/links.js +63 -0
  419. package/dist/notifications/links.js.map +1 -0
  420. package/dist/notifications/optouts.d.ts +48 -0
  421. package/dist/notifications/optouts.d.ts.map +1 -0
  422. package/dist/notifications/optouts.js +93 -0
  423. package/dist/notifications/optouts.js.map +1 -0
  424. package/dist/notifications/preferences.d.ts +158 -0
  425. package/dist/notifications/preferences.d.ts.map +1 -0
  426. package/dist/notifications/preferences.js +195 -0
  427. package/dist/notifications/preferences.js.map +1 -0
  428. package/dist/notifications/routes.d.ts +28 -0
  429. package/dist/notifications/routes.d.ts.map +1 -0
  430. package/dist/notifications/routes.js +184 -0
  431. package/dist/notifications/routes.js.map +1 -0
  432. package/dist/notifications/tokens.d.ts +60 -0
  433. package/dist/notifications/tokens.d.ts.map +1 -0
  434. package/dist/notifications/tokens.js +125 -0
  435. package/dist/notifications/tokens.js.map +1 -0
  436. package/dist/notify.d.ts +84 -0
  437. package/dist/notify.d.ts.map +1 -0
  438. package/dist/notify.js +149 -0
  439. package/dist/notify.js.map +1 -0
  440. package/dist/web/archive.d.ts +49 -0
  441. package/dist/web/archive.d.ts.map +1 -0
  442. package/dist/web/archive.js +59 -0
  443. package/dist/web/archive.js.map +1 -0
  444. package/dist/web/assets.d.ts +6 -5
  445. package/dist/web/assets.d.ts.map +1 -1
  446. package/dist/web/assets.js +5 -7
  447. package/dist/web/assets.js.map +1 -1
  448. package/dist/web/authors.d.ts +143 -0
  449. package/dist/web/authors.d.ts.map +1 -0
  450. package/dist/web/authors.js +178 -0
  451. package/dist/web/authors.js.map +1 -0
  452. package/dist/web/comments.d.ts +60 -0
  453. package/dist/web/comments.d.ts.map +1 -0
  454. package/dist/web/comments.js +207 -0
  455. package/dist/web/comments.js.map +1 -0
  456. package/dist/web/context.d.ts +162 -28
  457. package/dist/web/context.d.ts.map +1 -1
  458. package/dist/web/context.js +123 -26
  459. package/dist/web/context.js.map +1 -1
  460. package/dist/web/conversation.d.ts +209 -0
  461. package/dist/web/conversation.d.ts.map +1 -0
  462. package/dist/web/conversation.js +557 -0
  463. package/dist/web/conversation.js.map +1 -0
  464. package/dist/web/documents.d.ts +36 -6
  465. package/dist/web/documents.d.ts.map +1 -1
  466. package/dist/web/documents.js +52 -10
  467. package/dist/web/documents.js.map +1 -1
  468. package/dist/web/feed-atom.d.ts +22 -0
  469. package/dist/web/feed-atom.d.ts.map +1 -0
  470. package/dist/web/feed-atom.js +83 -0
  471. package/dist/web/feed-atom.js.map +1 -0
  472. package/dist/web/feed-item.d.ts +145 -0
  473. package/dist/web/feed-item.d.ts.map +1 -0
  474. package/dist/web/feed-item.js +120 -0
  475. package/dist/web/feed-item.js.map +1 -0
  476. package/dist/web/feed-json.d.ts +79 -0
  477. package/dist/web/feed-json.d.ts.map +1 -0
  478. package/dist/web/feed-json.js +53 -0
  479. package/dist/web/feed-json.js.map +1 -0
  480. package/dist/web/feed-rss.d.ts +26 -0
  481. package/dist/web/feed-rss.d.ts.map +1 -0
  482. package/dist/web/feed-rss.js +203 -0
  483. package/dist/web/feed-rss.js.map +1 -0
  484. package/dist/web/feed-source.d.ts +244 -0
  485. package/dist/web/feed-source.d.ts.map +1 -0
  486. package/dist/web/feed-source.js +201 -0
  487. package/dist/web/feed-source.js.map +1 -0
  488. package/dist/web/feed-xml.d.ts +63 -0
  489. package/dist/web/feed-xml.d.ts.map +1 -0
  490. package/dist/web/feed-xml.js +116 -0
  491. package/dist/web/feed-xml.js.map +1 -0
  492. package/dist/web/feeds.d.ts +31 -96
  493. package/dist/web/feeds.d.ts.map +1 -1
  494. package/dist/web/feeds.js +94 -198
  495. package/dist/web/feeds.js.map +1 -1
  496. package/dist/web/health.d.ts +31 -0
  497. package/dist/web/health.d.ts.map +1 -0
  498. package/dist/web/health.js +44 -0
  499. package/dist/web/health.js.map +1 -0
  500. package/dist/web/index.d.ts +30 -10
  501. package/dist/web/index.d.ts.map +1 -1
  502. package/dist/web/index.js +18 -7
  503. package/dist/web/index.js.map +1 -1
  504. package/dist/web/navigation.d.ts +73 -0
  505. package/dist/web/navigation.d.ts.map +1 -0
  506. package/dist/web/navigation.js +110 -0
  507. package/dist/web/navigation.js.map +1 -0
  508. package/dist/web/negotiate.d.ts +9 -0
  509. package/dist/web/negotiate.d.ts.map +1 -1
  510. package/dist/web/negotiate.js +6 -1
  511. package/dist/web/negotiate.js.map +1 -1
  512. package/dist/web/recent.d.ts +43 -0
  513. package/dist/web/recent.d.ts.map +1 -0
  514. package/dist/web/recent.js +37 -0
  515. package/dist/web/recent.js.map +1 -0
  516. package/dist/web/render.d.ts +197 -9
  517. package/dist/web/render.d.ts.map +1 -1
  518. package/dist/web/render.js +334 -17
  519. package/dist/web/render.js.map +1 -1
  520. package/dist/web/routes.d.ts +25 -8
  521. package/dist/web/routes.d.ts.map +1 -1
  522. package/dist/web/routes.js +845 -93
  523. package/dist/web/routes.js.map +1 -1
  524. package/dist/web/sanitize.d.ts +19 -0
  525. package/dist/web/sanitize.d.ts.map +1 -0
  526. package/dist/web/sanitize.js +272 -0
  527. package/dist/web/sanitize.js.map +1 -0
  528. package/dist/web/search.d.ts +79 -0
  529. package/dist/web/search.d.ts.map +1 -0
  530. package/dist/web/search.js +94 -0
  531. package/dist/web/search.js.map +1 -0
  532. package/dist/web/sitemap.d.ts +120 -0
  533. package/dist/web/sitemap.d.ts.map +1 -0
  534. package/dist/web/sitemap.js +199 -0
  535. package/dist/web/sitemap.js.map +1 -0
  536. package/dist/web/taxonomy.d.ts +152 -0
  537. package/dist/web/taxonomy.d.ts.map +1 -0
  538. package/dist/web/taxonomy.js +237 -0
  539. package/dist/web/taxonomy.js.map +1 -0
  540. package/dist/web/templates.d.ts +54 -16
  541. package/dist/web/templates.d.ts.map +1 -1
  542. package/dist/web/templates.js +109 -28
  543. package/dist/web/templates.js.map +1 -1
  544. package/dist/web/themes.d.ts +205 -0
  545. package/dist/web/themes.d.ts.map +1 -0
  546. package/dist/web/themes.js +277 -0
  547. package/dist/web/themes.js.map +1 -0
  548. package/dist/webmention/discovery.d.ts +51 -0
  549. package/dist/webmention/discovery.d.ts.map +1 -0
  550. package/dist/webmention/discovery.js +176 -0
  551. package/dist/webmention/discovery.js.map +1 -0
  552. package/dist/webmention/html.d.ts +71 -0
  553. package/dist/webmention/html.d.ts.map +1 -0
  554. package/dist/webmention/html.js +285 -0
  555. package/dist/webmention/html.js.map +1 -0
  556. package/dist/webmention/index.d.ts +12 -0
  557. package/dist/webmention/index.d.ts.map +1 -0
  558. package/dist/webmention/index.js +8 -0
  559. package/dist/webmention/index.js.map +1 -0
  560. package/dist/webmention/links.d.ts +18 -0
  561. package/dist/webmention/links.d.ts.map +1 -0
  562. package/dist/webmention/links.js +64 -0
  563. package/dist/webmention/links.js.map +1 -0
  564. package/dist/webmention/microformats.d.ts +93 -0
  565. package/dist/webmention/microformats.d.ts.map +1 -0
  566. package/dist/webmention/microformats.js +376 -0
  567. package/dist/webmention/microformats.js.map +1 -0
  568. package/dist/webmention/receive.d.ts +141 -0
  569. package/dist/webmention/receive.d.ts.map +1 -0
  570. package/dist/webmention/receive.js +264 -0
  571. package/dist/webmention/receive.js.map +1 -0
  572. package/dist/webmention/routes.d.ts +37 -0
  573. package/dist/webmention/routes.d.ts.map +1 -0
  574. package/dist/webmention/routes.js +75 -0
  575. package/dist/webmention/routes.js.map +1 -0
  576. package/dist/webmention/service.d.ts +94 -0
  577. package/dist/webmention/service.d.ts.map +1 -0
  578. package/dist/webmention/service.js +207 -0
  579. package/dist/webmention/service.js.map +1 -0
  580. package/package.json +11 -6
  581. package/templates/site/content/_data/site.json +1 -0
  582. package/templates/site/content/posts/2026-01-01-hello-world.md +5 -3
  583. package/templates/site/geekity.config.ts +11 -1
  584. package/templates/site/tsconfig.json +1 -1
  585. package/themes/default/README.md +1096 -50
  586. package/themes/default/layouts/404.njk +2 -3
  587. package/themes/default/layouts/author.njk +37 -0
  588. package/themes/default/layouts/base.njk +172 -31
  589. package/themes/default/layouts/category.njk +27 -0
  590. package/themes/default/layouts/front-page.njk +57 -0
  591. package/themes/default/layouts/home.njk +17 -1
  592. package/themes/default/layouts/page.njk +57 -5
  593. package/themes/default/layouts/post.njk +78 -19
  594. package/themes/default/layouts/search.njk +57 -0
  595. package/themes/default/layouts/tag.njk +3 -6
  596. package/themes/default/mail/comment-digest.html.njk +37 -0
  597. package/themes/default/mail/comment-digest.subject.njk +3 -0
  598. package/themes/default/mail/comment-digest.txt.njk +40 -0
  599. package/themes/default/mail/comment-pending.html.njk +27 -0
  600. package/themes/default/mail/comment-pending.subject.njk +2 -0
  601. package/themes/default/mail/comment-pending.txt.njk +24 -0
  602. package/themes/default/mail/comment-reply.html.njk +21 -0
  603. package/themes/default/mail/comment-reply.subject.njk +2 -0
  604. package/themes/default/mail/comment-reply.txt.njk +22 -0
  605. package/themes/default/mail/contact-message.html.njk +24 -0
  606. package/themes/default/mail/contact-message.subject.njk +5 -0
  607. package/themes/default/mail/contact-message.txt.njk +20 -0
  608. package/themes/default/mail/password-changed.html.njk +18 -0
  609. package/themes/default/mail/password-changed.subject.njk +2 -0
  610. package/themes/default/mail/password-changed.txt.njk +17 -0
  611. package/themes/default/mail/password-reset.html.njk +22 -0
  612. package/themes/default/mail/password-reset.subject.njk +2 -0
  613. package/themes/default/mail/password-reset.txt.njk +18 -0
  614. package/themes/default/mail/test.html.njk +14 -0
  615. package/themes/default/mail/test.subject.njk +2 -0
  616. package/themes/default/mail/test.txt.njk +12 -0
  617. package/themes/default/partials/archive.njk +27 -0
  618. package/themes/default/partials/bio.njk +77 -0
  619. package/themes/default/partials/comment-form.njk +109 -0
  620. package/themes/default/partials/contact-form.njk +88 -0
  621. package/themes/default/partials/conversation.njk +139 -0
  622. package/themes/default/partials/feeds.njk +26 -0
  623. package/themes/default/partials/jsonld.njk +120 -0
  624. package/themes/default/partials/pagination.njk +12 -6
  625. package/themes/default/partials/post-list.njk +51 -18
  626. package/themes/default/partials/search-form.njk +17 -0
  627. package/themes/default/partials/tags.njk +26 -5
  628. package/themes/default/static/highlight.js +4 -0
  629. package/themes/default/static/style.css +1119 -194
  630. package/themes/default/theme.json +5 -0
  631. package/admin/layouts/document-editor.njk +0 -167
  632. package/admin/layouts/federation.njk +0 -145
  633. package/admin/layouts/login.njk +0 -23
  634. package/admin/layouts/settings.njk +0 -116
  635. package/admin/layouts/setup.njk +0 -27
  636. package/admin/layouts/users.njk +0 -91
  637. /package/admin/{layouts/_flash.njk → components/flash.njk} +0 -0
@@ -0,0 +1,163 @@
1
+ import { FORM_LOADED_FIELD, FORM_TRAP_FIELD } from '../forms/protection.js';
2
+ /**
3
+ * The contact form: what it is called, what it carries, and when a page has
4
+ * one at all.
5
+ *
6
+ * A page opts in with `contact: true` in its front matter — one key, ignored by
7
+ * Eleventy, the same shape as `navigation: true` — and the theme renders
8
+ * `partials/contact-form.njk` under the page's content. Nothing about where the
9
+ * message goes is here or anywhere the template can reach: the address is read
10
+ * when a submission arrives, so it cannot appear in the HTML however a theme is
11
+ * written.
12
+ */
13
+ /** The front matter key a page opts in with. */
14
+ export const CONTACT_FRONT_MATTER_KEY = 'contact';
15
+ /** Where the contact form posts, under the CMS's own prefix. */
16
+ export const CONTACT_POST_PATH = '/_geekity/contact';
17
+ /** The query the redirect after a submission carries. */
18
+ export const CONTACT_NOTICE_PARAM = 'contact';
19
+ /** The anchor the form and its thank-you sit at, so a redirect lands on them. */
20
+ export const CONTACT_ANCHOR = 'contact';
21
+ /**
22
+ * What that query means, and what the sender is told.
23
+ *
24
+ * A redirect rather than a rendered page, so a refresh does not send the
25
+ * message a second time; a query rather than a flash, because a visitor has no
26
+ * session to hang one on.
27
+ */
28
+ export const CONTACT_NOTICES = {
29
+ sent: 'Thank you — your message has been sent.',
30
+ };
31
+ /** The fields the contact form submits. */
32
+ export const CONTACT_FIELDS = {
33
+ /** Which page the form was on, as its slug. */
34
+ page: 'page',
35
+ /** The sender's name. Required. */
36
+ name: 'name',
37
+ /** Their email, which the message replies to. Required. */
38
+ email: 'email',
39
+ /** What it is about. Optional. */
40
+ subject: 'subject',
41
+ /** The message itself, as plain text. Required. */
42
+ message: 'message',
43
+ /** The honeypot, which every public form here spells the same way. */
44
+ trap: FORM_TRAP_FIELD,
45
+ /** When the form was rendered, in epoch milliseconds. */
46
+ loaded: FORM_LOADED_FIELD,
47
+ };
48
+ /** The longest a name may be. */
49
+ export const MAXIMUM_CONTACT_NAME_LENGTH = 80;
50
+ /** The longest an email address may be. */
51
+ export const MAXIMUM_CONTACT_EMAIL_LENGTH = 500;
52
+ /** The longest a subject may be. */
53
+ export const MAXIMUM_CONTACT_SUBJECT_LENGTH = 200;
54
+ /** The longest a message may be, in characters. */
55
+ export const MAXIMUM_CONTACT_MESSAGE_LENGTH = 10_000;
56
+ /** Whether this document offers a contact form. */
57
+ export function contactOpen(document) {
58
+ return document.extra[CONTACT_FRONT_MATTER_KEY] === true;
59
+ }
60
+ /** A fresh, empty form for a page that asked for one. */
61
+ export function contactForm(document, now = new Date()) {
62
+ return refilledContactForm(document, blankContactValues(), {}, now);
63
+ }
64
+ /**
65
+ * The same form with what somebody typed still in it.
66
+ *
67
+ * What a refused submission is answered with: losing a paragraph because an
68
+ * email address had a typo in it is the fastest way to lose a message.
69
+ */
70
+ export function refilledContactForm(document, values, problems, now = new Date(), error) {
71
+ return {
72
+ action: CONTACT_POST_PATH,
73
+ fields: CONTACT_FIELDS,
74
+ page: document.slug,
75
+ loaded: String(now.getTime()),
76
+ values,
77
+ problems,
78
+ nameLength: MAXIMUM_CONTACT_NAME_LENGTH,
79
+ subjectLength: MAXIMUM_CONTACT_SUBJECT_LENGTH,
80
+ messageLength: MAXIMUM_CONTACT_MESSAGE_LENGTH,
81
+ ...(error === undefined ? {} : { error }),
82
+ };
83
+ }
84
+ /** What a form holds before anybody has typed in it. */
85
+ export function blankContactValues() {
86
+ return { name: '', email: '', subject: '', message: '' };
87
+ }
88
+ /** What a submitted form typed, for putting back in a refused one. */
89
+ export function contactValuesOf(form) {
90
+ return {
91
+ name: form.name,
92
+ email: form.email,
93
+ subject: form.subject,
94
+ message: form.message,
95
+ };
96
+ }
97
+ /**
98
+ * The form for a document, or `undefined` when it does not offer one.
99
+ *
100
+ * What the renderer is handed at boot, and asked per render: a `contact: true`
101
+ * added to a page in the editor puts a form on it on the very next request.
102
+ */
103
+ export function contactFormFor(options) {
104
+ if (!contactOpen(options.document))
105
+ return undefined;
106
+ return contactForm(options.document, options.now);
107
+ }
108
+ /**
109
+ * The thank-you a sender is shown after a submission, from the query the
110
+ * redirect carried, or `undefined` when the URL says nothing.
111
+ *
112
+ * An unknown value says nothing at all rather than being printed, so the query
113
+ * cannot be used to put words on somebody's page.
114
+ */
115
+ export function contactNoticeFor(query) {
116
+ return query === undefined ? undefined : CONTACT_NOTICES[query];
117
+ }
118
+ /**
119
+ * What is wrong with a submitted form, as a person would be told it.
120
+ *
121
+ * Only the things a person can fix. The honeypot, the form's age and the rate
122
+ * limit are not here: none of them is the sender's mistake, and two of them
123
+ * should not be explained to whoever tripped them.
124
+ */
125
+ export function contactProblems(form) {
126
+ const problems = {};
127
+ const name = form.name.trim();
128
+ if (name === '')
129
+ problems.name = 'A message needs a name to come from.';
130
+ else if (name.length > MAXIMUM_CONTACT_NAME_LENGTH) {
131
+ problems.name = `That name is longer than ${String(MAXIMUM_CONTACT_NAME_LENGTH)} characters.`;
132
+ }
133
+ // Required, unlike a comment's, because the whole message is a request for
134
+ // an answer: a reply-to is what the site is being asked for.
135
+ const email = form.email.trim();
136
+ if (email === '')
137
+ problems.email = 'A message needs an address to reply to.';
138
+ else if (!isEmail(email))
139
+ problems.email = 'That does not look like an email address.';
140
+ if (form.subject.trim().length > MAXIMUM_CONTACT_SUBJECT_LENGTH) {
141
+ problems.subject = `That subject is longer than ${String(MAXIMUM_CONTACT_SUBJECT_LENGTH)} characters.`;
142
+ }
143
+ const message = form.message.trim();
144
+ if (message === '')
145
+ problems.message = 'A message needs something in it.';
146
+ else if (message.length > MAXIMUM_CONTACT_MESSAGE_LENGTH) {
147
+ problems.message = `That message is longer than ${String(MAXIMUM_CONTACT_MESSAGE_LENGTH)} characters.`;
148
+ }
149
+ return problems;
150
+ }
151
+ /**
152
+ * Whether something looks like an email address.
153
+ *
154
+ * Deliberately loose, and the same rule the comment form uses: the only thing
155
+ * a stricter one would buy is refusing valid addresses. Whether it can be
156
+ * delivered to is a question the provider answers.
157
+ */
158
+ function isEmail(value) {
159
+ if (value.length > MAXIMUM_CONTACT_EMAIL_LENGTH)
160
+ return false;
161
+ return /^[^\s@]+@[^\s@.]+(\.[^\s@.]+)+$/.test(value);
162
+ }
163
+ //# sourceMappingURL=form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/contact/form.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The contact form (TASK-56): a page that offers one, the defences in front of
3
+ * it, the file every message lands in, and the mail that carries it.
4
+ *
5
+ * The barrel every other module and the package index import from, so nothing
6
+ * outside here has to know which file a name lives in.
7
+ */
8
+ export { blankContactValues, CONTACT_ANCHOR, CONTACT_FIELDS, CONTACT_FRONT_MATTER_KEY, CONTACT_NOTICE_PARAM, CONTACT_NOTICES, CONTACT_POST_PATH, contactForm, contactFormFor, contactNoticeFor, contactOpen, contactProblems, contactValuesOf, MAXIMUM_CONTACT_EMAIL_LENGTH, MAXIMUM_CONTACT_MESSAGE_LENGTH, MAXIMUM_CONTACT_NAME_LENGTH, MAXIMUM_CONTACT_SUBJECT_LENGTH, refilledContactForm, } from './form.ts';
9
+ export type { ContactForm, ContactFormContext, ContactProblems } from './form.ts';
10
+ export { addContactMessage, CONTACT_DATA_DIRECTORY, contactDirectory, contactMessageFile, contactMessageId, countContactMessagesByStatus, countUnreadContactMessages, deleteContactMessage, listContactMessages, readContactMessage, setContactMessageRead, } from './records.ts';
11
+ export type { ContactMessage, ContactStatus, NewContactMessage } from './records.ts';
12
+ export { CONTACT_RATE_LIMIT, CONTACT_RATE_WINDOW_SECONDS, contactKeys, submitContactMessage, } from './submission.ts';
13
+ export type { ContactOutcome, ContactRefusal, ContactThrottle, SubmitContactMessageOptions, } from './submission.ts';
14
+ export { CONTACT_MESSAGE_TEMPLATE, contactRecipient, sendContactMessage } from './delivery.ts';
15
+ export type { ContactRecipientOptions, SendContactMessageOptions } from './delivery.ts';
16
+ export { mountContact } from './routes.ts';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contact/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,eAAe,EACf,4BAA4B,EAC5B,8BAA8B,EAC9B,2BAA2B,EAC3B,8BAA8B,EAC9B,mBAAmB,GACpB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAElF,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,4BAA4B,EAC5B,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,WAAW,EACX,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC/F,YAAY,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAExF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The contact form (TASK-56): a page that offers one, the defences in front of
3
+ * it, the file every message lands in, and the mail that carries it.
4
+ *
5
+ * The barrel every other module and the package index import from, so nothing
6
+ * outside here has to know which file a name lives in.
7
+ */
8
+ export { blankContactValues, CONTACT_ANCHOR, CONTACT_FIELDS, CONTACT_FRONT_MATTER_KEY, CONTACT_NOTICE_PARAM, CONTACT_NOTICES, CONTACT_POST_PATH, contactForm, contactFormFor, contactNoticeFor, contactOpen, contactProblems, contactValuesOf, MAXIMUM_CONTACT_EMAIL_LENGTH, MAXIMUM_CONTACT_MESSAGE_LENGTH, MAXIMUM_CONTACT_NAME_LENGTH, MAXIMUM_CONTACT_SUBJECT_LENGTH, refilledContactForm, } from './form.js';
9
+ export { addContactMessage, CONTACT_DATA_DIRECTORY, contactDirectory, contactMessageFile, contactMessageId, countContactMessagesByStatus, countUnreadContactMessages, deleteContactMessage, listContactMessages, readContactMessage, setContactMessageRead, } from './records.js';
10
+ export { CONTACT_RATE_LIMIT, CONTACT_RATE_WINDOW_SECONDS, contactKeys, submitContactMessage, } from './submission.js';
11
+ export { CONTACT_MESSAGE_TEMPLATE, contactRecipient, sendContactMessage } from './delivery.js';
12
+ export { mountContact } from './routes.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contact/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,eAAe,EACf,4BAA4B,EAC5B,8BAA8B,EAC9B,2BAA2B,EAC3B,8BAA8B,EAC9B,mBAAmB,GACpB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,4BAA4B,EAC5B,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,WAAW,EACX,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAG/F,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * A contact message as the file that holds it.
3
+ *
4
+ * decision-9 makes the filesystem the source of truth for everything durable,
5
+ * and a message somebody typed into this site is exactly that: nothing else
6
+ * holds it, and no other server can be asked for it again. So it is written to
7
+ * disk **before** anything is emailed, which is the point — a provider that is
8
+ * down, a key that has expired, an address that bounces, and the message is
9
+ * still on the Messages screen tomorrow.
10
+ *
11
+ * Two things separate this from the comment files:
12
+ *
13
+ * - **It lives under `data/`, not under `content/`.** A message carries the
14
+ * sender's email address and was never meant to be published; `content/` is
15
+ * in git and is copied to the public site by an Eleventy build.
16
+ * - **There is no SQLite index.** A contact inbox is tens or hundreds of
17
+ * files, the screen reads all of them to sort them anyway, and an index
18
+ * would be a second thing to keep true for no query it makes faster. Files
19
+ * are truth here with nothing derived from them at all.
20
+ *
21
+ * One JSON file per message, named by its id, which starts with the instant it
22
+ * arrived — so `ls` is the inbox in order, and so no two messages can collide.
23
+ */
24
+ /** Where the messages live, relative to the data directory. */
25
+ export declare const CONTACT_DATA_DIRECTORY = "contact";
26
+ /** The absolute path of one site's `data/contact`. */
27
+ export declare function contactDirectory(dataDir: string): string;
28
+ /**
29
+ * What became of a submission, as the file records it.
30
+ *
31
+ * `spam` is a checker's opinion rather than a person's, and the message is
32
+ * kept and shown on a list of its own rather than thrown away: a false
33
+ * positive on a contact form is a customer whose message vanished, which is a
34
+ * worse failure than a spam list somebody has to glance at. Nothing else is
35
+ * ever written — a submission the honeypot, the clock or the rate limit
36
+ * refused leaves no file at all, because storing those would hand an anonymous
37
+ * caller a way to fill the disk.
38
+ */
39
+ export type ContactStatus = 'received' | 'spam';
40
+ /** One contact message, as its file says it. */
41
+ export interface ContactMessage {
42
+ /** Its id, which is its filename and starts with the instant it arrived. */
43
+ id: string;
44
+ /** When it arrived, as a UTC instant. */
45
+ received: string;
46
+ /** Whether a checker thought it was spam. */
47
+ status: ContactStatus;
48
+ /** Whether somebody has read it on the Messages screen. */
49
+ read: boolean;
50
+ /** The page the form was on. */
51
+ page: {
52
+ /** Its slug. */
53
+ slug: string;
54
+ /** Its permalink, so the screen can link to where the form was. */
55
+ permalink: string;
56
+ /** Its title at the time, so a page since renamed still reads sensibly. */
57
+ title: string;
58
+ };
59
+ /** Who sent it. */
60
+ from: {
61
+ /** The name they gave. */
62
+ name: string;
63
+ /** The address they gave, which the message replies to. */
64
+ email: string;
65
+ };
66
+ /** What it is about. May be empty. */
67
+ subject: string;
68
+ /** The message itself, as plain text. */
69
+ message: string;
70
+ /**
71
+ * A salted hash of the address it came from, or `null` when the site could
72
+ * not tell. The same hash a comment keeps, so a run from one machine is
73
+ * visible across both.
74
+ */
75
+ addressHash: string | null;
76
+ }
77
+ /** A message on its way in, before it has been given an id. */
78
+ export type NewContactMessage = Omit<ContactMessage, 'id' | 'read'> & {
79
+ /** Whether it has been read. Defaults to no, which is what a new one is. */
80
+ read?: boolean | undefined;
81
+ };
82
+ /**
83
+ * The id a message received at this instant gets.
84
+ *
85
+ * The instant first, compacted so it is one filename-safe token, and then
86
+ * enough randomness that two messages in the same millisecond cannot collide.
87
+ */
88
+ export declare function contactMessageId(received: Date): string;
89
+ /** The absolute path of one message's file. */
90
+ export declare function contactMessageFile(dataDir: string, id: string): string;
91
+ /**
92
+ * Write a message, and hand back what was written.
93
+ *
94
+ * The id is minted here rather than by the caller, so the only thing that
95
+ * decides what a message is called is the thing that stores it.
96
+ */
97
+ export declare function addContactMessage(dataDir: string, message: NewContactMessage): Promise<ContactMessage>;
98
+ /**
99
+ * Every message the directory holds, newest first.
100
+ *
101
+ * A file that will not parse is skipped rather than thrown over: a directory
102
+ * somebody has been editing by hand should cost that message rather than take
103
+ * the Messages screen down, and the file is still there to be looked at.
104
+ */
105
+ export declare function listContactMessages(dataDir: string, options?: {
106
+ status?: ContactStatus | undefined;
107
+ }): ContactMessage[];
108
+ /** How many messages are waiting to be read, which is the number on the dashboard. */
109
+ export declare function countUnreadContactMessages(dataDir: string): number;
110
+ /** How many messages each list holds, for the tabs on the Messages screen. */
111
+ export declare function countContactMessagesByStatus(dataDir: string): Record<ContactStatus, number>;
112
+ /** One message, or `undefined` for an id nothing is filed under. */
113
+ export declare function readContactMessage(dataDir: string, id: string): ContactMessage | undefined;
114
+ /**
115
+ * Mark a message read, or unread again, and hand back what it now says.
116
+ *
117
+ * The file is re-read inside the write, so two admins with the screen open
118
+ * cannot make one of them undo the other's delete by writing back a message
119
+ * that is not there any more.
120
+ */
121
+ export declare function setContactMessageRead(dataDir: string, id: string, read: boolean): Promise<ContactMessage | undefined>;
122
+ /** Delete a message. `false` when there was nothing of that id to delete. */
123
+ export declare function deleteContactMessage(dataDir: string, id: string): Promise<boolean>;
124
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/contact/records.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,YAAY,CAAC;AAEhD,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,CAAC;AAEhD,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,EAAE,EAAE,MAAM,CAAC;IACX,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,MAAM,EAAE,aAAa,CAAC;IACtB,2DAA2D;IAC3D,IAAI,EAAE,OAAO,CAAC;IACd,gCAAgC;IAChC,IAAI,EAAE;QACJ,gBAAgB;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,mEAAmE;QACnE,SAAS,EAAE,MAAM,CAAC;QAClB,2EAA2E;QAC3E,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,mBAAmB;IACnB,IAAI,EAAE;QACJ,0BAA0B;QAC1B,IAAI,EAAE,MAAM,CAAC;QACb,2DAA2D;QAC3D,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG;IACpE,4EAA4E;IAC5E,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC5B,CAAC;AAWF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,IAAI,GAAG,MAAM,CAGvD;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAKtE;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,cAAc,CAAC,CAgBzB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;CAAO,GACnD,cAAc,EAAE,CAalB;AAED,sFAAsF;AACtF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,8EAA8E;AAC9E,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAI3F;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAO1F;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAmBrC;AAED,6EAA6E;AAC7E,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASxF"}
@@ -0,0 +1,218 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { readdirSync } from 'node:fs';
3
+ import { rm } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { readFileIfPresentSync, withFileLock, writeFileAtomically, writeFileAtomicallySync, } from '../files/atomic.js';
6
+ /**
7
+ * A contact message as the file that holds it.
8
+ *
9
+ * decision-9 makes the filesystem the source of truth for everything durable,
10
+ * and a message somebody typed into this site is exactly that: nothing else
11
+ * holds it, and no other server can be asked for it again. So it is written to
12
+ * disk **before** anything is emailed, which is the point — a provider that is
13
+ * down, a key that has expired, an address that bounces, and the message is
14
+ * still on the Messages screen tomorrow.
15
+ *
16
+ * Two things separate this from the comment files:
17
+ *
18
+ * - **It lives under `data/`, not under `content/`.** A message carries the
19
+ * sender's email address and was never meant to be published; `content/` is
20
+ * in git and is copied to the public site by an Eleventy build.
21
+ * - **There is no SQLite index.** A contact inbox is tens or hundreds of
22
+ * files, the screen reads all of them to sort them anyway, and an index
23
+ * would be a second thing to keep true for no query it makes faster. Files
24
+ * are truth here with nothing derived from them at all.
25
+ *
26
+ * One JSON file per message, named by its id, which starts with the instant it
27
+ * arrived — so `ls` is the inbox in order, and so no two messages can collide.
28
+ */
29
+ /** Where the messages live, relative to the data directory. */
30
+ export const CONTACT_DATA_DIRECTORY = 'contact';
31
+ /** The absolute path of one site's `data/contact`. */
32
+ export function contactDirectory(dataDir) {
33
+ return path.join(dataDir, CONTACT_DATA_DIRECTORY);
34
+ }
35
+ /**
36
+ * Ids a file may be named after: the instant, a dash and eight hex characters.
37
+ *
38
+ * Checked on the way in as well as on the way out, so an id off a form can
39
+ * never name a path — there is no separator, no dot and no letter outside the
40
+ * set in anything that matches.
41
+ */
42
+ const SAFE_ID = /^[0-9]{8}T[0-9]{9}Z-[0-9a-f]{8}$/;
43
+ /**
44
+ * The id a message received at this instant gets.
45
+ *
46
+ * The instant first, compacted so it is one filename-safe token, and then
47
+ * enough randomness that two messages in the same millisecond cannot collide.
48
+ */
49
+ export function contactMessageId(received) {
50
+ const stamp = received.toISOString().replace(/[-:.]/g, '');
51
+ return `${stamp}-${randomBytes(4).toString('hex')}`;
52
+ }
53
+ /** The absolute path of one message's file. */
54
+ export function contactMessageFile(dataDir, id) {
55
+ if (!SAFE_ID.test(id)) {
56
+ throw new Error(`"${id}" is not an id a contact message file can be named after.`);
57
+ }
58
+ return path.join(contactDirectory(dataDir), `${id}.json`);
59
+ }
60
+ /**
61
+ * Write a message, and hand back what was written.
62
+ *
63
+ * The id is minted here rather than by the caller, so the only thing that
64
+ * decides what a message is called is the thing that stores it.
65
+ */
66
+ export async function addContactMessage(dataDir, message) {
67
+ const stored = {
68
+ ...message,
69
+ id: contactMessageId(new Date(message.received)),
70
+ read: message.read ?? false,
71
+ };
72
+ await writeFileAtomically(contactMessageFile(dataDir, stored.id), `${JSON.stringify(stored, null, 2)}\n`,
73
+ // The sender's address is in here, so the file is the site's own to read
74
+ // and nobody else's, exactly like the credentials beside it.
75
+ { mode: 0o600 });
76
+ return stored;
77
+ }
78
+ /**
79
+ * Every message the directory holds, newest first.
80
+ *
81
+ * A file that will not parse is skipped rather than thrown over: a directory
82
+ * somebody has been editing by hand should cost that message rather than take
83
+ * the Messages screen down, and the file is still there to be looked at.
84
+ */
85
+ export function listContactMessages(dataDir, options = {}) {
86
+ const messages = [];
87
+ for (const id of contactMessageIds(dataDir)) {
88
+ const message = readContactMessage(dataDir, id);
89
+ if (message === undefined)
90
+ continue;
91
+ if (options.status !== undefined && message.status !== options.status)
92
+ continue;
93
+ messages.push(message);
94
+ }
95
+ // By id, which begins with the instant, so the order is chronological
96
+ // without a date having to be parsed for every comparison.
97
+ return messages.sort((one, other) => (one.id < other.id ? 1 : one.id > other.id ? -1 : 0));
98
+ }
99
+ /** How many messages are waiting to be read, which is the number on the dashboard. */
100
+ export function countUnreadContactMessages(dataDir) {
101
+ return listContactMessages(dataDir).filter((message) => !message.read).length;
102
+ }
103
+ /** How many messages each list holds, for the tabs on the Messages screen. */
104
+ export function countContactMessagesByStatus(dataDir) {
105
+ const counts = { received: 0, spam: 0 };
106
+ for (const message of listContactMessages(dataDir))
107
+ counts[message.status] += 1;
108
+ return counts;
109
+ }
110
+ /** One message, or `undefined` for an id nothing is filed under. */
111
+ export function readContactMessage(dataDir, id) {
112
+ if (!SAFE_ID.test(id))
113
+ return undefined;
114
+ const raw = readFileIfPresentSync(contactMessageFile(dataDir, id));
115
+ if (raw === undefined)
116
+ return undefined;
117
+ return messageFrom(raw, id);
118
+ }
119
+ /**
120
+ * Mark a message read, or unread again, and hand back what it now says.
121
+ *
122
+ * The file is re-read inside the write, so two admins with the screen open
123
+ * cannot make one of them undo the other's delete by writing back a message
124
+ * that is not there any more.
125
+ */
126
+ export async function setContactMessageRead(dataDir, id, read) {
127
+ if (!SAFE_ID.test(id))
128
+ return undefined;
129
+ const file = contactMessageFile(dataDir, id);
130
+ // The read, the change and the write inside one lock, with the writer that
131
+ // does not queue: a message deleted while this screen was open must not be
132
+ // written back by a Mark read that arrived a moment later.
133
+ return await withFileLock(file, () => {
134
+ const current = readFileIfPresentSync(file);
135
+ if (current === undefined)
136
+ return undefined;
137
+ const message = messageFrom(current, id);
138
+ if (message === undefined)
139
+ return undefined;
140
+ const updated = { ...message, read };
141
+ writeFileAtomicallySync(file, `${JSON.stringify(updated, null, 2)}\n`, { mode: 0o600 });
142
+ return updated;
143
+ });
144
+ }
145
+ /** Delete a message. `false` when there was nothing of that id to delete. */
146
+ export async function deleteContactMessage(dataDir, id) {
147
+ if (!SAFE_ID.test(id))
148
+ return false;
149
+ const file = contactMessageFile(dataDir, id);
150
+ return await withFileLock(file, async () => {
151
+ if (readFileIfPresentSync(file) === undefined)
152
+ return false;
153
+ await rm(file, { force: true });
154
+ return true;
155
+ });
156
+ }
157
+ /** Every id the directory holds, unsorted. A missing directory is no messages. */
158
+ function contactMessageIds(dataDir) {
159
+ let entries;
160
+ try {
161
+ entries = readdirSync(contactDirectory(dataDir));
162
+ }
163
+ catch {
164
+ return [];
165
+ }
166
+ return entries
167
+ .filter((name) => name.endsWith('.json'))
168
+ .map((name) => name.slice(0, -'.json'.length))
169
+ .filter((id) => SAFE_ID.test(id));
170
+ }
171
+ /**
172
+ * One file's bytes as the message it names, or `undefined` when they are not
173
+ * one.
174
+ *
175
+ * Read key by key and tolerantly, like every other file this CMS treats as
176
+ * truth: a message missing a field it should have is shown with that field
177
+ * empty rather than taking the screen down.
178
+ */
179
+ function messageFrom(source, id) {
180
+ let parsed;
181
+ try {
182
+ parsed = JSON.parse(source);
183
+ }
184
+ catch {
185
+ return undefined;
186
+ }
187
+ if (typeof parsed !== 'object' || parsed === null)
188
+ return undefined;
189
+ const file = parsed;
190
+ const page = record(file['page']);
191
+ const from = record(file['from']);
192
+ const status = file['status'];
193
+ const addressHash = file['addressHash'];
194
+ return {
195
+ id,
196
+ received: text(file['received']),
197
+ status: status === 'spam' ? 'spam' : 'received',
198
+ read: file['read'] === true,
199
+ page: {
200
+ slug: text(page['slug']),
201
+ permalink: text(page['permalink']),
202
+ title: text(page['title']),
203
+ },
204
+ from: { name: text(from['name']), email: text(from['email']) },
205
+ subject: text(file['subject']),
206
+ message: text(file['message']),
207
+ addressHash: typeof addressHash === 'string' && addressHash !== '' ? addressHash : null,
208
+ };
209
+ }
210
+ /** One value as a string, or the empty one. */
211
+ function text(value) {
212
+ return typeof value === 'string' ? value : '';
213
+ }
214
+ /** One value as an object, or an empty one. */
215
+ function record(value) {
216
+ return typeof value === 'object' && value !== null ? value : {};
217
+ }
218
+ //# sourceMappingURL=records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/contact/records.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { Hono } from 'hono';
2
+ import type { GeekityEnv } from '../env.ts';
3
+ /**
4
+ * The one public endpoint the contact form adds: where the form on a page
5
+ * posts to.
6
+ *
7
+ * A single path rather than one per page because the page is a field of the
8
+ * form. That keeps it out of the way of every permalink a site might mint — a
9
+ * path under `/_geekity/` is the CMS's own, like the comment endpoint beside
10
+ * it — and means the route table does not grow with the site.
11
+ *
12
+ * Everything it answers is either a redirect or the page rendered again with
13
+ * the form still filled in. There is no JSON, no fragment and no script.
14
+ */
15
+ /** Register the contact endpoint. Mounted by the public site. */
16
+ export declare function mountContact(app: Hono<GeekityEnv>): void;
17
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/contact/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AAK1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAoB5C;;;;;;;;;;;GAWG;AAEH,iEAAiE;AACjE,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CA0FxD"}