@geekity/cms 0.30.0 → 0.32.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 (1637) hide show
  1. package/README.md +26 -4266
  2. package/admin/components/badge.njk +4 -0
  3. package/admin/components/fields.njk +41 -24
  4. package/admin/components/job-status.njk +45 -0
  5. package/admin/components/setting-fields.njk +38 -0
  6. package/admin/layouts/settings-page.njk +16 -0
  7. package/admin/pages/comments/all.njk +1 -2
  8. package/admin/pages/documents/editor.njk +17 -15
  9. package/admin/pages/documents/syndication.njk +4 -4
  10. package/admin/pages/federation/followers.njk +56 -6
  11. package/admin/pages/federation/settings.njk +1 -1
  12. package/admin/pages/media/library.njk +52 -3
  13. package/admin/pages/navigation/menus.njk +2 -2
  14. package/admin/pages/plugins/screen.njk +5 -46
  15. package/admin/pages/settings/discussion.njk +76 -47
  16. package/admin/pages/settings/email.njk +75 -47
  17. package/admin/pages/settings/general.njk +15 -15
  18. package/admin/pages/settings/permalinks.njk +8 -8
  19. package/admin/pages/settings/privacy.njk +6 -6
  20. package/admin/pages/settings/reading.njk +36 -36
  21. package/admin/pages/tools/dev-mode.njk +29 -3
  22. package/admin/pages/tools/export.njk +89 -0
  23. package/admin/pages/tools/maintenance.njk +51 -0
  24. package/admin/pages/users/apps.njk +2 -2
  25. package/admin/pages/users/edit.njk +3 -3
  26. package/admin/pages/users/new.njk +2 -2
  27. package/admin/static/admin.css +80 -0
  28. package/dist/access-log.d.ts +2 -10
  29. package/dist/access-log.d.ts.map +1 -1
  30. package/dist/access-log.js +1 -1
  31. package/dist/access-log.js.map +1 -1
  32. package/dist/{web → admin}/admin-bar.d.ts +3 -2
  33. package/dist/admin/admin-bar.d.ts.map +1 -0
  34. package/dist/{web → admin}/admin-bar.js +20 -17
  35. package/dist/admin/admin-bar.js.map +1 -0
  36. package/dist/{indieauth → admin}/app-activity.d.ts +2 -2
  37. package/dist/admin/app-activity.d.ts.map +1 -0
  38. package/dist/{indieauth → admin}/app-activity.js +6 -7
  39. package/dist/admin/app-activity.js.map +1 -0
  40. package/dist/admin/appearance.d.ts +4 -8
  41. package/dist/admin/appearance.d.ts.map +1 -1
  42. package/dist/admin/appearance.js +19 -20
  43. package/dist/admin/appearance.js.map +1 -1
  44. package/dist/admin/assets.d.ts +1 -8
  45. package/dist/admin/assets.d.ts.map +1 -1
  46. package/dist/admin/assets.js +2 -9
  47. package/dist/admin/assets.js.map +1 -1
  48. package/dist/admin/comments.d.ts +2 -2
  49. package/dist/admin/comments.d.ts.map +1 -1
  50. package/dist/admin/comments.js +17 -19
  51. package/dist/admin/comments.js.map +1 -1
  52. package/dist/admin/confirm-password.d.ts +4 -0
  53. package/dist/admin/confirm-password.d.ts.map +1 -0
  54. package/dist/admin/confirm-password.js +11 -0
  55. package/dist/admin/confirm-password.js.map +1 -0
  56. package/dist/{indieauth → admin}/connected-apps.d.ts +2 -2
  57. package/dist/admin/connected-apps.d.ts.map +1 -0
  58. package/dist/{indieauth → admin}/connected-apps.js +33 -39
  59. package/dist/admin/connected-apps.js.map +1 -0
  60. package/dist/admin/credentials.js +1 -1
  61. package/dist/admin/credentials.js.map +1 -1
  62. package/dist/admin/dev-mode.d.ts +1 -1
  63. package/dist/admin/dev-mode.d.ts.map +1 -1
  64. package/dist/admin/dev-mode.js +35 -10
  65. package/dist/admin/dev-mode.js.map +1 -1
  66. package/dist/admin/documents.d.ts +16 -301
  67. package/dist/admin/documents.d.ts.map +1 -1
  68. package/dist/admin/documents.js +94 -1040
  69. package/dist/admin/documents.js.map +1 -1
  70. package/dist/admin/editor-actions.d.ts +3 -2
  71. package/dist/admin/editor-actions.d.ts.map +1 -1
  72. package/dist/admin/editor-actions.js +8 -9
  73. package/dist/admin/editor-actions.js.map +1 -1
  74. package/dist/admin/editor-fields.d.ts +45 -0
  75. package/dist/admin/editor-fields.d.ts.map +1 -0
  76. package/dist/admin/editor-fields.js +71 -0
  77. package/dist/admin/editor-fields.js.map +1 -0
  78. package/dist/admin/editor-layout.d.ts +2 -10
  79. package/dist/admin/editor-layout.d.ts.map +1 -1
  80. package/dist/admin/editor-layout.js +2 -2
  81. package/dist/admin/editor-layout.js.map +1 -1
  82. package/dist/admin/export.d.ts +10 -0
  83. package/dist/admin/export.d.ts.map +1 -0
  84. package/dist/admin/export.js +72 -0
  85. package/dist/admin/export.js.map +1 -0
  86. package/dist/admin/federation.d.ts +6 -17
  87. package/dist/admin/federation.d.ts.map +1 -1
  88. package/dist/admin/federation.js +49 -104
  89. package/dist/admin/federation.js.map +1 -1
  90. package/dist/admin/flash.d.ts +2 -2
  91. package/dist/admin/flash.d.ts.map +1 -1
  92. package/dist/admin/headers.d.ts +1 -1
  93. package/dist/admin/headers.d.ts.map +1 -1
  94. package/dist/admin/headers.js +1 -1
  95. package/dist/admin/headers.js.map +1 -1
  96. package/dist/admin/index.d.ts +36 -21
  97. package/dist/admin/index.d.ts.map +1 -1
  98. package/dist/admin/index.js +26 -14
  99. package/dist/admin/index.js.map +1 -1
  100. package/dist/{indieauth/consent.d.ts → admin/indieauth-consent.d.ts} +4 -4
  101. package/dist/admin/indieauth-consent.d.ts.map +1 -0
  102. package/dist/{indieauth/consent.js → admin/indieauth-consent.js} +20 -23
  103. package/dist/admin/indieauth-consent.js.map +1 -0
  104. package/dist/admin/job-status.d.ts +25 -0
  105. package/dist/admin/job-status.d.ts.map +1 -0
  106. package/dist/admin/job-status.js +53 -0
  107. package/dist/admin/job-status.js.map +1 -0
  108. package/dist/admin/legacy-migrations.d.ts +131 -0
  109. package/dist/admin/legacy-migrations.d.ts.map +1 -0
  110. package/dist/admin/legacy-migrations.js +236 -0
  111. package/dist/admin/legacy-migrations.js.map +1 -0
  112. package/dist/admin/maintenance.d.ts +9 -0
  113. package/dist/admin/maintenance.d.ts.map +1 -0
  114. package/dist/admin/maintenance.js +42 -0
  115. package/dist/admin/maintenance.js.map +1 -0
  116. package/dist/admin/media.d.ts +8 -109
  117. package/dist/admin/media.d.ts.map +1 -1
  118. package/dist/admin/media.js +35 -188
  119. package/dist/admin/media.js.map +1 -1
  120. package/dist/admin/menu.d.ts.map +1 -1
  121. package/dist/admin/menu.js +11 -7
  122. package/dist/admin/menu.js.map +1 -1
  123. package/dist/admin/messages.d.ts +1 -1
  124. package/dist/admin/messages.d.ts.map +1 -1
  125. package/dist/admin/messages.js +11 -14
  126. package/dist/admin/messages.js.map +1 -1
  127. package/dist/admin/navigation.d.ts +3 -3
  128. package/dist/admin/navigation.d.ts.map +1 -1
  129. package/dist/admin/navigation.js +34 -33
  130. package/dist/admin/navigation.js.map +1 -1
  131. package/dist/admin/personal-data.d.ts +1 -1
  132. package/dist/admin/personal-data.d.ts.map +1 -1
  133. package/dist/admin/personal-data.js +3 -6
  134. package/dist/admin/personal-data.js.map +1 -1
  135. package/dist/admin/plugins.d.ts +1 -1
  136. package/dist/admin/plugins.d.ts.map +1 -1
  137. package/dist/admin/plugins.js +28 -79
  138. package/dist/admin/plugins.js.map +1 -1
  139. package/dist/admin/preview.d.ts +2 -1
  140. package/dist/admin/preview.d.ts.map +1 -1
  141. package/dist/admin/preview.js +31 -36
  142. package/dist/admin/preview.js.map +1 -1
  143. package/dist/admin/recovery.d.ts +3 -3
  144. package/dist/admin/recovery.d.ts.map +1 -1
  145. package/dist/admin/recovery.js +12 -14
  146. package/dist/admin/recovery.js.map +1 -1
  147. package/dist/admin/routes.d.ts +1 -3
  148. package/dist/admin/routes.d.ts.map +1 -1
  149. package/dist/admin/routes.js +34 -29
  150. package/dist/admin/routes.js.map +1 -1
  151. package/dist/admin/setting-overrides.d.ts +4 -0
  152. package/dist/admin/setting-overrides.d.ts.map +1 -0
  153. package/dist/admin/setting-overrides.js +24 -0
  154. package/dist/admin/setting-overrides.js.map +1 -0
  155. package/dist/admin/settings-discussion.d.ts +6 -4
  156. package/dist/admin/settings-discussion.d.ts.map +1 -1
  157. package/dist/admin/settings-discussion.js +79 -46
  158. package/dist/admin/settings-discussion.js.map +1 -1
  159. package/dist/admin/settings-email.d.ts +12 -9
  160. package/dist/admin/settings-email.d.ts.map +1 -1
  161. package/dist/admin/settings-email.js +94 -80
  162. package/dist/admin/settings-email.js.map +1 -1
  163. package/dist/admin/settings-federation.d.ts +0 -7
  164. package/dist/admin/settings-federation.d.ts.map +1 -1
  165. package/dist/admin/settings-federation.js +0 -36
  166. package/dist/admin/settings-federation.js.map +1 -1
  167. package/dist/admin/settings-fields.d.ts +32 -0
  168. package/dist/admin/settings-fields.d.ts.map +1 -0
  169. package/dist/admin/settings-fields.js +368 -0
  170. package/dist/admin/settings-fields.js.map +1 -0
  171. package/dist/admin/settings-general.d.ts.map +1 -1
  172. package/dist/admin/settings-general.js +17 -23
  173. package/dist/admin/settings-general.js.map +1 -1
  174. package/dist/admin/settings-page.d.ts +5 -32
  175. package/dist/admin/settings-page.d.ts.map +1 -1
  176. package/dist/admin/settings-page.js +39 -55
  177. package/dist/admin/settings-page.js.map +1 -1
  178. package/dist/admin/settings-pages.d.ts +1 -1
  179. package/dist/admin/settings-pages.d.ts.map +1 -1
  180. package/dist/admin/settings-permalinks.js +3 -3
  181. package/dist/admin/settings-permalinks.js.map +1 -1
  182. package/dist/admin/settings-privacy.js +1 -1
  183. package/dist/admin/settings-privacy.js.map +1 -1
  184. package/dist/admin/settings-reading.d.ts +2 -2
  185. package/dist/admin/settings-reading.d.ts.map +1 -1
  186. package/dist/admin/settings-reading.js +17 -16
  187. package/dist/admin/settings-reading.js.map +1 -1
  188. package/dist/admin/settings-renderer.d.ts +32 -0
  189. package/dist/admin/settings-renderer.d.ts.map +1 -0
  190. package/dist/admin/settings-renderer.js +79 -0
  191. package/dist/admin/settings-renderer.js.map +1 -0
  192. package/dist/admin/settings.d.ts +0 -579
  193. package/dist/admin/settings.d.ts.map +1 -1
  194. package/dist/admin/settings.js +1 -1155
  195. package/dist/admin/settings.js.map +1 -1
  196. package/dist/admin/site-setting-edit.d.ts +4 -0
  197. package/dist/admin/site-setting-edit.d.ts.map +1 -0
  198. package/dist/admin/site-setting-edit.js +20 -0
  199. package/dist/admin/site-setting-edit.js.map +1 -0
  200. package/dist/admin/syndication-targets.d.ts +1 -1
  201. package/dist/admin/syndication-targets.d.ts.map +1 -1
  202. package/dist/admin/syndication-targets.js +11 -15
  203. package/dist/admin/syndication-targets.js.map +1 -1
  204. package/dist/admin/taxonomy.d.ts +2 -2
  205. package/dist/admin/taxonomy.d.ts.map +1 -1
  206. package/dist/admin/taxonomy.js +17 -29
  207. package/dist/admin/taxonomy.js.map +1 -1
  208. package/dist/admin/templates.d.ts +2 -0
  209. package/dist/admin/templates.d.ts.map +1 -1
  210. package/dist/admin/templates.js +3 -1
  211. package/dist/admin/templates.js.map +1 -1
  212. package/dist/admin/tools.d.ts +4 -4
  213. package/dist/admin/tools.d.ts.map +1 -1
  214. package/dist/admin/tools.js +3 -6
  215. package/dist/admin/tools.js.map +1 -1
  216. package/dist/admin/uploads.d.ts +1 -106
  217. package/dist/admin/uploads.d.ts.map +1 -1
  218. package/dist/admin/uploads.js +2 -191
  219. package/dist/admin/uploads.js.map +1 -1
  220. package/dist/admin/users.d.ts +20 -15
  221. package/dist/admin/users.d.ts.map +1 -1
  222. package/dist/admin/users.js +84 -81
  223. package/dist/admin/users.js.map +1 -1
  224. package/dist/avatars/avatars.d.ts +2 -4
  225. package/dist/avatars/avatars.d.ts.map +1 -1
  226. package/dist/avatars/avatars.js +15 -19
  227. package/dist/avatars/avatars.js.map +1 -1
  228. package/dist/avatars/index.d.ts +1 -2
  229. package/dist/avatars/index.d.ts.map +1 -1
  230. package/dist/avatars/index.js +1 -2
  231. package/dist/avatars/index.js.map +1 -1
  232. package/dist/cache.d.ts +4 -4
  233. package/dist/cache.d.ts.map +1 -1
  234. package/dist/cache.js +13 -9
  235. package/dist/cache.js.map +1 -1
  236. package/dist/cli.d.ts +2 -33
  237. package/dist/cli.d.ts.map +1 -1
  238. package/dist/cli.js +729 -344
  239. package/dist/cli.js.map +1 -1
  240. package/dist/cms/app.d.ts +11 -0
  241. package/dist/cms/app.d.ts.map +1 -0
  242. package/dist/cms/app.js +126 -0
  243. package/dist/cms/app.js.map +1 -0
  244. package/dist/cms/boot.d.ts +26 -0
  245. package/dist/cms/boot.d.ts.map +1 -0
  246. package/dist/cms/boot.js +128 -0
  247. package/dist/cms/boot.js.map +1 -0
  248. package/dist/cms/create.d.ts +272 -0
  249. package/dist/cms/create.d.ts.map +1 -0
  250. package/dist/cms/create.js +181 -0
  251. package/dist/cms/create.js.map +1 -0
  252. package/dist/cms/lifecycle.d.ts +11 -0
  253. package/dist/cms/lifecycle.d.ts.map +1 -0
  254. package/dist/cms/lifecycle.js +15 -0
  255. package/dist/cms/lifecycle.js.map +1 -0
  256. package/dist/{web/errors.d.ts → cms/server-error.d.ts} +2 -8
  257. package/dist/cms/server-error.d.ts.map +1 -0
  258. package/dist/{web/errors.js → cms/server-error.js} +4 -14
  259. package/dist/cms/server-error.js.map +1 -0
  260. package/dist/cms/services.d.ts +57 -0
  261. package/dist/cms/services.d.ts.map +1 -0
  262. package/dist/cms/services.js +410 -0
  263. package/dist/cms/services.js.map +1 -0
  264. package/dist/cms/site-records.d.ts +4 -0
  265. package/dist/cms/site-records.d.ts.map +1 -0
  266. package/dist/cms/site-records.js +13 -0
  267. package/dist/cms/site-records.js.map +1 -0
  268. package/dist/comments/akismet.d.ts +14 -47
  269. package/dist/comments/akismet.d.ts.map +1 -1
  270. package/dist/comments/akismet.js +30 -71
  271. package/dist/comments/akismet.js.map +1 -1
  272. package/dist/comments/conversation.d.ts +340 -0
  273. package/dist/comments/conversation.d.ts.map +1 -0
  274. package/dist/comments/conversation.js +51 -0
  275. package/dist/comments/conversation.js.map +1 -0
  276. package/dist/comments/form.d.ts +2 -2
  277. package/dist/comments/form.d.ts.map +1 -1
  278. package/dist/comments/form.js +2 -2
  279. package/dist/comments/form.js.map +1 -1
  280. package/dist/comments/index.d.ts +15 -9
  281. package/dist/comments/index.d.ts.map +1 -1
  282. package/dist/comments/index.js +9 -6
  283. package/dist/comments/index.js.map +1 -1
  284. package/dist/comments/intake.d.ts +147 -0
  285. package/dist/comments/intake.d.ts.map +1 -0
  286. package/dist/comments/intake.js +161 -0
  287. package/dist/comments/intake.js.map +1 -0
  288. package/dist/comments/markdown.d.ts.map +1 -1
  289. package/dist/comments/markdown.js +2 -9
  290. package/dist/comments/markdown.js.map +1 -1
  291. package/dist/comments/migrations.d.ts +45 -0
  292. package/dist/comments/migrations.d.ts.map +1 -0
  293. package/dist/comments/migrations.js +194 -0
  294. package/dist/comments/migrations.js.map +1 -0
  295. package/dist/comments/moderate.d.ts +2 -2
  296. package/dist/comments/moderate.d.ts.map +1 -1
  297. package/dist/comments/moderate.js +3 -13
  298. package/dist/comments/moderate.js.map +1 -1
  299. package/dist/comments/page-form.d.ts +52 -0
  300. package/dist/comments/page-form.d.ts.map +1 -0
  301. package/dist/comments/page-form.js +62 -0
  302. package/dist/comments/page-form.js.map +1 -0
  303. package/dist/comments/policy.d.ts +24 -35
  304. package/dist/comments/policy.d.ts.map +1 -1
  305. package/dist/comments/policy.js +16 -38
  306. package/dist/comments/policy.js.map +1 -1
  307. package/dist/comments/records.d.ts +17 -195
  308. package/dist/comments/records.d.ts.map +1 -1
  309. package/dist/comments/records.js +24 -394
  310. package/dist/comments/records.js.map +1 -1
  311. package/dist/comments/reply-notices.d.ts +10 -11
  312. package/dist/comments/reply-notices.d.ts.map +1 -1
  313. package/dist/comments/reply-notices.js +32 -20
  314. package/dist/comments/reply-notices.js.map +1 -1
  315. package/dist/comments/reply-post.d.ts +2 -2
  316. package/dist/comments/reply-post.d.ts.map +1 -1
  317. package/dist/comments/reply-post.js +8 -7
  318. package/dist/comments/reply-post.js.map +1 -1
  319. package/dist/comments/routes.d.ts +3 -3
  320. package/dist/comments/routes.d.ts.map +1 -1
  321. package/dist/comments/routes.js +19 -31
  322. package/dist/comments/routes.js.map +1 -1
  323. package/dist/comments/submission.d.ts +5 -102
  324. package/dist/comments/submission.d.ts.map +1 -1
  325. package/dist/comments/submission.js +5 -22
  326. package/dist/comments/submission.js.map +1 -1
  327. package/dist/comments/viewer.d.ts +1 -25
  328. package/dist/comments/viewer.d.ts.map +1 -1
  329. package/dist/comments/viewer.js +1 -44
  330. package/dist/comments/viewer.js.map +1 -1
  331. package/dist/config-loader.d.ts +35 -0
  332. package/dist/config-loader.d.ts.map +1 -0
  333. package/dist/config-loader.js +117 -0
  334. package/dist/config-loader.js.map +1 -0
  335. package/dist/config.d.ts +99 -51
  336. package/dist/config.d.ts.map +1 -1
  337. package/dist/config.js +137 -181
  338. package/dist/config.js.map +1 -1
  339. package/dist/contact/delivery.d.ts +3 -5
  340. package/dist/contact/delivery.d.ts.map +1 -1
  341. package/dist/contact/delivery.js +5 -14
  342. package/dist/contact/delivery.js.map +1 -1
  343. package/dist/contact/form.d.ts +2 -4
  344. package/dist/contact/form.d.ts.map +1 -1
  345. package/dist/contact/form.js +3 -4
  346. package/dist/contact/form.js.map +1 -1
  347. package/dist/contact/index.d.ts +3 -3
  348. package/dist/contact/index.d.ts.map +1 -1
  349. package/dist/contact/index.js +3 -3
  350. package/dist/contact/index.js.map +1 -1
  351. package/dist/contact/records.d.ts +0 -2
  352. package/dist/contact/records.d.ts.map +1 -1
  353. package/dist/contact/records.js +11 -16
  354. package/dist/contact/records.js.map +1 -1
  355. package/dist/contact/submission.d.ts +2 -2
  356. package/dist/contact/submission.d.ts.map +1 -1
  357. package/dist/contact/submission.js +7 -16
  358. package/dist/contact/submission.js.map +1 -1
  359. package/dist/{images → content}/alt-text.d.ts +1 -1
  360. package/dist/content/alt-text.d.ts.map +1 -0
  361. package/dist/{images → content}/alt-text.js +3 -5
  362. package/dist/content/alt-text.js.map +1 -0
  363. package/dist/content/authors.d.ts +79 -0
  364. package/dist/content/authors.d.ts.map +1 -0
  365. package/dist/content/authors.js +106 -0
  366. package/dist/content/authors.js.map +1 -0
  367. package/dist/content/citation.d.ts +4 -0
  368. package/dist/content/citation.d.ts.map +1 -1
  369. package/dist/content/citation.js +3 -0
  370. package/dist/content/citation.js.map +1 -1
  371. package/dist/content/comments.d.ts +3 -0
  372. package/dist/content/comments.d.ts.map +1 -0
  373. package/dist/content/comments.js +3 -0
  374. package/dist/content/comments.js.map +1 -0
  375. package/dist/content/contact.d.ts +3 -0
  376. package/dist/content/contact.d.ts.map +1 -0
  377. package/dist/content/contact.js +3 -0
  378. package/dist/content/contact.js.map +1 -0
  379. package/dist/content/document-form.d.ts +197 -0
  380. package/dist/content/document-form.d.ts.map +1 -0
  381. package/dist/content/document-form.js +201 -0
  382. package/dist/content/document-form.js.map +1 -0
  383. package/dist/content/document-kind.d.ts +55 -0
  384. package/dist/content/document-kind.d.ts.map +1 -0
  385. package/dist/content/document-kind.js +30 -0
  386. package/dist/content/document-kind.js.map +1 -0
  387. package/dist/{admin/enclosure-field.d.ts → content/enclosure-form.d.ts} +14 -34
  388. package/dist/content/enclosure-form.d.ts.map +1 -0
  389. package/dist/{admin/enclosure-field.js → content/enclosure-form.js} +43 -81
  390. package/dist/content/enclosure-form.js.map +1 -0
  391. package/dist/content/enclosure.d.ts.map +1 -1
  392. package/dist/content/enclosure.js +3 -4
  393. package/dist/content/enclosure.js.map +1 -1
  394. package/dist/{admin/event-field.d.ts → content/event-form.d.ts} +6 -5
  395. package/dist/content/event-form.d.ts.map +1 -0
  396. package/dist/{admin/event-field.js → content/event-form.js} +45 -12
  397. package/dist/content/event-form.js.map +1 -0
  398. package/dist/content/excerpt.d.ts +26 -0
  399. package/dist/content/excerpt.d.ts.map +1 -0
  400. package/dist/content/excerpt.js +46 -0
  401. package/dist/content/excerpt.js.map +1 -0
  402. package/dist/content/form-error.d.ts +10 -0
  403. package/dist/content/form-error.d.ts.map +1 -0
  404. package/dist/content/form-error.js +2 -0
  405. package/dist/content/form-error.js.map +1 -0
  406. package/dist/{web → content}/guids.d.ts +12 -5
  407. package/dist/content/guids.d.ts.map +1 -0
  408. package/dist/{web → content}/guids.js +28 -5
  409. package/dist/content/guids.js.map +1 -0
  410. package/dist/content/image-tags.d.ts +25 -0
  411. package/dist/content/image-tags.d.ts.map +1 -0
  412. package/dist/content/image-tags.js +55 -0
  413. package/dist/content/image-tags.js.map +1 -0
  414. package/dist/content/index.d.ts +11 -6
  415. package/dist/content/index.d.ts.map +1 -1
  416. package/dist/content/index.js +6 -3
  417. package/dist/content/index.js.map +1 -1
  418. package/dist/content/kept-properties.d.ts +0 -1
  419. package/dist/content/kept-properties.d.ts.map +1 -1
  420. package/dist/content/kept-properties.js +2 -2
  421. package/dist/content/kept-properties.js.map +1 -1
  422. package/dist/content/language.d.ts +19 -0
  423. package/dist/content/language.d.ts.map +1 -0
  424. package/dist/content/language.js +19 -0
  425. package/dist/content/language.js.map +1 -0
  426. package/dist/content/license.d.ts.map +1 -0
  427. package/dist/content/license.js.map +1 -0
  428. package/dist/{web → content}/locale.d.ts +0 -6
  429. package/dist/content/locale.d.ts.map +1 -0
  430. package/dist/{web → content}/locale.js +1 -6
  431. package/dist/content/locale.js.map +1 -0
  432. package/dist/content/location-form.d.ts +17 -0
  433. package/dist/content/location-form.d.ts.map +1 -0
  434. package/dist/{admin/location-field.js → content/location-form.js} +2 -23
  435. package/dist/content/location-form.js.map +1 -0
  436. package/dist/content/location.d.ts.map +1 -1
  437. package/dist/content/location.js +1 -3
  438. package/dist/content/location.js.map +1 -1
  439. package/dist/content/locations.d.ts +0 -1
  440. package/dist/content/locations.d.ts.map +1 -1
  441. package/dist/content/locations.js +2 -2
  442. package/dist/content/locations.js.map +1 -1
  443. package/dist/content/markdown-links.d.ts +11 -0
  444. package/dist/content/markdown-links.d.ts.map +1 -0
  445. package/dist/content/markdown-links.js +223 -0
  446. package/dist/content/markdown-links.js.map +1 -0
  447. package/dist/content/markdown.d.ts +2 -1
  448. package/dist/content/markdown.d.ts.map +1 -1
  449. package/dist/content/markdown.js +1 -1
  450. package/dist/content/markdown.js.map +1 -1
  451. package/dist/content/media.d.ts +7 -0
  452. package/dist/content/media.d.ts.map +1 -1
  453. package/dist/content/media.js +12 -1
  454. package/dist/content/media.js.map +1 -1
  455. package/dist/content/metadata/strip-job.d.ts +16 -0
  456. package/dist/content/metadata/strip-job.d.ts.map +1 -0
  457. package/dist/content/metadata/strip-job.js +24 -0
  458. package/dist/content/metadata/strip-job.js.map +1 -0
  459. package/dist/content/metadata/sweep.d.ts +13 -1
  460. package/dist/content/metadata/sweep.d.ts.map +1 -1
  461. package/dist/content/metadata/sweep.js +47 -25
  462. package/dist/content/metadata/sweep.js.map +1 -1
  463. package/dist/content/migrated.js +1 -1
  464. package/dist/content/migrated.js.map +1 -1
  465. package/dist/content/parser.d.ts.map +1 -1
  466. package/dist/content/parser.js +1 -12
  467. package/dist/content/parser.js.map +1 -1
  468. package/dist/content/permalink-file.d.ts +2 -1
  469. package/dist/content/permalink-file.d.ts.map +1 -1
  470. package/dist/content/permalink-file.js +3 -3
  471. package/dist/content/permalink-file.js.map +1 -1
  472. package/dist/content/permalinks.d.ts +34 -0
  473. package/dist/content/permalinks.d.ts.map +1 -0
  474. package/dist/{web/documents.js → content/permalinks.js} +6 -56
  475. package/dist/content/permalinks.js.map +1 -0
  476. package/dist/content/photo-form.d.ts +29 -0
  477. package/dist/content/photo-form.d.ts.map +1 -0
  478. package/dist/{admin/photo-field.js → content/photo-form.js} +43 -40
  479. package/dist/content/photo-form.js.map +1 -0
  480. package/dist/content/photo.d.ts +2 -1
  481. package/dist/content/photo.d.ts.map +1 -1
  482. package/dist/content/photo.js +2 -2
  483. package/dist/content/photo.js.map +1 -1
  484. package/dist/content/post-fields.d.ts +40 -0
  485. package/dist/content/post-fields.d.ts.map +1 -0
  486. package/dist/content/post-fields.js +252 -0
  487. package/dist/content/post-fields.js.map +1 -0
  488. package/dist/content/read-form.d.ts +31 -0
  489. package/dist/content/read-form.d.ts.map +1 -0
  490. package/dist/content/read-form.js +157 -0
  491. package/dist/content/read-form.js.map +1 -0
  492. package/dist/content/read.d.ts +9 -2
  493. package/dist/content/read.d.ts.map +1 -1
  494. package/dist/content/read.js +28 -13
  495. package/dist/content/read.js.map +1 -1
  496. package/dist/content/redirects.d.ts +84 -0
  497. package/dist/content/redirects.d.ts.map +1 -0
  498. package/dist/content/redirects.js +234 -0
  499. package/dist/content/redirects.js.map +1 -0
  500. package/dist/content/sanitize.d.ts +13 -0
  501. package/dist/content/sanitize.d.ts.map +1 -0
  502. package/dist/{web → content}/sanitize.js +4 -71
  503. package/dist/content/sanitize.js.map +1 -0
  504. package/dist/content/save.d.ts +5 -8
  505. package/dist/content/save.d.ts.map +1 -1
  506. package/dist/content/save.js +5 -26
  507. package/dist/content/save.js.map +1 -1
  508. package/dist/content/schedule.d.ts +2 -2
  509. package/dist/content/schedule.d.ts.map +1 -1
  510. package/dist/content/schedule.js +1 -3
  511. package/dist/content/schedule.js.map +1 -1
  512. package/dist/content/served.d.ts +58 -0
  513. package/dist/content/served.d.ts.map +1 -0
  514. package/dist/content/served.js +65 -0
  515. package/dist/content/served.js.map +1 -0
  516. package/dist/content/site-links.d.ts +72 -0
  517. package/dist/content/site-links.d.ts.map +1 -0
  518. package/dist/content/site-links.js +191 -0
  519. package/dist/content/site-links.js.map +1 -0
  520. package/dist/content/sqlite/archive.d.ts +4 -0
  521. package/dist/content/sqlite/archive.d.ts.map +1 -0
  522. package/dist/content/sqlite/archive.js +96 -0
  523. package/dist/content/sqlite/archive.js.map +1 -0
  524. package/dist/content/sqlite/backlinks.d.ts +4 -0
  525. package/dist/content/sqlite/backlinks.d.ts.map +1 -0
  526. package/dist/content/sqlite/backlinks.js +46 -0
  527. package/dist/content/sqlite/backlinks.js.map +1 -0
  528. package/dist/content/sqlite/catalog.d.ts +4 -0
  529. package/dist/content/sqlite/catalog.d.ts.map +1 -0
  530. package/dist/content/sqlite/catalog.js +62 -0
  531. package/dist/content/sqlite/catalog.js.map +1 -0
  532. package/dist/content/sqlite/clauses.d.ts +57 -0
  533. package/dist/content/sqlite/clauses.d.ts.map +1 -0
  534. package/dist/content/sqlite/clauses.js +76 -0
  535. package/dist/content/sqlite/clauses.js.map +1 -0
  536. package/dist/content/sqlite/database.d.ts +26 -0
  537. package/dist/content/sqlite/database.d.ts.map +1 -0
  538. package/dist/content/sqlite/database.js +27 -0
  539. package/dist/content/sqlite/database.js.map +1 -0
  540. package/dist/content/sqlite/documents.d.ts +12 -0
  541. package/dist/content/sqlite/documents.d.ts.map +1 -0
  542. package/dist/content/sqlite/documents.js +40 -0
  543. package/dist/content/sqlite/documents.js.map +1 -0
  544. package/dist/content/sqlite/lookups.d.ts +6 -0
  545. package/dist/content/sqlite/lookups.d.ts.map +1 -0
  546. package/dist/content/sqlite/lookups.js +25 -0
  547. package/dist/content/sqlite/lookups.js.map +1 -0
  548. package/dist/content/sqlite/open.d.ts +8 -0
  549. package/dist/content/sqlite/open.d.ts.map +1 -0
  550. package/dist/content/sqlite/open.js +63 -0
  551. package/dist/content/sqlite/open.js.map +1 -0
  552. package/dist/content/sqlite/replies.d.ts +4 -0
  553. package/dist/content/sqlite/replies.d.ts.map +1 -0
  554. package/dist/content/sqlite/replies.js +21 -0
  555. package/dist/content/sqlite/replies.js.map +1 -0
  556. package/dist/content/sqlite/rows.d.ts +22 -0
  557. package/dist/content/sqlite/rows.d.ts.map +1 -0
  558. package/dist/content/sqlite/rows.js +93 -0
  559. package/dist/content/sqlite/rows.js.map +1 -0
  560. package/dist/content/sqlite/schedule.d.ts +4 -0
  561. package/dist/content/sqlite/schedule.d.ts.map +1 -0
  562. package/dist/content/sqlite/schedule.js +16 -0
  563. package/dist/content/sqlite/schedule.js.map +1 -0
  564. package/dist/content/sqlite/schema.d.ts +10 -0
  565. package/dist/content/sqlite/schema.d.ts.map +1 -0
  566. package/dist/content/sqlite/schema.js +209 -0
  567. package/dist/content/sqlite/schema.js.map +1 -0
  568. package/dist/content/sqlite/search.d.ts +4 -0
  569. package/dist/content/sqlite/search.d.ts.map +1 -0
  570. package/dist/content/sqlite/search.js +54 -0
  571. package/dist/content/sqlite/search.js.map +1 -0
  572. package/dist/content/sqlite/spellings.d.ts +6 -0
  573. package/dist/content/sqlite/spellings.d.ts.map +1 -0
  574. package/dist/content/sqlite/spellings.js +80 -0
  575. package/dist/content/sqlite/spellings.js.map +1 -0
  576. package/dist/content/sqlite/taxonomy.d.ts +4 -0
  577. package/dist/content/sqlite/taxonomy.d.ts.map +1 -0
  578. package/dist/content/sqlite/taxonomy.js +75 -0
  579. package/dist/content/sqlite/taxonomy.js.map +1 -0
  580. package/dist/content/sqlite/writes.d.ts +9 -0
  581. package/dist/content/sqlite/writes.d.ts.map +1 -0
  582. package/dist/content/sqlite/writes.js +164 -0
  583. package/dist/content/sqlite/writes.js.map +1 -0
  584. package/dist/content/store.d.ts +142 -138
  585. package/dist/content/store.d.ts.map +1 -1
  586. package/dist/content/store.js +0 -1051
  587. package/dist/content/store.js.map +1 -1
  588. package/dist/content/sync.d.ts +12 -4
  589. package/dist/content/sync.d.ts.map +1 -1
  590. package/dist/content/sync.js +24 -41
  591. package/dist/content/sync.js.map +1 -1
  592. package/dist/content/syndicate-to.d.ts +25 -0
  593. package/dist/content/syndicate-to.d.ts.map +1 -0
  594. package/dist/content/syndicate-to.js +8 -0
  595. package/dist/content/syndicate-to.js.map +1 -0
  596. package/dist/{web → content}/taxonomy.d.ts +1 -1
  597. package/dist/content/taxonomy.d.ts.map +1 -0
  598. package/dist/{web → content}/taxonomy.js +2 -2
  599. package/dist/content/taxonomy.js.map +1 -0
  600. package/dist/content/time.d.ts +2 -2
  601. package/dist/content/time.d.ts.map +1 -1
  602. package/dist/content/time.js +3 -3
  603. package/dist/content/time.js.map +1 -1
  604. package/dist/content/trash.d.ts +35 -0
  605. package/dist/content/trash.d.ts.map +1 -0
  606. package/dist/content/trash.js +83 -0
  607. package/dist/content/trash.js.map +1 -0
  608. package/dist/content/uploads.d.ts +24 -0
  609. package/dist/content/uploads.d.ts.map +1 -0
  610. package/dist/content/uploads.js +32 -0
  611. package/dist/content/uploads.js.map +1 -0
  612. package/dist/content/video.d.ts.map +1 -1
  613. package/dist/content/video.js +3 -5
  614. package/dist/content/video.js.map +1 -1
  615. package/dist/content/write.d.ts +84 -0
  616. package/dist/content/write.d.ts.map +1 -0
  617. package/dist/content/write.js +570 -0
  618. package/dist/content/write.js.map +1 -0
  619. package/dist/content/writer.d.ts.map +1 -1
  620. package/dist/content/writer.js +1 -2
  621. package/dist/content/writer.js.map +1 -1
  622. package/dist/dev-mode.d.ts +6 -26
  623. package/dist/dev-mode.d.ts.map +1 -1
  624. package/dist/dev-mode.js +5 -54
  625. package/dist/dev-mode.js.map +1 -1
  626. package/dist/drain.d.ts +2 -3
  627. package/dist/drain.d.ts.map +1 -1
  628. package/dist/drain.js +2 -1
  629. package/dist/drain.js.map +1 -1
  630. package/dist/export/export-archives.d.ts +29 -0
  631. package/dist/export/export-archives.d.ts.map +1 -0
  632. package/dist/export/export-archives.js +88 -0
  633. package/dist/export/export-archives.js.map +1 -0
  634. package/dist/export/export-job.d.ts +21 -0
  635. package/dist/export/export-job.d.ts.map +1 -0
  636. package/dist/export/export-job.js +63 -0
  637. package/dist/export/export-job.js.map +1 -0
  638. package/dist/export/site-export.d.ts +101 -0
  639. package/dist/export/site-export.d.ts.map +1 -0
  640. package/dist/export/site-export.js +243 -0
  641. package/dist/export/site-export.js.map +1 -0
  642. package/dist/export/site-import.d.ts +75 -0
  643. package/dist/export/site-import.d.ts.map +1 -0
  644. package/dist/export/site-import.js +492 -0
  645. package/dist/export/site-import.js.map +1 -0
  646. package/dist/exports.d.ts +31 -0
  647. package/dist/exports.d.ts.map +1 -0
  648. package/dist/exports.js +10 -0
  649. package/dist/exports.js.map +1 -0
  650. package/dist/federation/actor.d.ts +1 -1
  651. package/dist/federation/actor.d.ts.map +1 -1
  652. package/dist/federation/actor.js +6 -13
  653. package/dist/federation/actor.js.map +1 -1
  654. package/dist/federation/article.d.ts +4 -14
  655. package/dist/federation/article.d.ts.map +1 -1
  656. package/dist/federation/article.js +40 -51
  657. package/dist/federation/article.js.map +1 -1
  658. package/dist/federation/backfill.d.ts +7 -5
  659. package/dist/federation/backfill.d.ts.map +1 -1
  660. package/dist/federation/backfill.js +12 -13
  661. package/dist/federation/backfill.js.map +1 -1
  662. package/dist/federation/citations.d.ts.map +1 -1
  663. package/dist/federation/citations.js.map +1 -1
  664. package/dist/federation/cited-post.d.ts +1 -1
  665. package/dist/federation/cited-post.d.ts.map +1 -1
  666. package/dist/federation/cited-post.js +3 -5
  667. package/dist/federation/cited-post.js.map +1 -1
  668. package/dist/federation/delivery-plan.d.ts +85 -0
  669. package/dist/federation/delivery-plan.d.ts.map +1 -0
  670. package/dist/federation/delivery-plan.js +137 -0
  671. package/dist/federation/delivery-plan.js.map +1 -0
  672. package/dist/federation/delivery.d.ts +12 -34
  673. package/dist/federation/delivery.d.ts.map +1 -1
  674. package/dist/federation/delivery.js +79 -212
  675. package/dist/federation/delivery.js.map +1 -1
  676. package/dist/federation/federation.d.ts +12 -12
  677. package/dist/federation/federation.d.ts.map +1 -1
  678. package/dist/federation/federation.js +12 -18
  679. package/dist/federation/federation.js.map +1 -1
  680. package/dist/federation/followers.d.ts +1 -1
  681. package/dist/federation/followers.d.ts.map +1 -1
  682. package/dist/federation/handles.d.ts +1 -1
  683. package/dist/federation/handles.d.ts.map +1 -1
  684. package/dist/federation/handles.js +1 -1
  685. package/dist/federation/handles.js.map +1 -1
  686. package/dist/federation/inbox.d.ts +1 -1
  687. package/dist/federation/inbox.d.ts.map +1 -1
  688. package/dist/federation/inbox.js +6 -5
  689. package/dist/federation/inbox.js.map +1 -1
  690. package/dist/federation/index.d.ts +10 -8
  691. package/dist/federation/index.d.ts.map +1 -1
  692. package/dist/federation/index.js +7 -5
  693. package/dist/federation/index.js.map +1 -1
  694. package/dist/federation/keys.d.ts +8 -27
  695. package/dist/federation/keys.d.ts.map +1 -1
  696. package/dist/federation/keys.js +7 -50
  697. package/dist/federation/keys.js.map +1 -1
  698. package/dist/federation/mount.d.ts +2 -2
  699. package/dist/federation/mount.d.ts.map +1 -1
  700. package/dist/federation/mount.js +9 -7
  701. package/dist/federation/mount.js.map +1 -1
  702. package/dist/federation/paths.d.ts +1 -9
  703. package/dist/federation/paths.d.ts.map +1 -1
  704. package/dist/federation/paths.js +2 -12
  705. package/dist/federation/paths.js.map +1 -1
  706. package/dist/federation/profiles.d.ts +2 -2
  707. package/dist/federation/profiles.d.ts.map +1 -1
  708. package/dist/federation/profiles.js +6 -9
  709. package/dist/federation/profiles.js.map +1 -1
  710. package/dist/federation/queue.d.ts +32 -17
  711. package/dist/federation/queue.d.ts.map +1 -1
  712. package/dist/federation/queue.js +173 -39
  713. package/dist/federation/queue.js.map +1 -1
  714. package/dist/federation/quotes.d.ts +2 -2
  715. package/dist/federation/quotes.d.ts.map +1 -1
  716. package/dist/federation/quotes.js +4 -4
  717. package/dist/federation/quotes.js.map +1 -1
  718. package/dist/federation/records.d.ts +2 -2
  719. package/dist/federation/records.d.ts.map +1 -1
  720. package/dist/federation/records.js +12 -19
  721. package/dist/federation/records.js.map +1 -1
  722. package/dist/federation/relays.d.ts +8 -5
  723. package/dist/federation/relays.d.ts.map +1 -1
  724. package/dist/federation/relays.js +52 -53
  725. package/dist/federation/relays.js.map +1 -1
  726. package/dist/federation/resend-job.d.ts +44 -0
  727. package/dist/federation/resend-job.d.ts.map +1 -0
  728. package/dist/federation/resend-job.js +59 -0
  729. package/dist/federation/resend-job.js.map +1 -0
  730. package/dist/federation/site-loaders.js +1 -1
  731. package/dist/federation/site-loaders.js.map +1 -1
  732. package/dist/federation/webfinger.d.ts +23 -0
  733. package/dist/federation/webfinger.d.ts.map +1 -0
  734. package/dist/federation/webfinger.js +70 -0
  735. package/dist/federation/webfinger.js.map +1 -0
  736. package/dist/files/atomic.d.ts +9 -0
  737. package/dist/files/atomic.d.ts.map +1 -1
  738. package/dist/files/atomic.js +5 -10
  739. package/dist/files/atomic.js.map +1 -1
  740. package/dist/files/data-files.d.ts +104 -0
  741. package/dist/files/data-files.d.ts.map +1 -0
  742. package/dist/files/data-files.js +330 -0
  743. package/dist/files/data-files.js.map +1 -0
  744. package/dist/files/export-archives.d.ts +12 -0
  745. package/dist/files/export-archives.d.ts.map +1 -0
  746. package/dist/files/export-archives.js +67 -0
  747. package/dist/files/export-archives.js.map +1 -0
  748. package/dist/files/static-files.d.ts +21 -0
  749. package/dist/files/static-files.d.ts.map +1 -0
  750. package/dist/files/static-files.js +73 -0
  751. package/dist/files/static-files.js.map +1 -0
  752. package/dist/files/tar.d.ts +29 -0
  753. package/dist/files/tar.d.ts.map +1 -0
  754. package/dist/files/tar.js +206 -0
  755. package/dist/files/tar.js.map +1 -0
  756. package/dist/files/upload-types.d.ts +81 -0
  757. package/dist/files/upload-types.d.ts.map +1 -0
  758. package/dist/files/upload-types.js +234 -0
  759. package/dist/files/upload-types.js.map +1 -0
  760. package/dist/forms/checker.d.ts +93 -0
  761. package/dist/forms/checker.d.ts.map +1 -0
  762. package/dist/forms/checker.js +2 -0
  763. package/dist/forms/checker.js.map +1 -0
  764. package/dist/forms/protection.d.ts +3 -9
  765. package/dist/forms/protection.d.ts.map +1 -1
  766. package/dist/forms/protection.js +5 -11
  767. package/dist/forms/protection.js.map +1 -1
  768. package/dist/images/icons.d.ts.map +1 -1
  769. package/dist/images/icons.js +4 -2
  770. package/dist/images/icons.js.map +1 -1
  771. package/dist/images/index.d.ts +2 -1
  772. package/dist/images/index.d.ts.map +1 -1
  773. package/dist/images/index.js +2 -1
  774. package/dist/images/index.js.map +1 -1
  775. package/dist/images/markup.d.ts +4 -21
  776. package/dist/images/markup.d.ts.map +1 -1
  777. package/dist/images/markup.js +15 -70
  778. package/dist/images/markup.js.map +1 -1
  779. package/dist/images/paths.d.ts +1 -14
  780. package/dist/images/paths.d.ts.map +1 -1
  781. package/dist/images/paths.js +9 -23
  782. package/dist/images/paths.js.map +1 -1
  783. package/dist/images/variants.d.ts +5 -3
  784. package/dist/images/variants.d.ts.map +1 -1
  785. package/dist/images/variants.js +23 -22
  786. package/dist/images/variants.js.map +1 -1
  787. package/dist/index.d.ts +4 -291
  788. package/dist/index.d.ts.map +1 -1
  789. package/dist/index.js +3 -839
  790. package/dist/index.js.map +1 -1
  791. package/dist/indexnow.d.ts +4 -2
  792. package/dist/indexnow.d.ts.map +1 -1
  793. package/dist/indexnow.js +52 -45
  794. package/dist/indexnow.js.map +1 -1
  795. package/dist/indieauth/client.d.ts +1 -1
  796. package/dist/indieauth/client.d.ts.map +1 -1
  797. package/dist/indieauth/client.js +1 -1
  798. package/dist/indieauth/client.js.map +1 -1
  799. package/dist/indieauth/grants.d.ts +1 -1
  800. package/dist/indieauth/grants.d.ts.map +1 -1
  801. package/dist/indieauth/identity.d.ts +2 -2
  802. package/dist/indieauth/identity.d.ts.map +1 -1
  803. package/dist/indieauth/identity.js +5 -7
  804. package/dist/indieauth/identity.js.map +1 -1
  805. package/dist/indieauth/redeem.d.ts +1 -1
  806. package/dist/indieauth/redeem.d.ts.map +1 -1
  807. package/dist/indieauth/redeem.js.map +1 -1
  808. package/dist/indieauth/request.d.ts +5 -1
  809. package/dist/indieauth/request.d.ts.map +1 -1
  810. package/dist/indieauth/request.js +6 -2
  811. package/dist/indieauth/request.js.map +1 -1
  812. package/dist/indieauth/tokens.d.ts +1 -3
  813. package/dist/indieauth/tokens.d.ts.map +1 -1
  814. package/dist/indieauth/tokens.js +4 -6
  815. package/dist/indieauth/tokens.js.map +1 -1
  816. package/dist/jobs/file-records.d.ts +13 -0
  817. package/dist/jobs/file-records.d.ts.map +1 -0
  818. package/dist/jobs/file-records.js +104 -0
  819. package/dist/jobs/file-records.js.map +1 -0
  820. package/dist/jobs/in-process.d.ts +10 -0
  821. package/dist/jobs/in-process.d.ts.map +1 -0
  822. package/dist/jobs/in-process.js +45 -0
  823. package/dist/jobs/in-process.js.map +1 -0
  824. package/dist/jobs/lock-file.d.ts +13 -0
  825. package/dist/jobs/lock-file.d.ts.map +1 -0
  826. package/dist/jobs/lock-file.js +107 -0
  827. package/dist/jobs/lock-file.js.map +1 -0
  828. package/dist/jobs/processes.d.ts +11 -0
  829. package/dist/jobs/processes.d.ts.map +1 -0
  830. package/dist/jobs/processes.js +50 -0
  831. package/dist/jobs/processes.js.map +1 -0
  832. package/dist/jobs/queue.d.ts +18 -0
  833. package/dist/jobs/queue.d.ts.map +1 -0
  834. package/dist/jobs/queue.js +220 -0
  835. package/dist/jobs/queue.js.map +1 -0
  836. package/dist/jobs/types.d.ts +159 -0
  837. package/dist/jobs/types.d.ts.map +1 -0
  838. package/dist/jobs/types.js +22 -0
  839. package/dist/jobs/types.js.map +1 -0
  840. package/dist/mail/index.d.ts +1 -7
  841. package/dist/mail/index.d.ts.map +1 -1
  842. package/dist/mail/index.js +0 -3
  843. package/dist/mail/index.js.map +1 -1
  844. package/dist/mail/provider.d.ts +7 -0
  845. package/dist/mail/provider.d.ts.map +1 -1
  846. package/dist/maintenance.d.ts +6 -43
  847. package/dist/maintenance.d.ts.map +1 -1
  848. package/dist/maintenance.js +4 -67
  849. package/dist/maintenance.js.map +1 -1
  850. package/dist/media/library.d.ts +99 -0
  851. package/dist/media/library.d.ts.map +1 -0
  852. package/dist/media/library.js +160 -0
  853. package/dist/media/library.js.map +1 -0
  854. package/dist/media/uploads.d.ts +106 -0
  855. package/dist/media/uploads.d.ts.map +1 -0
  856. package/dist/media/uploads.js +192 -0
  857. package/dist/media/uploads.js.map +1 -0
  858. package/dist/micropub/content.js +1 -1
  859. package/dist/micropub/content.js.map +1 -1
  860. package/dist/micropub/create.d.ts +7 -15
  861. package/dist/micropub/create.d.ts.map +1 -1
  862. package/dist/micropub/create.js +48 -184
  863. package/dist/micropub/create.js.map +1 -1
  864. package/dist/micropub/endpoint.d.ts +2 -2
  865. package/dist/micropub/endpoint.d.ts.map +1 -1
  866. package/dist/micropub/endpoint.js +36 -47
  867. package/dist/micropub/endpoint.js.map +1 -1
  868. package/dist/micropub/media.d.ts +1 -3
  869. package/dist/micropub/media.d.ts.map +1 -1
  870. package/dist/micropub/media.js +10 -12
  871. package/dist/micropub/media.js.map +1 -1
  872. package/dist/micropub/update.d.ts +6 -14
  873. package/dist/micropub/update.d.ts.map +1 -1
  874. package/dist/micropub/update.js +37 -90
  875. package/dist/micropub/update.js.map +1 -1
  876. package/dist/notifications/comments.d.ts +8 -6
  877. package/dist/notifications/comments.d.ts.map +1 -1
  878. package/dist/notifications/comments.js +80 -76
  879. package/dist/notifications/comments.js.map +1 -1
  880. package/dist/notifications/digest.d.ts +6 -8
  881. package/dist/notifications/digest.d.ts.map +1 -1
  882. package/dist/notifications/digest.js +15 -26
  883. package/dist/notifications/digest.js.map +1 -1
  884. package/dist/notifications/index.d.ts +9 -6
  885. package/dist/notifications/index.d.ts.map +1 -1
  886. package/dist/notifications/index.js +6 -5
  887. package/dist/notifications/index.js.map +1 -1
  888. package/dist/notifications/links.d.ts.map +1 -1
  889. package/dist/notifications/links.js +2 -2
  890. package/dist/notifications/links.js.map +1 -1
  891. package/dist/{mail/service.d.ts → notifications/mail-service.d.ts} +16 -20
  892. package/dist/notifications/mail-service.d.ts.map +1 -0
  893. package/dist/{mail/service.js → notifications/mail-service.js} +100 -121
  894. package/dist/notifications/mail-service.js.map +1 -0
  895. package/dist/{mail/templates.d.ts → notifications/mail-templates.d.ts} +4 -2
  896. package/dist/notifications/mail-templates.d.ts.map +1 -0
  897. package/dist/{mail/templates.js → notifications/mail-templates.js} +5 -3
  898. package/dist/notifications/mail-templates.js.map +1 -0
  899. package/dist/notifications/optouts.d.ts +0 -2
  900. package/dist/notifications/optouts.d.ts.map +1 -1
  901. package/dist/notifications/optouts.js +2 -4
  902. package/dist/notifications/optouts.js.map +1 -1
  903. package/dist/notifications/tokens.d.ts +2 -4
  904. package/dist/notifications/tokens.d.ts.map +1 -1
  905. package/dist/notifications/tokens.js +4 -30
  906. package/dist/notifications/tokens.js.map +1 -1
  907. package/dist/notify.d.ts +4 -2
  908. package/dist/notify.d.ts.map +1 -1
  909. package/dist/notify.js +35 -35
  910. package/dist/notify.js.map +1 -1
  911. package/dist/plugin.d.ts +59 -19
  912. package/dist/plugin.d.ts.map +1 -1
  913. package/dist/plugin.js +22 -4
  914. package/dist/plugin.js.map +1 -1
  915. package/dist/plugins/changes.d.ts +0 -1
  916. package/dist/plugins/changes.d.ts.map +1 -1
  917. package/dist/plugins/changes.js +2 -3
  918. package/dist/plugins/changes.js.map +1 -1
  919. package/dist/plugins/data.d.ts +19 -0
  920. package/dist/plugins/data.d.ts.map +1 -0
  921. package/dist/plugins/data.js +117 -0
  922. package/dist/plugins/data.js.map +1 -0
  923. package/dist/plugins/enabled.d.ts +1 -13
  924. package/dist/plugins/enabled.d.ts.map +1 -1
  925. package/dist/plugins/enabled.js +12 -26
  926. package/dist/plugins/enabled.js.map +1 -1
  927. package/dist/plugins/fetch.d.ts +1 -1
  928. package/dist/plugins/fetch.d.ts.map +1 -1
  929. package/dist/plugins/fetch.js +1 -1
  930. package/dist/plugins/fetch.js.map +1 -1
  931. package/dist/plugins/folder.d.ts.map +1 -1
  932. package/dist/plugins/folder.js +1 -3
  933. package/dist/plugins/folder.js.map +1 -1
  934. package/dist/plugins/install.d.ts +1 -1
  935. package/dist/plugins/install.d.ts.map +1 -1
  936. package/dist/plugins/install.js +14 -45
  937. package/dist/plugins/install.js.map +1 -1
  938. package/dist/plugins/manage.d.ts +1 -1
  939. package/dist/plugins/manage.d.ts.map +1 -1
  940. package/dist/plugins/migrate-settings.d.ts +7 -0
  941. package/dist/plugins/migrate-settings.d.ts.map +1 -0
  942. package/dist/plugins/migrate-settings.js +41 -0
  943. package/dist/plugins/migrate-settings.js.map +1 -0
  944. package/dist/plugins/registry.d.ts +10 -14
  945. package/dist/plugins/registry.d.ts.map +1 -1
  946. package/dist/plugins/registry.js +63 -45
  947. package/dist/plugins/registry.js.map +1 -1
  948. package/dist/plugins/settings.d.ts +5 -57
  949. package/dist/plugins/settings.d.ts.map +1 -1
  950. package/dist/plugins/settings.js +47 -181
  951. package/dist/plugins/settings.js.map +1 -1
  952. package/dist/plugins/site.d.ts +3 -2
  953. package/dist/plugins/site.d.ts.map +1 -1
  954. package/dist/plugins/site.js +8 -8
  955. package/dist/plugins/site.js.map +1 -1
  956. package/dist/plugins/upgrade.d.ts.map +1 -1
  957. package/dist/plugins/upgrade.js +4 -3
  958. package/dist/plugins/upgrade.js.map +1 -1
  959. package/dist/privacy/erase.d.ts +1 -1
  960. package/dist/privacy/erase.d.ts.map +1 -1
  961. package/dist/privacy/retention.d.ts +6 -2
  962. package/dist/privacy/retention.d.ts.map +1 -1
  963. package/dist/privacy/retention.js +20 -15
  964. package/dist/privacy/retention.js.map +1 -1
  965. package/dist/settings/akismet.d.ts +9 -0
  966. package/dist/settings/akismet.d.ts.map +1 -0
  967. package/dist/settings/akismet.js +46 -0
  968. package/dist/settings/akismet.js.map +1 -0
  969. package/dist/settings/declare.d.ts +15 -0
  970. package/dist/settings/declare.d.ts.map +1 -0
  971. package/dist/settings/declare.js +20 -0
  972. package/dist/settings/declare.js.map +1 -0
  973. package/dist/settings/dev-mode.d.ts +29 -0
  974. package/dist/settings/dev-mode.d.ts.map +1 -0
  975. package/dist/settings/dev-mode.js +77 -0
  976. package/dist/settings/dev-mode.js.map +1 -0
  977. package/dist/settings/kinds.d.ts +13 -0
  978. package/dist/settings/kinds.d.ts.map +1 -0
  979. package/dist/settings/kinds.js +108 -0
  980. package/dist/settings/kinds.js.map +1 -0
  981. package/dist/settings/mail.d.ts +10 -0
  982. package/dist/settings/mail.d.ts.map +1 -0
  983. package/dist/settings/mail.js +92 -0
  984. package/dist/settings/mail.js.map +1 -0
  985. package/dist/settings/maintenance.d.ts +6 -0
  986. package/dist/settings/maintenance.d.ts.map +1 -0
  987. package/dist/settings/maintenance.js +33 -0
  988. package/dist/settings/maintenance.js.map +1 -0
  989. package/dist/settings/names.d.ts +7 -0
  990. package/dist/settings/names.d.ts.map +1 -0
  991. package/dist/settings/names.js +31 -0
  992. package/dist/settings/names.js.map +1 -0
  993. package/dist/settings/presence.d.ts +2 -0
  994. package/dist/settings/presence.d.ts.map +1 -0
  995. package/dist/settings/presence.js +7 -0
  996. package/dist/settings/presence.js.map +1 -0
  997. package/dist/settings/registry.d.ts +14 -0
  998. package/dist/settings/registry.d.ts.map +1 -0
  999. package/dist/settings/registry.js +369 -0
  1000. package/dist/settings/registry.js.map +1 -0
  1001. package/dist/settings/types.d.ts +176 -0
  1002. package/dist/settings/types.d.ts.map +1 -0
  1003. package/dist/settings/types.js +2 -0
  1004. package/dist/settings/types.js.map +1 -0
  1005. package/dist/site/admin-paths.d.ts +23 -0
  1006. package/dist/site/admin-paths.d.ts.map +1 -0
  1007. package/dist/site/admin-paths.js +25 -0
  1008. package/dist/site/admin-paths.js.map +1 -0
  1009. package/dist/site/admin-theme.d.ts.map +1 -0
  1010. package/dist/site/admin-theme.js.map +1 -0
  1011. package/dist/{web → site}/authors.d.ts +1 -72
  1012. package/dist/site/authors.d.ts.map +1 -0
  1013. package/dist/site/authors.js +107 -0
  1014. package/dist/site/authors.js.map +1 -0
  1015. package/dist/site/feed-settings.d.ts +28 -0
  1016. package/dist/site/feed-settings.d.ts.map +1 -0
  1017. package/dist/site/feed-settings.js +22 -0
  1018. package/dist/site/feed-settings.js.map +1 -0
  1019. package/dist/{web/indexnow.d.ts → site/indexnow-key.d.ts} +1 -1
  1020. package/dist/site/indexnow-key.d.ts.map +1 -0
  1021. package/dist/{web/indexnow.js → site/indexnow-key.js} +1 -1
  1022. package/dist/site/indexnow-key.js.map +1 -0
  1023. package/dist/site/namespace.d.ts +88 -0
  1024. package/dist/site/namespace.d.ts.map +1 -0
  1025. package/dist/site/namespace.js +471 -0
  1026. package/dist/site/namespace.js.map +1 -0
  1027. package/dist/{web → site}/navigation.d.ts +7 -1
  1028. package/dist/site/navigation.d.ts.map +1 -0
  1029. package/dist/{web → site}/navigation.js +18 -0
  1030. package/dist/site/navigation.js.map +1 -0
  1031. package/dist/{notifications/preferences.d.ts → site/notification-preferences.d.ts} +2 -2
  1032. package/dist/site/notification-preferences.d.ts.map +1 -0
  1033. package/dist/{notifications/preferences.js → site/notification-preferences.js} +1 -1
  1034. package/dist/site/notification-preferences.js.map +1 -0
  1035. package/dist/site/passwords.d.ts.map +1 -0
  1036. package/dist/site/passwords.js.map +1 -0
  1037. package/dist/{privacy/policy.d.ts → site/retention-policy.d.ts} +1 -1
  1038. package/dist/site/retention-policy.d.ts.map +1 -0
  1039. package/dist/{privacy/policy.js → site/retention-policy.js} +1 -1
  1040. package/dist/site/retention-policy.js.map +1 -0
  1041. package/dist/site/robots.d.ts +62 -0
  1042. package/dist/site/robots.d.ts.map +1 -0
  1043. package/dist/site/robots.js +130 -0
  1044. package/dist/site/robots.js.map +1 -0
  1045. package/dist/site/setting-rows.d.ts +45 -0
  1046. package/dist/site/setting-rows.d.ts.map +1 -0
  1047. package/dist/site/setting-rows.js +361 -0
  1048. package/dist/site/setting-rows.js.map +1 -0
  1049. package/dist/site/setting-values.d.ts +63 -0
  1050. package/dist/site/setting-values.d.ts.map +1 -0
  1051. package/dist/site/setting-values.js +170 -0
  1052. package/dist/site/setting-values.js.map +1 -0
  1053. package/dist/site/settings-form.d.ts +30 -0
  1054. package/dist/site/settings-form.d.ts.map +1 -0
  1055. package/dist/site/settings-form.js +54 -0
  1056. package/dist/site/settings-form.js.map +1 -0
  1057. package/dist/site/settings.d.ts +29 -0
  1058. package/dist/site/settings.d.ts.map +1 -0
  1059. package/dist/site/settings.js +44 -0
  1060. package/dist/site/settings.js.map +1 -0
  1061. package/dist/site/site-json.d.ts +38 -0
  1062. package/dist/site/site-json.d.ts.map +1 -0
  1063. package/dist/site/site-json.js +69 -0
  1064. package/dist/site/site-json.js.map +1 -0
  1065. package/dist/{admin/accounts.d.ts → site/users.d.ts} +25 -50
  1066. package/dist/site/users.d.ts.map +1 -0
  1067. package/dist/{admin/accounts.js → site/users.js} +51 -166
  1068. package/dist/site/users.js.map +1 -0
  1069. package/dist/store/admin-store.d.ts +72 -0
  1070. package/dist/store/admin-store.d.ts.map +1 -0
  1071. package/dist/store/admin-store.js +62 -0
  1072. package/dist/store/admin-store.js.map +1 -0
  1073. package/dist/store/comments.d.ts +253 -0
  1074. package/dist/store/comments.d.ts.map +1 -0
  1075. package/dist/store/comments.js +234 -0
  1076. package/dist/store/comments.js.map +1 -0
  1077. package/dist/store/conversation.d.ts +28 -0
  1078. package/dist/store/conversation.d.ts.map +1 -0
  1079. package/dist/store/conversation.js +16 -0
  1080. package/dist/store/conversation.js.map +1 -0
  1081. package/dist/store/deliveries.d.ts +92 -0
  1082. package/dist/store/deliveries.d.ts.map +1 -0
  1083. package/dist/store/deliveries.js +88 -0
  1084. package/dist/store/deliveries.js.map +1 -0
  1085. package/dist/store/followers.d.ts +91 -0
  1086. package/dist/store/followers.d.ts.map +1 -0
  1087. package/dist/store/followers.js +79 -0
  1088. package/dist/store/followers.js.map +1 -0
  1089. package/dist/{federation/replies.d.ts → store/inbox-replies.d.ts} +2 -2
  1090. package/dist/store/inbox-replies.d.ts.map +1 -0
  1091. package/dist/{federation/replies.js → store/inbox-replies.js} +2 -4
  1092. package/dist/store/inbox-replies.js.map +1 -0
  1093. package/dist/store/inbox.d.ts +132 -0
  1094. package/dist/store/inbox.d.ts.map +1 -0
  1095. package/dist/store/inbox.js +157 -0
  1096. package/dist/store/inbox.js.map +1 -0
  1097. package/dist/store/profiles.d.ts +49 -0
  1098. package/dist/store/profiles.d.ts.map +1 -0
  1099. package/dist/store/profiles.js +71 -0
  1100. package/dist/store/profiles.js.map +1 -0
  1101. package/dist/store/queue-database.d.ts +11 -0
  1102. package/dist/store/queue-database.d.ts.map +1 -0
  1103. package/dist/store/queue-database.js +39 -0
  1104. package/dist/store/queue-database.js.map +1 -0
  1105. package/dist/store/relays.d.ts +66 -0
  1106. package/dist/store/relays.d.ts.map +1 -0
  1107. package/dist/store/relays.js +69 -0
  1108. package/dist/store/relays.js.map +1 -0
  1109. package/dist/store/rows.d.ts +33 -0
  1110. package/dist/store/rows.d.ts.map +1 -0
  1111. package/dist/store/rows.js +44 -0
  1112. package/dist/store/rows.js.map +1 -0
  1113. package/dist/store/schema.d.ts +14 -0
  1114. package/dist/store/schema.d.ts.map +1 -0
  1115. package/dist/store/schema.js +588 -0
  1116. package/dist/store/schema.js.map +1 -0
  1117. package/dist/store/sessions.d.ts +90 -0
  1118. package/dist/store/sessions.d.ts.map +1 -0
  1119. package/dist/store/sessions.js +107 -0
  1120. package/dist/store/sessions.js.map +1 -0
  1121. package/dist/store/state.d.ts +18 -0
  1122. package/dist/store/state.d.ts.map +1 -0
  1123. package/dist/store/state.js +19 -0
  1124. package/dist/store/state.js.map +1 -0
  1125. package/dist/store/tokens.d.ts +75 -0
  1126. package/dist/store/tokens.d.ts.map +1 -0
  1127. package/dist/store/tokens.js +82 -0
  1128. package/dist/store/tokens.js.map +1 -0
  1129. package/dist/store/webmentions.d.ts +66 -0
  1130. package/dist/store/webmentions.d.ts.map +1 -0
  1131. package/dist/store/webmentions.js +84 -0
  1132. package/dist/store/webmentions.js.map +1 -0
  1133. package/dist/supervisor/control.d.ts +1 -0
  1134. package/dist/supervisor/control.d.ts.map +1 -1
  1135. package/dist/supervisor/control.js +10 -0
  1136. package/dist/supervisor/control.js.map +1 -1
  1137. package/dist/supervisor/primary.d.ts +1 -1
  1138. package/dist/supervisor/primary.d.ts.map +1 -1
  1139. package/dist/supervisor/primary.js +46 -14
  1140. package/dist/supervisor/primary.js.map +1 -1
  1141. package/dist/supervisor/worker.d.ts +6 -2
  1142. package/dist/supervisor/worker.d.ts.map +1 -1
  1143. package/dist/supervisor/worker.js +4 -4
  1144. package/dist/supervisor/worker.js.map +1 -1
  1145. package/dist/testing.d.ts +8 -0
  1146. package/dist/testing.d.ts.map +1 -0
  1147. package/dist/testing.js +6 -0
  1148. package/dist/testing.js.map +1 -0
  1149. package/dist/{web → theme}/assets.d.ts +1 -32
  1150. package/dist/theme/assets.d.ts.map +1 -0
  1151. package/dist/{web → theme}/assets.js +2 -100
  1152. package/dist/theme/assets.js.map +1 -0
  1153. package/dist/theme/site-data.d.ts +187 -0
  1154. package/dist/theme/site-data.d.ts.map +1 -0
  1155. package/dist/theme/site-data.js +160 -0
  1156. package/dist/theme/site-data.js.map +1 -0
  1157. package/dist/{web → theme}/templates.d.ts +2 -0
  1158. package/dist/theme/templates.d.ts.map +1 -0
  1159. package/dist/{web → theme}/templates.js +9 -32
  1160. package/dist/theme/templates.js.map +1 -0
  1161. package/dist/{web → theme}/themes.d.ts +1 -0
  1162. package/dist/theme/themes.d.ts.map +1 -0
  1163. package/dist/{web → theme}/themes.js +6 -3
  1164. package/dist/theme/themes.js.map +1 -0
  1165. package/dist/util/client-id.d.ts.map +1 -0
  1166. package/dist/util/client-id.js.map +1 -0
  1167. package/dist/util/clock.d.ts +9 -0
  1168. package/dist/util/clock.d.ts.map +1 -0
  1169. package/dist/util/clock.js +2 -0
  1170. package/dist/util/clock.js.map +1 -0
  1171. package/dist/util/errors.d.ts +3 -0
  1172. package/dist/util/errors.d.ts.map +1 -0
  1173. package/dist/util/errors.js +5 -0
  1174. package/dist/util/errors.js.map +1 -0
  1175. package/dist/util/files.d.ts +5 -0
  1176. package/dist/util/files.d.ts.map +1 -0
  1177. package/dist/util/files.js +38 -0
  1178. package/dist/util/files.js.map +1 -0
  1179. package/dist/util/forms.d.ts +5 -0
  1180. package/dist/util/forms.d.ts.map +1 -0
  1181. package/dist/util/forms.js +17 -0
  1182. package/dist/util/forms.js.map +1 -0
  1183. package/dist/util/guards.d.ts +4 -0
  1184. package/dist/util/guards.d.ts.map +1 -0
  1185. package/dist/util/guards.js +12 -0
  1186. package/dist/util/guards.js.map +1 -0
  1187. package/dist/{webmention/html.d.ts → util/html-tree.d.ts} +2 -2
  1188. package/dist/util/html-tree.d.ts.map +1 -0
  1189. package/dist/{webmention/html.js → util/html-tree.js} +3 -64
  1190. package/dist/util/html-tree.js.map +1 -0
  1191. package/dist/util/html.d.ts +4 -0
  1192. package/dist/util/html.d.ts.map +1 -0
  1193. package/dist/util/html.js +34 -0
  1194. package/dist/util/html.js.map +1 -0
  1195. package/dist/util/public-address.d.ts.map +1 -0
  1196. package/dist/util/public-address.js.map +1 -0
  1197. package/dist/util/queue.d.ts +7 -0
  1198. package/dist/util/queue.d.ts.map +1 -0
  1199. package/dist/util/queue.js +23 -0
  1200. package/dist/util/queue.js.map +1 -0
  1201. package/dist/util/retry.d.ts +11 -0
  1202. package/dist/util/retry.d.ts.map +1 -0
  1203. package/dist/util/retry.js +14 -0
  1204. package/dist/util/retry.js.map +1 -0
  1205. package/dist/util/url.d.ts +11 -0
  1206. package/dist/util/url.d.ts.map +1 -0
  1207. package/dist/util/url.js +20 -0
  1208. package/dist/util/url.js.map +1 -0
  1209. package/dist/web/absolute-urls.js +1 -1
  1210. package/dist/web/absolute-urls.js.map +1 -1
  1211. package/dist/web/archive.js +2 -2
  1212. package/dist/web/archive.js.map +1 -1
  1213. package/dist/web/asset-routes.d.ts +52 -0
  1214. package/dist/web/asset-routes.d.ts.map +1 -0
  1215. package/dist/web/asset-routes.js +115 -0
  1216. package/dist/web/asset-routes.js.map +1 -0
  1217. package/dist/{avatars/routes.d.ts → web/avatar-routes.d.ts} +2 -2
  1218. package/dist/web/avatar-routes.d.ts.map +1 -0
  1219. package/dist/{avatars/routes.js → web/avatar-routes.js} +3 -3
  1220. package/dist/web/avatar-routes.js.map +1 -0
  1221. package/dist/web/comment-routes.d.ts +18 -0
  1222. package/dist/web/comment-routes.d.ts.map +1 -0
  1223. package/dist/web/comment-routes.js +177 -0
  1224. package/dist/web/comment-routes.js.map +1 -0
  1225. package/dist/web/compression.d.ts.map +1 -1
  1226. package/dist/web/compression.js +1 -2
  1227. package/dist/web/compression.js.map +1 -1
  1228. package/dist/{contact/routes.d.ts → web/contact-routes.d.ts} +2 -2
  1229. package/dist/web/contact-routes.d.ts.map +1 -0
  1230. package/dist/{contact/routes.js → web/contact-routes.js} +15 -18
  1231. package/dist/web/contact-routes.js.map +1 -0
  1232. package/dist/web/context.d.ts +6 -187
  1233. package/dist/web/context.d.ts.map +1 -1
  1234. package/dist/web/context.js +13 -174
  1235. package/dist/web/context.js.map +1 -1
  1236. package/dist/web/conversation.d.ts +6 -357
  1237. package/dist/web/conversation.d.ts.map +1 -1
  1238. package/dist/web/conversation.js +177 -151
  1239. package/dist/web/conversation.js.map +1 -1
  1240. package/dist/web/document-page.d.ts +13 -0
  1241. package/dist/web/document-page.d.ts.map +1 -0
  1242. package/dist/web/document-page.js +137 -0
  1243. package/dist/web/document-page.js.map +1 -0
  1244. package/dist/web/document-site.d.ts +14 -0
  1245. package/dist/web/document-site.d.ts.map +1 -0
  1246. package/dist/web/document-site.js +29 -0
  1247. package/dist/web/document-site.js.map +1 -0
  1248. package/dist/web/embed.d.ts +13 -0
  1249. package/dist/web/embed.d.ts.map +1 -0
  1250. package/dist/web/embed.js +66 -0
  1251. package/dist/web/embed.js.map +1 -0
  1252. package/dist/{env.d.ts → web/env.d.ts} +36 -26
  1253. package/dist/web/env.d.ts.map +1 -0
  1254. package/dist/web/env.js.map +1 -0
  1255. package/dist/web/federation-routes.d.ts +33 -0
  1256. package/dist/web/federation-routes.d.ts.map +1 -0
  1257. package/dist/web/federation-routes.js +282 -0
  1258. package/dist/web/federation-routes.js.map +1 -0
  1259. package/dist/web/feed-atom.d.ts.map +1 -1
  1260. package/dist/web/feed-atom.js +4 -3
  1261. package/dist/web/feed-atom.js.map +1 -1
  1262. package/dist/web/feed-item.d.ts +4 -39
  1263. package/dist/web/feed-item.d.ts.map +1 -1
  1264. package/dist/web/feed-item.js +9 -73
  1265. package/dist/web/feed-item.js.map +1 -1
  1266. package/dist/web/feed-json.js +2 -2
  1267. package/dist/web/feed-json.js.map +1 -1
  1268. package/dist/web/feed-routes.d.ts +35 -0
  1269. package/dist/web/feed-routes.d.ts.map +1 -0
  1270. package/dist/web/feed-routes.js +126 -0
  1271. package/dist/web/feed-routes.js.map +1 -0
  1272. package/dist/web/feed-rss.d.ts.map +1 -1
  1273. package/dist/web/feed-rss.js +9 -6
  1274. package/dist/web/feed-rss.js.map +1 -1
  1275. package/dist/web/feed-source.d.ts +11 -41
  1276. package/dist/web/feed-source.d.ts.map +1 -1
  1277. package/dist/web/feed-source.js +11 -48
  1278. package/dist/web/feed-source.js.map +1 -1
  1279. package/dist/web/feeds.d.ts +4 -2
  1280. package/dist/web/feeds.d.ts.map +1 -1
  1281. package/dist/web/feeds.js +8 -5
  1282. package/dist/web/feeds.js.map +1 -1
  1283. package/dist/web/health.d.ts +1 -1
  1284. package/dist/web/health.d.ts.map +1 -1
  1285. package/dist/web/health.js +1 -1
  1286. package/dist/web/health.js.map +1 -1
  1287. package/dist/web/index.d.ts +53 -29
  1288. package/dist/web/index.d.ts.map +1 -1
  1289. package/dist/web/index.js +37 -18
  1290. package/dist/web/index.js.map +1 -1
  1291. package/dist/{indieauth/activity-log.d.ts → web/indieauth-activity-log.d.ts} +3 -5
  1292. package/dist/web/indieauth-activity-log.d.ts.map +1 -0
  1293. package/dist/{indieauth/activity-log.js → web/indieauth-activity-log.js} +7 -10
  1294. package/dist/web/indieauth-activity-log.js.map +1 -0
  1295. package/dist/{indieauth/bearer.d.ts → web/indieauth-bearer.d.ts} +5 -5
  1296. package/dist/web/indieauth-bearer.d.ts.map +1 -0
  1297. package/dist/{indieauth/bearer.js → web/indieauth-bearer.js} +5 -5
  1298. package/dist/web/indieauth-bearer.js.map +1 -0
  1299. package/dist/{indieauth/discovery.d.ts → web/indieauth-discovery.d.ts} +2 -11
  1300. package/dist/web/indieauth-discovery.d.ts.map +1 -0
  1301. package/dist/{indieauth/discovery.js → web/indieauth-discovery.js} +7 -17
  1302. package/dist/web/indieauth-discovery.js.map +1 -0
  1303. package/dist/{indieauth/token.d.ts → web/indieauth-token.d.ts} +2 -2
  1304. package/dist/web/indieauth-token.d.ts.map +1 -0
  1305. package/dist/{indieauth/token.js → web/indieauth-token.js} +9 -9
  1306. package/dist/web/indieauth-token.js.map +1 -0
  1307. package/dist/web/listing-pages.d.ts +19 -0
  1308. package/dist/web/listing-pages.d.ts.map +1 -0
  1309. package/dist/web/listing-pages.js +147 -0
  1310. package/dist/web/listing-pages.js.map +1 -0
  1311. package/dist/web/listing-subject.d.ts +58 -0
  1312. package/dist/web/listing-subject.d.ts.map +1 -0
  1313. package/dist/web/listing-subject.js +89 -0
  1314. package/dist/web/listing-subject.js.map +1 -0
  1315. package/dist/web/llms.d.ts +16 -15
  1316. package/dist/web/llms.d.ts.map +1 -1
  1317. package/dist/web/llms.js +23 -35
  1318. package/dist/web/llms.js.map +1 -1
  1319. package/dist/web/maintenance.d.ts +1 -1
  1320. package/dist/web/maintenance.d.ts.map +1 -1
  1321. package/dist/web/maintenance.js +10 -16
  1322. package/dist/web/maintenance.js.map +1 -1
  1323. package/dist/web/manifest.d.ts +1 -1
  1324. package/dist/web/manifest.d.ts.map +1 -1
  1325. package/dist/web/manifest.js +2 -1
  1326. package/dist/web/manifest.js.map +1 -1
  1327. package/dist/web/markdown-page.d.ts +76 -0
  1328. package/dist/web/markdown-page.d.ts.map +1 -0
  1329. package/dist/web/markdown-page.js +79 -0
  1330. package/dist/web/markdown-page.js.map +1 -0
  1331. package/dist/web/markdown-representation.d.ts +15 -0
  1332. package/dist/web/markdown-representation.d.ts.map +1 -0
  1333. package/dist/web/markdown-representation.js +130 -0
  1334. package/dist/web/markdown-representation.js.map +1 -0
  1335. package/dist/web/negotiate.d.ts +5 -12
  1336. package/dist/web/negotiate.d.ts.map +1 -1
  1337. package/dist/web/negotiate.js +11 -18
  1338. package/dist/web/negotiate.js.map +1 -1
  1339. package/dist/{notifications/routes.d.ts → web/notification-routes.d.ts} +2 -2
  1340. package/dist/web/notification-routes.d.ts.map +1 -0
  1341. package/dist/{notifications/routes.js → web/notification-routes.js} +24 -29
  1342. package/dist/web/notification-routes.js.map +1 -0
  1343. package/dist/web/oembed.d.ts +2 -2
  1344. package/dist/web/oembed.d.ts.map +1 -1
  1345. package/dist/web/oembed.js +5 -4
  1346. package/dist/web/oembed.js.map +1 -1
  1347. package/dist/web/opensearch.d.ts.map +1 -1
  1348. package/dist/web/opensearch.js +2 -1
  1349. package/dist/web/opensearch.js.map +1 -1
  1350. package/dist/web/page-sources.d.ts +14 -0
  1351. package/dist/web/page-sources.d.ts.map +1 -0
  1352. package/dist/web/page-sources.js +43 -0
  1353. package/dist/web/page-sources.js.map +1 -0
  1354. package/dist/web/pagination.d.ts +1 -0
  1355. package/dist/web/pagination.d.ts.map +1 -1
  1356. package/dist/web/pagination.js +4 -1
  1357. package/dist/web/pagination.js.map +1 -1
  1358. package/dist/web/paths.d.ts +52 -0
  1359. package/dist/web/paths.d.ts.map +1 -0
  1360. package/dist/web/paths.js +208 -0
  1361. package/dist/web/paths.js.map +1 -0
  1362. package/dist/web/pingback-routes.d.ts +5 -0
  1363. package/dist/web/pingback-routes.d.ts.map +1 -0
  1364. package/dist/web/pingback-routes.js +100 -0
  1365. package/dist/web/pingback-routes.js.map +1 -0
  1366. package/dist/{plugins/federation.d.ts → web/plugin-federation.d.ts} +3 -3
  1367. package/dist/web/plugin-federation.d.ts.map +1 -0
  1368. package/dist/{plugins/federation.js → web/plugin-federation.js} +55 -45
  1369. package/dist/web/plugin-federation.js.map +1 -0
  1370. package/dist/{plugins/mount.d.ts → web/plugin-routes.d.ts} +3 -3
  1371. package/dist/web/plugin-routes.d.ts.map +1 -0
  1372. package/dist/{plugins/mount.js → web/plugin-routes.js} +2 -2
  1373. package/dist/web/plugin-routes.js.map +1 -0
  1374. package/dist/web/redirects.d.ts +1 -84
  1375. package/dist/web/redirects.d.ts.map +1 -1
  1376. package/dist/web/redirects.js +0 -232
  1377. package/dist/web/redirects.js.map +1 -1
  1378. package/dist/web/render.d.ts +55 -12
  1379. package/dist/web/render.d.ts.map +1 -1
  1380. package/dist/web/render.js +123 -65
  1381. package/dist/web/render.js.map +1 -1
  1382. package/dist/web/request.d.ts +44 -0
  1383. package/dist/web/request.d.ts.map +1 -0
  1384. package/dist/web/request.js +82 -0
  1385. package/dist/web/request.js.map +1 -0
  1386. package/dist/web/robots.d.ts +1 -46
  1387. package/dist/web/robots.d.ts.map +1 -1
  1388. package/dist/web/robots.js +5 -131
  1389. package/dist/web/robots.js.map +1 -1
  1390. package/dist/web/routes.d.ts +5 -39
  1391. package/dist/web/routes.d.ts.map +1 -1
  1392. package/dist/web/routes.js +102 -1374
  1393. package/dist/web/routes.js.map +1 -1
  1394. package/dist/web/search.d.ts.map +1 -1
  1395. package/dist/web/search.js +2 -9
  1396. package/dist/web/search.js.map +1 -1
  1397. package/dist/{admin → web}/session.d.ts +1 -5
  1398. package/dist/web/session.d.ts.map +1 -0
  1399. package/dist/{admin → web}/session.js +0 -4
  1400. package/dist/web/session.js.map +1 -0
  1401. package/dist/web/share-image.d.ts.map +1 -1
  1402. package/dist/web/share-image.js +3 -2
  1403. package/dist/web/share-image.js.map +1 -1
  1404. package/dist/web/signed-in.d.ts +27 -0
  1405. package/dist/web/signed-in.d.ts.map +1 -0
  1406. package/dist/web/signed-in.js +45 -0
  1407. package/dist/web/signed-in.js.map +1 -0
  1408. package/dist/web/site-index.d.ts +13 -0
  1409. package/dist/web/site-index.d.ts.map +1 -0
  1410. package/dist/web/site-index.js +142 -0
  1411. package/dist/web/site-index.js.map +1 -0
  1412. package/dist/web/site-view.d.ts +43 -0
  1413. package/dist/web/site-view.d.ts.map +1 -0
  1414. package/dist/web/site-view.js +65 -0
  1415. package/dist/web/site-view.js.map +1 -0
  1416. package/dist/web/sitemap.d.ts.map +1 -1
  1417. package/dist/web/sitemap.js +5 -7
  1418. package/dist/web/sitemap.js.map +1 -1
  1419. package/dist/{admin → web}/throttle.d.ts +3 -4
  1420. package/dist/web/throttle.d.ts.map +1 -0
  1421. package/dist/{admin → web}/throttle.js +1 -2
  1422. package/dist/web/throttle.js.map +1 -0
  1423. package/dist/web/webmention-routes.d.ts +11 -0
  1424. package/dist/web/webmention-routes.d.ts.map +1 -0
  1425. package/dist/web/webmention-routes.js +51 -0
  1426. package/dist/web/webmention-routes.js.map +1 -0
  1427. package/dist/web/well-known.d.ts +2 -2
  1428. package/dist/web/well-known.d.ts.map +1 -1
  1429. package/dist/web/well-known.js +10 -20
  1430. package/dist/web/well-known.js.map +1 -1
  1431. package/dist/webmention/cited-picture.d.ts +1 -2
  1432. package/dist/webmention/cited-picture.d.ts.map +1 -1
  1433. package/dist/webmention/cited-picture.js +5 -7
  1434. package/dist/webmention/cited-picture.js.map +1 -1
  1435. package/dist/webmention/discovery.d.ts +2 -13
  1436. package/dist/webmention/discovery.d.ts.map +1 -1
  1437. package/dist/webmention/discovery.js +18 -73
  1438. package/dist/webmention/discovery.js.map +1 -1
  1439. package/dist/webmention/endpoint.d.ts +27 -0
  1440. package/dist/webmention/endpoint.d.ts.map +1 -0
  1441. package/dist/webmention/endpoint.js +26 -0
  1442. package/dist/webmention/endpoint.js.map +1 -0
  1443. package/dist/webmention/fetch-public.d.ts +18 -1
  1444. package/dist/webmention/fetch-public.d.ts.map +1 -1
  1445. package/dist/webmention/fetch-public.js +57 -23
  1446. package/dist/webmention/fetch-public.js.map +1 -1
  1447. package/dist/webmention/index.d.ts +11 -9
  1448. package/dist/webmention/index.d.ts.map +1 -1
  1449. package/dist/webmention/index.js +7 -6
  1450. package/dist/webmention/index.js.map +1 -1
  1451. package/dist/webmention/links.d.ts +0 -55
  1452. package/dist/webmention/links.d.ts.map +1 -1
  1453. package/dist/webmention/links.js +2 -185
  1454. package/dist/webmention/links.js.map +1 -1
  1455. package/dist/webmention/microformats.d.ts +1 -1
  1456. package/dist/webmention/microformats.d.ts.map +1 -1
  1457. package/dist/webmention/microformats.js +1 -1
  1458. package/dist/webmention/microformats.js.map +1 -1
  1459. package/dist/webmention/pingback.d.ts +13 -12
  1460. package/dist/webmention/pingback.d.ts.map +1 -1
  1461. package/dist/webmention/pingback.js +6 -113
  1462. package/dist/webmention/pingback.js.map +1 -1
  1463. package/dist/webmention/receive.d.ts +9 -4
  1464. package/dist/webmention/receive.d.ts.map +1 -1
  1465. package/dist/webmention/receive.js +35 -41
  1466. package/dist/webmention/receive.js.map +1 -1
  1467. package/dist/webmention/reply-context/fediverse.d.ts +29 -0
  1468. package/dist/webmention/reply-context/fediverse.d.ts.map +1 -0
  1469. package/dist/webmention/reply-context/fediverse.js +59 -0
  1470. package/dist/webmention/reply-context/fediverse.js.map +1 -0
  1471. package/dist/webmention/reply-context/json-ld.d.ts +5 -0
  1472. package/dist/webmention/reply-context/json-ld.d.ts.map +1 -0
  1473. package/dist/webmention/reply-context/json-ld.js +78 -0
  1474. package/dist/webmention/reply-context/json-ld.js.map +1 -0
  1475. package/dist/webmention/reply-context/oembed.d.ts +21 -0
  1476. package/dist/webmention/reply-context/oembed.d.ts.map +1 -0
  1477. package/dist/webmention/reply-context/oembed.js +137 -0
  1478. package/dist/webmention/reply-context/oembed.js.map +1 -0
  1479. package/dist/webmention/reply-context/page.d.ts +30 -0
  1480. package/dist/webmention/reply-context/page.d.ts.map +1 -0
  1481. package/dist/webmention/reply-context/page.js +134 -0
  1482. package/dist/webmention/reply-context/page.js.map +1 -0
  1483. package/dist/webmention/reply-context/source.d.ts +29 -0
  1484. package/dist/webmention/reply-context/source.d.ts.map +1 -0
  1485. package/dist/webmention/reply-context/source.js +46 -0
  1486. package/dist/webmention/reply-context/source.js.map +1 -0
  1487. package/dist/webmention/reply-context.d.ts +5 -34
  1488. package/dist/webmention/reply-context.d.ts.map +1 -1
  1489. package/dist/webmention/reply-context.js +9 -432
  1490. package/dist/webmention/reply-context.js.map +1 -1
  1491. package/dist/webmention/reply-contexts.d.ts +17 -6
  1492. package/dist/webmention/reply-contexts.d.ts.map +1 -1
  1493. package/dist/webmention/reply-contexts.js +19 -20
  1494. package/dist/webmention/reply-contexts.js.map +1 -1
  1495. package/dist/webmention/routes.d.ts +1 -1
  1496. package/dist/webmention/routes.d.ts.map +1 -1
  1497. package/dist/webmention/routes.js +5 -8
  1498. package/dist/webmention/routes.js.map +1 -1
  1499. package/dist/webmention/service.d.ts +9 -7
  1500. package/dist/webmention/service.d.ts.map +1 -1
  1501. package/dist/webmention/service.js +111 -110
  1502. package/dist/webmention/service.js.map +1 -1
  1503. package/dist/webmention/syndication.d.ts +3 -26
  1504. package/dist/webmention/syndication.d.ts.map +1 -1
  1505. package/dist/webmention/syndication.js +6 -20
  1506. package/dist/webmention/syndication.js.map +1 -1
  1507. package/package.json +17 -11
  1508. package/themes/default/README.md +36 -2339
  1509. package/themes/default/layouts/base.njk +5 -4
  1510. package/themes/default/layouts/front-page.njk +4 -3
  1511. package/themes/default/layouts/page.njk +2 -0
  1512. package/themes/default/partials/comment-form.njk +1 -1
  1513. package/themes/default/partials/contact-form.njk +1 -2
  1514. package/themes/default/partials/conversation.njk +1 -2
  1515. package/themes/default/partials/markdown-footer.md.njk +24 -0
  1516. package/themes/default/partials/markdown-header.md.njk +31 -0
  1517. package/themes/default/partials/post-list.njk +11 -11
  1518. package/themes/default/src/style.css +18 -6
  1519. package/themes/default/static/style.css +19 -0
  1520. package/dist/admin/accounts.d.ts.map +0 -1
  1521. package/dist/admin/accounts.js.map +0 -1
  1522. package/dist/admin/admin-theme.d.ts.map +0 -1
  1523. package/dist/admin/admin-theme.js.map +0 -1
  1524. package/dist/admin/enclosure-field.d.ts.map +0 -1
  1525. package/dist/admin/enclosure-field.js.map +0 -1
  1526. package/dist/admin/event-field.d.ts.map +0 -1
  1527. package/dist/admin/event-field.js.map +0 -1
  1528. package/dist/admin/location-field.d.ts +0 -27
  1529. package/dist/admin/location-field.d.ts.map +0 -1
  1530. package/dist/admin/location-field.js.map +0 -1
  1531. package/dist/admin/passwords.d.ts.map +0 -1
  1532. package/dist/admin/passwords.js.map +0 -1
  1533. package/dist/admin/photo-field.d.ts +0 -38
  1534. package/dist/admin/photo-field.d.ts.map +0 -1
  1535. package/dist/admin/photo-field.js.map +0 -1
  1536. package/dist/admin/read-field.d.ts +0 -22
  1537. package/dist/admin/read-field.d.ts.map +0 -1
  1538. package/dist/admin/read-field.js +0 -61
  1539. package/dist/admin/read-field.js.map +0 -1
  1540. package/dist/admin/session.d.ts.map +0 -1
  1541. package/dist/admin/session.js.map +0 -1
  1542. package/dist/admin/store.d.ts +0 -1025
  1543. package/dist/admin/store.d.ts.map +0 -1
  1544. package/dist/admin/store.js +0 -1628
  1545. package/dist/admin/store.js.map +0 -1
  1546. package/dist/admin/throttle.d.ts.map +0 -1
  1547. package/dist/admin/throttle.js.map +0 -1
  1548. package/dist/avatars/routes.d.ts.map +0 -1
  1549. package/dist/avatars/routes.js.map +0 -1
  1550. package/dist/contact/routes.d.ts.map +0 -1
  1551. package/dist/contact/routes.js.map +0 -1
  1552. package/dist/env.d.ts.map +0 -1
  1553. package/dist/env.js.map +0 -1
  1554. package/dist/federation/replies.d.ts.map +0 -1
  1555. package/dist/federation/replies.js.map +0 -1
  1556. package/dist/images/alt-text.d.ts.map +0 -1
  1557. package/dist/images/alt-text.js.map +0 -1
  1558. package/dist/indieauth/activity-log.d.ts.map +0 -1
  1559. package/dist/indieauth/activity-log.js.map +0 -1
  1560. package/dist/indieauth/app-activity.d.ts.map +0 -1
  1561. package/dist/indieauth/app-activity.js.map +0 -1
  1562. package/dist/indieauth/bearer.d.ts.map +0 -1
  1563. package/dist/indieauth/bearer.js.map +0 -1
  1564. package/dist/indieauth/client-id.d.ts.map +0 -1
  1565. package/dist/indieauth/client-id.js.map +0 -1
  1566. package/dist/indieauth/connected-apps.d.ts.map +0 -1
  1567. package/dist/indieauth/connected-apps.js.map +0 -1
  1568. package/dist/indieauth/consent.d.ts.map +0 -1
  1569. package/dist/indieauth/consent.js.map +0 -1
  1570. package/dist/indieauth/discovery.d.ts.map +0 -1
  1571. package/dist/indieauth/discovery.js.map +0 -1
  1572. package/dist/indieauth/token.d.ts.map +0 -1
  1573. package/dist/indieauth/token.js.map +0 -1
  1574. package/dist/mail/service.d.ts.map +0 -1
  1575. package/dist/mail/service.js.map +0 -1
  1576. package/dist/mail/templates.d.ts.map +0 -1
  1577. package/dist/mail/templates.js.map +0 -1
  1578. package/dist/notifications/preferences.d.ts.map +0 -1
  1579. package/dist/notifications/preferences.js.map +0 -1
  1580. package/dist/notifications/routes.d.ts.map +0 -1
  1581. package/dist/notifications/routes.js.map +0 -1
  1582. package/dist/plugins/federation.d.ts.map +0 -1
  1583. package/dist/plugins/federation.js.map +0 -1
  1584. package/dist/plugins/mount.d.ts.map +0 -1
  1585. package/dist/plugins/mount.js.map +0 -1
  1586. package/dist/privacy/policy.d.ts.map +0 -1
  1587. package/dist/privacy/policy.js.map +0 -1
  1588. package/dist/web/admin-bar.d.ts.map +0 -1
  1589. package/dist/web/admin-bar.js.map +0 -1
  1590. package/dist/web/assets.d.ts.map +0 -1
  1591. package/dist/web/assets.js.map +0 -1
  1592. package/dist/web/authors.d.ts.map +0 -1
  1593. package/dist/web/authors.js +0 -211
  1594. package/dist/web/authors.js.map +0 -1
  1595. package/dist/web/documents.d.ts +0 -81
  1596. package/dist/web/documents.d.ts.map +0 -1
  1597. package/dist/web/documents.js.map +0 -1
  1598. package/dist/web/errors.d.ts.map +0 -1
  1599. package/dist/web/errors.js.map +0 -1
  1600. package/dist/web/guids.d.ts.map +0 -1
  1601. package/dist/web/guids.js.map +0 -1
  1602. package/dist/web/indexnow.d.ts.map +0 -1
  1603. package/dist/web/indexnow.js.map +0 -1
  1604. package/dist/web/license.d.ts.map +0 -1
  1605. package/dist/web/license.js.map +0 -1
  1606. package/dist/web/locale.d.ts.map +0 -1
  1607. package/dist/web/locale.js.map +0 -1
  1608. package/dist/web/navigation.d.ts.map +0 -1
  1609. package/dist/web/navigation.js.map +0 -1
  1610. package/dist/web/recent.d.ts +0 -43
  1611. package/dist/web/recent.d.ts.map +0 -1
  1612. package/dist/web/recent.js +0 -37
  1613. package/dist/web/recent.js.map +0 -1
  1614. package/dist/web/sanitize.d.ts +0 -29
  1615. package/dist/web/sanitize.d.ts.map +0 -1
  1616. package/dist/web/sanitize.js.map +0 -1
  1617. package/dist/web/taxonomy.d.ts.map +0 -1
  1618. package/dist/web/taxonomy.js.map +0 -1
  1619. package/dist/web/templates.d.ts.map +0 -1
  1620. package/dist/web/templates.js.map +0 -1
  1621. package/dist/web/themes.d.ts.map +0 -1
  1622. package/dist/web/themes.js.map +0 -1
  1623. package/dist/webmention/html.d.ts.map +0 -1
  1624. package/dist/webmention/html.js.map +0 -1
  1625. package/dist/webmention/public-address.d.ts.map +0 -1
  1626. package/dist/webmention/public-address.js.map +0 -1
  1627. /package/dist/{web → content}/license.d.ts +0 -0
  1628. /package/dist/{web → content}/license.js +0 -0
  1629. /package/dist/{admin → site}/admin-theme.d.ts +0 -0
  1630. /package/dist/{admin → site}/admin-theme.js +0 -0
  1631. /package/dist/{admin → site}/passwords.d.ts +0 -0
  1632. /package/dist/{admin → site}/passwords.js +0 -0
  1633. /package/dist/{indieauth → util}/client-id.d.ts +0 -0
  1634. /package/dist/{indieauth → util}/client-id.js +0 -0
  1635. /package/dist/{webmention → util}/public-address.d.ts +0 -0
  1636. /package/dist/{webmention → util}/public-address.js +0 -0
  1637. /package/dist/{env.js → web/env.js} +0 -0
