@geekity/cms 0.27.0 → 0.29.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 (319) hide show
  1. package/README.md +469 -84
  2. package/admin/layouts/shell.njk +6 -0
  3. package/admin/pages/comments/all.njk +3 -1
  4. package/admin/pages/documents/editor.njk +4 -0
  5. package/admin/pages/federation/followers.njk +8 -2
  6. package/admin/pages/plugins/installed.njk +1 -1
  7. package/admin/pages/tools/dev-mode.njk +47 -0
  8. package/admin/static/admin.css +8 -0
  9. package/admin/static/editor-actions.js +13 -9
  10. package/admin/static/editor.js +31 -17
  11. package/dist/admin/comments.d.ts +2 -0
  12. package/dist/admin/comments.d.ts.map +1 -1
  13. package/dist/admin/comments.js +19 -49
  14. package/dist/admin/comments.js.map +1 -1
  15. package/dist/admin/dev-mode.d.ts +9 -0
  16. package/dist/admin/dev-mode.d.ts.map +1 -0
  17. package/dist/admin/dev-mode.js +42 -0
  18. package/dist/admin/dev-mode.js.map +1 -0
  19. package/dist/admin/documents.d.ts +5 -0
  20. package/dist/admin/documents.d.ts.map +1 -1
  21. package/dist/admin/documents.js +45 -17
  22. package/dist/admin/documents.js.map +1 -1
  23. package/dist/admin/editor-actions.js +3 -1
  24. package/dist/admin/editor-actions.js.map +1 -1
  25. package/dist/admin/federation.d.ts +21 -1
  26. package/dist/admin/federation.d.ts.map +1 -1
  27. package/dist/admin/federation.js +19 -7
  28. package/dist/admin/federation.js.map +1 -1
  29. package/dist/admin/menu.d.ts.map +1 -1
  30. package/dist/admin/menu.js +2 -0
  31. package/dist/admin/menu.js.map +1 -1
  32. package/dist/admin/own-paragraph.d.ts +7 -0
  33. package/dist/admin/own-paragraph.d.ts.map +1 -0
  34. package/dist/admin/own-paragraph.js +9 -0
  35. package/dist/admin/own-paragraph.js.map +1 -0
  36. package/dist/admin/plugins.d.ts.map +1 -1
  37. package/dist/admin/plugins.js +19 -8
  38. package/dist/admin/plugins.js.map +1 -1
  39. package/dist/admin/preview.d.ts.map +1 -1
  40. package/dist/admin/preview.js +4 -1
  41. package/dist/admin/preview.js.map +1 -1
  42. package/dist/admin/routes.d.ts.map +1 -1
  43. package/dist/admin/routes.js +4 -0
  44. package/dist/admin/routes.js.map +1 -1
  45. package/dist/admin/settings-email.d.ts.map +1 -1
  46. package/dist/admin/settings-email.js +4 -0
  47. package/dist/admin/settings-email.js.map +1 -1
  48. package/dist/admin/store.d.ts +68 -12
  49. package/dist/admin/store.d.ts.map +1 -1
  50. package/dist/admin/store.js +167 -18
  51. package/dist/admin/store.js.map +1 -1
  52. package/dist/admin/templates.d.ts +1 -0
  53. package/dist/admin/templates.d.ts.map +1 -1
  54. package/dist/admin/templates.js +1 -0
  55. package/dist/admin/templates.js.map +1 -1
  56. package/dist/admin/uploads.d.ts +7 -1
  57. package/dist/admin/uploads.d.ts.map +1 -1
  58. package/dist/admin/uploads.js +33 -30
  59. package/dist/admin/uploads.js.map +1 -1
  60. package/dist/cli.d.ts +1 -1
  61. package/dist/cli.d.ts.map +1 -1
  62. package/dist/cli.js +78 -12
  63. package/dist/cli.js.map +1 -1
  64. package/dist/comments/form.d.ts +3 -1
  65. package/dist/comments/form.d.ts.map +1 -1
  66. package/dist/comments/form.js +2 -1
  67. package/dist/comments/form.js.map +1 -1
  68. package/dist/comments/index.d.ts +2 -2
  69. package/dist/comments/index.d.ts.map +1 -1
  70. package/dist/comments/index.js +1 -1
  71. package/dist/comments/index.js.map +1 -1
  72. package/dist/comments/records.d.ts +25 -20
  73. package/dist/comments/records.d.ts.map +1 -1
  74. package/dist/comments/records.js +113 -33
  75. package/dist/comments/records.js.map +1 -1
  76. package/dist/comments/reply-notices.d.ts +42 -0
  77. package/dist/comments/reply-notices.d.ts.map +1 -0
  78. package/dist/comments/reply-notices.js +76 -0
  79. package/dist/comments/reply-notices.js.map +1 -0
  80. package/dist/comments/reply-post.d.ts +49 -0
  81. package/dist/comments/reply-post.d.ts.map +1 -0
  82. package/dist/comments/reply-post.js +66 -0
  83. package/dist/comments/reply-post.js.map +1 -0
  84. package/dist/comments/routes.d.ts +6 -1
  85. package/dist/comments/routes.d.ts.map +1 -1
  86. package/dist/comments/routes.js +49 -21
  87. package/dist/comments/routes.js.map +1 -1
  88. package/dist/comments/submission.d.ts +8 -25
  89. package/dist/comments/submission.d.ts.map +1 -1
  90. package/dist/comments/submission.js +19 -38
  91. package/dist/comments/submission.js.map +1 -1
  92. package/dist/comments/viewer.d.ts.map +1 -1
  93. package/dist/comments/viewer.js +1 -0
  94. package/dist/comments/viewer.js.map +1 -1
  95. package/dist/config.d.ts +21 -0
  96. package/dist/config.d.ts.map +1 -1
  97. package/dist/config.js +2 -0
  98. package/dist/config.js.map +1 -1
  99. package/dist/content/citation.d.ts +1 -0
  100. package/dist/content/citation.d.ts.map +1 -1
  101. package/dist/content/citation.js +2 -0
  102. package/dist/content/citation.js.map +1 -1
  103. package/dist/content/markdown.d.ts.map +1 -1
  104. package/dist/content/markdown.js +33 -1
  105. package/dist/content/markdown.js.map +1 -1
  106. package/dist/content/migrated.d.ts +6 -0
  107. package/dist/content/migrated.d.ts.map +1 -0
  108. package/dist/content/migrated.js +12 -0
  109. package/dist/content/migrated.js.map +1 -0
  110. package/dist/content/original.d.ts +8 -0
  111. package/dist/content/original.d.ts.map +1 -0
  112. package/dist/content/original.js +15 -0
  113. package/dist/content/original.js.map +1 -0
  114. package/dist/content/parser.d.ts +5 -0
  115. package/dist/content/parser.d.ts.map +1 -1
  116. package/dist/content/parser.js +1 -1
  117. package/dist/content/parser.js.map +1 -1
  118. package/dist/content/schedule.d.ts +7 -0
  119. package/dist/content/schedule.d.ts.map +1 -1
  120. package/dist/content/schedule.js +11 -2
  121. package/dist/content/schedule.js.map +1 -1
  122. package/dist/content/store.d.ts +43 -11
  123. package/dist/content/store.d.ts.map +1 -1
  124. package/dist/content/store.js +170 -10
  125. package/dist/content/store.js.map +1 -1
  126. package/dist/content/sync.d.ts +8 -0
  127. package/dist/content/sync.d.ts.map +1 -1
  128. package/dist/content/sync.js +172 -57
  129. package/dist/content/sync.js.map +1 -1
  130. package/dist/content/video.d.ts +12 -0
  131. package/dist/content/video.d.ts.map +1 -0
  132. package/dist/content/video.js +91 -0
  133. package/dist/content/video.js.map +1 -0
  134. package/dist/dev-mode.d.ts +28 -0
  135. package/dist/dev-mode.d.ts.map +1 -0
  136. package/dist/dev-mode.js +64 -0
  137. package/dist/dev-mode.js.map +1 -0
  138. package/dist/drain.d.ts +9 -3
  139. package/dist/drain.d.ts.map +1 -1
  140. package/dist/drain.js +24 -3
  141. package/dist/drain.js.map +1 -1
  142. package/dist/federation/article.d.ts.map +1 -1
  143. package/dist/federation/article.js +2 -1
  144. package/dist/federation/article.js.map +1 -1
  145. package/dist/federation/backfill.d.ts +114 -0
  146. package/dist/federation/backfill.d.ts.map +1 -0
  147. package/dist/federation/backfill.js +312 -0
  148. package/dist/federation/backfill.js.map +1 -0
  149. package/dist/federation/delivery.d.ts +2 -0
  150. package/dist/federation/delivery.d.ts.map +1 -1
  151. package/dist/federation/delivery.js +46 -16
  152. package/dist/federation/delivery.js.map +1 -1
  153. package/dist/federation/inbox.d.ts.map +1 -1
  154. package/dist/federation/inbox.js +14 -4
  155. package/dist/federation/inbox.js.map +1 -1
  156. package/dist/federation/mount.d.ts.map +1 -1
  157. package/dist/federation/mount.js +8 -4
  158. package/dist/federation/mount.js.map +1 -1
  159. package/dist/federation/records.d.ts +13 -0
  160. package/dist/federation/records.d.ts.map +1 -1
  161. package/dist/federation/records.js +49 -10
  162. package/dist/federation/records.js.map +1 -1
  163. package/dist/federation/relays.d.ts.map +1 -1
  164. package/dist/federation/relays.js +16 -1
  165. package/dist/federation/relays.js.map +1 -1
  166. package/dist/federation/replies.d.ts +6 -0
  167. package/dist/federation/replies.d.ts.map +1 -1
  168. package/dist/federation/replies.js +18 -1
  169. package/dist/federation/replies.js.map +1 -1
  170. package/dist/index.d.ts +13 -3
  171. package/dist/index.d.ts.map +1 -1
  172. package/dist/index.js +95 -14
  173. package/dist/index.js.map +1 -1
  174. package/dist/indexnow.d.ts +1 -1
  175. package/dist/indexnow.d.ts.map +1 -1
  176. package/dist/indexnow.js +7 -0
  177. package/dist/indexnow.js.map +1 -1
  178. package/dist/init.d.ts +1 -1
  179. package/dist/init.d.ts.map +1 -1
  180. package/dist/init.js +1 -7
  181. package/dist/init.js.map +1 -1
  182. package/dist/mail/service.d.ts +3 -1
  183. package/dist/mail/service.d.ts.map +1 -1
  184. package/dist/mail/service.js +17 -0
  185. package/dist/mail/service.js.map +1 -1
  186. package/dist/notify.d.ts +1 -1
  187. package/dist/notify.d.ts.map +1 -1
  188. package/dist/notify.js +7 -0
  189. package/dist/notify.js.map +1 -1
  190. package/dist/plugin.d.ts +67 -4
  191. package/dist/plugin.d.ts.map +1 -1
  192. package/dist/plugin.js.map +1 -1
  193. package/dist/plugins/folder.d.ts +2 -0
  194. package/dist/plugins/folder.d.ts.map +1 -1
  195. package/dist/plugins/folder.js +26 -5
  196. package/dist/plugins/folder.js.map +1 -1
  197. package/dist/plugins/manage.d.ts +8 -6
  198. package/dist/plugins/manage.d.ts.map +1 -1
  199. package/dist/plugins/manage.js +1 -4
  200. package/dist/plugins/manage.js.map +1 -1
  201. package/dist/plugins/site.d.ts.map +1 -1
  202. package/dist/plugins/site.js +56 -0
  203. package/dist/plugins/site.js.map +1 -1
  204. package/dist/supervisor/control.d.ts +15 -0
  205. package/dist/supervisor/control.d.ts.map +1 -0
  206. package/dist/supervisor/control.js +89 -0
  207. package/dist/supervisor/control.js.map +1 -0
  208. package/dist/supervisor/primary.d.ts +18 -4
  209. package/dist/supervisor/primary.d.ts.map +1 -1
  210. package/dist/supervisor/primary.js +128 -16
  211. package/dist/supervisor/primary.js.map +1 -1
  212. package/dist/supervisor/protocol.d.ts +12 -0
  213. package/dist/supervisor/protocol.d.ts.map +1 -1
  214. package/dist/supervisor/protocol.js +21 -2
  215. package/dist/supervisor/protocol.js.map +1 -1
  216. package/dist/supervisor/supervision.d.ts +1 -0
  217. package/dist/supervisor/supervision.d.ts.map +1 -1
  218. package/dist/supervisor/supervision.js +14 -1
  219. package/dist/supervisor/supervision.js.map +1 -1
  220. package/dist/supervisor/worker.d.ts.map +1 -1
  221. package/dist/supervisor/worker.js +31 -6
  222. package/dist/supervisor/worker.js.map +1 -1
  223. package/dist/web/context.d.ts +22 -0
  224. package/dist/web/context.d.ts.map +1 -1
  225. package/dist/web/context.js +20 -4
  226. package/dist/web/context.js.map +1 -1
  227. package/dist/web/conversation.d.ts +155 -16
  228. package/dist/web/conversation.d.ts.map +1 -1
  229. package/dist/web/conversation.js +602 -135
  230. package/dist/web/conversation.js.map +1 -1
  231. package/dist/web/documents.d.ts +0 -22
  232. package/dist/web/documents.d.ts.map +1 -1
  233. package/dist/web/documents.js +1 -34
  234. package/dist/web/documents.js.map +1 -1
  235. package/dist/web/feed-item.d.ts +19 -14
  236. package/dist/web/feed-item.d.ts.map +1 -1
  237. package/dist/web/feed-item.js +10 -9
  238. package/dist/web/feed-item.js.map +1 -1
  239. package/dist/web/feed-rss.d.ts +2 -2
  240. package/dist/web/feed-rss.d.ts.map +1 -1
  241. package/dist/web/feed-rss.js +36 -12
  242. package/dist/web/feed-rss.js.map +1 -1
  243. package/dist/web/feed-source.d.ts +30 -1
  244. package/dist/web/feed-source.d.ts.map +1 -1
  245. package/dist/web/feed-source.js +25 -0
  246. package/dist/web/feed-source.js.map +1 -1
  247. package/dist/web/feeds.d.ts +1 -1
  248. package/dist/web/feeds.d.ts.map +1 -1
  249. package/dist/web/feeds.js +7 -2
  250. package/dist/web/feeds.js.map +1 -1
  251. package/dist/web/guids.d.ts +59 -0
  252. package/dist/web/guids.d.ts.map +1 -0
  253. package/dist/web/guids.js +79 -0
  254. package/dist/web/guids.js.map +1 -0
  255. package/dist/web/index.d.ts +5 -6
  256. package/dist/web/index.d.ts.map +1 -1
  257. package/dist/web/index.js +3 -3
  258. package/dist/web/index.js.map +1 -1
  259. package/dist/web/redirects.d.ts +29 -10
  260. package/dist/web/redirects.d.ts.map +1 -1
  261. package/dist/web/redirects.js +99 -53
  262. package/dist/web/redirects.js.map +1 -1
  263. package/dist/web/render.d.ts +19 -9
  264. package/dist/web/render.d.ts.map +1 -1
  265. package/dist/web/render.js +44 -42
  266. package/dist/web/render.js.map +1 -1
  267. package/dist/web/routes.d.ts +2 -2
  268. package/dist/web/routes.d.ts.map +1 -1
  269. package/dist/web/routes.js +94 -31
  270. package/dist/web/routes.js.map +1 -1
  271. package/dist/webmention/index.d.ts +1 -1
  272. package/dist/webmention/index.d.ts.map +1 -1
  273. package/dist/webmention/index.js +1 -1
  274. package/dist/webmention/index.js.map +1 -1
  275. package/dist/webmention/links.d.ts +55 -0
  276. package/dist/webmention/links.d.ts.map +1 -1
  277. package/dist/webmention/links.js +162 -0
  278. package/dist/webmention/links.js.map +1 -1
  279. package/dist/webmention/microformats.d.ts +15 -0
  280. package/dist/webmention/microformats.d.ts.map +1 -1
  281. package/dist/webmention/microformats.js +63 -1
  282. package/dist/webmention/microformats.js.map +1 -1
  283. package/dist/webmention/pingback.js +2 -1
  284. package/dist/webmention/pingback.js.map +1 -1
  285. package/dist/webmention/receive.d.ts +5 -0
  286. package/dist/webmention/receive.d.ts.map +1 -1
  287. package/dist/webmention/receive.js +99 -3
  288. package/dist/webmention/receive.js.map +1 -1
  289. package/dist/webmention/reply-context.d.ts +15 -0
  290. package/dist/webmention/reply-context.d.ts.map +1 -1
  291. package/dist/webmention/reply-context.js +29 -0
  292. package/dist/webmention/reply-context.js.map +1 -1
  293. package/dist/webmention/reply-contexts.d.ts +6 -0
  294. package/dist/webmention/reply-contexts.d.ts.map +1 -1
  295. package/dist/webmention/reply-contexts.js +16 -10
  296. package/dist/webmention/reply-contexts.js.map +1 -1
  297. package/dist/webmention/routes.d.ts.map +1 -1
  298. package/dist/webmention/routes.js +2 -1
  299. package/dist/webmention/routes.js.map +1 -1
  300. package/dist/webmention/service.d.ts +31 -2
  301. package/dist/webmention/service.d.ts.map +1 -1
  302. package/dist/webmention/service.js +183 -33
  303. package/dist/webmention/service.js.map +1 -1
  304. package/package.json +1 -2
  305. package/themes/default/README.md +390 -75
  306. package/themes/default/layouts/base.njk +19 -2
  307. package/themes/default/layouts/comment.njk +90 -0
  308. package/themes/default/layouts/front-page.njk +9 -47
  309. package/themes/default/layouts/page.njk +2 -0
  310. package/themes/default/layouts/post.njk +22 -4
  311. package/themes/default/partials/backlinks.njk +19 -0
  312. package/themes/default/partials/body-end.njk +1 -0
  313. package/themes/default/partials/cited-page.njk +3 -1
  314. package/themes/default/partials/comment-form.njk +10 -0
  315. package/themes/default/partials/comment.njk +79 -0
  316. package/themes/default/partials/conversation.njk +14 -59
  317. package/themes/default/partials/head-end.njk +1 -0
  318. package/themes/default/src/style.css +46 -0
  319. package/themes/default/static/style.css +75 -0