@@ -1,2341 +1,38 @@
1
1
  # Default theme
2
2
 
3
- Templates ship here, inside the published package, so every site gets them
4
- without copying anything.
5
-
6
- ```
7
- themes/default/
8
- theme.json the manifest: name, kind and description
9
- layouts/
10
- base.njk the page every other layout extends
11
- home.njk the post archive, paginated
12
- front-page.njk the page the Reading setting serves at /
13
- post.njk one post
14
- page.njk one page
15
- tag.njk a tag archive, paginated
16
- category.njk a category archive, paginated
17
- author.njk one person's archive, paginated
18
- search.njk the search form and what it found, paginated
19
- 404.njk nothing at this URL
20
- 410.njk what was at this URL was deleted
21
- 500.njk the request failed on the server
22
- 503.njk the site is in maintenance mode
23
- comment.njk a native comment on a page of its own
24
- partials/
25
- post-list.njk the h-feed a listing is made of
26
- pagination.njk previous/next pager
27
- tags.njk macros for tag and category links
28
- kicker.njk the line above an entry naming its kind and date
29
- reply-context.njk the citation of what a reply answers
30
- event.njk when and where an event is
31
- citations.njk the citation of what a like, repost or bookmark cites
32
- cited-page.njk the name and author both citations print
33
- read.njk what a read post read, and how far
34
- bio.njk who an entry is by, as an h-card
35
- menu.njk one named menu, as a nav of links
36
- feeds.njk macros for the feed links in <head>
37
- conversation.njk the replies, likes and boosts under a post or an open page
38
- comment.njk one reply and its answers, as the thread and a comment page print it
39
- comment-form.njk the form under a post or page that is taking comments
40
- contact-form.njk the form on a page whose front matter says contact: true
41
- archive.njk every post by month, on a page that says archive: true
42
- backlinks.njk the site's own posts and pages that link to this one
43
- search-form.njk the search box, on the search page
44
- head-end.njk empty: what a site adds to the end of every <head>
45
- body-end.njk empty: what a site adds to the end of every <body>
46
- mail/
47
- test.*.njk the Send test email message
48
- password-reset.*.njk the forgot-password link
49
- password-changed.*.njk the notice sent once a password has been set
50
- comment-pending.*.njk the moderation notice
51
- comment-digest.*.njk the hourly or daily digest of what is waiting
52
- comment-reply.*.njk the notice a commenter gets about a reply
53
- contact-message.*.njk a message from a page's contact form
54
- src/
55
- style.css the stylesheet's source, compiled with Tailwind
56
- static/
57
- style.css the compiled stylesheet, served at /theme/style.css
58
- highlight.js the code highlighter
59
- ```
60
-
61
- Plain Nunjucks templates and one stylesheet. The stylesheet is written in
62
- Tailwind v4 (`src/style.css`) and compiled when the package is built, so
63
- `static/style.css` is a build product: gitignored in this repository, written
64
- by `pnpm build` (which the root `prepare` runs after an install and before a
65
- publish), and shipped in the package like every other file here. The compile
66
- happens once, on the machine that builds the package. A reader gets one plain
67
- CSS file and no script, web font or CDN request because of it.
68
-
69
- The design is Paper (doc-9, decision-22): one warm column, serif throughout,
70
- sans for small labels, and a kicker above every entry naming its kind.
71
-
72
- **Rebuilding the stylesheet.** After editing `src/style.css`:
73
-
74
- ```
75
- pnpm --filter @geekity/cms build:theme
76
- ```
77
-
78
- Tailwind scans only this theme's `layouts/` and `partials/`, so a utility class
79
- written in a template here is compiled in and nothing outside the theme is
80
- read. The templates themselves use the semantic class names below and the look
81
- lives in `src/style.css`, through `@apply`.
82
-
83
- **A site theme owes Tailwind nothing.** Its own `static/style.css` replaces this
84
- one wholesale, and any CSS that knows the class names in this README will do.
85
- The demo theme's is plain CSS. A site that only wants different colours
86
- overrides the `--color-*` custom properties instead; see [Colours](#colours).
87
-
88
- ## The manifest
89
-
90
- `theme.json` is what makes a directory a theme:
91
-
92
- ```json
93
- {
94
- "name": "Default",
95
- "kind": "site",
96
- "description": "One line about the theme.",
97
- "colorScheme": "light dark",
98
- "themeColor": { "light": "#faf7f2", "dark": "#171412" }
99
- }
100
- ```
101
-
102
- `name` is what a person sees, `kind` is `site` — the only kind there is, and
103
- the field exists so another can be added later without the format changing —
104
- and `description` is optional. The directory name is the theme's id. A
105
- directory without a readable manifest, or one naming a kind this CMS does not
106
- have, is not a theme.
107
-
108
- `colorScheme` and `themeColor` are the colours the head declares before the
109
- stylesheet loads (see [The head](#the-head)). `colorScheme` is one of `normal`,
110
- `light`, `dark`, `light dark`, `dark light` or `only light`, preferred scheme
111
- first. `themeColor` is the colour of the browser around the page, one per
112
- scheme, as a three- or six-digit hex colour with no alpha. Make it the page's
113
- own background. A value outside those is ignored, and so is one a theme leaves
114
- out: each falls back to this theme's, just as a template does.
115
-
116
- ## Overriding a template
117
-
118
- A site keeps its themes under one directory, `themes/` by default and
119
- `themesDir` in the config, with one folder per theme and a `theme.json` in
120
- each. Which one it wears is one setting, `theme` in `content/_data/site.json`,
121
- holding the folder's name:
122
-
123
- ```
124
- themes/
125
- midnight/
126
- theme.json
127
- layouts/post.njk
128
- static/style.css
129
- ```
130
-
131
- ```json
132
- { "theme": "midnight" }
133
- ```
134
-
135
- The setting is what **Appearance > Themes** in the admin writes: the screen
136
- lists this theme and every folder under `themes/`, marks the one in use, and
137
- Activate on another writes its name — or, on the packaged theme, takes the key
138
- out again. A change takes effect on the next request, with no restart, and a
139
- folder whose manifest will not read is listed under "Not themes" with the
140
- reason rather than quietly left out.
141
-
142
- Resolution order for any template is then that theme first, this directory
143
- second, one file at a time. The theme above replaces the post layout and keeps
144
- receiving updates to every other template, and `static/style.css` replaces the
145
- stylesheet the same way — assets under `/theme/` resolve in the same order.
146
- Link a theme file with the `asset` filter rather than a literal `/theme/`
147
- path: `{{ "style.css" | asset }}` puts a hash of the file in the URL, so a
148
- reader's browser keeps it for a year and still fetches the new one the moment
149
- it changes. The plain `/theme/style.css` keeps working, cached for an hour. A
150
- site that has chosen no theme reads this one and nothing else, and a theme
151
- sitting in `themes/` that the setting does not name is never on the path at
152
- all. A `theme` naming a folder that is not there, or is not a theme, falls back
153
- to this one with a warning in the log rather than a broken site.
154
-
155
- The admin is not a theme and has no setting. Its templates live in their own
156
- tree with a loader of their own, off this search path entirely, so no theme can
157
- shadow the login form or the CSRF field inside it.
158
-
159
- An override is a normal Nunjucks template, so it can extend or include the
160
- packaged ones by name:
161
-
162
- ```njk
163
- {% extends "layouts/base.njk" %}
164
-
165
- {% block content %}
166
- <article>
167
- <h1>{{ title }}</h1>
168
- {{ content | safe }}
169
- </article>
170
- {% endblock %}
171
- ```
172
-
173
- `layouts/base.njk` defines the blocks `title`, `speculationRules`,
174
- `viewTransitions`, `head`, `alternates`, `header`, `breadcrumbs`, `content`,
175
- `footer` and `scripts`, so most sites never have to copy it.
176
-
177
- Two layouts are named for the pages the Reading settings pick:
178
-
179
- | Template | Rendered for | Shipped here | Falls back to |
180
- | ------------------------ | ----------------------------- | ------------ | ------------------ |
181
- | `layouts/front-page.njk` | the page served at `/` | yes | `layouts/page.njk` |
182
- | `layouts/posts-page.njk` | the listing on the posts page | no | `layouts/home.njk` |
183
-
184
- `layouts/front-page.njk` is a layout here rather than only an override point:
185
- see [The front page](#the-front-page) for what it draws. Overriding it is how a
186
- front page is laid out differently from every other page without overriding the
187
- layout they all use, and the fallback above is reached only by a theme that
188
- replaced it with nothing.
189
-
190
- There is no posts page layout: a site that sets one gets the listing layout
191
- until it writes one. On the posts page the page's own front matter and rendered
192
- body are on the context beside the listing, so `{{ content | safe }}` prints its
193
- words above the posts; `layouts/home.njk` already does.
194
-
195
- ## Adding to every page
196
-
197
- Analytics, a site verification tag or a chat widget goes on every page, and
198
- none of them needs a layout copied. `layouts/base.njk` includes two partials
199
- that this theme ships empty:
200
-
201
- | Partial | Printed |
202
- | ----------------------- | -------------------------------------------------- |
203
- | `partials/head-end.njk` | last in `<head>`, after the cards and the JSON-LD |
204
- | `partials/body-end.njk` | last in `<body>`, after the footer and the scripts |
205
-
206
- A site theme that holds one of them, and nothing else but its `theme.json`,
207
- adds that markup to every public HTML page: posts, pages, listings, search, the
208
- front page, and the 404, 410, 500 and 503 pages. Every other template still
209
- comes from this directory, so the site keeps receiving updates to them, and
210
- the two partial names stay as they are. The includes sit outside every block,
211
- so a layout that overrides `head` or `scripts` without `super()` keeps them.
212
-
213
- They are part of the HTML page and nothing else. The Markdown, plain text and
214
- JSON representations of a page, the feeds, `llms.txt`, mail and the admin do
215
- not print them. The editor's preview renders the post or page layout in the
216
- admin, so the context there carries `editorPreview: true` and `layouts/base.njk`
217
- leaves both partials out. A theme that replaces `layouts/base.njk` and includes
218
- them itself should test the same key.
219
-
220
- Umami and Google Analytics 4, in `themes/analytics/`:
221
-
222
- ```
223
- themes/analytics/
224
- theme.json
225
- partials/head-end.njk
226
- ```
227
-
228
- ```json
229
- { "name": "Analytics", "kind": "site" }
230
- ```
231
-
232
- ```njk
233
- <script defer src="https://cloud.umami.is/script.js" data-website-id="your-website-id"></script>
234
- <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
235
- <script>
236
- window.dataLayer = window.dataLayer || [];
237
- function gtag(){dataLayer.push(arguments);}
238
- gtag('js', new Date());
239
- gtag('config', 'G-XXXXXXXXXX');
240
- </script>
241
- ```
242
-
243
- Then wear the theme, either with Activate on **Appearance > Themes** or with
244
- `"theme": "analytics"` in `content/_data/site.json`. A site that already wears
245
- a theme of its own puts the partial in that theme instead.
246
-
247
- The partial is a template, rendered with the page's context, so it can read a
248
- value out of `site.json`, as in `data-website-id="{{ site.umamiId }}"`.
249
-
250
- **In Docker.** The image reads themes from `/site/themes`. Mount the site's
251
- `themes/` directory there, read-only, which is the commented line in
252
- `deploy/compose.yaml`:
253
-
254
- ```yaml
255
- volumes:
256
- - ./themes:/site/themes:ro
257
- ```
258
-
259
- No custom image is needed. A change to the partial shows on the next request.
260
-
261
- **Under a Content-Security-Policy.** The default headers set only
262
- `frame-ancestors`, so they restrict nothing a page loads, and the inline gtag
263
- configuration runs as it is. A site that sets a policy of its own with
264
- `securityHeaders` must allow what the snippets load and where they send data:
265
-
266
- | Directive | Umami | Google Analytics 4 |
267
- | ------------- | -------------------------- | ---------------------------------------------------------------------------------------------- |
268
- | `script-src` | `https://cloud.umami.is` | `https://*.googletagmanager.com`, and the inline configuration (see below) |
269
- | `connect-src` | `https://gateway.umami.is` | `https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com` |
270
- | `img-src` | none | `https://*.google-analytics.com https://*.googletagmanager.com` |
271
-
272
- The inline `<script>` that configures gtag needs one of these:
273
-
274
- - Move it to a file in the theme, `themes/analytics/static/gtag.js`, and load
275
- it with `<script src="{{ "gtag.js" | asset }}"></script>`. It is then served
276
- from the site, and `script-src 'self'` covers it.
277
- - Allow its hash, `'sha256-…'`, in `script-src`. The hash covers every byte
278
- between the tags, so it changes when the measurement ID or the whitespace
279
- changes.
280
- - Allow `'unsafe-inline'` in `script-src`. This also allows any injected
281
- inline script, so prefer one of the other two.
282
-
283
- ## The page shell
284
-
285
- `layouts/base.njk` is the page shell: the skip link, one `.global-wrapper` at the
286
- 42rem measure, `.global-header`, `<main id="main">` and the footer, in that
287
- order. The shape is the andrewshell.org design's (decision-16) and the look is
288
- Paper's (doc-9).
289
-
290
- **One home for each kind of link.** The header carries the site menu, on every
291
- page. [The bio](#the-bio) carries the person whose page or post it is. The
292
- footer carries the site's own links. The search box is on the search page and
293
- nowhere else.
294
-
295
- **The header has one rule, and the menu under it.** On the front page it is the
296
- site title as `h1.main-heading`, linked home, with `site.tagline` in a
297
- `p.site-tagline` under it; on every other page it is `a.header-link-home`, the site
298
- title small and linked home, and no tagline. The wrapper carries
299
- `data-is-root-path="true"` at `/` and nothing anywhere else, which is how the
300
- stylesheet tells the two apart. `menus.primary` is printed inside the
301
- `.global-header` on both: at the root it is a line of its own under the
302
- tagline, and everywhere else the stylesheet lays the header out as one line so
303
- it sits beside the link home. See [Navigation](#navigation).
304
-
305
- **The footer** prints the copyright with the current year and the site
306
- author's display name, `soloAuthor.name`, or the site title on a site with
307
- several authors,
308
- then the page's license, when it has one, as `<a rel="license">` with its name,
309
- then `Published with Geekity`, and then `menus.footer` — the `footer` area this
310
- theme declares, and the whole of what the footer links. A site that wants its
311
- feed there types an `RSS | /feed/` line into it, the way the starter site does.
312
- Nothing in the footer is read off an account: it used to hold one `rel="me"`
313
- link per entry of `siteAuthor.links`, which was one nominated user's profile
314
- presented as the site's. An empty or missing footer menu prints no list at all,
315
- and the copyright line stands on its own. The year is
316
- `{{ "now" | date("year") }}` — `now` is the one word the `date` filter reads
317
- rather than parses — so it is the year at the moment the page is rendered, in
318
- the site's own timezone. The license is `license`: a post's own from its
319
- `license` front matter, else the site's from Settings > General, and nothing
320
- on a site with none.
321
-
322
- **The breadcrumb** opens `<main>` on a page with a place in the site's
323
- hierarchy (TASK-150): a post filed under a category, and a category, tag or
324
- author archive. It is `nav.breadcrumbs` labelled `Breadcrumb`, holding an `ol`
325
- whose items run Home, then the post's first category and the post, or the
326
- archive's term or person, then `Page N` past page one. Every crumb but the last
327
- is a link; the last is the page itself, a `span` with `aria-current="page"`.
328
- The trail is `breadcrumbs`, a list of `{ name, url }` that `layouts/base.njk`
329
- sets before anything is printed, and the JSON-LD prints the same list, so the
330
- two cannot disagree. Every other page has an empty trail and prints nothing. A
331
- site that wants no visible breadcrumb empties the `breadcrumbs` block, and the
332
- `BreadcrumbList` stays in the graph.
333
-
334
- Webrings, badges and anything else that is markup rather than a link are
335
- deliberately not in the package. They go in a site theme's `footer`
336
- block:
337
-
338
- ```njk
339
- {% extends "layouts/base.njk" %}
340
-
341
- {% block footer %}
342
- {{ super() }}
343
- <p class="webring">
344
- <a href="https://example.ring/previous">&larr;</a>
345
- An <a href="https://example.ring">example webring</a>
346
- <a href="https://example.ring/next">&rarr;</a>
347
- </p>
348
- {% endblock %}
349
- ```
350
-
351
- ### The admin bar
352
-
353
- When somebody signed in to the admin reads the site, the CMS puts the admin bar
354
- across the top of every HTML page: the site title linking to the dashboard,
355
- **+ New**, **Edit Post** or **Edit Page** on a post or a page, and the account
356
- menu. A theme draws none of it and needs no template for it. The CMS inserts a
357
- `<geekity-admin-bar>` element straight after the page's `<body>` tag, and an
358
- anonymous reader's page is unchanged, byte for byte.
359
-
360
- The bar keeps its markup and its stylesheet in a shadow root, so a theme's CSS
361
- cannot restyle it and its CSS cannot restyle the theme. The element carries an
362
- inline style that resets it and fixes it to the top of the window, full width,
363
- above the page. Padding or margins on `<body>`, and rules such as `body > *` or
364
- a grid on `<body>`, do not move or resize it. It stays in place while the page
365
- scrolls.
366
-
367
- The bar takes no room by itself, so the CMS makes room for it from outside. On
368
- a page that carries the bar:
369
-
370
- - `<html>` has the class `geekity-admin-bar`.
371
- - A stylesheet in `<head>` declares `--geekity-admin-bar-height` on `:root`
372
- and sets `margin-top: var(--geekity-admin-bar-height) !important` on
373
- `<html>`, which pushes the whole page down below the bar. The height is
374
- 40px, or 80px on a screen 600px wide or less, where the bar can wrap onto a
375
- second line. A script in the bar then measures it and sets the real height
376
- on `<html>`, so the push matches the bar at any width, and is 0 when a theme
377
- hides the bar.
378
- - The same stylesheet sets `scroll-padding-top` on `<html>` to the bar's
379
- height, so a link to an anchor on the page, such as a skip link or a
380
- footnote, scrolls its target to below the bar. It is declared with no
381
- specificity, so a theme that sets its own `scroll-padding-top` keeps it.
382
-
383
- A theme with a sticky or fixed header of its own offsets it by the bar's
384
- height. The fallback of `0` applies on every page the bar is not on:
385
-
386
- ```css
387
- .site-header {
388
- position: sticky;
389
- top: var(--geekity-admin-bar-height, 0);
390
- }
391
- ```
392
-
393
- A theme that sets its own `scroll-padding-top` adds the bar to it the same
394
- way, for example `calc(var(--geekity-admin-bar-height, 0px) + 4rem)`. Use the
395
- `geekity-admin-bar` class for any other change, for example
396
- `.geekity-admin-bar .site-header { ... }`.
397
-
398
- A theme that must not show the bar can hide it:
399
-
400
- ```css
401
- geekity-admin-bar {
402
- display: none !important;
403
- }
404
- ```
405
-
406
- The `!important` is needed because it has to beat the element's inline style.
407
- With script on, the bar then measures 0 and the page moves back up. For a
408
- reader without script, the theme also removes the margin:
409
-
410
- ```css
411
- html.geekity-admin-bar {
412
- margin-top: 0 !important;
413
- }
414
- ```
415
-
416
- ### An entry
417
-
418
- `layouts/post.njk` and `layouts/page.njk` draw the same thing, the entry, and it
419
- is a microformats2 `h-entry`. An article, and a page:
420
-
421
- ```html
422
- <article class="blog-post h-entry">
423
- <header class="post-header">
424
- <p class="kicker">
425
- <span class="kicker-kind">Article</span>
426
- <span class="kicker-dot" aria-hidden="true">·</span>
427
- <a href="/category/notes/" class="p-category" rel="category">notes</a>
428
- </p>
429
- <h1 class="p-name">Hello</h1>
430
- <p class="post-deck p-summary">The description the author wrote.</p>
431
- </header>
432
- <section class="e-content">
433
- <p>The rendered body.</p>
434
- <p class="entry-meta">
435
- <a class="u-syndication small" href="https://news.indieweb.org/en"
436
- >IndieNews</a
437
- >
438
- <a href="/2026/09/hello/" class="u-url"
439
- ><time class="small dt-published" datetime="2026-09-02T09:00:00.000Z"
440
- >Published 2 September 2026</time
441
- ></a
442
- >
443
- <span aria-hidden="true">·</span>
444
- <time class="small dt-updated" datetime="2026-09-05T09:00:00.000Z"
445
- >Updated 5 September 2026</time
446
- >
447
- <span aria-hidden="true">·</span>
448
- <span class="small"
449
- >Also on
450
- <a class="u-syndication" href="https://news.indieweb.org/en/…"
451
- >news.indieweb.org</a
452
- ></span
453
- >
454
- </p>
455
- </section>
456
- <p class="post-categories">…tags…</p>
457
- <hr />
458
- <footer>…the bio…</footer>
459
- </article>
460
- <nav class="blog-post-nav" aria-label="Neighbouring posts">…</nav>
461
- ```
462
-
463
- **The kicker names the kind.** `p.kicker`, from `partials/kicker.njk`, opens
464
- every entry: the kind word in `span.kicker-kind` (Article, Note or Reply for a
465
- post, from its `postType`; Page for a page), then the categories it is filed
466
- under as `p-category` links. It is the only place the kind is said, small and
467
- once, and an article is additionally the only kind with a visible headline.
468
- The deck is the front matter's `description` as a `p.post-deck.p-summary`,
469
- printed only when the author wrote one.
470
-
471
- **The Published line is inside the `e-content`**, which is the one thing about
472
- this shape worth knowing. It is what the source theme does: the date and the
473
- permalink are part of the words, so a reader, or a fediverse peer reading the
474
- `e-content`, takes them with the post. The permalink wraps the `dt-published`
475
- time as a `u-url`, and a `dt-updated` follows it after a middle dot only when
476
- the update happened on a different day in the site's own timezone; a typo fixed
477
- an hour later is not news. The line opens with a `u-syndication` link to each
478
- syndication target the post selects (`syndicateTo`, TASK-155), which is what
479
- IndieNews and Bridgy Publish look for on the page, and ends with the post's
480
- copies elsewhere (`syndication`) after **Also on**. A page never prints either,
481
- and its line is a `p.page-meta` rather than a `p.entry-meta`.
482
-
483
- **A post first published elsewhere says so.** A post whose front matter names
484
- its original with `canonical_href`, an absolute http or https URL such as a
485
- Substack essay's, has it on the context as `original` (TASK-293). The line
486
- then reads **Originally published at** and links the original as a second
487
- `u-url` of the h-entry, after the permalink's. The link says the publication's
488
- name when the front matter gives one in `canonical_name`, such as `Smarter
489
- Engineers` for a guest post on someone else's Substack (TASK-316), and the
490
- original's host otherwise. It is no
491
- `u-syndication`, because the original is not a copy of this post, and
492
- original-post-discovery reads a copy's off-site `u-url` as its original. The
493
- same URL is the page's `rel="canonical"` and its `og:url`, so search engines
494
- credit the original. A value that is not an absolute http or https URL is
495
- ignored, and `geekity sync` names the file. So is a `canonical_name` with no
496
- valid `canonical_href` beside it.
497
-
498
- **Every post's page has one `h1`.** A post with a name of its own is headed by
499
- it, as above. So is a post with a title and no words, such as a titled like,
500
- repost or bookmark (TASK-256): its header comes first and its citation after
501
- it. A title that only repeats a post's opening words is no name. A note, or a
502
- reply without a title, opens on its words instead,
503
- set larger, so it has no `header`; in its place is an `h1` a screen reader and
504
- a crawler navigate by and a sighted reader never sees, saying what the post is,
505
- who wrote it and when, and then the kicker:
506
-
507
- ```html
508
- <h1 class="screen-reader-text">Note by Ada Lovelace, 11 September 2026</h1>
509
- <p class="kicker"><span class="kicker-kind">Note</span></p>
510
- ```
511
-
512
- It says `Reply`, `Like`, `Repost` or `Bookmark` for those and leaves out the name or the date the post does
513
- not have. It is not a `p-name`: a parser that found one would take the post
514
- for an article. The headings under it step down one level at a time: the
515
- conversation and the comment form are `h2`, and a refused form's error summary
516
- is an `h3` inside the form's section.
517
-
518
- **A reply cites what it answers** with `partials/reply-context.njk`, a
519
- `div.reply-context.cite.u-in-reply-to.h-cite`: a rule on the start side, a
520
- `p.cite-line` saying "In reply to" and the target's name, author and date as
521
- far as they are known, and its excerpt as a `blockquote.cite-quote.p-content`.
522
- Where it goes depends on whether the post shows a title. An untitled reply
523
- opens on it, under the kicker and before its words. A titled reply prints its
524
- `header.post-header` first and the citation after it, before the `e-content`,
525
- so the page opens on its title rather than on a link. A listing follows the
526
- same rule: an untitled entry cites under its kicker, a titled one after its
527
- title and summary. The same partial cites a reply in a feed. The layouts own
528
- the placement; the partial owns only what the citation says, and it stays
529
- inside the `h-entry` wherever it is printed.
530
-
531
- **A reply to a silo copy cites the original** (TASK-197). When the post a
532
- reply answers is a copy of a post on another site, and that post lists the
533
- copy as its `u-syndication`, `replyContext.original` is the original's URL and
534
- the rest of `replyContext` describes the original. The `h-cite` links the
535
- original, and a `p.cite-copy` after it says "Also in reply to its copy on" the
536
- silo's host, linking the copy as a `u-in-reply-to` of its own, so a parser
537
- reads both as what the post answers.
538
-
539
- **An RSVP says whether its author is going** (TASK-198). An RSVP is a reply
540
- to an event whose `rsvp` is `yes`, `no`, `maybe` or `interested`.
541
- `partials/rsvp.njk` opens its `e-content` with a `p.rsvp-line` holding a
542
- `data.p-rsvp` whose value is the answer and whose text is Going, Not going,
543
- Maybe or Interested; it prints `rsvp.line`, the HTML the feeds also carry. Its
544
- reply context says "RSVP to" rather than "In reply to", and when the event's
545
- page is an `h-event`, or describes a schema.org `Event` in JSON-LD, its start
546
- follows the event's name as a `time.dt-start` and its place as a
547
- `span.p-location`, inside the `h-cite`. A start the event page wrote with no
548
- zone is that wall clock where the event is, so it is printed in UTC
549
- (`replyContext.startZone`) rather than moved into the site's zone. Its kicker
550
- and its hidden `h1` say RSVP. An RSVP to a silo copy of an event cites the
551
- original as a reply does.
552
-
553
- **An event is an h-event** (TASK-200, decision-32). A post whose front matter
554
- has a readable `start` is an event: its title is the event's name, its body
555
- the description, and `end` and `location` are optional. `layouts/post.njk`
556
- types its article `h-event` rather than `h-entry`, its kicker and hidden `h1`
557
- say Event, and `partials/event.njk` follows the title with a
558
- `dl.event-details`. The When row is a `time.dt-start` and, with an end, a
559
- `time.dt-end`, each written with the `date` filter's `datetime` format in the
560
- site's zone, with the instant as its `datetime`. The Where row is a
561
- `span.p-location` holding the place's words, or "Online at" an
562
- `a.p-location` that links the address to join an online event. The JSON-LD
563
- describes the post as a schema.org `Event` instead of a `BlogPosting`. That
564
- `Event` is a `Place` or a `VirtualLocation`, carries an attendance mode and an
565
- `EventScheduled` status, and names the author's Person as its `organizer`.
566
- The answers it is given are grouped in `conversation.rsvps`; see [The
567
- conversation](#the-conversation).
568
-
569
- **A like, a repost or a bookmark cites what it cites** (TASK-169) with
570
- `partials/citations.njk`, beside the reply context and drawn the same way: one
571
- `div.reply-context.cite.h-cite` per entry of `citations`, classed
572
- `u-repost-of`, `u-like-of` or `u-bookmark-of`, whose `p.cite-line` says
573
- Reposted, Liked or Bookmarked and links the target as its `u-url`. The link
574
- says the target's name, as the reply context does, with its author after it as
575
- a `p-author h-card`, when the target was read when the post was saved
576
- (TASK-244): its `h-entry` name, its oEmbed title, or its `og:title` or
577
- `<title>`. A YouTube, TikTok, Reddit or Giphy target is named by that
578
- provider's known oEmbed endpoint, asked before its page, which is read only
579
- when the endpoint names nothing (TASK-251, TASK-253). Any other target's oEmbed
580
- title comes from the endpoint its page links. A title that is only a site suffix such as "- YouTube" is
581
- no title. A comment on this site (`replyContext.comment`, TASK-326) says "a
582
- comment". With only an author it says "a post", and with neither "a page on"
583
- the target's host, never the bare URL, which can run to hundreds of
584
- characters: "Bookmarked a page on example.com" (TASK-255). Its kicker
585
- and its hidden `h1` say Repost, Like or Bookmark. It is placed as the reply
586
- context is: at the top of an untitled post, after the header of a titled one.
587
- A listing cites each entry's from the same partial, by the same rule.
588
-
589
- **Both citations name the cited page** through `partials/cited-page.njk`, so
590
- they print its name and author by one rule. A name that already ends in " by "
591
- and the author's name, in any case, does not print the author again
592
- (TASK-247): SoundCloud titles a track "Flickermood by Forss" and names Forss
593
- as its author. The title link stops before " by ", and the words after it
594
- become the `p-author h-card`, so the line reads "Liked Flickermood by Forss"
595
- and a parser still reads the whole title as the `p-name`. A name that mentions
596
- its author anywhere else, such as "Rick Astley - Never Gonna Give You Up" by
597
- Rick Astley, keeps the author after it. A page that names no author but names
598
- its site, its `og:site_name`, is followed by a middle dot and the site as a
599
- plain `span.cite-site`, never an `h-card`, because a site is not an author: a
600
- bookmark of a Scripting News post reads "Bookmarked RSS tip #2 · Scripting
601
- News". A like's, a repost's or a bookmark's citation never prints the page's
602
- description, which on many sites describes the site rather than the post.
603
-
604
- **A citation shows who wrote the cited page** (TASK-199) as fully as the page
605
- said. The author's photo, an `img.u-photo.cite-avatar` with empty alt text
606
- since their name is beside it, comes before the name inside the `h-card`; a
607
- fediverse author's handle follows the name as a `span.p-nickname.cite-handle`
608
- reading `@user@host`. The photo is the `u-photo` of the `h-entry` author's
609
- `h-card` (or of the page's `h-card` for the same person), the avatar of the
610
- account an ActivityPub object is attributed to, or the author image in the
611
- page's JSON-LD, copied into the site's uploads as a picture is, and left out
612
- when the post has removed its preview. A Mastodon status is read from the
613
- ActivityPub object its page links, so its author's display name, handle,
614
- avatar, words, first image and date come from the status itself rather than
615
- from the page's `og:title`. A page with no `h-entry` and no object is read
616
- from its JSON-LD (`headline`, `author`, `datePublished`, `image`) and then its
617
- Open Graph, Twitter and `article:` tags.
618
-
619
- **A citation shows the cited page's picture** (TASK-252) when the page named
620
- one and the post has not removed its preview. The picture is the page's
621
- oEmbed photo, else its oEmbed thumbnail, else its `og:image`, else its
622
- `twitter:image`, copied into the site's uploads when the post was saved, so
623
- a reader's browser never asks the cited site or its CDN for anything. It is
624
- `context.picture`: `{ src, width, height, kind, video, shownInFull, imgHtml }`, where
625
- `imgHtml` is an `img.u-photo` inside the `h-cite`, responsive when the copy has
626
- variants. A repost of a photo (`shownInFull`, such as a Giphy GIF) prints it in full
627
- under the line, as `a.cite-photo` linking to the target, with the target's
628
- name as its alt text. Every other citation with a picture (a like, a bookmark,
629
- a reply, a repost of a video or a page) is classed `cite-with-thumb` and
630
- prints it as `a.cite-thumb` beside the line: a 6rem thumbnail linking to the
631
- target, out of the tab order and hidden from assistive technology because the
632
- title beside it is the same link, with an empty alt text. A video's thumbnail
633
- is also classed `cite-video`, which the stylesheet draws a play mark on. A
634
- picture that could not be copied, was bigger than the site's upload limit or
635
- was never named leaves the citation as it was, with no picture. A post whose
636
- front matter says `preview: false`, which the editor's remove control writes,
637
- prints the plain citation, and so do its listing entry and its feed items.
638
- There is never an iframe or a script from the cited page.
639
-
640
- **A cited URL that is itself an image** (TASK-255), one that answers with an
641
- `image/*` type rather than a page, is copied as its own picture of kind
642
- `photo` and has no name, so `context.image` is true and the line says "an
643
- image from" its host as the link: "Reposted an image from edu.casio.com". A
644
- repost shows it in full like a reposted GIF, a like or a bookmark as a
645
- thumbnail beside "Liked an image from edu.casio.com", and a post that hid its
646
- preview still says it reposted an image. Its alt text is the post's
647
- `cited-alt`, which the editor's card writes, else the post's title, else
648
- empty. An image that could not be copied, or was bigger than the site's
649
- upload limit, keeps no context, so its citation says "a page on" its host.
650
-
651
- The feeds open each
652
- citing post's HTML with the same citation as a `p.cite-line`, the picture
653
- after it with an absolute `src`.
654
-
655
- **A read says what was read** (TASK-229) with `partials/read.njk`, a
656
- `p.read-line` at the top of the `e-content` that reads like indiebookclub's
657
- own: a `data.p-read-status` whose value is `to-read`, `reading` or `finished`
658
- and whose text is Want to read, Currently reading or Finished reading, then a
659
- `span.p-read-of.h-cite` with the title as a `cite.p-name` (linked as its
660
- `u-url` when the post gives one), the author as a `p-author`, and the
661
- identifier as a `data.p-uid` whose value is the `isbn:` or `doi:` uid. Its
662
- kicker and its hidden `h1` say Read. A listing prints each entry's from the
663
- same partial. The partial prints `read.line`, the same HTML the feeds and the
664
- federated Note open with (TASK-233); `read.status`, `read.statusLabel` and
665
- `read.of` are there for a theme that marks it up differently.
666
-
667
- The tags are printed under the words and inside the article, so that each link
668
- is a `p-category` of this entry, as one `p.post-categories` from
669
- `partials/tags.njk`. The stylesheet draws the `#` in front of each, so the
670
- category a parser reads is the tag and not the hash.
671
-
672
- **A post with a recording plays it** (TASK-213), from
673
- `partials/recording.njk`, between the header and the `e-content`. The main file
674
- is the browser's own player, `<audio controls>` or `<video controls>` as
675
- `enclosure.player` says, with `preload="metadata"` so a reader who never presses
676
- play downloads nothing, and a download link inside it for a browser that plays
677
- neither. The `<source>` carries `u-audio` or `u-video`, so a microformats parser
678
- finds the file on the entry. A video whose transcript is WebVTT also gets it as
679
- a `<track kind="captions">`. Under the player a `figcaption` links the
680
- transcript, as Captions for VTT or SRT and Transcript otherwise, and lists the
681
- other versions by their title, else their media type. A post without a
682
- recording, or whose front matter is missing what a player needs, prints none of
683
- it.
684
-
685
- **A post with photos shows them** (TASK-166), from `partials/photos.njk`, after
686
- the recording and before the `e-content`, each in a `figure.post-photo`. Each is
687
- the context's `photo.html`: an `<img class="u-photo">` with its alt text, so a
688
- microformats parser finds every photo on the entry, and a `<picture>` with the
689
- upload's resizes when it has them. The alt text is the post's own, else the
690
- media library's. A listing prints them the same way above each entry, and the
691
- JSON-LD `BlogPosting` lists each as an `ImageObject` with its absolute `url`
692
- and its alt text as `caption`. The first photo is the post's share image when
693
- its front matter names no `image`.
694
-
695
- ```html
696
- <figure class="post-recording">
697
- <audio controls preload="metadata" aria-label="Recording of Episode twelve">
698
- <source
699
- class="u-audio"
700
- src="/uploads/2026/10/episode-12.mp3"
701
- type="audio/mpeg"
702
- />
703
- <a href="/uploads/2026/10/episode-12.mp3">Download the recording</a>
704
- </audio>
705
- <figcaption class="post-recording-links">
706
- <p><a href="/uploads/2026/10/episode-12.vtt">Captions</a></p>
707
- <p id="post-recording-versions">Other versions</p>
708
- <ul aria-labelledby="post-recording-versions">
709
- <li>
710
- <a href="/uploads/2026/10/episode-12.mp4" type="video/mp4">Video</a>
711
- </li>
712
- </ul>
713
- </figcaption>
714
- </figure>
715
- ```
716
-
717
- After the entry a post prints its backlinks, then `nav.blog-post-nav`: the
718
- `previous` and `next` posts as two cards, `rel="prev"` and `rel="next"`, each
719
- opening on a `span.blog-post-nav-label`, and nothing at all at the ends of the
720
- archive; then the conversation and the comment form. A page prints its
721
- backlinks too, then the conversation and the comment form when it takes
722
- comments, and the contact form when its front matter asked for one. A page has
723
- no neighbours, no tags and no syndication links, because none of those are
724
- things a page has.
725
-
726
- ### Backlinks
727
-
728
- A link from one of the site's posts or pages to another sends no webmention.
729
- The CMS records it instead, and the linked document's context carries
730
- `backlinks`: the listed posts and pages whose body links to it, newest first,
731
- each as `{ title, url, date }`. `title` is the linking document's title, or a
732
- note's first words. `date` is absent for a page that has none. A link counts
733
- whether it is absolute or relative, with or without its trailing slash, a query
734
- or a fragment, and through a `redirect_from` of the target. A link to `/`
735
- counts for the page the Reading setting makes the homepage. A link to a URL the
736
- site answers with a redirect counts for the document the redirect leads to,
737
- whether the redirect is a `redirect_from` or an entry in `_data/redirects.json`
738
- or `_data/redirects/*.json`, `/?p=123` included. A link with a query names the
739
- query's redirect only when the query matches the redirect's source exactly, as
740
- a request does, so `/?p=123&ref=feed` is a link to `/`. A document that links
741
- to itself is not its own backlink. Drafts, future-dated, unlisted,
742
- private and trashed documents never appear. `backlinks` is always a list, and
743
- it is empty when nothing links to the document.
744
-
745
- `partials/backlinks.njk` prints a `section.backlinks` under the entry, apart
746
- from the conversation: an `h2` that says "Linked from", then an `ol` of links,
747
- each with its date in a `<time>`. It prints nothing for an empty list, so
748
- `layouts/post.njk` and `layouts/page.njk` include it with no condition. The
749
- front page has `backlinks` on its context too, but `layouts/front-page.njk`
750
- does not print them, because most of a site links home. A site theme that wants
751
- them there adds the same include to its own `layouts/front-page.njk`:
752
-
753
- ```njk
754
- {% include "partials/backlinks.njk" %}
755
- ```
756
-
757
- A site theme moves the list by including the partial somewhere else in its own
758
- layout, and leaves it out by overriding a layout without the include. To change
759
- the markup, add a `partials/backlinks.njk` to the site theme. That file replaces
760
- this one, as any other template is replaced.
761
-
762
- ### The bio
763
-
764
- `partials/bio.njk` is who the page is by: the `p-author h-card` the rule and
765
- the footer at the end of an entry hold, and the heading of an author archive.
766
- It replaces `partials/byline.njk`, which is gone — a byline was a name in a
767
- meta line, and this is the whole credit.
768
-
769
- ```njk
770
- {% set bioAuthor = author or siteAuthor %}
771
- {% include "partials/bio.njk" %}
772
- ```
773
-
774
- `bioAuthor` is the one thing to set, and nothing renders when it is absent.
775
- It is one of the profile objects the context already carries: `author` on a
776
- post, falling back to `siteAuthor`; `siteAuthor` on a page; the archive's
777
- person on an author archive.
778
-
779
- What the bio prints: a round `u-photo` at 50px when they have an avatar,
780
- "Written by" and their name as a `p-name u-url` linked `rel="author me"` to
781
- their archive, then `p-job-title` and `p-locality` when the profile says, then
782
- their `p-note` and their `rel="me"` links as a `ul.hlist.bio-links` — each
783
- link's `rel` is on it ready to print, `me` first and then whatever else the
784
- Links box typed. Set `bioRelMe` to `false` to keep the card and drop its
785
- claims: the name is linked `rel="author"` alone and `me` comes off every
786
- link.
787
-
788
- **The homepage** speaks for the site's author only on a site whose Site author
789
- setting names a user, which `soloAuthor` on the context says. Then
790
- `layouts/base.njk` puts that person's `rel="me"` claims in the head of `/` as
791
- `<link rel="me">` elements, one for their author archive and one for each
792
- profile link, whichever layout draws the page (decision-45). Their author
793
- archive's link home carries `rel="me"` back, so the homepage and the archive
794
- name each other. `layouts/home.njk` at `/` also ends with that person's card,
795
- with the same claims, and sets `bioHome` to the homepage's absolute URL, which
796
- puts a `data.u-url.u-uid` holding it first in the card, so a parser running the
797
- [representative h-card algorithm](https://microformats.org/wiki/representative-h-card-parsing)
798
- on the homepage finds this card as the site's (TASK-193). The archive stays a
799
- second `u-url`. No other page sets `bioHome`. A static front page prints no
800
- card: see [The front page](#the-front-page) for how a site puts one back. On a
801
- site with several authors neither homepage prints a card or makes a claim. Each of
802
- those is printed only when the profile says it, so a profile holding a name
803
- alone prints a name alone. A name this site has no account for is printed
804
- unlinked, because the file still said somebody wrote this.
805
-
806
- **The person and only the person.** The site menu used to be printed here as
807
- well, because the design had no header navigation; it is in the header on
808
- every page now — see [Navigation](#navigation) — so the bio says nothing about
809
- the site. The note and the links used to be behind a `bioProfile` switch that
810
- an author archive set and an entry did not, on the grounds that the page
811
- footer already carried the site's identity links. It carries nobody's, so the
812
- switch is gone and the card is the same wherever it appears: at the top of
813
- somebody's archive, and under each of their posts.
814
-
815
- `bioLead` is what the line opens with, `Written by` unless a layout sets
816
- another; `layouts/author.njk` sets `Posts by`, because the card there heads
817
- somebody's writing rather than crediting one piece of it.
818
-
819
- **A site that used `partials/byline.njk`** — `{{ byline.line(author) }}` from
820
- an overridden layout — either includes this partial instead or writes the line
821
- itself. It was four lines of markup, and the object behind it has not changed:
822
-
823
- ```njk
824
- {% set writer = author or { name: site.title } %}
825
- <span class="p-author h-card">
826
- {%- if writer.url %}<a class="u-url" href="{{ writer.url | url }}">{{ writer.name }}</a>
827
- {%- else %}{{ writer.name }}{% endif %}
828
- </span>
829
- ```
830
-
831
- `apps/demo/themes/demo/layouts/post.njk` is that, worked: the demo wants a
832
- byline under the title rather than a bio in the footer, so it writes one.
833
-
834
- ### A listing
835
-
836
- `partials/post-list.njk` is the feed every listing is made of (the home page,
837
- the posts page, a tag, category or author archive, and a front page that
838
- lists `newestPosts`) and it is a microformats2 `h-feed` of `h-entry` items, each opening on
839
- its kicker:
840
-
841
- ```html
842
- <div class="feed h-feed">
843
- <article class="feed-item h-entry">
844
- <p class="kicker">
845
- <span class="kicker-kind">Article</span>
846
- <span class="kicker-dot" aria-hidden="true">·</span>
847
- <time class="feed-date dt-published" datetime="…">2 September 2026</time>
848
- <span class="kicker-dot" aria-hidden="true">·</span>
849
- <a href="/category/notes/" class="p-category" rel="category">notes</a>
850
- </p>
851
- <h2 class="feed-title p-name">
852
- <a href="/2026/09/hello/" class="u-url">Hello</a>
853
- </h2>
854
- <div class="feed-excerpt p-summary">
855
- <p>What the post is about, in about 280 characters...</p>
856
- </div>
857
- <p class="feed-more">
858
- <a href="/2026/09/hello/" aria-label="Continue reading: Hello">
859
- Continue reading<span aria-hidden="true"> &rarr;</span>
860
- </a>
861
- </p>
862
- </article>
863
- <hr class="feed-separator" />
864
- <article class="feed-item h-entry">
865
- <p class="kicker">
866
- <span class="kicker-kind">Note</span>
867
- <span class="kicker-dot" aria-hidden="true">·</span>
868
- <a href="/2026/09/coffee/" class="u-url"
869
- ><time class="feed-date dt-published" datetime="…"
870
- >3 September 2026</time
871
- ></a
872
- >
873
- </p>
874
- <div class="feed-excerpt e-content"><p>The whole note.</p></div>
875
- </article>
876
- </div>
877
- ```
878
-
879
- **Only an article has a headline.** A note, which has no title, is printed
880
- whole as its `e-content`, and the date in its kicker is its `u-url`. A reply is
881
- a note that first cites what it answers with `partials/reply-context.njk`, and
882
- a reply with a title is an article that cites it after its title and summary.
883
- A like, a repost and a bookmark place `partials/citations.njk` the same way,
884
- and one with a title and no words is listed by its title too. An article's categories are in
885
- its kicker; a note's are on its own page.
886
-
887
- The excerpt is the entry's `summary`, the very line the feeds publish, so a
888
- reader and a feed cannot be told two different things, through Nunjucks'
889
- `truncate(280)`, which cuts at the last space before 280 characters and adds an
890
- ellipsis. A `description` shorter than that is printed whole. It is plain text
891
- rather than markup, deliberately: an excerpt with half a code block in it is
892
- not an excerpt.
893
-
894
- Each entry of a listing carries `replyContext` beside `inReplyTo` when it is a
895
- reply whose target has been fetched, the same object a reply's own page gets,
896
- so the feed can cite it in full.
897
-
898
- An entry's tags are not in a feed item. They belong under the entry, where
899
- there is one post's worth of them rather than twenty.
900
-
901
- **The heading level belongs to the layout**, not to the list. `feedHeading` is
902
- the level `post-list.njk` prints its titles at and defaults to 2, which is what
903
- a listing wants; a page whose feed sits under a heading of its own sets 3
904
- before including it, so the outline never skips a level:
905
-
906
- ```njk
907
- {% set feedHeading = 3 %}
908
- {% include "partials/post-list.njk" %}
909
- ```
910
-
911
- A listing with nothing on it says `No posts found.` in `p.empty`.
912
-
913
- `partials/pagination.njk` prints two links in `nav.pagination`, `← Previous`
914
- to the page before this one and `Next →` to the page after it, with `rel="prev"`
915
- and `rel="next"`, and nothing at all on a listing of one page. The arrows
916
- themselves are `aria-hidden`, because a screen reader that announced them would
917
- read the decoration and then the word.
918
-
919
- Each listing layout heads its own page: `layouts/home.njk` with the listing's
920
- title, and the three archives with a `header.archive-header` whose kicker says
921
- what kind of archive it is (Tag, Category, Author) over an `h1.page-title`
922
- naming whose: the term, or the person, followed by their `h-card`. The
923
- category archive's header is also a `header.category-header` and prints a
924
- `div.category-description` under the name when the context carries a
925
- `categoryDescription`; the CMS has no store of term descriptions, so nothing
926
- writes one today and the header is the heading alone.
927
-
928
- `layouts/404.njk` says `Content not found.` under a `Not found` kicker and
929
- links home and to the search. The 500 and 503 pages below open on a kicker the
930
- same way.
931
-
932
- `layouts/410.njk` is the page at the URL of a post or page that was moved to
933
- the trash (TASK-195). It says `This content has been deleted.` under a
934
- `Deleted` kicker and links home and to the search. The context is the 404's:
935
- `title`, `url` and `page.url`, and nothing of the deleted document. Its `.md`
936
- and `.json` representations answer 410 too. A trashed draft was never public,
937
- so its URL is a 404 instead.
938
-
939
- `layouts/500.njk` is the page a request gets when the server fails while
940
- answering it (TASK-129). It says `Something went wrong.` in an
941
- `article.server-error` and links home. The context is the 404's: `title`,
942
- `url` and `page.url`. The response is a 500 with `Cache-Control: no-store`, and
943
- the error itself goes to the server's log, never into the page. If this template
944
- fails too, the CMS answers with a plain HTML page of its own instead, so a theme
945
- cannot turn one failure into a crash. A request for the `.md` or `.json`
946
- representation gets a 500 in that format and never reaches this template.
947
-
948
- `layouts/503.njk` is the page a request gets while the site is in maintenance
949
- mode (TASK-130). It says `Down for maintenance.` in an `article.maintenance`.
950
- The context is the 404's, plus `until` (an ISO 8601 time) and `untilText` (the
951
- same time as an HTTP date) when the operator named when the site should be
952
- back. The response is a 503 with `Retry-After` and `Cache-Control: no-store`.
953
- The theme's files under `/theme/` are still served, so the page can use the
954
- stylesheet. If this template fails, the CMS answers with a plain HTML page of
955
- its own.
956
-
957
- ### Search
958
-
959
- `layouts/search.njk` is the page at `/search/` (TASK-22). It prints
960
- `partials/search-form.njk`, and once there is a `query` a `p.search-summary`
961
- saying how many documents matched, a `div.search-results` of
962
- `article.search-result` entries, and the ordinary pager. It asks not to be
963
- indexed with `<meta name="robots" content="noindex">` in its `head` block.
964
-
965
- The context is a listing's with two differences. `query` is the words searched
966
- for, trimmed, and an empty string on the page before a search. Each entry in
967
- `posts` carries a `snippet`: a few words of
968
- HTML around the match, escaped, with every matched word in `<mark>`. Print it
969
- with `safe`. The results are best match first rather than newest first, and
970
- only what the public site would serve is ever among them.
971
-
972
- `partials/search-form.njk` is a `GET` form to `/search/` with the words in `q`,
973
- so it needs no JavaScript and a search is a URL. This layout is the only thing
974
- that includes it: the box used to be in the footer of every page as well, and
975
- search is a menu item now — a `Search | /search/` line like any other. The
976
- search page takes the path `/search/` ahead of any document permalinked there.
977
-
978
- ### The front page
979
-
980
- `layouts/front-page.njk` is what a site gets at `/` once the Reading settings
981
- name a homepage (`homepage` in `content/_data/site.json`). It is that page read
982
- somewhere else: the same document, the same context, with `page.url` saying `/`
983
- rather than the permalink that redirects there.
984
-
985
- It draws the page's own title and words, and nothing else (decision-44):
986
-
987
- 1. `h2.section-title`, the page's title. It is an `h2` because
988
- `layouts/base.njk` heads the root path with the site title as the `h1`.
989
- 2. `div.page-body.e-content`, the rendered body. There is no Published line,
990
- because a front page is read as the site rather than as a page somebody
991
- wrote on a Tuesday.
992
- 3. The contact form, when the page's front matter says `contact: true`.
993
-
994
- It lists no posts, prints no line of links and prints no bio. A site that wants
995
- any of them on its front page overrides `layouts/front-page.njk` in its own
996
- theme. Two context keys are the front page's alone, for that override:
997
-
998
- - `newestPosts(count)` is a function. It returns the newest `count` published
999
- posts as entries, in the same shape a listing's `posts` are, so
1000
- `partials/post-list.njk` prints them. Drafts, future-dated, unlisted and
1001
- trashed posts are left out. The theme chooses the count. The query runs only
1002
- when a template calls it, so a front page that lists nothing costs nothing.
1003
- A count that is not a whole number of at least 1 is an error.
1004
- - `postsPage` is `{ title, url }` for the page carrying the listing, and is
1005
- **absent** when the site names none, because a link to a listing that has no
1006
- URL is a link to nothing.
1007
-
1008
- `newestPosts` replaces `recentPosts`, which held this month's posts when there
1009
- were at least five and the five newest otherwise. An override that read
1010
- `recentPosts` calls `newestPosts(5)` instead. This override draws the page's
1011
- words, the five newest posts, a line of links and the site author's card as
1012
- the site's representative h-card:
1013
-
1014
- ```njk
1015
- {% extends "layouts/base.njk" %}
1016
-
1017
- {% block content %}
1018
- <div class="page-body e-content">
1019
- {{ content | safe }}
1020
- </div>
1021
-
1022
- <h2 class="section-title">Recent Posts</h2>
1023
- {% set posts = newestPosts(5) %}
1024
- {% set feedHeading = 3 %}
1025
- {% include "partials/post-list.njk" %}
1026
-
1027
- <p class="front-links">
1028
- {% if postsPage %}
1029
- <a href="{{ postsPage.url | url }}">{{ postsPage.title }}<span aria-hidden="true"> &rarr;</span></a>
1030
- {% endif %}
1031
- <a href="{{ "/search/" | url }}">Search<span aria-hidden="true"> &rarr;</span></a>
1032
- </p>
1033
-
1034
- {% if soloAuthor %}
1035
- {% set bioAuthor = soloAuthor %}
1036
- {% set bioHome = "/" | absoluteUrl %}
1037
- <hr>
1038
- <footer>
1039
- {% include "partials/bio.njk" %}
1040
- </footer>
1041
- {% endif %}
1042
- {% endblock %}
1043
- ```
1044
-
1045
- The stylesheet still styles `h2.section-title` and `p.front-links`, so an
1046
- override can use both. Whatever the override prints, the solo author's
1047
- `rel="me"` claims stay in the head of `/` (decision-45).
1048
-
1049
- ### An archive page
1050
-
1051
- A page whose front matter says `archive: true` prints every post the site has
1052
- published under its own words, newest month first:
1053
-
1054
- ```html
1055
- <section class="archive">
1056
- <h2>September 2026</h2>
1057
- <ol class="list-none">
1058
- <li>
1059
- <a href="/2026/09/hello/"><span>Hello</span></a>
1060
- </li>
1061
- </ol>
1062
- <h2>August 2026</h2>
1063
- …
1064
- </section>
1065
- ```
1066
-
1067
- `partials/archive.njk` draws it and `layouts/page.njk` includes it. It is the
1068
- whole archive on one page and nothing paginates it: an archive page is a way of
1069
- finding one piece of writing rather than a listing to read through, which is
1070
- also why there are no excerpts on it. Drafts and posts whose date has not
1071
- arrived are absent, as they are everywhere else.
1072
-
1073
- `archiveMonths` is what it loops over, and it is on the context **only** for a
1074
- page whose front matter says `archive: true`, so a layout asks:
1075
-
1076
- ```njk
1077
- {% if archiveMonths %}
1078
- {% include "partials/archive.njk" %}
1079
- {% endif %}
1080
- ```
1081
-
1082
- Each entry of it is `{ month, posts }`: `month` is the heading, `September
1083
- 2026`, and `posts` is `{ title, url, date }` for each post of it, newest first.
1084
- Which month a post belongs to is the CMS's answer rather than the template's,
1085
- because a date in a file is a UTC instant and the site's `timezone` is the lens
1086
- it is read through — so the grouping and the date printed under an entry are
1087
- one decision rather than two.
1088
-
1089
- The key is honoured wherever it is written, exactly as `contact: true` is: a
1090
- theme that wants the list on some other kind of page adds the same three lines
1091
- to that layout.
1092
-
1093
- ### The head
1094
-
1095
- Beside the title, the canonical link and the feeds, every page carries a
1096
- description, Open Graph and Twitter card tags, the site's icons and one
1097
- JSON-LD graph. All of it is in the `head` block, so an override that only means
1098
- to add a tag calls `{{ super() }}` first.
1099
-
1100
- **The description** is the page's own `description` from the front matter, else
1101
- the entry's `summary` — the line the feeds publish — else `site.tagline`. It is
1102
- printed once, as `<meta name="description">`, and the Open Graph and Twitter
1103
- descriptions say the same thing.
1104
-
1105
- **The colours** come from the theme's `theme.json` (see
1106
- [The manifest](#the-manifest)), on the context as `theme`.
1107
- `<meta name="color-scheme">` comes before the stylesheet, so a reader in dark
1108
- mode gets a dark canvas rather than a white flash while the stylesheet loads.
1109
- `<meta name="theme-color">` is printed twice, once with
1110
- `media="(prefers-color-scheme: light)"` and once for dark.
1111
-
1112
- **The card.** `og:title` is the page's title, or the site's on the front page;
1113
- `og:site_name` is always the site's. `og:type` is `article` on a rendered post
1114
- or page and `website` everywhere else, a listing carrying a page's front matter
1115
- included. `og:url` is the canonical URL: the page's own, or the `original` a
1116
- post first published elsewhere names. An article also carries
1117
- `article:published_time`, `article:modified_time` (the `updated` date, else the
1118
- publish date), `article:author` (the author's profile URL here, else their
1119
- name) and one `article:tag` per tag.
1120
-
1121
- **The picture** is the first of these that the page has, on the context as
1122
- `shareImage`:
1123
-
1124
- 1. The `image` in the entry's front matter.
1125
- 2. `site.avatar`, which only a hand edit of `site.json` sets.
1126
- 3. The profile photo of the person the page is by: the writer of a post or page
1127
- that names an author, the user of an author archive, and the site's author
1128
- on a solo-author site's homepage and other pages about nobody in particular.
1129
- 4. The site icon, from `icon` in `site.json`.
1130
-
1131
- A page with none of these prints no `og:image` and no `twitter:image` rather
1132
- than an empty one. The picture always carries `og:image:alt` and
1133
- `twitter:image:alt`. The alt text is what the media library says about the
1134
- upload, else words for what it shows. For the entry's own picture, its
1135
- `imageAlt` front matter comes first, and the entry's title last. For the
1136
- avatar, the last resort is the site's author; for a photo, that person's
1137
- display name; for the icon, the site's title. `og:image:width` and
1138
- `og:image:height` are printed when the image's variants have been derived,
1139
- because the size is read from their sidecar rather than from the file.
1140
- `twitter:card` is `summary_large_image` for a picture at least 1200 pixels wide
1141
- and wider than it is tall. Everything else, a square photo included, is
1142
- `summary`, the small picture beside the words.
1143
-
1144
- **The icons** come from the site's `icon` setting in `site.json`, else
1145
- `site.avatar`, through the derived images (decision-10). `icon` exists for a
1146
- site whose avatar is a face and whose mark is a logo. The head links `icon` at
1147
- 32 and 16 pixels and `apple-touch-icon` at 180, each a square PNG cropped from
1148
- the middle of the picture and encoded the first time a browser asks for it. An
1149
- SVG icon is linked first as itself, with `type="image/svg+xml"` and no
1150
- `sizes`, and the PNGs are rasterised from it. They are on the context as
1151
- `icons`, a list of `{ rel, sizes, type, href }`, which is empty, and the links
1152
- are not printed at all, when the site has neither setting, when it names a
1153
- file no icon can be made of, or when image optimization is off.
1154
-
1155
- **The favicon and the manifest** are routes, not templates. `/favicon.ico`
1156
- holds 16, 32 and 48 pixel frames drawn from the same picture, because browsers
1157
- and crawlers ask the root for it whatever the head links; it answers 404 on a
1158
- site with no icon. `/manifest.webmanifest` gives the site's title as `name` and
1159
- `short_name`, `start_url` `/`, `display` `minimal-ui`, the theme's light
1160
- `themeColor` as `theme_color` and `background_color`, and icons at 192 and 512
1161
- pixels plus a maskable 512 whose picture sits inside the middle 80%, padded
1162
- with its own dominant colour. The head links it on every page.
1163
-
1164
- **The search description** is a route too. `/opensearch.xml` is an OpenSearch
1165
- 1.1 description that names the site by its title (cut to the 16 characters
1166
- `ShortName` allows), describes it by its tagline, else its title, shows
1167
- `/favicon.ico` as its `Image` when the site has an icon, and searches
1168
- `/search/?q={searchTerms}`. Every URL in it is absolute and carries the base
1169
- path. The head links it on every page with `<link rel="search"
1170
- type="application/opensearchdescription+xml">`, outside the `head` block, so a
1171
- browser offers the site as a search engine. A theme that replaces `base.njk`
1172
- copies that line to keep the offer.
1173
-
1174
- **The embed card** is a route too. `/_geekity/oembed?url=…` is an oEmbed
1175
- provider: for the URL of a published post or page it answers a `rich` embed
1176
- with the title, the author and their archive, the site as provider, a
1177
- thumbnail when the post's own `image` has a recorded size, and an `html`
1178
- card that is a blockquote of escaped text and links with no script, followed
1179
- by a hidden, sandboxed `<iframe>` of `/_geekity/embed?url=…`. That route is
1180
- the same card as a page of its own, and it is the one response another site may
1181
- frame. WordPress keeps a card from a provider it does not know only when the
1182
- card has an iframe, and it shows the frame once the page inside reports its
1183
- height. A consumer that strips iframes still has the blockquote. `maxwidth`
1184
- and `maxheight` are honoured, `format=xml` gives the same fields as XML, and
1185
- any other URL is a 404. The head of each post and page links it with
1186
- `<link rel="alternate" type="application/json+oembed">` and its `text/xml+oembed`
1187
- twin, outside the `head` block. A theme that replaces `base.njk` copies those
1188
- lines to keep the card.
1189
-
1190
- **The structured data** is `partials/jsonld.njk`, one `<script
1191
- type="application/ld+json">` holding one `@graph` per page, and it is the only
1192
- structured data the theme emits — there is no Microdata anywhere, by
1193
- decision-16, because the visible markup already carries microformats2 for the
1194
- IndieWeb. The graph holds:
1195
-
1196
- - `WebSite`, always, with the site's title, tagline and URL, and a
1197
- `potentialAction` that is a `SearchAction` on
1198
- `/search/?q={search_term_string}`. On a site whose author is a user, its
1199
- `publisher` and `about` both point at that user's Person, on every page. On
1200
- a site with several authors its `publisher` is the Organization below and it
1201
- has no `about`. With a site license, `license` is its URL.
1202
- - `Organization`, on a site with several authors only: the site itself, named
1203
- for its title, at `{baseUrl}/#organization`. It publishes the `WebSite` and
1204
- every entry.
1205
- - `Person`, from `siteAuthor`: their name, archive URL, avatar, bio, job title
1206
- and location, and a `sameAs` of their profile links and their actor id, which
1207
- is what asserts that the schema.org Person and the fediverse actor are one
1208
- identity. Identity comes from a user profile, so a site — or a byline —
1209
- naming nobody with an account here prints no Person, and the `author`
1210
- reference goes with it. A site with several authors prints no Person on a
1211
- page about nobody in particular.
1212
- - `ProfilePage` on an author archive, whose `mainEntity` is that Person.
1213
- - `BlogPosting` on a post and `Article` on a page, with the headline, URL,
1214
- `mainEntityOfPage`, `datePublished`, `dateModified`, description, image,
1215
- `author`, and the site's `publisher`: the site author's Person, or the
1216
- Organization. A post with no title is headed by the words its `<title>`
1217
- names it by, its `label`, cut on a word boundary to at most 110 characters.
1218
- A post with no words either is labelled by what it is (TASK-261): "Liked",
1219
- "Reposted", "Bookmarked" or "Reply to" and the cited page's stored title,
1220
- else the host its citation line names ("Liked a page on www.youtube.com"), and
1221
- for a photo post its first photo's alt text, cut to ten words like a note's
1222
- (TASK-264), or "Photo" when that photo has none. The kicker still says Photo
1223
- and no heading is printed. "Untitled" is the last fallback.
1224
- Its `image` is the picture the Open Graph tags print, so the two always
1225
- agree (see [The head](#the-head)). Its `license` is the page's license URL,
1226
- the front matter's over the site's, and absent with none.
1227
- - `BreadcrumbList` wherever the page prints a breadcrumb (see
1228
- [The page shell](#the-page-shell)): one `ListItem` per crumb with its
1229
- `position`, `name` and absolute URL as `item`.
1230
-
1231
- A site that wants a different graph — more types, an `Organization` publisher,
1232
- nothing at all — writes its own `partials/jsonld.njk` and that file replaces
1233
- this one, like any other partial.
1234
-
1235
- ### Prefetching and page transitions
1236
-
1237
- Two tags near the top of `<head>` make moving between pages feel faster. Both
1238
- are progressive enhancement: a browser that does not support one ignores it.
1239
-
1240
- **Speculation rules.** The `speculationRules` block prints one `<script
1241
- type="speculationrules">` that asks the browser to prefetch a page the reader
1242
- is about to open. It covers links to this site's own pages, at `moderate`
1243
- eagerness, which in Chrome is a hover of about 200 milliseconds or the start of
1244
- a tap. It never prefetches:
1245
-
1246
- - `/admin` and anything under `/admin/`, which includes `/admin/logout`.
1247
- - Any path with `logout` in it.
1248
- - A feed: `/feed/`, `/feed/atom/`, `/feed/json/`, and any other path that has
1249
- a `/feed/` segment, such as a tag's feed or a post's comments feed.
1250
- - A file under `/uploads/`.
1251
- - A URL with a query, such as `/search/?q=words`.
1252
-
1253
- Every pattern is written through the `url` filter, so a site served from a
1254
- subdirectory prefetches only its own pages.
1255
-
1256
- **No rules reach a signed-in reader.** The CMS removes every `<script
1257
- type="speculationrules">` from a page it draws for somebody signed in, the same
1258
- page that carries [the admin bar](#the-admin-bar). That page is drawn for one
1259
- person and never stored, so a prefetch of it is wasted work. A site theme that
1260
- writes its own rules does not have to check who is reading.
1261
-
1262
- **View transitions.** The `viewTransitions` block prints a `<style>` that opts
1263
- into cross-document view transitions. The browser cross-fades from one page of
1264
- the site to the next, with no JavaScript. The rule is inside `@media
1265
- (prefers-reduced-motion: no-preference)`, so a reader who asks for reduced
1266
- motion gets no transition:
1267
-
1268
- ```css
1269
- @media (prefers-reduced-motion: no-preference) {
1270
- @view-transition {
1271
- navigation: auto;
1272
- }
1273
- }
1274
- ```
1275
-
1276
- A site theme that wants a different animation adds `::view-transition-*` rules
1277
- to its own stylesheet.
1278
-
1279
- **Turning either off.** Override its block with nothing in it:
1280
-
1281
- ```njk
1282
- {% extends "layouts/base.njk" %}
1283
-
1284
- {% block speculationRules %}{% endblock %}
1285
- {% block viewTransitions %}{% endblock %}
1286
- ```
1287
-
1288
- ### Colours
1289
-
1290
- Every colour is a custom property on `:root`, and these tokens are the
1291
- override contract: a site that only wants different colours redefines them in
1292
- its own stylesheet rather than replacing this one. Tailwind's own palette is
1293
- cleared in `src/style.css`, and its utilities (`text-ink`, `bg-paper`,
1294
- `border-edge` and the rest) are mapped onto these properties with
1295
- `@theme inline`, so redefining a token recolours every rule that uses it.
1296
-
1297
- A second scheme under `@media (prefers-color-scheme: dark)` redefines the same
1298
- tokens on dark paper, and `color-scheme: light dark` lets a browser paint its
1299
- own form controls and scrollbars to match. There is no toggle: the reader's
1300
- system setting is the setting.
1301
-
1302
- Both schemes meet the Colour contrast rule of the
1303
- [specification.website checklist](https://specification.website/checklist.md):
1304
- every run of text, the small kickers, dates and meta lines included, at 7:1
1305
- (WCAG 1.4.6, AAA) on whatever it sits on, and every border a reader relies on
1306
- (a field, a card, a code block, the focus outline, the citation and blockquote
1307
- rules) at 3:1 (1.4.11). Nothing a reader needs is drawn at reduced opacity.
1308
- That is a test rather than a claim: `src/web/theme-colors.test.ts` reads the
1309
- tokens out of the compiled stylesheet and computes the ratio of every pair the
1310
- design puts on screen, so a colour changed in `src/style.css` that breaks one
1311
- fails the build.
1312
-
1313
- | Token | Utility | What it colours |
1314
- | ------------------------- | ---------- | ---------------------------------------------------------------------- |
1315
- | `--color-body` | `paper` | The paper, and the words on a submit button. |
1316
- | `--color-text` | `ink` | Body text and headlines. |
1317
- | `--color-muted` | `muted` | Kickers, dates, meta lines, decks, quotes, the bio note, the footer. |
1318
- | `--color-primary` | `accent` | Links, a kicker's kind word, the citation rule, the submit button. |
1319
- | `--color-secondary` | `accent-2` | Tag links and the focus outline. |
1320
- | `--color-rule` | `rule` | Decorative hairlines only: between entries, above the footer. |
1321
- | `--color-edge` | `edge` | Borders a reader relies on: fields, the previous and next cards, code. |
1322
- | `--color-base` | `surface` | A sunk surface: fields, notices, a face with no avatar. |
1323
- | `--color-base-2` | — | A cooler sunk surface. |
1324
- | `--color-base-3` | — | The raised surface: the focused skip link. |
1325
- | `--color-code-background` | `code` | Behind `code` and a fenced block. |
1326
- | `--color-code-text` | — | Code with no highlighting on it. |
1327
- | `--color-error` | `error` | A field that will not do, and what the form says about it. |
1328
-
1329
- `--color-rule` is the one token in no contrast pair, because it is decoration:
1330
- a site that recolours it may make it as faint as it likes. Anything drawn with
1331
- `--color-edge` has to stay at 3:1 against the paper and the surface.
1332
-
1333
- ### Code highlighting
1334
-
1335
- The CMS renders a fenced block as `<pre tabindex="0"><code
1336
- class="language-x">` and highlights nothing: highlighting is a theme's, and it
1337
- happens on the client (decision-16). The `tabindex` is the core's doing and is
1338
- there on every `<pre>`, because a block wider than the measure scrolls sideways
1339
- and a scroll container nothing can focus cannot be read from a keyboard.
1340
-
1341
- This theme highlights with **[highlight.js](https://highlightjs.org/)**,
1342
- self-hosted at `static/highlight.js` and pinned — nothing is fetched from a
1343
- CDN. The `scripts` block of `layouts/base.njk` loads it, deferred, **only on a
1344
- page whose rendered body holds a `language-` class**, so a page with no code on
1345
- it ships no JavaScript at all. Auto-detection is off: a block with no language,
1346
- or one naming a language the bundle does not carry, is left exactly as the
1347
- renderer wrote it — its class, its plain text, no highlighting.
1348
-
1349
- The bundle carries the core and eighteen grammars — `bash`, `css`, `diff`,
1350
- `dockerfile`, `go`, `ini`, `javascript`, `json`, `markdown`, `nginx`, `php`,
1351
- `python`, `rust`, `shell`, `sql`, `typescript`, `xml` and `yaml`, with their
1352
- aliases, so `js`, `ts`, `html`, `yml` and `sh` all work. It is a build product
1353
- that is nevertheless committed, so a site gets a working highlighter out of the
1354
- package with no build step. Rebuilding it is one command:
1355
-
1356
- ```
1357
- pnpm --filter @geekity/cms build:highlight
1358
- ```
1359
-
1360
- Run that after bumping the `highlight.js` devDependency or editing the language
1361
- list in `scripts/build-highlight.js`, and commit what it writes.
1362
- `src/web/highlight-bundle.test.ts` fails when the committed file was built from
1363
- a different version of highlight.js than the one installed.
1364
-
1365
- **The palette is Tomorrow**, the one both of the sites this design comes from
1366
- used: Chris Kempson's Tomorrow on light paper and Tomorrow Night on dark, from
1367
- highlight.js's own base16 themes, which is also what decides which `hljs-`
1368
- scope takes which slot. None of the light colours is the stock one: a syntax
1369
- theme is drawn for an editor, where a faint comment is a feature, and here code
1370
- is text like any other and meets 7:1 on its block, the bar every run of text
1371
- on the page meets. Each light colour was darkened along its own hue until it
1372
- did and no further, and four of the dark ones (the comment grey, the red, the
1373
- blue and the `base0F` brown) were lightened toward white the same way. Every one
1374
- of them is in the contrast test beside the rest of the palette.
1375
-
1376
- | Token | base16 | What it colours |
1377
- | --------------------------------- | -------- | ------------------------------------------ |
1378
- | `--color-code-comment` | `base03` | Comments. |
1379
- | `--color-code-tag` | `base04` | Markup tags. |
1380
- | `--color-code-name` | `base08` | Variables, names, selectors, list bullets. |
1381
- | `--color-code-literal` | `base09` | Numbers, constants, attributes, links. |
1382
- | `--color-code-class` | `base0A` | Class names, bold. |
1383
- | `--color-code-string` | `base0B` | Strings and inline code. |
1384
- | `--color-code-support` | `base0C` | Built-ins, regexps, quotes. |
1385
- | `--color-code-function` | `base0D` | Function names, headings, attributes. |
1386
- | `--color-code-keyword` | `base0E` | Keywords, types, italic. |
1387
- | `--color-code-meta` | `base0F` | Preprocessor and embedded-language lines. |
1388
- | `--color-code-added-background` | — | An added line in a diff. |
1389
- | `--color-code-removed-background` | — | A removed line in a diff. |
1390
-
1391
- Delimiters, operators and substitutions are deliberately `--color-code-text`,
1392
- the block's own ink: they are most of the characters on screen and a colour
1393
- there is noise. A `diff` block reads by the line rather than by the token, the
1394
- way the source site's `prism-diff.css` did: `.hljs-addition` and
1395
- `.hljs-deletion` take a tint out to the edges of the block and keep the ink, so
1396
- the `+` and the `-` in the text carry the meaning as well as the colour does.
1397
-
1398
- **Swapping it.** Four sizes of change, smallest first:
1399
-
1400
- - _Another palette, same highlighter._ Redefine the dozen `--color-code-*`
1401
- tokens. Note that `static/style.css` is an all-or-nothing override: a theme
1402
- that ships its own stylesheet owns these rules too, and a fenced block on it
1403
- is unhighlighted until it writes them.
1404
- - _Other languages._ Edit `LANGUAGES` in `scripts/build-highlight.js`, rebuild,
1405
- and commit — or put your own `static/highlight.js` in your theme, which the
1406
- `/theme/` route resolves before the packaged one.
1407
- - _Another highlighter._ Override the `scripts` block with your own tag. The
1408
- markup is `pre > code.language-x`, which is what every highlighter reads.
1409
- - _None._ Override the `scripts` block with nothing in it.
1410
-
1411
- ```njk
1412
- {% extends "layouts/base.njk" %}
1413
-
1414
- {% block scripts %}{% endblock %}
1415
- ```
1416
-
1417
- ### Videos
1418
-
1419
- A YouTube or Vimeo URL on a line of its own in a post plays as the video
1420
- (decision-34; doc-2 lists the addresses it knows). The core renders it into the
1421
- body, so it reaches a theme inside `content` as:
1422
-
1423
- ```html
1424
- <figure class="video-embed video-embed-youtube">
1425
- <iframe
1426
- src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
1427
- width="560"
1428
- height="315"
1429
- title="YouTube video"
1430
- loading="lazy"
1431
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
1432
- referrerpolicy="strict-origin-when-cross-origin"
1433
- allowfullscreen
1434
- ></iframe>
1435
- <figcaption>
1436
- <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
1437
- >https://www.youtube.com/watch?v=dQw4w9WgXcQ</a
1438
- >
1439
- </figcaption>
1440
- </figure>
1441
- ```
1442
-
1443
- It is one line in the real output. A Vimeo video is the same with
1444
- `video-embed-vimeo`, a `https://player.vimeo.com/video/ID?dnt=1` player and the
1445
- title `Vimeo video`. The core sets no style on it: with nothing from the theme
1446
- the player is the providers' standard 560 by 315.
1447
-
1448
- **Paper** makes it responsive. `.video-embed iframe` takes the full width of
1449
- the text column at 16:9, with the rounded corners an image has, and the
1450
- figcaption takes the muted caption style every figure in a post has. A theme
1451
- that ships its own `static/style.css` writes that rule itself:
1452
-
1453
- ```css
1454
- .video-embed iframe {
1455
- width: 100%;
1456
- height: auto;
1457
- aspect-ratio: 16 / 9;
1458
- }
1459
- ```
1460
-
1461
- The figcaption is the link a feed reader, a fediverse server or the embed view
1462
- keeps when it drops the iframe. A theme that would rather not show it on the
1463
- page can hide `.video-embed figcaption`; the feeds carry it either way.
1464
-
1465
- ## Mail templates
1466
-
1467
- The messages the CMS sends live under `mail/` and resolve the same way, so a
1468
- `mail/test.txt.njk` in the theme the site wears replaces the text of the test
1469
- message and leaves its subject and HTML twin coming from the package. Each message is up to three
1470
- files:
1471
-
1472
- | File | What it is |
1473
- | ------------------------- | -------------------------------------------------------------- |
1474
- | `mail/<name>.subject.njk` | The subject. Rendered to a single line. Optional. |
1475
- | `mail/<name>.txt.njk` | The plain text body. **Required.** |
1476
- | `mail/<name>.html.njk` | The HTML twin. Optional; without it the message is plain text. |
1477
-
1478
- A message that has no `.txt.njk` anywhere on the search path is an error rather
1479
- than an empty email, so a typo in a template name is reported instead of sent.
1480
-
1481
- The package ships seven messages:
1482
-
1483
- | Name | When it goes | What `data` carries |
1484
- | ------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
1485
- | `test` | Send test email, on Settings. | Nothing. |
1486
- | `password-reset` | Somebody asked to reset a password. | `username`, `resetUrl`, `expiresAt`, `expiresInHours`. |
1487
- | `password-changed` | A reset link was used. | `username`, `signedOut` (how many sessions ended). |
1488
- | `comment-pending` | A comment or webmention is waiting for approval. | `comment`, `post` (`title`, `url`), `actions` (one `{ action, label, url }` each for approve, spam and delete), `queueUrl`. |
1489
- | `comment-digest` | An hourly or daily digest of what is waiting. | `items` (one `{ comment, post, actions }` each), `total` waiting, `more` beyond the ones listed, the `mode` that asked for it, `queueUrl`. |
1490
- | `comment-reply` | A reply to somebody's comment was approved. | `reply`, `comment` (the one it answers), `post`, `unsubscribeUrl`. |
1491
- | `contact-message` | Somebody filled in a page's contact form. | `message` (`id`, `subject`, `text`, `received`), `from` (`name`, `email`), `page` (`slug`, `permalink`, `title`, `url`), `messagesUrl`. |
1492
-
1493
- The `comment` inside each of a digest's `items` is the same shape as
1494
- `comment-pending`'s, and its `actions` are the same three links, minted per
1495
- recipient because each is spent the first time it is used. A digest is built
1496
- from the queue as it stands when it is sent, so an item moderated in the
1497
- meantime is simply not in it, and no digest is sent to somebody with an empty
1498
- queue.
1499
-
1500
- `comment` and `reply` above are the same shape: `author`, `website`, `source`
1501
- (`comment` or `webmention`), `kind`, `text`, `html`, `submitted`, and `url`.
1502
- The commenter's email address is deliberately **not** among them — the message
1503
- goes to a moderator, but it also goes through a mail provider's servers, and
1504
- nothing on that screen needs it.
1505
-
1506
- The links in `actions` and `unsubscribeUrl` each carry a signed token and work
1507
- without a login. Print them; do not try to build one.
1508
-
1509
- `contact-message` is sent with reply-to set to the sender, so answering it in a
1510
- mail reader answers the person who wrote it. `message.text` is plain text as it
1511
- was typed — print it, do not render it as Markdown or as HTML.
1512
-
1513
- The subject and the plain text body render with autoescaping **off**, because
1514
- neither is HTML: `&` between two query parameters stays an `&`, and an
1515
- apostrophe in somebody's name stays an apostrophe. The HTML twin escapes as
1516
- every other template does.
1517
-
1518
- The context is `site` (that is `content/_data/site.json`), `baseUrl`, and
1519
- whatever the feature that sent it passed as `data`. The `date`, `url` and
1520
- `absoluteUrl` filters are the same ones a page has, so a message can write a
1521
- link with `{{ "/admin/" | absoluteUrl }}`.
1522
-
1523
- Writing `mail/welcome.txt.njk` into the theme is enough to add a message this
1524
- package never shipped; nothing has to be registered.
1525
-
1526
- ## Template context
1527
-
1528
- The context mirrors what an Eleventy layout receives, so a layout ported from
1529
- an Eleventy build needs few edits. It is part of the package's semver contract.
1530
-
1531
- Every template gets:
1532
-
1533
- | Key | What it holds |
1534
- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1535
- | `site` | `content/_data/site.json`, if the site has one, over the defaults `title` and `url`. Any key in the file is readable, so `site.tagline`, `site.author` and anything else a site adds are all available. |
1536
- | `menus` | Every menu the site stores, by name, marked for this page: `menus.primary`, `menus.footer`, and any other name. See [Navigation](#navigation). |
1537
- | `siteAuthor` | Who the page is by, as a profile. **Absent** when nobody matches. See [Bylines and author archives](#bylines-and-author-archives). |
1538
- | `soloAuthor` | The site author's profile, on every page of a site whose Site author setting names a user. **Absent** on a site with several authors. See [The bio](#the-bio). |
1539
- | `icons` | The site's icons, as `{ rel, sizes, type, href }`. Empty until the site has an icon or an avatar to derive them from. See [The head](#the-head). |
1540
- | `theme` | The colours the theme declares, as `{ colorScheme, themeColor: { light, dark } }`, each absent when no theme declares it. See [The manifest](#the-manifest). |
1541
- | `shareImage` | The picture a shared link shows, as `{ url, alt, size: { width, height }, card }`. `size` is absent when it is not known. **Absent** when there is no picture. See [The head](#the-head). |
1542
- | `llmsTxt` | The path of `/llms.txt`, while the site serves it. **Absent** when the site has turned it off. The default theme links it from the front page only. |
1543
- | `license` | The page's license as `{ name, url }`: the page's `license` front matter, else the site's. **Undefined** with none. It replaces the raw front matter value on the context. |
1544
- | `siteLicense` | The site's own license as `{ name, url }`, whatever the page says. **Undefined** with none. |
1545
-
1546
- A document — one post, one page, or one entry of a listing — adds:
1547
-
1548
- | Key | What it holds |
1549
- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
1550
- | `title` | Display title. |
1551
- | `date` | Publish date, a JavaScript `Date` at the UTC instant the file holds. Absent when the document has none. |
1552
- | `updated` | Last modified date, a `Date`, when the front matter has one. |
1553
- | `tags` | The document's tags, in file order. |
1554
- | `categories` | The document's categories, in file order. |
1555
- | `content` | The Markdown body rendered to HTML. Print it with `\| safe`. |
1556
- | `summary` | Its `description` as plain text, else an excerpt of the body, else empty. The line the feeds publish. |
1557
- | `url` | The document's URL path, the same value as `page.url`. |
1558
- | `page.url` | The document's URL path. Always ends in `/`. |
1559
- | `page.date` | The same `Date` as `date`. |
1560
- | `page.fileSlug` | The permalink's last segment. |
1561
- | `page.inputPath` | The source file, relative to the content directory. |
1562
- | `type` | `post` or `page`. |
1563
- | `permalink`, `slug`, `draft`, `description` | Straight from the front matter. |
1564
- | `lang` | The `lang` front matter as a canonical tag, such as `fr-CA`. Absent when it names none or no tag. |
1565
- | `enclosure` | The post's recording, parsed, with `player` (`audio` or `video`) and `transcript.captions`. Absent with none. |
1566
- | `photos` | The post's photos, each `{ url, alt, html }`; `html` is the `img.u-photo`, responsive. Empty with none. |
1567
- | `citations` | What it reposts, likes or bookmarks, each `{ property, url, context }`; `context` as `replyContext`, when known. |
1568
- | `read` | A read post's `{ status, statusLabel, of, line }`; `of` is `{ name, author, uid, uidLabel, url }`. |
1569
- | `rsvp` | An RSVP's `{ value, label, line }`: `yes`, `no`, `maybe` or `interested`, its words, and its `p-rsvp` line. |
1570
- | `event` | An event's `{ start, end, location }`: `Date`s, and `{ kind: "place", name }` or `{ kind: "virtual", url }`. |
1571
- | `syndicateTo` | On a post's page, the syndication targets it selects, each `{ id, name, url }`. Empty with none. |
1572
- | `syndication` | Its copies elsewhere, each `{ url, label }`: front matter `syndication`, and on its page the copies targets made. |
1573
- | `original` | Where it was first published, `{ url, label }`: `canonical_href`, named by `canonical_name` or its host. |
1574
- | `author` | Who wrote it, as a profile rather than a string. See [Bylines and author archives](#bylines-and-author-archives). |
1575
- | `activityStreams` | The post's ActivityPub object id, absolute. Only on a rendered published post. |
1576
- | `previous` | The published post before this one by date, as `{ title, url }`. Absent on the oldest post. |
1577
- | `next` | The published post after it. Absent on the newest post, and on a page. |
1578
- | `backlinks` | The site's listed posts and pages that link to it, newest first, each `{ title, url, date }`. Empty with none. |
1579
- | `newestPosts` | A function, front page only: `newestPosts(5)` is the five newest published posts, as entries. |
1580
- | `postsPage` | The page carrying the listing, as `{ title, url }`, on the front page only. Absent when the site names none. |
1581
- | `archiveMonths` | Every published post as `{ month, posts }`, newest month first. Only on a page that says `archive: true`. |
1582
- | `webmention` | Where a webmention about this page is sent. Only on a rendered document, and only while the site takes them. |
1583
- | `noindex` | `true` on an unlisted or unpublished page, which the base layout prints as a robots `noindex` meta. Else absent. |
1584
- | `conversation` | The replies, likes and boosts under the post. Only when there are any. See [The conversation](#the-conversation). |
1585
- | everything else | Any front matter key the CMS does not model is on the context under its own name. |
1586
-
1587
- A listing — the home page, a tag archive, a category archive or an author
1588
- archive — adds:
1589
-
1590
- | Key | What it holds |
1591
- | ------------------------------------------ | --------------------------------------------------------------------------------- |
1592
- | `title` | The site title on the home page, the term on a taxonomy archive. |
1593
- | `posts` | The documents on this page of the listing, newest first, each in the shape above. |
1594
- | `pagination.items` | The same array, under the name Eleventy gives it. |
1595
- | `pagination.pageNumber` | Zero-based index of this page, as in Eleventy. |
1596
- | `pagination.totalPages`, `.total`, `.size` | How many pages, how many documents, how many per page. |
1597
- | `pagination.href.first/last/next/previous` | Pager URLs. `next` and `previous` are `null` at the ends. |
1598
- | `pagination.pages` | Every page's URL, in order. |
1599
- | `tag` | The tag, on a tag archive only. |
1600
- | `category` | The category, on a category archive only. |
1601
- | `author` | The person, on an author archive only. Same shape as a post's `author`. |
1602
-
1603
- How many posts a listing page holds comes from `postsPerPage` in
1604
- `content/_data/site.json`, and defaults to 10. How many entries a feed holds
1605
- comes from `feedSize` in the same file, and defaults to 20. The `<html lang>`
1606
- comes from `language`, and defaults to `en`. A document whose `lang` names
1607
- another language carries it on its own `<article>`, in an entry, a listing and
1608
- search results alike, through `partials/lang.njk`, and the dates inside that
1609
- article are written in it (see the `date` filter). The rssCloud and WebSub server the
1610
- feeds advertise comes from `notifyServer`, and is empty for none; the theme
1611
- writes nothing for it, because a cloud is advertised in the feed rather than on
1612
- the page.
1613
-
1614
- ## Bylines and author archives
1615
-
1616
- `author` on a document is the person the front matter names, resolved against
1617
- the site's users. The fields come from their profile on **Users** in the admin,
1618
- which is the one place identity is written:
1619
-
1620
- | Key | What it holds |
1621
- | ----------------- | -------------------------------------------------------------------------------------------------------------- |
1622
- | `author.name` | What to print: their display name, else their username, else the raw name the file gives. |
1623
- | `author.url` | Their archive, `/author/{username}/`. **Absent** when the name is nobody this site has. |
1624
- | `author.username` | Their login. Absent for the same reason `url` is. |
1625
- | `author.bio` | What they wrote about themselves, when they wrote any. |
1626
- | `author.avatar` | Their picture, as a path or URL. Absent when they have none. |
1627
- | `author.jobTitle` | What they do, when their profile says. |
1628
- | `author.location` | Where they are, as they wrote it. |
1629
- | `author.links` | `{ label, href }` for each link on their profile, in the order they listed them. Absent when they listed none. |
1630
-
1631
- `author` is absent altogether when the document names no author, so anything
1632
- printing it is `{% if author %}`. Guard the link with `{% if author.url %}`: a
1633
- file may name somebody who has no account here — a guest post, or a colleague
1634
- whose account has gone — and that name is still printed, it simply links
1635
- nowhere. [The bio](#the-bio) does both, and is what the theme prints instead of
1636
- the `partials/byline.njk` it used to have.
1637
-
1638
- `layouts/author.njk` is that person's archive, at `/author/{username}/`, with
1639
- their pages at `/author/{username}/page/2/` and their three feeds under
1640
- `/author/{username}/feed/`. It is headed with their name and then the bio —
1641
- the avatar, the note and the `rel="me"` links, the same card that ends each of
1642
- their posts — and lists their published posts newest first. The source design has no author archive, so
1643
- the heading is the CMS's own; the h-card under it is the one partial the theme
1644
- has. A user with no profile still has one; they are called by their username.
1645
-
1646
- `siteAuthor` is the same object on a different question: not who wrote this
1647
- document, but who the page in front of the reader is by. It is what the bio and
1648
- any structured data a theme emits should both read, so that what a reader sees
1649
- and what a machine reads cannot drift apart. The footer does not read it at
1650
- all: the site's links are `menus.footer`, typed rather than borrowed from an
1651
- account. It resolves in this order:
1652
-
1653
- - the document's own `author`, on a post or a page that names one;
1654
- - the person whose archive it is, on an author archive;
1655
- - the site author, `soloAuthor`, everywhere else — the home page, a taxonomy
1656
- archive, a page that names nobody, the 404.
1657
-
1658
- It is **absent** when none of those name anybody this site has, which on a
1659
- site with several authors is every page about nobody in particular. Write
1660
- `{% if siteAuthor %}` around the bio.
1661
-
1662
- The URL is not only a page. Each user is an ActivityPub actor at that address,
1663
- so it is the page a follower lands on when they click through from the
1664
- fediverse. `author` is therefore a reserved first URL segment, like `tag` and
1665
- `feed`: no document can be permalinked under it and no taxonomy base can take
1666
- it.
1667
-
1668
- ## Navigation
1669
-
1670
- A site stores its menus by name in `content/_data/site.json`:
1671
-
1672
- ```json
1673
- {
1674
- "menus": {
1675
- "primary": [
1676
- { "label": "Home", "url": "/" },
1677
- { "label": "About", "url": "/about/" }
1678
- ],
1679
- "footer": [
1680
- { "label": "Colophon", "url": "/colophon/" },
1681
- { "label": "Mastodon", "url": "https://example.social/@me", "rel": "me" }
1682
- ]
1683
- }
1684
- }
1685
- ```
1686
-
1687
- Every template gets them as `menus`, keyed by the same names, already in order
1688
- and already knowing which item is the page being looked at:
1689
-
1690
- ```njk
1691
- {% for item in menus.footer %}
1692
- <a href="{{ item.url | url }}">{{ item.label }}</a>
1693
- {% endfor %}
1694
- ```
1695
-
1696
- That is the whole interface, so a theme renders a menu this one has never heard
1697
- of by looping over the name a site stored it under.
1698
-
1699
- ### The areas a theme declares
1700
-
1701
- `theme.json` says where a theme renders a menu, so the Navigation screen can
1702
- offer those places and nowhere else. This theme declares two:
1703
-
1704
- ```json
1705
- {
1706
- "name": "Default",
1707
- "kind": "site",
1708
- "areas": [
1709
- { "name": "primary", "label": "Site menu" },
1710
- { "name": "footer", "label": "Footer links" }
1711
- ]
1712
- }
1713
- ```
1714
-
1715
- `name` is the key under `menus`, in `site.json` and on the context. `label` is
1716
- what a person choosing an area reads; leave it out and it falls back to the
1717
- name. Declare a third area and a site can fill it in; declare none and the
1718
- theme renders no menu.
1719
-
1720
- The declaration is for the screen, not for the render. Every menu the site
1721
- stores is on the context whether or not a theme declared an area for it — a
1722
- menu nothing loops over is rendered nowhere and kept, which is what lets
1723
- somebody write the menu a theme will use before switching to that theme. An
1724
- `areas` that is missing, or that will not read, leaves the theme with none
1725
- rather than failing to load it.
1726
-
1727
- ### One item
1728
-
1729
- Each item is `{ label, url, current }`, plus `rel` on a link that carries `rel`
1730
- values. `url` is a site-root path or an absolute URL for somewhere else, so put
1731
- it through the `url` filter and a site served from a subdirectory still links
1732
- correctly. `current` is true for the item whose path is the one being rendered,
1733
- comparing without the trailing slash; an item pointing off the site is never
1734
- current. `rel` is one string holding every value the line carried, already
1735
- lower case and each said once — print it as the whole attribute,
1736
- `rel="{{ item.rel }}"`, rather than building one of your own. `me` is the value
1737
- most menus have, for the IndieWeb's identity check: Mastodon verifies a link on
1738
- a profile by looking for a `rel="me"` link back, so a footer link to a profile
1739
- marked `me` is what makes the tick appear.
1740
-
1741
- ### Where this theme prints them
1742
-
1743
- `partials/menu.njk` holds the markup, as one macro:
1744
-
1745
- ```njk
1746
- {% import "partials/menu.njk" as nav %}
1747
- {{ nav.list(menus.footer, "Footer") }}
1748
- ```
1749
-
1750
- The second argument is the `aria-label`, which is what tells a reader on a
1751
- screen reader which of a page's menus this one is. Nothing is printed for a
1752
- menu with nothing in it.
1753
-
1754
- `menus.primary` is printed once, in `header.global-header`, on every page
1755
- (TASK-105). At the root, where the header is the site title with the tagline
1756
- under it, the menu is a line of its own under the tagline; everywhere else,
1757
- where the header is the small link home, the stylesheet lays the header out as
1758
- one line and the menu sits beside it. Nothing decides between two places any
1759
- more: a reader looks in the same spot whatever they are reading, and a layout
1760
- that overrides the `content` block cannot lose the navigation by not printing
1761
- a bio.
1762
-
1763
- `menus.footer` is printed in the `footer` block of `layouts/base.njk`, under
1764
- the copyright line, and is the whole of what the footer links — a feed, a
1765
- colophon, a webring, a profile marked `me`. The footer reads nothing off an
1766
- account: it used to print one `rel="me"` link per entry of `siteAuthor.links`,
1767
- which meant one nominated user's links stood for the site and nobody else's
1768
- appeared at all.
1769
-
1770
- Every menu reads the same: a link in one is the colour, the underline and the
1771
- size of a link in the page's prose, in the sans heading font a `nav` is set in
1772
- (TASK-111). A menu is navigation somebody is meant to use rather than a
1773
- footnote, so none of them is set smaller than the words around it, and the same
1774
- goes for the `rel="me"` links `partials/bio.njk` prints under a post. The two
1775
- links that read as plain text instead are the site title and
1776
- `a.header-link-home`, which are the page saying where it is rather than
1777
- somewhere to go, and the stylesheet names those two rather than reaching for
1778
- every link that happens to sit in a `<header>`.
1779
-
1780
- `menus.primary` is the whole of the site menu, in the order the Navigation
1781
- screen names it. Both areas are declared in this theme's `theme.json`, which is
1782
- what puts them on `/admin/navigation` with those labels; a theme that renders a
1783
- third menu declares a third area. A page cannot put itself in a menu; a page
1784
- that should be linked — including the one a site serves as its front page,
1785
- typed `Home | /` — is typed into the box there.
1786
-
1787
- Because the menus live in `site.json`, an Eleventy build of the same content
1788
- renders the same ones; `docs/eleventy.config.example.js` builds them as
1789
- `collections.menus`.
1790
-
1791
- ## Feeds
1792
-
1793
- `partials/feeds.njk` holds two macros. `feedLinks(root, title)` writes the three
1794
- `rel="alternate"` links for a listing's RSS, Atom and JSON feeds — RSS first,
1795
- because it is what most subscribers hold:
1796
-
1797
- ```njk
1798
- {% import "partials/feeds.njk" as feeds %}
1799
- {{ feeds.feedLinks("/", site.title) }}
1800
- ```
1801
-
1802
- `root` is any listing URL ending in `/`; the macro appends `feed/`, `feed/atom/`
1803
- and `feed/json/` and puts each through the `url` filter, so the links carry the
1804
- base path of a site served from a subdirectory. The arguments carry everything
1805
- the macro needs, so it can be imported without `with context`.
1806
-
1807
- `commentsFeedLink(feed, title)` writes the one link a comments feed has. It
1808
- takes the feed's own URL rather than a root, because a comments feed is RSS 2.0
1809
- and nothing else:
1810
-
1811
- ```njk
1812
- {{ feeds.commentsFeedLink("/comments/feed/", site.title + " comments") }}
1813
- ```
1814
-
1815
- `layouts/tag.njk`, `layouts/category.njk` and `layouts/author.njk` each call
1816
- `feedLinks` over their own root, so an archive advertises its own three feeds
1817
- as well as the site's — on an author archive that root is `author.url`.
1818
-
1819
- `layouts/base.njk` fills the `alternates` block with `feedLinks` over `/` and
1820
- then the site's comments feed, so every page that extends it advertises all
1821
- four. On a published post, and on a page that takes comments, it adds that
1822
- document's own comments feed, from `commentsFeed` on the context. On a
1823
- published post it also adds a link pointing at the post's ActivityPub object:
1824
-
1825
- ```html
1826
- <link
1827
- rel="alternate"
1828
- type="application/activity+json"
1829
- href="https://example.com/2026/09/hello/"
1830
- />
1831
- ```
1832
-
1833
- A post's object id is its permalink, so that link usually points at the page it
1834
- is on: it says the URL answers ActivityStreams as well as HTML. A post migrated
1835
- from elsewhere keeps the id its file names, and the link points there instead.
1836
- It comes from `activityStreams`, which is only on the context of a rendered
1837
- published post, as `commentsFeed` is only on a document that has a comments
1838
- feed, so a layout that overrides the block and does not call `super()` has to
1839
- emit both itself. They are on the context rather
1840
- than in `post.njk` so that a site which overrides that layout, as the demo
1841
- does, keeps them.
1842
-
1843
- After the block, and outside it, the base layout writes the webmention
1844
- endpoint:
1845
-
1846
- ```html
1847
- <link rel="webmention" href="/_geekity/webmention" />
1848
- ```
1849
-
1850
- It comes from `webmention` on the context, which is on a rendered document only
1851
- when the site is taking webmentions, so a theme asks `{% if webmention %}` and a
1852
- site that has turned them off advertises nothing. The CMS also sends the same
1853
- endpoint as a `Link` header on every representation of a document, so a sender
1854
- that does not parse HTML still finds it.
1855
-
1856
- The pingback endpoint follows it, as an absolute URL because the spec asks for
1857
- one:
1858
-
1859
- ```html
1860
- <link rel="pingback" href="https://example.com/_geekity/pingback" />
1861
- ```
1862
-
1863
- It comes from `pingback` on the context, which is there only when the site
1864
- takes webmentions and the document can be answered: every post, and a page
1865
- only while it takes comments. The same endpoint goes out in an `X-Pingback`
1866
- header under the same rule.
1867
-
1868
- On the front page it also writes the site's index for language models:
1869
-
1870
- ```html
1871
- <link rel="describedby" type="text/markdown" href="/llms.txt" />
1872
- ```
1873
-
1874
- It comes from `llmsTxt` on the context, which is absent once the site turns the
1875
- file off. The CMS sends the same link as a `Link` header on the front page.
1876
-
1877
- `layouts/tag.njk` and `layouts/category.njk` override that block, call `super()`
1878
- and add the archive's own three feeds:
1879
-
1880
- ```njk
1881
- {% import "partials/feeds.njk" as feeds %}
1882
- {% set tagRoot = "/" + (site.tagBase or "tag") + "/" + (tag | lower | urlencode) + "/" %}
1883
- {% block alternates %}
1884
- {{ super() }}
1885
- {{ feeds.feedLinks(tagRoot, site.title + ": " + tag) }}
1886
- {% endblock %}
1887
- ```
1888
-
1889
- A layout that does not extend the base one has to emit the links itself. The
1890
- feeds are generated by the CMS, not by a template, so nothing here decides what
1891
- goes in them.
1892
-
1893
- Category archives get their own three the same way.
1894
-
1895
- ## The conversation
1896
-
1897
- `conversation` is what has been said about a post: the fediverse replies, likes
1898
- and boosts its inbox was sent, the fediverse posts that quoted it with its
1899
- author's approval, the comments people left on the page itself, and the
1900
- webmentions other pages sent it.
1901
- It is on the context of a rendered post **only when there is something in it**,
1902
- so a post nobody has answered renders no empty section and a layout can simply
1903
- ask:
1904
-
1905
- ```njk
1906
- {% if conversation %}
1907
- {% include "partials/conversation.njk" %}
1908
- {% endif %}
1909
- ```
1910
-
1911
- That is what `layouts/post.njk` does, and `layouts/page.njk` with it.
1912
-
1913
- A page takes part only while it takes comments: when its front matter says
1914
- `comments: true` and the site has not switched comments off. A page is closed
1915
- by default, as WordPress closes them, and the CMS leaves `conversation` off the
1916
- context of a closed page however much has been approved against it, so a page
1917
- that turns comments off shows nothing. A post is different: it keeps its thread
1918
- after it closes. Webmentions to a page are received and moderated either way,
1919
- exactly as webmentions to a post are, and appear once the page opens.
1920
-
1921
- `partials/conversation.njk` is the whole
1922
- section — a `div.reactions-section` of the likes, the boosts and the mentions
1923
- as facepiles grouped by kind, and then the thread as
1924
- `div#comments.comments-area` — and a site replaces it with a
1925
- `partials/conversation.njk` of its own in the theme it wears, exactly as it
1926
- replaces any other template. It defines two macros, `face(item, icon, href)`
1927
- and `group(items, label, kind, icon, source)`, and a layout that wants to place
1928
- the pieces itself can import them:
1929
-
1930
- ```njk
1931
- {% import "partials/conversation.njk" as reactions with context %}
1932
- {{ reactions.group(conversation.likes, "Likes", "p-like", "❤️") }}
1933
- ```
1934
-
1935
- One reply and everything under it is `comment(reply, classes)` in
1936
- `partials/comment.njk`, which the thread and a comment's own page both import.
1937
- `classes` is the reply's microformat: `h-entry`, the default, under a post, and
1938
- `p-comment h-cite` on a comment's page, where the comment is the one entry.
1939
-
1940
- A reply post prints its replies inside its own `h-entry`, as `p-comment
1941
- h-cite`s, so the page it answers finds them there when it is sent a salmention
1942
- (TASK-320). `layouts/post.njk` includes `partials/conversation.njk` twice for
1943
- one, setting `conversationPart`: `"replies"` inside the entry for the thread,
1944
- and `"reactions"` below it for the facepiles, whose links are not the entry's.
1945
- Left unset, the partial prints the whole section.
1946
-
1947
- ### The shape
1948
-
1949
- The conversation is deliberately **not** spelled in ActivityPub's vocabulary.
1950
- Native comments and webmentions land in the same thread, so every
1951
- entry says where it came from and what it is, and nothing else about it changes
1952
- with the source. A theme that never looks at `source` renders all of them
1953
- correctly; one that does can style them apart. The one thing a theme does have
1954
- to look at is `kind`: a `mention` is in `conversation.mentions` rather than in
1955
- the thread, because it is not an answer.
1956
-
1957
- | Key | What it holds |
1958
- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
1959
- | `replies` | The replies to the post, oldest first, each carrying its own `replies`. See below. |
1960
- | `likes` | The likes, oldest first, in the same shape. |
1961
- | `boosts` | The boosts, oldest first, in the same shape. A webmention `repost` is one of them. |
1962
- | `mentions` | The pages and fediverse quotes about this post, oldest first, in the same shape. |
1963
- | `rsvps` | An event's answers, each group `{ value, label, people }`, going, maybe, interested, not going, only with people in it. |
1964
- | `counts.replies` | How many replies, counted through the whole thread rather than the top of it. |
1965
- | `counts.likes`, `counts.boosts`, `counts.mentions` | How many of each. |
1966
- | `counts.rsvps` | How many people answered an event, in every group. |
1967
- | `counts.total` | All of them added up. Zero never reaches a template: there would be no `conversation` at all. |
1968
-
1969
- Each entry — a reply, a like or a boost — is:
1970
-
1971
- | Key | What it holds |
1972
- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1973
- | `id` | What it is called: a fediverse reply's note id, a reaction's activity id, a stored comment's key. It is what an answer to it names. |
1974
- | `key` | Its key (decision-51): 16 lowercase hex digits, the comment's id for one stored on the site and the key of its guid for anything else. Its anchor is `#comment-{key}` and its replies feed `/replies/{key}/`. |
1975
- | `guid` | What the comments feeds call it: a native comment's `/comment/{key}/` page, a stored comment's origin (else its `url`), a fediverse note's id, a reply post's object id. |
1976
- | `migrated` | `true` for a comment imported from the site this one replaced (decision-51). It shows like any other, but no feed carries it and no count a feed advertises includes it. |
1977
- | `source` | `"activitypub"` for a fediverse reply, `"comment"` for one left on the page, `"webmention"` for another page linking here, `"post"` for a reply post a user of the site wrote (its `url` is the post's permalink). |
1978
- | `kind` | `"reply"`, `"like"`, `"boost"`, `"repost"` or `"mention"`. A `repost` comes from a webmention; a `mention` is a webmention or an approved fediverse quote. |
1979
- | `author.name` | The best name available: their display name, else their handle, else their server, else their id. For a webmention, the source's `h-card` name, else its host. |
1980
- | `author.handle` | `@user@host`, or `null`. Taken from the follower or actor profile the site holds, else guessed from the actor URL unless it ends in a number. `null` for a native comment. |
1981
- | `author.url` | Their profile page, the website a commenter typed, or a webmention author's `u-url`. May be `null`, so guard the link. |
1982
- | `author.avatar` | Their avatar, or `null`. The site knows one for a fediverse actor whose profile it holds or whose imported comment brought their icon, and for a webmention whose `h-card` carried a `u-photo`. Always a same-origin `/_geekity/avatars/…` path, never the remote URL: see [Avatars](#avatars). |
1983
- | `author.actorId` | Their id, which is what identifies them however they are named. `null` for a native comment, and for an imported one whose actor the export did not name. |
1984
- | `author.username` | The site user they are, or `null`: whoever wrote a reply post, or the user a comment imported from the site this one replaced names (a WordPress comment written signed in). Such a comment's `author` is that user's, as a reply post's is: their display name, avatar and author archive. |
1985
- | `url` | Where it can be read: the remote note's `url` for a fediverse reply or quote, the source page for a webmention, and `/comment/{key}/`, its own page, for a native comment (see [A comment's own page](#a-comments-own-page)). |
1986
- | `content` | What it says, **already sanitised**, so print it with `\| safe`. Empty for a like or a boost. |
1987
- | `published` | A `Date`: when it was published, or when it arrived if it did not say. Use the `date` filter. |
1988
- | `inReplyTo` | What it answers — the post's ActivityPub id, or another reply's — and `null` for a reaction. |
1989
- | `status` | `"published"`. On the record for the sources that moderate. |
1990
- | `replies` | The replies to this one, oldest first, nested as deep as the site has seen. One may be a placeholder: see below. |
1991
- | `rsvp` | An RSVP: `{ value, label }`, its `p-rsvp` (`yes`, `no`, `maybe` or `interested`) and the words for it. Absent otherwise. A webmention reply that is an RSVP carries one, and the default theme prints it as a `data.p-rsvp` at the top of the comment's content. On an event, an RSVP is in `rsvps` rather than the thread, and so is a fediverse `Accept` (`yes`), `TentativeAccept` (`maybe`) or `Reject` (`no`) of the event. Each person is there once, with their latest answer. |
1992
-
1993
- Four rules decide what is in the thread, and they are the CMS's rather than a
1994
- theme's: a reply whose author deleted it is gone, and its own answers move up to
1995
- whatever it was answering; a comment a reader may not see (waiting for a
1996
- moderator, spam or deleted) that has a visible reply under it is a placeholder,
1997
- and one with none is not there at all; a like is counted once per actor,
1998
- so an imported like and the same actor's like
1999
- delivered here are one, and it disappears when that actor undoes it; and a note answering something this post
2000
- has nothing to do with is left out.
2001
-
2002
- A fediverse reply or reaction imported from the site this one replaced is
2003
- shown once, however often its server sends it again or the reply backfill
2004
- reads it: the imported comment is the one in the thread, with the moderator's
2005
- decision on it, and the answers to its note thread under it.
2006
-
2007
- A placeholder in `replies` is `{ id, withheld: true, replies }`: the hidden
2008
- comment's id and the replies under it, and nothing that names its author or
2009
- says what it said. Test `reply.withheld` before printing an author. A pending
2010
- or spam comment's placeholder sits where the comment would; a deleted one's
2011
- under the post, since nothing is known of what it answered. `counts.replies`
2012
- and the comments feeds count and carry the visible replies only.
2013
-
2014
- `content` is safe to print whatever produced it, and safe for different reasons.
2015
- A fediverse reply is HTML somebody else's server composed, rebuilt from an
2016
- allowlist — `a`, `p`, `br`, lists, `blockquote`, `pre`, `code` and the inline
2017
- emphasis tags, with every link carrying `rel="nofollow noopener noreferrer"`. A
2018
- webmention's `e-content` goes through the same allowlist. A native comment is
2019
- Markdown the commenter typed, rendered with raw HTML off, no images embedded,
2020
- and every link carrying `rel="nofollow ugc"`. Nothing else survives any of the
2021
- three routes, so a theme may print all of them directly.
2022
-
2023
- The packaged partial draws each entry as the source design does: an
2024
- `li.comment.h-entry` carrying `id="comment-{{ reply.key }}"` and a
2025
- `comment-{{ reply.source }}` class, holding an `article.comment-body` whose
2026
- `footer.comment-meta` is the author as a `.comment-author.vcard.p-author.h-card`
2027
- (their avatar, or the first letter of their name in a `span.comment-avatar-empty`
2028
- when there is none, and their link marked `rel="nofollow ugc"` unless `author.username` says the
2029
- author is a user of the site, as a reply post's author is) and the permalink as a `.comment-metadata` link around a
2030
- `time.dt-published`, with a `span.comment-source` saying "via webmention" or
2031
- "via the fediverse" when it came that way, then the words in
2032
- `div.comment-content.e-content`. Answers nest in an
2033
- `ol.children` inside what they answer. A Reply link in a `div.reply` goes on the
2034
- ones written here — `source == "comment"` — when the post is still open; it
2035
- carries the comment's key to the form as `?reply_to=`, so threading needs no
2036
- JavaScript. A fediverse reply is answered on the server that holds it and a
2037
- webmention on the page that sent it, so neither gets one. A placeholder is an
2038
- `li.comment.comment-placeholder` with the hidden comment's anchor, a
2039
- `p.comment-withheld` printing `withheld()` ("This comment is no longer
2040
- shown."), and its replies in an `ol.children`; it is no microformat of its own.
2041
-
2042
- Above the thread, `group()` draws one `div.reaction-group` per kind that has
2043
- anything — `p-like`, `p-repost` and `p-mention`, the source theme's classes — as
2044
- an `h2.reaction-title` of the label and the count, in a `span.reaction-count`,
2045
- beside a `div.facepile` of
2046
- `a.u-url` faces. A face is a round `u-photo` avatar; a reaction whose author the
2047
- site knows no picture of is the emoji badge of its kind and a `.reaction-name`
2048
- instead. Likes and boosts link to the person, and a mention to the page it came
2049
- from, because the page is the thing worth reading.
2050
-
2051
- There is no key saying a post has stopped taking comments, and none is needed:
2052
- **the absence of `commentForm` is what a closed post looks like**, since the CMS
2053
- puts the form on the context only while the site switch, the closing window and
2054
- the post's own front matter all agree it is open. The packaged partial prints
2055
- `p.no-comments` under a thread when there is no form, which is WordPress's
2056
- "Comments are closed." and the reason a reader cannot find one.
2057
-
2058
- An Eleventy build of the same content gets the same thing from the same files:
2059
- `docs/eleventy.config.example.js` adds a `conversation` filter over
2060
- `federation.inbox`, and puts the post's ActivityPub id on the context as
2061
- `activityStreams` and its slug as `geekitySlug`, so a layout reads
2062
-
2063
- ```njk
2064
- {% set conversation = federation.inbox | conversation(activityStreams, geekitySlug) %}
2065
- ```
2066
-
2067
- and then loops over exactly the keys above. The slug is what names the post's
2068
- comment file under `content/_data/comments/`; the format is documented in
2069
- backlog doc-6. Two things differ: a native comment's `url` is its
2070
- `#comment-{key}` anchor, because a static build writes no comment pages, and
2071
- the site's own reply posts (`source: "post"`) are not in the thread.
2072
-
2073
- ### A comment's own page
2074
-
2075
- A comment left through the form has a page of its own at `/comment/{key}/`,
2076
- which is its `url` everywhere: the thread's permalink, the comments feeds and
2077
- what another site fetches when it replies to the comment. The anchor
2078
- `#comment-{key}` on the post stays, so an old link still scrolls to it. A
2079
- webmention and a fediverse reply have no such page: their `url` is where they
2080
- came from.
2081
-
2082
- `layouts/comment.njk` draws it, with the comment as the page's one `h-entry`.
2083
- It answers only while the post shows its conversation, and is `noindex` and on
2084
- no list: not the sitemap, the post feeds or search. Its context:
2085
-
2086
- | Key | What it holds |
2087
- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2088
- | `comment` | The comment, an entry of the shape above, its `replies` nested at every depth by the thread's own rules. |
2089
- | `ancestors` | What it answers, from the top-level comment down to its parent. An ancestor a reader may not see (waiting for a moderator, spam, deleted, withdrawn) is `null`, so the chain keeps its place. |
2090
- | `post` | The post it is on, as a listing entry is: `label`, `url`, `date`, `summary`, `author` and the rest. |
2091
- | `threadUrl` | The comment's anchor on the post, for a link back to the whole conversation. |
2092
-
2093
- The packaged layout prints the chain above the comment as nested
2094
- `u-in-reply-to h-cite`s: the comment cites its parent, which cites its own,
2095
- down to the post, which is printed first, so a parser and a reader meet the
2096
- thread in the same order. A `null` ancestor is a cite with no author and no
2097
- words, printing the same `withheld()` the thread's placeholder does. The replies follow inside the
2098
- entry as `comment(reply, "p-comment h-cite")`, with no Reply links, and a
2099
- link to `threadUrl` ends the page.
2100
-
2101
- ### Avatars
2102
-
2103
- A reader's browser never fetches a face from the server it lives on. The
2104
- `author.avatar` a theme is handed is a path on this site, under
2105
- `/_geekity/avatars/`, and the CMS fetches the picture, crops it to an
2106
- 80-pixel square WebP (twice the 40 pixels this theme shows a commenter at)
2107
- and keeps it under `data/avatars/`. Print it as it is; it needs no filter.
2108
-
2109
- The route serves only the pictures the site shows: a follower who has
2110
- liked, boosted, quoted or answered a post, and the author of an approved
2111
- comment or webmention. Any other URL is a 404, and nothing is fetched for it.
2112
- Pictures are fetched in the background when the site starts and every six
2113
- hours after, refreshed once they are a week old, and forgotten once nothing
2114
- shows them. A picture that cannot be fetched, is over 2 MB, or is not a JPEG,
2115
- PNG, GIF, WebP or AVIF is served as a grey placeholder, and tried again an
2116
- hour later. `data/avatars/` is a cache: delete it and every picture is fetched
2117
- again.
2118
-
2119
- ## The comment form
2120
-
2121
- `commentForm` is on the context of a rendered post **only when that post is
2122
- still taking comments**. A site switch, a closing window counted from the
2123
- post's date, and `comments: true` or `comments: false` in the post's own front
2124
- matter all agree before it gets here, so a layout asks:
2125
-
2126
- ```njk
2127
- {% if commentForm %}
2128
- {% include "partials/comment-form.njk" %}
2129
- {% endif %}
2130
- ```
2131
-
2132
- That is what `layouts/post.njk` does, after the conversation. A closed post
2133
- still shows the thread — including every fediverse reply, which arrives whether
2134
- a post is open or not — and simply has no form, which is what the conversation
2135
- reads to print "Comments are closed." A site replaces
2136
- `partials/comment-form.njk` the way it replaces any other template.
2137
-
2138
- The packaged partial is a `section#respond.comment-respond` with an
2139
- `h2.comment-reply-title`, one paragraph per field and the button in a
2140
- `p.form-submit` — the source design's names, so the stylesheet styles every
2141
- label, field and button of both forms through `.comment-respond` and a site
2142
- that has CSS for the WordPress theme can bring it. The name and email fields sit
2143
- in a `div.field-row`, side by side on a wide screen, in both forms. On a site
2144
- that takes webmentions a `p.comment-help` under the heading says a reader can
2145
- reply from their own site instead.
2146
-
2147
- | Key | What it holds |
2148
- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2149
- | `action` | Where the form posts. One fixed path; the post travels as a field. |
2150
- | `fields` | The name each field is submitted under: `post`, `name`, `email`, `url`, `body`, `inReplyTo`, `trap`, `loaded`, `notify`, `csrf`, `listed`. Use these rather than typing the names. |
2151
- | `post` | The post's slug, for the hidden field. |
2152
- | `loaded` | When this form was rendered, in epoch milliseconds, for the hidden field. A submission that comes back too fast is refused. |
2153
- | `values` | What is in the fields: empty on a fresh form, what was typed on a refused one. |
2154
- | `problems` | One message per field a person has to put right — `name`, `email`, `url`, `body`. |
2155
- | `error` | A message about the submission as a whole, when there is one. |
2156
- | `notifiable` | Whether to offer `fields.notify`, the "email me when somebody replies" box. False on a site that sends no mail, where the box would promise a message nothing could send. |
2157
- | `nameLength`, `bodyLength` | The `maxlength` for the two fields that have one. |
2158
-
2159
- `fields.trap` is a honeypot: render it, hide it from sight and from assistive
2160
- technology, and give it `tabindex="-1"` and `autocomplete="off"`. A submission
2161
- that filled it is dropped. **Do not** remove it from a replacement partial —
2162
- it is one of three things standing between the site and a spam queue.
2163
-
2164
- ### When a submission is refused
2165
-
2166
- The CMS answers a refused comment with the post again, `problems` and `error`
2167
- filled in. The packaged partial tells assistive technology about it in two
2168
- places, and a replacement partial should do the same:
2169
-
2170
- - **A summary before the form.** One element with `role="alert"`,
2171
- `tabindex="-1"` and `autofocus`, labelled by a heading that is `error`, or a
2172
- sentence of your own when there is none, and holding a link to each field in
2173
- `problems` (`<a href="#comment-name">…</a>`). `autofocus` moves focus to it on
2174
- load with no script, so it has to be the first `autofocus` on the page.
2175
- - **On each field in `problems`.** `aria-invalid="true"` and
2176
- `aria-describedby` naming the element that holds its message, so a screen
2177
- reader reads the message with the field.
2178
-
2179
- The contact form below keeps the same contract.
2180
-
2181
- ### When somebody is signed in
2182
-
2183
- Two more keys are on `commentForm` when — and only when — the request carries a
2184
- valid session for this site's admin:
2185
-
2186
- | Key | What it holds |
2187
- | ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
2188
- | `signedInAs` | Who the reply will be posted as: `name`, which is their display name or their username, and `url`, which is their author archive. |
2189
- | `csrfToken` | The value for a hidden `fields.csrf`, without which the submission is refused. |
2190
-
2191
- **A replacement partial has to draw both forms**, because the site draws this
2192
- one for its own author and the stranger's one for everybody else:
2193
-
2194
- ```njk
2195
- {% if commentForm.signedInAs %}
2196
- <input type="hidden" name="{{ commentForm.fields.csrf }}" value="{{ commentForm.csrfToken }}" />
2197
- <p class="comment-signed-in">
2198
- Commenting as <a href="{{ commentForm.signedInAs.url }}">{{ commentForm.signedInAs.name }}</a>.
2199
- </p>
2200
- <p class="comment-check">
2201
- <input id="comment-listed" name="{{ commentForm.fields.listed }}" type="checkbox" value="1"{% if commentForm.values.listed %} checked{% endif %} />
2202
- <label for="comment-listed">Include in posts and feeds</label>
2203
- </p>
2204
- {% else %}
2205
- {# fields.loaded, fields.trap, and the name, email and website boxes #}
2206
- {% endif %}
2207
- ```
2208
-
2209
- The signed-in branch renders **no** name, email or website box, **no**
2210
- `fields.trap` and **no** `fields.loaded`: the site already knows all three
2211
- answers, and a session is better evidence that this is a person than a honeypot
2212
- or a stopwatch. It renders `fields.csrf` instead, because this is the one form
2213
- on the public site that acts on somebody's behalf — without the token, a page
2214
- on another site could make them post under their own name. The comment box, the
2215
- reply-to field and the notify box are the same in both branches.
2216
-
2217
- What the signed-in branch posts is a reply post rather than a comment (CMS
2218
- README, "Replying while signed in"). It adds `fields.listed`, a checkbox
2219
- labelled "Include in posts and feeds" and unchecked by default: unchecked
2220
- publishes the reply post unlisted, checked publishes it public. The stranger's
2221
- branch never draws it, and the CMS ignores it from a stranger.
2222
-
2223
- A Reply link, `?reply_to={key}#respond`, goes on every entry of the thread for
2224
- somebody signed in, and on `source == "comment"` entries only for a stranger.
2225
- `partials/comment.njk` asks `reply.source == "comment" or
2226
- commentForm.signedInAs`.
2227
-
2228
- The account's email address is **never** on the context. The comment is stored
2229
- with it, exactly as a stranger's is, and it is no more printable than theirs.
2230
-
2231
- A page carrying this form is one reader's page rather than the post, so the CMS
2232
- answers it `Cache-Control: private, no-store` and with no ETag. A theme does
2233
- not have to do anything about that, but it is why printing somebody's name into
2234
- a page is safe here.
2235
-
2236
- Two more keys travel beside it, both from the URL rather than from the post:
2237
-
2238
- | Key | What it holds |
2239
- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
2240
- | `commentNotice` | The thank-you after a submission, from the `?comment=` the redirect carried. |
2241
- | `commentReplyTo`, `commentReplyingTo` | The comment a `?reply_to=` link named and the name on it, once the CMS has checked it is an approved comment on this very post. |
2242
-
2243
- ## The contact form
2244
-
2245
- `contactForm` is on the context of a rendered document **only when its front
2246
- matter says `contact: true`**, so a layout asks:
2247
-
2248
- ```njk
2249
- {% if contactForm %}
2250
- {% include "partials/contact-form.njk" %}
2251
- {% endif %}
2252
- ```
2253
-
2254
- That is what `layouts/page.njk` does, after the page's content. The key is
2255
- honoured wherever it is written, so a theme that wants the form under a post as
2256
- well only has to add the same two lines to `layouts/post.njk`; the editor
2257
- offers the checkbox on pages.
2258
-
2259
- `archive: true` is the other front matter key of this kind: it puts every
2260
- published post on the page instead of a form. See
2261
- [An archive page](#an-archive-page).
2262
-
2263
- The packaged partial is a `section#contact.comment-respond.contact-form`: it is
2264
- the same kind of form as the comment one, so it wears the same class and the two
2265
- share one set of rules rather than keeping two that drift.
2266
-
2267
- | Key | What it holds |
2268
- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
2269
- | `action` | Where the form posts. One fixed path; the page travels as a field. |
2270
- | `fields` | The name each field is submitted under: `page`, `name`, `email`, `subject`, `message`, `trap`, `loaded`. Use these rather than typing the names. |
2271
- | `page` | The page's slug, for the hidden field. |
2272
- | `loaded` | When this form was rendered, in epoch milliseconds, for the hidden field. A submission that comes back too fast is refused. |
2273
- | `values` | What is in the fields: empty on a fresh form, what was typed on a refused one. |
2274
- | `problems` | One message per field a person has to put right — `name`, `email`, `subject`, `message`. |
2275
- | `error` | A message about the submission as a whole, when there is one. |
2276
- | `nameLength`, `subjectLength`, `messageLength` | The `maxlength` for the three fields that have one. |
2277
-
2278
- `fields.trap` is the same honeypot the comment form carries, and the same rules
2279
- apply: render it, hide it, and **do not** remove it from a replacement partial.
2280
-
2281
- One more key travels beside it, from the URL rather than from the page:
2282
-
2283
- | Key | What it holds |
2284
- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
2285
- | `contactNotice` | The thank-you after a message was sent, from the `?contact=` the redirect carried. The shipped partial shows it **instead of** the form. |
2286
-
2287
- **Where the message goes is never on the context.** The contact address is read
2288
- when a submission arrives, so it cannot end up in the HTML however this partial
2289
- is rewritten. Do not try to print it, and do not put a `mailto:` beside the
2290
- form.
2291
-
2292
- ## Taxonomy macros
2293
-
2294
- `partials/tags.njk` holds one macro per taxonomy, and each renders nothing at
2295
- all for an empty list. Both are one `p.post-categories` of `p-category` links,
2296
- which is the shape the design prints in a feed item's meta line and under an
2297
- entry — `categories` marks each link `rel="category"` and `list` marks each
2298
- `rel="tag"`, so under an entry the two read as one line of what it is filed
2299
- under, categories first:
2300
-
2301
- ```njk
2302
- {% import "partials/tags.njk" as taxonomy with context %}
2303
- {{ taxonomy.categories(categories) }}
2304
- {{ taxonomy.list(tags) }}
2305
- ```
2306
-
2307
- `list` links to `/{{ site.tagBase }}/{tag}/`, the tag in lower case, and `categories` to
2308
- `/{{ site.categoryBase }}/{category}/`, which is where the CMS serves each
2309
- archive. Both bases are settings — `tag` and `category` until the settings
2310
- screen says otherwise — and they reach a template through `site`, which is why
2311
- the import above carries `with context`: a macro imported without it cannot see
2312
- `site` and would fall back to the defaults.
2313
-
2314
- ## Filters
2315
-
2316
- | Filter | What it does |
2317
- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2318
- | `date(format, zone, locale)` | Formats a `Date` or a date string. `long` gives `2 September 2026` on an `en` site and `2 septembre 2026` on an `fr` one; `full`, `medium` and `short` are Intl's other date styles, and `readable` (the default) is `long`. `datetime` gives `10 October 2026 at 09:00 GMT-5`, the day and the time with the zone named. `month` gives `September 2026`, `year` gives `2026`, `html` gives `2026-09-02` for a `<time datetime>`, and `iso` gives the full ISO 8601 instant. Every format but `html` and `iso` is written in the site's locale: the `locale` setting, or the `language` when that is empty. Bare `en` writes the day first, as `en-GB` does; `en-US` gives `September 2, 2026`. A date in a file is a UTC instant; every format but `iso` is rendered in the site's `timezone` setting, and `iso` stays the instant. Pass `zone` — an IANA name — to override the setting for one call. Pass `locale` — a BCP 47 tag — to write the words in another language for one call; it comes after `zone`, so pass `none` for the zone to keep the site's: `{{ date \| date("readable", none, lang) }}`. An empty locale, or one that is no tag, writes in the site's. A value that is not a date renders as the empty string. |
2319
- | `plural(forms, locale)` | Picks the form for a count by its plural category in the site's locale, through `Intl.PluralRules`: `{{ n \| plural({ one: "# reply", other: "# replies" }) }}`. The keys are the CLDR categories (`zero`, `one`, `two`, `few`, `many`, `other`), a category the forms leave out falls back to `other`, and `#` is the count in the locale's digits. Pass `locale` when the strings are in a language of their own: the default theme's words are English, so it passes `"en"`. |
2320
- | `url` | Prefixes a root-relative path with the base URL's path, so a site served from a subdirectory links correctly. Eleventy's filter of the same name. |
2321
- | `absoluteUrl` | The same path as a fully qualified URL against the site's `baseUrl`. |
2322
- | `asset` | The URL of a file in the theme's `static/` directory, with the base path in front and a hash of the file's bytes on the end: `{{ "style.css" \| asset }}` gives `/theme/style.css?v=3f2a9c01b7d4`. The file is found the way `/theme/` finds it, the chosen theme first. A hashed URL is served with `Cache-Control: public, max-age=31536000, immutable`, and editing the file changes the hash on the next render. A file no theme has gets its plain `/theme/` URL. |
2323
-
2324
- `date` reads one word rather than parsing it: `{{ "now" | date("year") }}` is
2325
- the year at the moment the page is rendered, in the site's own timezone. It is
2326
- what the footer's copyright line is written with, and it is the one date a page
2327
- has that no file carries.
2328
-
2329
- The default theme passes the document's `lang` as that locale for every date
2330
- inside its `<article>`: the Published and Updated lines, the hidden heading of
2331
- a note, the kicker in a listing or a search result (`kicker.line(…,
2332
- lang=post.lang)`), and the date of the post a reply answers. So a post that
2333
- says `lang: fr` on an `en` site reads `Published 2 septembre 2026`, while the
2334
- navigation, the conversation under it, the archive's month headings and the
2335
- footer stay in the site's locale. A theme that wants the same passes `lang`
2336
- on an entry and `post.lang` in a listing; one that passes nothing writes every
2337
- date in the site's locale, as before.
2338
-
2339
- Nunjucks' own filters — `default`, `join`, `urlencode`, `safe` and the rest —
2340
- are all available. Autoescaping is on, so rendered Markdown is the one thing
2341
- that has to be passed through `safe`.
3
+ The templates and the stylesheet that every Geekity CMS site gets, shipped
4
+ inside `@geekity/cms`. A site theme can replace one template at a time. The
5
+ rest come from this theme.
6
+
7
+ ## Override a template
8
+
9
+ ```sh
10
+ mkdir -p themes/mine/layouts
11
+ echo '{ "name": "Mine", "kind": "site" }' > themes/mine/theme.json
12
+ cp node_modules/@geekity/cms/themes/default/layouts/post.njk themes/mine/layouts/
13
+ ```
14
+
15
+ Edit the copy, then choose **Mine** on **Appearance > Themes** in the admin.
16
+ Every template that your theme does not ship still comes from the default
17
+ theme.
18
+
19
+ ## Documentation
20
+
21
+ [The default theme](https://github.com/geekitycom/cms/blob/main/docs/theme/README.md)
22
+ lists every file in this folder. Each topic has a page of its own:
23
+
24
+ - [The manifest](https://github.com/geekitycom/cms/blob/main/docs/theme/manifest.md)
25
+ - [Overriding a template](https://github.com/geekitycom/cms/blob/main/docs/theme/overriding-a-template.md)
26
+ - [Adding to every page](https://github.com/geekitycom/cms/blob/main/docs/theme/adding-to-every-page.md)
27
+ - [The page shell](https://github.com/geekitycom/cms/blob/main/docs/theme/page-shell.md)
28
+ - [Mail templates](https://github.com/geekitycom/cms/blob/main/docs/theme/mail-templates.md)
29
+ - [The Markdown representation](https://github.com/geekitycom/cms/blob/main/docs/theme/markdown.md)
30
+ - [Template context](https://github.com/geekitycom/cms/blob/main/docs/theme/template-context.md)
31
+ - [Bylines and author archives](https://github.com/geekitycom/cms/blob/main/docs/theme/bylines.md)
32
+ - [Navigation](https://github.com/geekitycom/cms/blob/main/docs/theme/navigation.md)
33
+ - [Feeds](https://github.com/geekitycom/cms/blob/main/docs/theme/feeds.md)
34
+ - [The conversation](https://github.com/geekitycom/cms/blob/main/docs/theme/conversation.md)
35
+ - [The comment form](https://github.com/geekitycom/cms/blob/main/docs/theme/comment-form.md)
36
+ - [The contact form](https://github.com/geekitycom/cms/blob/main/docs/theme/contact-form.md)
37
+ - [Taxonomy macros](https://github.com/geekitycom/cms/blob/main/docs/theme/taxonomy-macros.md)
38
+ - [Filters](https://github.com/geekitycom/cms/blob/main/docs/theme/filters.md)