@@ -28,6 +28,12 @@
28
28
  content the page body
29
29
  footer the site footer
30
30
  scripts scripts at the end of <body>
31
+
32
+ Two partials, empty here, are included outside every block so that no layout
33
+ can drop them: `partials/head-end.njk` last in <head> and
34
+ `partials/body-end.njk` last in <body>. A site theme fills either one to add
35
+ analytics or a widget to every page without overriding a layout. The
36
+ editor's preview in the admin sets `editorPreview` and leaves both out.
31
37
  #}{% import "partials/feeds.njk" as feeds %}
32
38
  {%- import "partials/menu.njk" as nav %}
33
39
  {#- Whether this is the front page, which is the one page the header and the
@@ -53,6 +59,7 @@
53
59
  {% set metaUrl = (page.url or "/") | absoluteUrl %}
54
60
  {% set metaImage = shareImage.url if shareImage else "" %}
55
61
  {% set isEntry = (type == "post" or type == "page") and not pagination %}
62
+ {% set canonicalUrl = original.url if (isEntry and original) else metaUrl %}
56
63
  {#- An author archive is the only page that is about a person: a listing whose
57
64
  `author` is whose page this is, rather than the posts page's writer. -#}
58
65
  {% set isAuthorArchive = pagination and author.url and url and (author.url in url) %}
@@ -102,7 +109,7 @@
102
109
  {% endif %}{% if noindex %}
103
110
  <meta name="robots" content="noindex">
104
111
  {% endif %}
105
- <link rel="canonical" href="{{ (page.url or "/") | absoluteUrl }}">
112
+ <link rel="canonical" href="{{ canonicalUrl }}">
106
113
  <link rel="stylesheet" href="{{ "style.css" | asset }}">
107
114
  {% block speculationRules %}
108
115
  {#
@@ -171,6 +178,14 @@
171
178
  {% if llmsTxt and atRoot %}
172
179
  <link rel="describedby" type="text/markdown" href="{{ llmsTxt | url }}">
173
180
  {% endif %}
181
+ {% if atRoot and soloAuthor %}
182
+ {% if soloAuthor.url %}
183
+ <link rel="me" href="{{ soloAuthor.url | url }}">
184
+ {% endif %}
185
+ {% for link in soloAuthor.links %}
186
+ <link rel="me" href="{{ link.href }}">
187
+ {% endfor %}
188
+ {% endif %}
174
189
  {% block head %}
175
190
  {#
176
191
  What a link to this page looks like when somebody posts it: the same
@@ -188,7 +203,7 @@
188
203
  {% if metaDescription %}
189
204
  <meta property="og:description" content="{{ metaDescription }}">
190
205
  {% endif %}
191
- <meta property="og:url" content="{{ metaUrl }}">
206
+ <meta property="og:url" content="{{ canonicalUrl }}">
192
207
  <meta property="og:type" content="{% if isEntry %}article{% else %}website{% endif %}">
193
208
  <meta property="og:site_name" content="{{ site.title }}">
194
209
  {% if shareImage %}
@@ -246,6 +261,7 @@
246
261
  <link rel="manifest" href="{{ "/manifest.webmanifest" | url }}">
247
262
  {% include "partials/jsonld.njk" %}
248
263
  {% endblock %}
264
+ {% if not editorPreview %}{% include "partials/head-end.njk" %}{% endif %}
249
265
  </head>
250
266
  <body>
251
267
  <a class="screen-reader-text" href="#main">Skip to content</a>
@@ -331,5 +347,6 @@
331
347
  <script src="{{ "highlight.js" | asset }}" defer></script>
332
348
  {% endif %}
333
349
  {% endblock %}
350
+ {% if not editorPreview %}{% include "partials/body-end.njk" %}{% endif %}
334
351
  </body>
335
352
  </html>
@@ -0,0 +1,90 @@
1
+ {#
2
+ A native comment on a page of its own (TASK-318), at `/comment/{id}/`: what
3
+ another site fetches when it replies to the comment, so the comment is the
4
+ page's one `h-entry`.
5
+
6
+ Above it is the thread it belongs to, from the post down, as nested
7
+ `u-in-reply-to h-cite`s: the comment cites its parent, which cites its own,
8
+ down to the post. A parser reads the chain a person reads, and the deepest
9
+ cite, printed first, is the post. An ancestor a reader may not see is in
10
+ `ancestors` as nothing, and stands in the chain with no author or words.
11
+ Below are its replies, cited in it with `p-comment`, then a link to the
12
+ whole thread on the post.
13
+
14
+ The context: `comment` is one entry of the thread as `partials/comment.njk`
15
+ prints it; `ancestors` is the chain from the top-level comment down to the
16
+ parent; `post` is the post as a listing entry is; `threadUrl` is the
17
+ comment's anchor on the post. The page is noindex and on no list.
18
+ #}
19
+ {% extends "layouts/base.njk" %}
20
+ {% import "partials/comment.njk" as thread with context %}
21
+
22
+ {% macro cited(index) %}
23
+ {% if index < 0 %}
24
+ {% set by = post.author or siteAuthor %}
25
+ <div class="comment-step u-in-reply-to h-cite">
26
+ <p class="cite-line">
27
+ <a class="u-url p-name" href="{{ post.url | url }}">{{ post.label }}</a>
28
+ {%- if by %} by <span class="p-author h-card">{% if by.url %}<a class="u-url p-name" href="{{ by.url }}">{{ by.name }}</a>{% else %}<span class="p-name">{{ by.name }}</span>{% endif %}</span>{% endif %}
29
+ {%- if post.date %}, <time class="dt-published" datetime="{{ post.date | date("iso") }}">{{ post.date | date }}</time>{% endif %}
30
+ </p>
31
+ {% if post.summary %}
32
+ <blockquote class="cite-quote p-summary">{{ post.summary }}</blockquote>
33
+ {% endif %}
34
+ </div>
35
+ {% else %}
36
+ {% set step = ancestors[index] %}
37
+ <div class="comment-step u-in-reply-to h-cite">
38
+ {{ cited(index - 1) }}
39
+ {% if step %}
40
+ <p class="cite-line">
41
+ <span class="p-author h-card">{% if step.author.url %}<a class="u-url p-name" href="{{ step.author.url }}"{% if step.source != "post" %} rel="nofollow ugc noopener noreferrer"{% endif %}>{{ step.author.name }}</a>{% else %}<span class="p-name">{{ step.author.name }}</span>{% endif %}</span>,
42
+ {%- if step.url %}
43
+ <a class="u-url" href="{{ step.url }}"{% if step.source != "comment" %} rel="nofollow noopener noreferrer"{% endif %}><time class="dt-published" datetime="{{ step.published | date("iso") }}">{{ step.published | date }}</time></a>
44
+ {%- else %}
45
+ <time class="dt-published" datetime="{{ step.published | date("iso") }}">{{ step.published | date }}</time>
46
+ {%- endif %}
47
+ </p>
48
+ <div class="cite-quote e-content">{{ step.content | safe }}</div>
49
+ {% else %}
50
+ <p class="cite-line comment-withheld">{{ thread.withheld() }}</p>
51
+ {% endif %}
52
+ </div>
53
+ {% endif %}
54
+ {% endmacro %}
55
+
56
+ {% block content %}
57
+ <article class="comment-page h-entry comment-{{ comment.source }}">
58
+ <h1 class="screen-reader-text">Comment by {{ comment.author.name }} on {{ post.label }}</h1>
59
+ <div class="cite comment-thread">
60
+ {{ cited(ancestors.length - 1) }}
61
+ </div>
62
+ <div class="comment-body">
63
+ <header class="comment-meta">
64
+ <div class="comment-author p-author h-card">
65
+ {%- if comment.author.avatar %}
66
+ <img class="avatar u-photo" src="{{ comment.author.avatar }}" alt="" width="40" height="40">
67
+ {%- else %}
68
+ <span class="comment-avatar-empty" aria-hidden="true">{{ comment.author.name | first | upper }}</span>
69
+ {%- endif %}
70
+ <b class="p-name">{% if comment.author.url %}<a class="u-url" href="{{ comment.author.url }}" rel="nofollow ugc noopener noreferrer">{{ comment.author.name }}</a>{% else %}{{ comment.author.name }}{% endif %}</b>
71
+ </div>
72
+ <div class="comment-metadata">
73
+ <a class="u-url" href="{{ comment.url }}"><time class="dt-published" datetime="{{ comment.published | date("iso") }}">{{ comment.published | date }}</time></a>
74
+ </div>
75
+ </header>
76
+ <div class="comment-content e-content">{{ comment.content | safe }}</div>
77
+ </div>
78
+ {% if comment.replies.length %}
79
+ <section class="comments-area">
80
+ <h2 class="comments-title">Replies</h2>
81
+ <ol class="comment-list">
82
+ {% for reply in comment.replies %}
83
+ {{ thread.comment(reply, "p-comment h-cite") }}
84
+ {% endfor %}
85
+ </ol>
86
+ </section>
87
+ {% endif %}
88
+ </article>
89
+ <p class="comment-back"><a href="{{ threadUrl | url }}">The whole conversation on {{ post.label }}</a></p>
90
+ {% endblock %}
@@ -1,60 +1,22 @@
1
1
  {#
2
- The front page: the page the Reading setting names, read at `/`, as the
3
- source design's `front-page.php` draws it (decision-16, TASK-85).
2
+ The front page: the page the Reading setting names, read at `/`.
4
3
 
5
- A site's own words, then what it has been writing. The words are the page's
6
- rendered body and nothing else — no title, because `layouts/base.njk` heads
7
- the root path with the site title and a second `h1` under it would be one
8
- heading too many, and no Published line, because a front page is read as the
9
- site rather than as a page somebody wrote on a Tuesday. Then `Recent Posts`
10
- over the same feed a listing is made of, headed at 3 so the outline never
11
- skips a level, then the line of links, then the bio.
12
-
13
- The bio is the site's author's on a solo author blog, with the `rel="me"`
14
- claims that make this page their identity (TASK-180). On any other site it is
15
- whoever the page is by, and it claims nothing: the homepage speaks for nobody.
16
-
17
- A theme that wants a different front page replaces this file; a site that
18
- wants no front page of its own sets no homepage, and `/` is the listing.
4
+ A site that wants more on its front page overrides this file in its own
5
+ theme. `newestPosts(count)` lists the newest published posts as listing
6
+ entries and `postsPage` names the page carrying the listing; the theme README
7
+ has an example. On a solo author site the base layout keeps the homepage's
8
+ `rel="me"` claims in the head whatever this file prints (decision-45).
19
9
  #}
20
10
  {% extends "layouts/base.njk" %}
21
11
 
22
- {#- Who the bio credits: the person the site's author setting names. -#}
23
- {% set bioAuthor = soloAuthor or siteAuthor %}
24
- {% set bioRelMe = true if soloAuthor else false %}
25
- {% set bioHome = "/" | absoluteUrl if soloAuthor else "" %}
26
-
27
12
  {% block content %}
13
+ {% if title %}
14
+ <h2 class="section-title">{{ title }}</h2>
15
+ {% endif %}
28
16
  <div class="page-body e-content">
29
17
  {{ content | safe }}
30
18
  </div>
31
19
 
32
- <h2 class="section-title">Recent Posts</h2>
33
- {#- The feed a listing prints, over the posts the CMS decided were recent:
34
- this month's writing when there is enough of it and the five newest
35
- otherwise. `feedHeading` is 3 because the `h2` above is what they sit
36
- under. -#}
37
- {% set posts = recentPosts %}
38
- {% set feedHeading = 3 %}
39
- {% include "partials/post-list.njk" %}
40
-
41
- {#- Where to go next. The posts page is here only when the site has one — on a
42
- site without one there is no second URL for the archive, and a link to one
43
- would be a link to nothing. Search is always here (TASK-22). -#}
44
- <p class="front-links">
45
- {% if postsPage %}
46
- <a href="{{ postsPage.url | url }}">{{ postsPage.title }}<span aria-hidden="true"> &rarr;</span></a>
47
- {% endif %}
48
- <a href="{{ "/search/" | url }}">Search<span aria-hidden="true"> &rarr;</span></a>
49
- </p>
50
-
51
- {% if bioAuthor %}
52
- <hr>
53
- <footer>
54
- {% include "partials/bio.njk" %}
55
- </footer>
56
- {% endif %}
57
-
58
20
  {# A homepage may ask for a contact form like any other page (TASK-56). #}
59
21
  {% if contactForm %}
60
22
  {% include "partials/contact-form.njk" %}
@@ -61,6 +61,8 @@
61
61
  {% endif %}
62
62
  </article>
63
63
 
64
+ {% include "partials/backlinks.njk" %}
65
+
64
66
  {% if conversation %}
65
67
  {% include "partials/conversation.njk" %}
66
68
  {% endif %}
@@ -9,8 +9,9 @@
9
9
  `p-summary`. A post with no title opens on its words, set larger. What a
10
10
  reply answers, and what a like, repost or bookmark cites, opens a post with
11
11
  no title and follows the header of one with a title, before its words.
12
- Then the Published line, the tags, a rule and the bio. The neighbouring posts,
13
- the conversation and the comment form follow the entry.
12
+ Then the Published line, the tags, a rule and the bio. The site's own posts
13
+ that link to it, the neighbouring posts, the conversation and the comment
14
+ form follow the entry.
14
15
 
15
16
  The Published line is inside the `e-content` on purpose. It is what the
16
17
  source design does, and it is what makes a reader who copies the words out
@@ -90,13 +91,19 @@
90
91
  {%- if updated and (not date or (updated | date("html")) != (date | date("html"))) %}
91
92
  {% if date %}<span aria-hidden="true">·</span> {% endif %}<time class="small dt-updated" datetime="{{ updated | date("iso") }}">Updated {{ updated | date("readable", none, lang) }}</time>
92
93
  {%- endif %}
94
+ {#- A second `u-url` rather than a `u-syndication`: the original is not
95
+ a copy of this post, and original-post-discovery reads a copy's
96
+ off-site `u-url` as the original. -#}
97
+ {%- if original %}
98
+ {% if date or updated %} <span aria-hidden="true">·</span> {% endif %}<span class="small">Originally published at <a class="u-url" href="{{ original.url }}">{{ original.label }}</a></span>
99
+ {%- endif %}
93
100
  {#- The post's copies elsewhere: those its targets answered with and
94
101
  those its front matter lists by hand. -#}
95
102
  {%- if syndication.length %}
96
- {% if date or updated %} <span aria-hidden="true">·</span> {% endif %}<span class="small">Also on {% for copy in syndication %}<a class="u-syndication" href="{{ copy.url }}">{{ copy.label }}</a>{% if not loop.last %}, {% endif %}{% endfor %}</span>
103
+ {% if date or updated or original %} <span aria-hidden="true">·</span> {% endif %}<span class="small">Also on {% for copy in syndication %}<a class="u-syndication" href="{{ copy.url }}">{{ copy.label }}</a>{% if not loop.last %}, {% endif %}{% endfor %}</span>
97
104
  {%- endif -%}
98
105
  {%- if location %}
99
- {% if date or updated or syndication.length %} <span aria-hidden="true">·</span> {% endif %}<span class="small">{% include "partials/location.njk" %}</span>
106
+ {% if date or updated or original or syndication.length %} <span aria-hidden="true">·</span> {% endif %}<span class="small">{% include "partials/location.njk" %}</span>
100
107
  {%- endif -%}
101
108
  </p>
102
109
  </section>
@@ -112,8 +119,18 @@
112
119
  {% include "partials/bio.njk" %}
113
120
  </footer>
114
121
  {% endif %}
122
+ {#- A reply post's replies are inside its entry as `p-comment h-cite`s, so
123
+ the page it answers reads them there when it is sent a salmention
124
+ (TASK-320). The likes and boosts stay below: their links are not the
125
+ entry's. -#}
126
+ {% if conversation and postType == "reply" %}
127
+ {% set conversationPart = "replies" %}
128
+ {% include "partials/conversation.njk" %}
129
+ {% endif %}
115
130
  </article>
116
131
 
132
+ {% include "partials/backlinks.njk" %}
133
+
117
134
  {# The post either side of this one, as two cards. Each is on the context only
118
135
  when there is one, so the ends of the archive draw one card and a site of a
119
136
  single post draws none. #}
@@ -132,6 +149,7 @@
132
149
 
133
150
  {# Only a post that has been replied to, liked or boosted has one of these. #}
134
151
  {% if conversation %}
152
+ {% set conversationPart = "reactions" if postType == "reply" else "all" %}
135
153
  {% include "partials/conversation.njk" %}
136
154
  {% endif %}
137
155
 
@@ -0,0 +1,19 @@
1
+ {#
2
+ The site's own posts and pages that link to this one (TASK-322), newest
3
+ first: what a link between two of its posts is instead of a webmention, so
4
+ it is listed apart from the conversation rather than counted in it.
5
+
6
+ `backlinks` is always a list, each entry `{ title, url, date }`, with no
7
+ `date` for a page that has none. An empty list prints nothing, so a layout
8
+ includes this unconditionally.
9
+ #}
10
+ {% if backlinks.length %}
11
+ <section class="backlinks" aria-labelledby="backlinks-title">
12
+ <h2 id="backlinks-title" class="backlinks-title">Linked from</h2>
13
+ <ol>
14
+ {% for link in backlinks %}
15
+ <li><a href="{{ link.url | url }}">{{ link.title }}</a>{% if link.date %} <time datetime="{{ link.date | date("iso") }}">{{ link.date | date("readable") }}</time>{% endif %}</li>
16
+ {% endfor %}
17
+ </ol>
18
+ </section>
19
+ {% endif %}
@@ -0,0 +1 @@
1
+ {#- Printed last in <body> by layouts/base.njk. Empty here; a site theme fills it. -#}
@@ -8,7 +8,8 @@
8
8
 
9
9
  `url` is the cited page, always its `u-url`. `context` is what the page said
10
10
  about itself, from the reply contexts file. With a name, the name is the
11
- link; with only an author, "a post" is; a URL that is itself an image
11
+ link; a comment on this site (`context.comment`, TASK-326) is "a comment";
12
+ with only an author, "a post" is; a URL that is itself an image
12
13
  (`context.image`, TASK-255) is "an image from" its host; anything else is "a
13
14
  page on" its host, never the bare URL, which can run to hundreds of
14
15
  characters. The author follows as an `h-card`.
@@ -42,6 +43,7 @@
42
43
  {%- if named %} <span class="p-name"><a class="u-url" href="{{ url }}">{{ title.slice(0, cut) }}</a>{{ byline.slice(0, 4) }}{{ card(context.author, byline.slice(4)) }}</span>
43
44
  {%- else %}
44
45
  {%- if context.name %} <a class="u-url p-name" href="{{ url }}">{{ context.name }}</a>
46
+ {%- elif context.comment %} <a class="u-url" href="{{ url }}">a comment</a>
45
47
  {%- elif context.author %} <a class="u-url" href="{{ url }}">a post</a>
46
48
  {%- elif context.image %} <a class="u-url" href="{{ url }}">an image from {{ url | host }}</a>
47
49
  {%- else %} <a class="u-url" href="{{ url }}">a page on {{ url | host }}</a>
@@ -87,6 +87,16 @@
87
87
  <p class="comment-signed-in">
88
88
  Commenting as <a href="{{ commentForm.signedInAs.url }}">{{ commentForm.signedInAs.name }}</a>.
89
89
  </p>
90
+
91
+ {#
92
+ What is posted here is a reply post (TASK-300). Unticked it is Unlisted:
93
+ its own page and the thread, and no listing or feed. Ticked it is Public.
94
+ It reaches whoever it answers either way.
95
+ #}
96
+ <p class="comment-check">
97
+ <input id="comment-listed" name="{{ commentForm.fields.listed }}" type="checkbox" value="1"{% if commentForm.values.listed %} checked{% endif %} />
98
+ <label for="comment-listed">Include in posts and feeds</label>
99
+ </p>
90
100
  {% else %}
91
101
  {#
92
102
  When this form was drawn, which is what the minimum submit time is
@@ -0,0 +1,79 @@
1
+ {#
2
+ One reply and everything under it. A macro rather than an include so it can
3
+ call itself: a reply that answers a reply is the same thing one level in.
4
+
5
+ `classes` is the reply's microformat. Under a post each reply is an
6
+ `h-entry` of its own. On a comment's own page (TASK-318) the comment is the
7
+ page's one entry, so its replies are `p-comment h-cite` inside it.
8
+
9
+ A `withheld` entry (TASK-325) is a comment a reader may not see that has
10
+ visible replies under it. It keeps its anchor and prints `withheld()` in
11
+ place of an author and words, as a comment's own page does for an ancestor,
12
+ and is no microformat of its own.
13
+ #}
14
+ {% macro withheld() %}This comment is no longer shown.{% endmacro %}
15
+
16
+ {% macro comment(reply, classes="h-entry") %}
17
+ {% if reply.withheld %}
18
+ <li id="comment-{{ reply.id }}" class="comment comment-placeholder">
19
+ <p class="comment-withheld">{{ withheld() }}</p>
20
+ <ol class="children">
21
+ {% for child in reply.replies %}
22
+ {{ comment(child, classes) }}
23
+ {% endfor %}
24
+ </ol>
25
+ </li>
26
+ {% else %}
27
+ <li id="comment-{{ reply.id }}" class="comment {{ classes }} comment-{{ reply.source }}">
28
+ <article class="comment-body">
29
+ <footer class="comment-meta">
30
+ <div class="comment-author vcard p-author h-card">
31
+ {%- if reply.author.avatar %}
32
+ <img class="avatar u-photo" src="{{ reply.author.avatar }}" alt="" width="40" height="40" loading="lazy">
33
+ {%- else %}
34
+ <span class="comment-avatar-empty" aria-hidden="true">{{ reply.author.name | first | upper }}</span>
35
+ {%- endif %}
36
+ <b class="fn p-name">{% if reply.author.url %}<a class="url u-url" href="{{ reply.author.url }}"{% if reply.source != "post" %} rel="nofollow ugc noopener noreferrer"{% endif %}>{{ reply.author.name }}</a>{% else %}{{ reply.author.name }}{% endif %}</b>
37
+ </div>
38
+ <div class="comment-metadata">
39
+ {%- if reply.url %}
40
+ <a class="u-url" href="{{ reply.url }}"{% if reply.source != "comment" and reply.source != "post" %} rel="nofollow noopener noreferrer"{% endif %}><time class="dt-published" datetime="{{ reply.published | date("iso") }}">{{ reply.published | date }}</time></a>
41
+ {%- else %}
42
+ <time class="dt-published" datetime="{{ reply.published | date("iso") }}">{{ reply.published | date }}</time>
43
+ {%- endif %}
44
+ {%- if reply.source == "webmention" %}
45
+ <span class="comment-source">via webmention</span>
46
+ {%- elif reply.source == "activitypub" %}
47
+ <span class="comment-source">via the fediverse</span>
48
+ {%- endif %}
49
+ </div>
50
+ </footer>
51
+ <div class="comment-content e-content">
52
+ {%- if reply.rsvp %}
53
+ <p class="comment-rsvp"><data class="p-rsvp" value="{{ reply.rsvp.value }}">{{ reply.rsvp.label }}</data></p>
54
+ {%- endif %}
55
+ {{ reply.content | safe }}
56
+ </div>
57
+ {#
58
+ Answering a comment on this site needs no JavaScript: the link carries
59
+ the comment's id to the form, which puts it in a hidden field. Somebody
60
+ signed in can answer anything in the thread, and their answer is a reply
61
+ post that reaches a webmention's page or a fediverse reply's author
62
+ (TASK-300). A visitor answers comments written here only: a fediverse
63
+ reply is answered on the server that holds it, and a webmention on the
64
+ page that sent it.
65
+ #}
66
+ {% if commentForm and (reply.source == "comment" or commentForm.signedInAs) %}
67
+ <div class="reply"><a class="comment-reply-link" href="{{ page.url }}?reply_to={{ reply.id | urlencode }}#respond">Reply</a></div>
68
+ {% endif %}
69
+ </article>
70
+ {% if reply.replies.length %}
71
+ <ol class="children">
72
+ {% for child in reply.replies %}
73
+ {{ comment(child, classes) }}
74
+ {% endfor %}
75
+ </ol>
76
+ {% endif %}
77
+ </li>
78
+ {% endif %}
79
+ {% endmacro %}
@@ -11,9 +11,16 @@
11
11
  the shape it is handed is documented in the theme README under "The
12
12
  conversation".
13
13
 
14
+ `conversationPart` is `reactions` or `replies` for one half of it, which is
15
+ how a reply post prints its replies inside its entry (TASK-320), and the
16
+ whole of it otherwise.
17
+
14
18
  The macros come first because Nunjucks defines one where it stands rather
15
- than hoisting it, and the two sections below call them.
19
+ than hoisting it, and the two sections below call them. One reply and
20
+ everything under it is `partials/comment.njk`, which a comment's own page
21
+ prints its replies with too.
16
22
  #}
23
+ {% import "partials/comment.njk" as thread with context %}
17
24
  {#
18
25
  One face in a facepile: somebody's avatar, linked to wherever they are.
19
26
 
@@ -52,60 +59,6 @@
52
59
  {% endif %}
53
60
  {% endmacro %}
54
61
 
55
- {#
56
- One reply and everything under it. A macro rather than an include so it can
57
- call itself: a reply that answers a reply is the same thing one level in.
58
- #}
59
- {% macro comment(reply) %}
60
- <li id="comment-{{ reply.id }}" class="comment h-entry comment-{{ reply.source }}">
61
- <article class="comment-body">
62
- <footer class="comment-meta">
63
- <div class="comment-author vcard p-author h-card">
64
- {%- if reply.author.avatar %}
65
- <img class="avatar u-photo" src="{{ reply.author.avatar }}" alt="" width="40" height="40" loading="lazy">
66
- {%- else %}
67
- <span class="comment-avatar-empty" aria-hidden="true">{{ reply.author.name | first | upper }}</span>
68
- {%- endif %}
69
- <b class="fn p-name">{% if reply.author.url %}<a class="url u-url" href="{{ reply.author.url }}" rel="nofollow ugc noopener noreferrer">{{ reply.author.name }}</a>{% else %}{{ reply.author.name }}{% endif %}</b>
70
- </div>
71
- <div class="comment-metadata">
72
- {%- if reply.url %}
73
- <a class="u-url" href="{{ reply.url }}"{% if reply.source != "comment" %} rel="nofollow noopener noreferrer"{% endif %}><time class="dt-published" datetime="{{ reply.published | date("iso") }}">{{ reply.published | date }}</time></a>
74
- {%- else %}
75
- <time class="dt-published" datetime="{{ reply.published | date("iso") }}">{{ reply.published | date }}</time>
76
- {%- endif %}
77
- {%- if reply.source == "webmention" %}
78
- <span class="comment-source">via webmention</span>
79
- {%- elif reply.source == "activitypub" %}
80
- <span class="comment-source">via the fediverse</span>
81
- {%- endif %}
82
- </div>
83
- </footer>
84
- <div class="comment-content e-content">
85
- {%- if reply.rsvp %}
86
- <p class="comment-rsvp"><data class="p-rsvp" value="{{ reply.rsvp.value }}">{{ reply.rsvp.label }}</data></p>
87
- {%- endif %}
88
- {{ reply.content | safe }}
89
- </div>
90
- {#
91
- Answering a comment on this site needs no JavaScript: the link carries
92
- the comment's id to the form, which puts it in a hidden field. A
93
- fediverse reply is answered on the server that holds it, and a webmention
94
- is answered on the page that sent it, so neither gets such a link.
95
- #}
96
- {% if commentForm and reply.source == "comment" %}
97
- <div class="reply"><a class="comment-reply-link" href="{{ page.url }}?reply_to={{ reply.id }}#respond">Reply</a></div>
98
- {% endif %}
99
- </article>
100
- {% if reply.replies.length %}
101
- <ol class="children">
102
- {% for child in reply.replies %}
103
- {{ comment(child) }}
104
- {% endfor %}
105
- </ol>
106
- {% endif %}
107
- </li>
108
- {% endmacro %}
109
62
 
110
63
  {#
111
64
  Who is coming to an event (TASK-200): each answer it was given, by a
@@ -114,7 +67,9 @@
114
67
  first. A post that is no event has no groups.
115
68
  #}
116
69
  {% set rsvpIcons = { yes: "✅", maybe: "🤔", interested: "⭐", no: "❌" } %}
117
- {% if conversation.rsvps.length %}
70
+ {% set showReactions = conversationPart != "replies" %}
71
+ {% set showReplies = conversationPart != "reactions" %}
72
+ {% if showReactions and conversation.rsvps.length %}
118
73
  <div class="reactions-section rsvps-section">
119
74
  {%- for answer in conversation.rsvps %}
120
75
  {{- group(answer.people, answer.label, "rsvp-" + answer.value, rsvpIcons[answer.value]) }}
@@ -128,7 +83,7 @@
128
83
  boosts, which is why there are three groups and not four. `p-like`,
129
84
  `p-repost` and `p-mention` are the source theme's classes for them.
130
85
  #}
131
- {% if conversation.likes.length or conversation.boosts.length or conversation.mentions.length %}
86
+ {% if showReactions and (conversation.likes.length or conversation.boosts.length or conversation.mentions.length) %}
132
87
  <div class="reactions-section">
133
88
  {{- group(conversation.likes, "Likes", "p-like", "❤️") }}
134
89
  {{- group(conversation.boosts, "Boosts", "p-repost", "🔁") }}
@@ -150,7 +105,7 @@
150
105
  switch, the closing window and the post's own front matter all agree it is
151
106
  open, so there is nothing else for a theme to work out.
152
107
  #}
153
- {% if conversation.replies.length %}
108
+ {% if showReplies and conversation.replies.length %}
154
109
  <div id="comments" class="comments-area">
155
110
  <h2 class="comments-title">
156
111
  {{- conversation.counts.replies | plural({ one: "One reply", other: "# replies" }, "en") -}}
@@ -158,7 +113,7 @@
158
113
 
159
114
  <ol class="comment-list">
160
115
  {% for reply in conversation.replies %}
161
- {{ comment(reply) }}
116
+ {{ thread.comment(reply, "p-comment h-cite" if conversationPart == "replies" else "h-entry") }}
162
117
  {% endfor %}
163
118
  </ol>
164
119
 
@@ -0,0 +1 @@
1
+ {#- Printed last in <head> by layouts/base.njk. Empty here; a site theme fills it. -#}
@@ -404,6 +404,10 @@
404
404
  @apply h-auto max-w-full rounded-md;
405
405
  }
406
406
 
407
+ :is(.e-content, .page-body) .video-embed iframe {
408
+ @apply aspect-video h-auto w-full rounded-md;
409
+ }
410
+
407
411
  :is(.e-content, .page-body) figcaption {
408
412
  @apply mt-2 font-sans text-sm text-muted;
409
413
  }
@@ -869,6 +873,26 @@
869
873
  @apply target;
870
874
  }
871
875
 
876
+ .backlinks {
877
+ @apply mt-12 font-sans text-sm;
878
+ }
879
+
880
+ .backlinks-title {
881
+ @apply mb-2 text-[0.72rem] font-normal tracking-[0.14em] text-muted uppercase;
882
+ }
883
+
884
+ .backlinks ol {
885
+ @apply list-none space-y-1 p-0;
886
+ }
887
+
888
+ .backlinks a {
889
+ @apply target;
890
+ }
891
+
892
+ .backlinks time {
893
+ @apply ms-1 text-muted;
894
+ }
895
+
872
896
  /* Pagination: two words, the newer at the start and the older at the end. */
873
897
  .pagination {
874
898
  @apply mt-12 flex flex-wrap gap-4 font-sans text-sm;
@@ -1023,6 +1047,28 @@
1023
1047
  @apply mt-8 font-sans text-sm text-muted italic;
1024
1048
  }
1025
1049
 
1050
+ /* A comment's own page (TASK-318): the thread above it as one citation,
1051
+ each step drawn flat although each is nested in the next. */
1052
+ .comment-thread {
1053
+ @apply mb-8;
1054
+ }
1055
+
1056
+ .comment-step > .comment-step {
1057
+ @apply mb-4;
1058
+ }
1059
+
1060
+ .comment-withheld {
1061
+ @apply italic;
1062
+ }
1063
+
1064
+ .comment-page .comments-area {
1065
+ @apply mt-10;
1066
+ }
1067
+
1068
+ .comment-back {
1069
+ @apply mt-10 font-sans text-sm;
1070
+ }
1071
+
1026
1072
  /* The comment form under an open post and the contact form on a page that
1027
1073
  asked for one, which wears the same class. */
1028
1074
  .comment-respond {