@geekity/cms 0.22.0 → 0.24.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 (227) hide show
  1. package/README.md +19 -4
  2. package/admin/pages/documents/editor.njk +18 -0
  3. package/admin/static/editor.js +1 -1
  4. package/dist/admin/comments.js +2 -2
  5. package/dist/admin/comments.js.map +1 -1
  6. package/dist/admin/documents.d.ts +36 -7
  7. package/dist/admin/documents.d.ts.map +1 -1
  8. package/dist/admin/documents.js +140 -29
  9. package/dist/admin/documents.js.map +1 -1
  10. package/dist/admin/editor-layout.d.ts +1 -1
  11. package/dist/admin/editor-layout.d.ts.map +1 -1
  12. package/dist/admin/editor-layout.js +6 -1
  13. package/dist/admin/editor-layout.js.map +1 -1
  14. package/dist/admin/event-field.d.ts +26 -0
  15. package/dist/admin/event-field.d.ts.map +1 -0
  16. package/dist/admin/event-field.js +74 -0
  17. package/dist/admin/event-field.js.map +1 -0
  18. package/dist/admin/federation.js +3 -3
  19. package/dist/admin/federation.js.map +1 -1
  20. package/dist/admin/media.js +2 -2
  21. package/dist/admin/media.js.map +1 -1
  22. package/dist/admin/preview.d.ts.map +1 -1
  23. package/dist/admin/preview.js +16 -1
  24. package/dist/admin/preview.js.map +1 -1
  25. package/dist/admin/store.d.ts +9 -0
  26. package/dist/admin/store.d.ts.map +1 -1
  27. package/dist/admin/store.js +16 -5
  28. package/dist/admin/store.js.map +1 -1
  29. package/dist/avatars/avatars.js +1 -1
  30. package/dist/avatars/avatars.js.map +1 -1
  31. package/dist/comments/policy.d.ts +10 -0
  32. package/dist/comments/policy.d.ts.map +1 -1
  33. package/dist/comments/policy.js +12 -0
  34. package/dist/comments/policy.js.map +1 -1
  35. package/dist/comments/records.d.ts +5 -1
  36. package/dist/comments/records.d.ts.map +1 -1
  37. package/dist/comments/records.js +16 -2
  38. package/dist/comments/records.js.map +1 -1
  39. package/dist/content/event.d.ts +40 -0
  40. package/dist/content/event.d.ts.map +1 -0
  41. package/dist/content/event.js +51 -0
  42. package/dist/content/event.js.map +1 -0
  43. package/dist/content/handles.d.ts +22 -0
  44. package/dist/content/handles.d.ts.map +1 -0
  45. package/dist/content/handles.js +68 -0
  46. package/dist/content/handles.js.map +1 -0
  47. package/dist/content/markdown.d.ts +8 -2
  48. package/dist/content/markdown.d.ts.map +1 -1
  49. package/dist/content/markdown.js +55 -5
  50. package/dist/content/markdown.js.map +1 -1
  51. package/dist/content/parser.d.ts +3 -0
  52. package/dist/content/parser.d.ts.map +1 -1
  53. package/dist/content/parser.js +1 -1
  54. package/dist/content/parser.js.map +1 -1
  55. package/dist/content/post-type.d.ts +12 -8
  56. package/dist/content/post-type.d.ts.map +1 -1
  57. package/dist/content/post-type.js +12 -2
  58. package/dist/content/post-type.js.map +1 -1
  59. package/dist/content/rsvp.d.ts +29 -0
  60. package/dist/content/rsvp.d.ts.map +1 -0
  61. package/dist/content/rsvp.js +49 -0
  62. package/dist/content/rsvp.js.map +1 -0
  63. package/dist/content/save.d.ts +2 -1
  64. package/dist/content/save.d.ts.map +1 -1
  65. package/dist/content/save.js +10 -3
  66. package/dist/content/save.js.map +1 -1
  67. package/dist/content/store.d.ts +11 -4
  68. package/dist/content/store.d.ts.map +1 -1
  69. package/dist/content/store.js +15 -4
  70. package/dist/content/store.js.map +1 -1
  71. package/dist/content/sync.d.ts.map +1 -1
  72. package/dist/content/sync.js +7 -6
  73. package/dist/content/sync.js.map +1 -1
  74. package/dist/env.d.ts +3 -0
  75. package/dist/env.d.ts.map +1 -1
  76. package/dist/federation/article.d.ts +9 -3
  77. package/dist/federation/article.d.ts.map +1 -1
  78. package/dist/federation/article.js +90 -34
  79. package/dist/federation/article.js.map +1 -1
  80. package/dist/federation/cited-post.d.ts +13 -0
  81. package/dist/federation/cited-post.d.ts.map +1 -0
  82. package/dist/federation/cited-post.js +75 -0
  83. package/dist/federation/cited-post.js.map +1 -0
  84. package/dist/federation/delivery.d.ts.map +1 -1
  85. package/dist/federation/delivery.js +31 -2
  86. package/dist/federation/delivery.js.map +1 -1
  87. package/dist/federation/handles.d.ts +28 -0
  88. package/dist/federation/handles.d.ts.map +1 -0
  89. package/dist/federation/handles.js +65 -0
  90. package/dist/federation/handles.js.map +1 -0
  91. package/dist/federation/inbox.d.ts +2 -1
  92. package/dist/federation/inbox.d.ts.map +1 -1
  93. package/dist/federation/inbox.js +11 -3
  94. package/dist/federation/inbox.js.map +1 -1
  95. package/dist/federation/mount.d.ts.map +1 -1
  96. package/dist/federation/mount.js +18 -2
  97. package/dist/federation/mount.js.map +1 -1
  98. package/dist/federation/profiles.d.ts.map +1 -1
  99. package/dist/federation/profiles.js +2 -8
  100. package/dist/federation/profiles.js.map +1 -1
  101. package/dist/federation/site-loaders.d.ts +15 -0
  102. package/dist/federation/site-loaders.d.ts.map +1 -0
  103. package/dist/federation/site-loaders.js +19 -0
  104. package/dist/federation/site-loaders.js.map +1 -0
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +23 -1
  107. package/dist/index.js.map +1 -1
  108. package/dist/indieauth/client.js +2 -2
  109. package/dist/indieauth/client.js.map +1 -1
  110. package/dist/micropub/create.d.ts +2 -1
  111. package/dist/micropub/create.d.ts.map +1 -1
  112. package/dist/micropub/create.js +57 -8
  113. package/dist/micropub/create.js.map +1 -1
  114. package/dist/micropub/endpoint.d.ts.map +1 -1
  115. package/dist/micropub/endpoint.js +12 -4
  116. package/dist/micropub/endpoint.js.map +1 -1
  117. package/dist/micropub/update.d.ts +3 -0
  118. package/dist/micropub/update.d.ts.map +1 -1
  119. package/dist/micropub/update.js +29 -4
  120. package/dist/micropub/update.js.map +1 -1
  121. package/dist/web/context.d.ts +32 -4
  122. package/dist/web/context.d.ts.map +1 -1
  123. package/dist/web/context.js +28 -1
  124. package/dist/web/context.js.map +1 -1
  125. package/dist/web/conversation.d.ts +26 -1
  126. package/dist/web/conversation.d.ts.map +1 -1
  127. package/dist/web/conversation.js +67 -10
  128. package/dist/web/conversation.js.map +1 -1
  129. package/dist/web/documents.d.ts +12 -0
  130. package/dist/web/documents.d.ts.map +1 -1
  131. package/dist/web/documents.js +20 -0
  132. package/dist/web/documents.js.map +1 -1
  133. package/dist/web/errors.d.ts.map +1 -1
  134. package/dist/web/errors.js +5 -3
  135. package/dist/web/errors.js.map +1 -1
  136. package/dist/web/feed-item.d.ts.map +1 -1
  137. package/dist/web/feed-item.js +8 -3
  138. package/dist/web/feed-item.js.map +1 -1
  139. package/dist/web/llms.d.ts +8 -0
  140. package/dist/web/llms.d.ts.map +1 -1
  141. package/dist/web/llms.js +25 -7
  142. package/dist/web/llms.js.map +1 -1
  143. package/dist/web/maintenance.d.ts.map +1 -1
  144. package/dist/web/maintenance.js +5 -2
  145. package/dist/web/maintenance.js.map +1 -1
  146. package/dist/web/negotiate.d.ts +4 -2
  147. package/dist/web/negotiate.d.ts.map +1 -1
  148. package/dist/web/negotiate.js +17 -7
  149. package/dist/web/negotiate.js.map +1 -1
  150. package/dist/web/render.d.ts +3 -0
  151. package/dist/web/render.d.ts.map +1 -1
  152. package/dist/web/render.js +39 -6
  153. package/dist/web/render.js.map +1 -1
  154. package/dist/web/routes.d.ts.map +1 -1
  155. package/dist/web/routes.js +67 -25
  156. package/dist/web/routes.js.map +1 -1
  157. package/dist/web/templates.d.ts +4 -1
  158. package/dist/web/templates.d.ts.map +1 -1
  159. package/dist/web/templates.js +11 -0
  160. package/dist/web/templates.js.map +1 -1
  161. package/dist/webmention/cited-picture.d.ts +11 -4
  162. package/dist/webmention/cited-picture.d.ts.map +1 -1
  163. package/dist/webmention/cited-picture.js +22 -13
  164. package/dist/webmention/cited-picture.js.map +1 -1
  165. package/dist/webmention/cited-start.d.ts +10 -0
  166. package/dist/webmention/cited-start.d.ts.map +1 -0
  167. package/dist/webmention/cited-start.js +21 -0
  168. package/dist/webmention/cited-start.js.map +1 -0
  169. package/dist/webmention/fetch-public.d.ts +2 -2
  170. package/dist/webmention/fetch-public.d.ts.map +1 -1
  171. package/dist/webmention/fetch-public.js +1 -1
  172. package/dist/webmention/fetch-public.js.map +1 -1
  173. package/dist/webmention/html.d.ts +2 -0
  174. package/dist/webmention/html.d.ts.map +1 -1
  175. package/dist/webmention/html.js +6 -0
  176. package/dist/webmention/html.js.map +1 -1
  177. package/dist/webmention/index.d.ts +2 -1
  178. package/dist/webmention/index.d.ts.map +1 -1
  179. package/dist/webmention/index.js +1 -0
  180. package/dist/webmention/index.js.map +1 -1
  181. package/dist/webmention/microformats.d.ts +25 -2
  182. package/dist/webmention/microformats.d.ts.map +1 -1
  183. package/dist/webmention/microformats.js +38 -4
  184. package/dist/webmention/microformats.js.map +1 -1
  185. package/dist/webmention/pingback.d.ts +42 -0
  186. package/dist/webmention/pingback.d.ts.map +1 -0
  187. package/dist/webmention/pingback.js +229 -0
  188. package/dist/webmention/pingback.js.map +1 -0
  189. package/dist/webmention/receive.d.ts +20 -2
  190. package/dist/webmention/receive.d.ts.map +1 -1
  191. package/dist/webmention/receive.js +25 -16
  192. package/dist/webmention/receive.js.map +1 -1
  193. package/dist/webmention/reply-context.d.ts +65 -18
  194. package/dist/webmention/reply-context.d.ts.map +1 -1
  195. package/dist/webmention/reply-context.js +403 -99
  196. package/dist/webmention/reply-context.js.map +1 -1
  197. package/dist/webmention/reply-contexts.d.ts +3 -1
  198. package/dist/webmention/reply-contexts.d.ts.map +1 -1
  199. package/dist/webmention/reply-contexts.js +53 -17
  200. package/dist/webmention/reply-contexts.js.map +1 -1
  201. package/dist/webmention/routes.d.ts.map +1 -1
  202. package/dist/webmention/routes.js +2 -0
  203. package/dist/webmention/routes.js.map +1 -1
  204. package/dist/webmention/service.d.ts +11 -0
  205. package/dist/webmention/service.d.ts.map +1 -1
  206. package/dist/webmention/service.js +27 -6
  207. package/dist/webmention/service.js.map +1 -1
  208. package/dist/webmention/syndication.d.ts +6 -0
  209. package/dist/webmention/syndication.d.ts.map +1 -1
  210. package/dist/webmention/syndication.js +12 -0
  211. package/dist/webmention/syndication.js.map +1 -1
  212. package/package.json +7 -7
  213. package/themes/default/README.md +127 -37
  214. package/themes/default/layouts/410.njk +10 -0
  215. package/themes/default/layouts/base.njk +3 -0
  216. package/themes/default/layouts/page.njk +7 -0
  217. package/themes/default/layouts/post.njk +7 -4
  218. package/themes/default/layouts/search.njk +1 -1
  219. package/themes/default/partials/cited-page.njk +7 -0
  220. package/themes/default/partials/conversation.njk +25 -5
  221. package/themes/default/partials/event.njk +27 -0
  222. package/themes/default/partials/jsonld.njk +28 -0
  223. package/themes/default/partials/post-list.njk +5 -2
  224. package/themes/default/partials/reply-context.njk +24 -4
  225. package/themes/default/partials/rsvp.njk +7 -0
  226. package/themes/default/src/style.css +20 -0
  227. package/themes/default/static/style.css +35 -0
@@ -17,6 +17,7 @@ themes/default/
17
17
  author.njk one person's archive, paginated
18
18
  search.njk the search form and what it found, paginated
19
19
  404.njk nothing at this URL
20
+ 410.njk what was at this URL was deleted
20
21
  500.njk the request failed on the server
21
22
  503.njk the site is in maintenance mode
22
23
  partials/
@@ -25,14 +26,15 @@ themes/default/
25
26
  tags.njk macros for tag and category links
26
27
  kicker.njk the line above an entry naming its kind and date
27
28
  reply-context.njk the citation of what a reply answers
29
+ event.njk when and where an event is
28
30
  citations.njk the citation of what a like, repost or bookmark cites
29
31
  cited-page.njk the name and author both citations print
30
32
  read.njk what a read post read, and how far
31
33
  bio.njk who an entry is by, as an h-card
32
34
  menu.njk one named menu, as a nav of links
33
35
  feeds.njk macros for the feed links in <head>
34
- conversation.njk the replies, likes and boosts under a post
35
- comment-form.njk the form under a post that is taking comments
36
+ conversation.njk the replies, likes and boosts under a post or an open page
37
+ comment-form.njk the form under a post or page that is taking comments
36
38
  contact-form.njk the form on a page whose front matter says contact: true
37
39
  archive.njk every post by month, on a page that says archive: true
38
40
  search-form.njk the search box, on the search page
@@ -418,6 +420,44 @@ title and summary. The same partial cites a reply in a feed. The layouts own
418
420
  the placement; the partial owns only what the citation says, and it stays
419
421
  inside the `h-entry` wherever it is printed.
420
422
 
423
+ **A reply to a silo copy cites the original** (TASK-197). When the post a
424
+ reply answers is a copy of a post on another site, and that post lists the
425
+ copy as its `u-syndication`, `replyContext.original` is the original's URL and
426
+ the rest of `replyContext` describes the original. The `h-cite` links the
427
+ original, and a `p.cite-copy` after it says "Also in reply to its copy on" the
428
+ silo's host, linking the copy as a `u-in-reply-to` of its own, so a parser
429
+ reads both as what the post answers.
430
+
431
+ **An RSVP says whether its author is going** (TASK-198). An RSVP is a reply
432
+ to an event whose `rsvp` is `yes`, `no`, `maybe` or `interested`.
433
+ `partials/rsvp.njk` opens its `e-content` with a `p.rsvp-line` holding a
434
+ `data.p-rsvp` whose value is the answer and whose text is Going, Not going,
435
+ Maybe or Interested; it prints `rsvp.line`, the HTML the feeds also carry. Its
436
+ reply context says "RSVP to" rather than "In reply to", and when the event's
437
+ page is an `h-event`, or describes a schema.org `Event` in JSON-LD, its start
438
+ follows the event's name as a `time.dt-start` and its place as a
439
+ `span.p-location`, inside the `h-cite`. A start the event page wrote with no
440
+ zone is that wall clock where the event is, so it is printed in UTC
441
+ (`replyContext.startZone`) rather than moved into the site's zone. Its kicker
442
+ and its hidden `h1` say RSVP. An RSVP to a silo copy of an event cites the
443
+ original as a reply does.
444
+
445
+ **An event is an h-event** (TASK-200, decision-32). A post whose front matter
446
+ has a readable `start` is an event: its title is the event's name, its body
447
+ the description, and `end` and `location` are optional. `layouts/post.njk`
448
+ types its article `h-event` rather than `h-entry`, its kicker and hidden `h1`
449
+ say Event, and `partials/event.njk` follows the title with a
450
+ `dl.event-details`. The When row is a `time.dt-start` and, with an end, a
451
+ `time.dt-end`, each written with the `date` filter's `datetime` format in the
452
+ site's zone, with the instant as its `datetime`. The Where row is a
453
+ `span.p-location` holding the place's words, or "Online at" an
454
+ `a.p-location` that links the address to join an online event. The JSON-LD
455
+ describes the post as a schema.org `Event` instead of a `BlogPosting`. That
456
+ `Event` is a `Place` or a `VirtualLocation`, carries an attendance mode and an
457
+ `EventScheduled` status, and names the author's Person as its `organizer`.
458
+ The answers it is given are grouped in `conversation.rsvps`; see [The
459
+ conversation](#the-conversation).
460
+
421
461
  **A like, a repost or a bookmark cites what it cites** (TASK-169) with
422
462
  `partials/citations.njk`, beside the reply context and drawn the same way: one
423
463
  `div.reply-context.cite.h-cite` per entry of `citations`, classed
@@ -452,6 +492,21 @@ bookmark of a Scripting News post reads "Bookmarked RSS tip #2 · Scripting
452
492
  News". A like's, a repost's or a bookmark's citation never prints the page's
453
493
  description, which on many sites describes the site rather than the post.
454
494
 
495
+ **A citation shows who wrote the cited page** (TASK-199) as fully as the page
496
+ said. The author's photo, an `img.u-photo.cite-avatar` with empty alt text
497
+ since their name is beside it, comes before the name inside the `h-card`; a
498
+ fediverse author's handle follows the name as a `span.p-nickname.cite-handle`
499
+ reading `@user@host`. The photo is the `u-photo` of the `h-entry` author's
500
+ `h-card` (or of the page's `h-card` for the same person), the avatar of the
501
+ account an ActivityPub object is attributed to, or the author image in the
502
+ page's JSON-LD, copied into the site's uploads as a picture is, and left out
503
+ when the post has removed its preview. A Mastodon status is read from the
504
+ ActivityPub object its page links, so its author's display name, handle,
505
+ avatar, words, first image and date come from the status itself rather than
506
+ from the page's `og:title`. A page with no `h-entry` and no object is read
507
+ from its JSON-LD (`headline`, `author`, `datePublished`, `image`) and then its
508
+ Open Graph, Twitter and `article:` tags.
509
+
455
510
  **A citation shows the cited page's picture** (TASK-252) when the page named
456
511
  one and the post has not removed its preview. The picture is the page's
457
512
  oEmbed photo, else its oEmbed thumbnail, else its `og:image`, else its
@@ -553,8 +608,9 @@ its front matter names no `image`.
553
608
  After the entry a post prints `nav.blog-post-nav`: the `previous` and `next`
554
609
  posts as two cards, `rel="prev"` and `rel="next"`, each opening on a
555
610
  `span.blog-post-nav-label`, and nothing at all at the ends of the archive; then
556
- the conversation and the comment form. A page prints the contact form when its
557
- front matter asked for one. A page has no neighbours, no tags and no
611
+ the conversation and the comment form. A page prints the conversation and the
612
+ comment form too when it takes comments, and the contact form when its front
613
+ matter asked for one. A page has no neighbours, no tags and no
558
614
  syndication links, because none of those are things a page has.
559
615
 
560
616
  ### The bio
@@ -724,6 +780,13 @@ writes one today and the header is the heading alone.
724
780
  links home and to the search. The 500 and 503 pages below open on a kicker the
725
781
  same way.
726
782
 
783
+ `layouts/410.njk` is the page at the URL of a post or page that was moved to
784
+ the trash (TASK-195). It says `This content has been deleted.` under a
785
+ `Deleted` kicker and links home and to the search. The context is the 404's:
786
+ `title`, `url` and `page.url`, and nothing of the deleted document. Its `.md`
787
+ and `.json` representations answer 410 too. A trashed draft was never public,
788
+ so its URL is a 404 instead.
789
+
727
790
  `layouts/500.njk` is the page a request gets when the server fails while
728
791
  answering it (TASK-129). It says `Something went wrong.` in an
729
792
  `article.server-error` and links home. The context is the 404's: `title`,
@@ -1261,6 +1324,8 @@ A document — one post, one page, or one entry of a listing — adds:
1261
1324
  | `photos` | The post's photos, each `{ url, alt, html }`; `html` is the `img.u-photo`, responsive. Empty with none. |
1262
1325
  | `citations` | What it reposts, likes or bookmarks, each `{ property, url, context }`; `context` as `replyContext`, when known. |
1263
1326
  | `read` | A read post's `{ status, statusLabel, of, line }`; `of` is `{ name, author, uid, uidLabel, url }`. |
1327
+ | `rsvp` | An RSVP's `{ value, label, line }`: `yes`, `no`, `maybe` or `interested`, its words, and its `p-rsvp` line. |
1328
+ | `event` | An event's `{ start, end, location }`: `Date`s, and `{ kind: "place", name }` or `{ kind: "virtual", url }`. |
1264
1329
  | `syndicateTo` | On a post's page, the syndication targets it selects, each `{ id, name, url }`. Empty with none. |
1265
1330
  | `syndication` | Its copies elsewhere, each `{ url, label }`: front matter `syndication`, and on its page the copies targets made. |
1266
1331
  | `author` | Who wrote it, as a profile rather than a string. See [Bylines and author archives](#bylines-and-author-archives). |
@@ -1543,6 +1608,18 @@ site that has turned them off advertises nothing. The CMS also sends the same
1543
1608
  endpoint as a `Link` header on every representation of a document, so a sender
1544
1609
  that does not parse HTML still finds it.
1545
1610
 
1611
+ The pingback endpoint follows it, as an absolute URL because the spec asks for
1612
+ one:
1613
+
1614
+ ```html
1615
+ <link rel="pingback" href="https://example.com/_geekity/pingback" />
1616
+ ```
1617
+
1618
+ It comes from `pingback` on the context, which is there only when the site
1619
+ takes webmentions and the document can be answered: every post, and a page
1620
+ only while it takes comments. The same endpoint goes out in an `X-Pingback`
1621
+ header under the same rule.
1622
+
1546
1623
  On the front page it also writes the site's index for language models:
1547
1624
 
1548
1625
  ```html
@@ -1586,7 +1663,17 @@ ask:
1586
1663
  {% endif %}
1587
1664
  ```
1588
1665
 
1589
- That is what `layouts/post.njk` does. `partials/conversation.njk` is the whole
1666
+ That is what `layouts/post.njk` does, and `layouts/page.njk` with it.
1667
+
1668
+ A page takes part only while it takes comments: when its front matter says
1669
+ `comments: true` and the site has not switched comments off. A page is closed
1670
+ by default, as WordPress closes them, and the CMS leaves `conversation` off the
1671
+ context of a closed page however much has been approved against it, so a page
1672
+ that turns comments off shows nothing. A post is different: it keeps its thread
1673
+ after it closes. Webmentions to a page are received and moderated either way,
1674
+ exactly as webmentions to a post are, and appear once the page opens.
1675
+
1676
+ `partials/conversation.njk` is the whole
1590
1677
  section — a `div.reactions-section` of the likes, the boosts and the mentions
1591
1678
  as facepiles grouped by kind, and then the thread as
1592
1679
  `div#comments.comments-area` — and a site replaces it with a
@@ -1610,34 +1697,37 @@ correctly; one that does can style them apart. The one thing a theme does have
1610
1697
  to look at is `kind`: a `mention` is in `conversation.mentions` rather than in
1611
1698
  the thread, because it is not an answer.
1612
1699
 
1613
- | Key | What it holds |
1614
- | -------------------------------------------------- | ------------------------------------------------------------------------------------------ |
1615
- | `replies` | The replies to the post, oldest first, each carrying its own `replies`. See below. |
1616
- | `likes` | The likes, oldest first, in the same shape. |
1617
- | `boosts` | The boosts, oldest first, in the same shape. A webmention `repost` is one of them. |
1618
- | `mentions` | The pages and fediverse quotes about this post, oldest first, in the same shape. |
1619
- | `counts.replies` | How many replies, counted through the whole thread rather than the top of it. |
1620
- | `counts.likes`, `counts.boosts`, `counts.mentions` | How many of each. |
1621
- | `counts.total` | All four added up. Zero never reaches a template: there would be no `conversation` at all. |
1700
+ | Key | What it holds |
1701
+ | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
1702
+ | `replies` | The replies to the post, oldest first, each carrying its own `replies`. See below. |
1703
+ | `likes` | The likes, oldest first, in the same shape. |
1704
+ | `boosts` | The boosts, oldest first, in the same shape. A webmention `repost` is one of them. |
1705
+ | `mentions` | The pages and fediverse quotes about this post, oldest first, in the same shape. |
1706
+ | `rsvps` | An event's answers, each group `{ value, label, people }`, going, maybe, interested, not going, only with people in it. |
1707
+ | `counts.replies` | How many replies, counted through the whole thread rather than the top of it. |
1708
+ | `counts.likes`, `counts.boosts`, `counts.mentions` | How many of each. |
1709
+ | `counts.rsvps` | How many people answered an event, in every group. |
1710
+ | `counts.total` | All of them added up. Zero never reaches a template: there would be no `conversation` at all. |
1622
1711
 
1623
1712
  Each entry — a reply, a like or a boost — is:
1624
1713
 
1625
- | Key | What it holds |
1626
- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1627
- | `id` | What it is called: a reply's own note id, which is what an answer to it names. |
1628
- | `source` | `"activitypub"` for a fediverse reply, `"comment"` for one left on the page, `"webmention"` for another page linking here. |
1629
- | `kind` | `"reply"`, `"like"`, `"boost"`, `"repost"` or `"mention"`. A `repost` comes from a webmention; a `mention` is a webmention or an approved fediverse quote. |
1630
- | `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. |
1631
- | `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. |
1632
- | `author.url` | Their profile page, the website a commenter typed, or a webmention author's `u-url`. May be `null`, so guard the link. |
1633
- | `author.avatar` | Their avatar, or `null`. The site knows one for a fediverse actor whose profile it holds 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). |
1634
- | `author.actorId` | Their id, which is what identifies them however they are named. `null` for a native comment. |
1635
- | `url` | Where it can be read: the remote note's `url` for a fediverse reply or quote, the source page for a webmention, and `{permalink}#comment-{id}` — this page's own anchor — for a native comment. |
1636
- | `content` | What it says, **already sanitised**, so print it with `\| safe`. Empty for a like or a boost. |
1637
- | `published` | A `Date`: when it was published, or when it arrived if it did not say. Use the `date` filter. |
1638
- | `inReplyTo` | What it answers — the post's ActivityPub id, or another reply's — and `null` for a reaction. |
1639
- | `status` | `"published"`. On the record for the sources that moderate. |
1640
- | `replies` | The replies to this one, oldest first, nested as deep as the site has seen. |
1714
+ | Key | What it holds |
1715
+ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1716
+ | `id` | What it is called: a reply's own note id, which is what an answer to it names. |
1717
+ | `source` | `"activitypub"` for a fediverse reply, `"comment"` for one left on the page, `"webmention"` for another page linking here. |
1718
+ | `kind` | `"reply"`, `"like"`, `"boost"`, `"repost"` or `"mention"`. A `repost` comes from a webmention; a `mention` is a webmention or an approved fediverse quote. |
1719
+ | `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. |
1720
+ | `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. |
1721
+ | `author.url` | Their profile page, the website a commenter typed, or a webmention author's `u-url`. May be `null`, so guard the link. |
1722
+ | `author.avatar` | Their avatar, or `null`. The site knows one for a fediverse actor whose profile it holds 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). |
1723
+ | `author.actorId` | Their id, which is what identifies them however they are named. `null` for a native comment. |
1724
+ | `url` | Where it can be read: the remote note's `url` for a fediverse reply or quote, the source page for a webmention, and `{permalink}#comment-{id}` — this page's own anchor — for a native comment. |
1725
+ | `content` | What it says, **already sanitised**, so print it with `\| safe`. Empty for a like or a boost. |
1726
+ | `published` | A `Date`: when it was published, or when it arrived if it did not say. Use the `date` filter. |
1727
+ | `inReplyTo` | What it answers — the post's ActivityPub id, or another reply's — and `null` for a reaction. |
1728
+ | `status` | `"published"`. On the record for the sources that moderate. |
1729
+ | `replies` | The replies to this one, oldest first, nested as deep as the site has seen. |
1730
+ | `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. |
1641
1731
 
1642
1732
  Three rules decide what is in the thread, and they are the CMS's rather than a
1643
1733
  theme's: a reply whose author deleted it is gone, and its own answers move up to
@@ -1898,13 +1988,13 @@ the import above carries `with context`: a macro imported without it cannot see
1898
1988
 
1899
1989
  ## Filters
1900
1990
 
1901
- | Filter | What it does |
1902
- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1903
- | `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`. `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. |
1904
- | `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"`. |
1905
- | `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. |
1906
- | `absoluteUrl` | The same path as a fully qualified URL against the site's `baseUrl`. |
1907
- | `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. |
1991
+ | Filter | What it does |
1992
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1993
+ | `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. |
1994
+ | `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"`. |
1995
+ | `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. |
1996
+ | `absoluteUrl` | The same path as a fully qualified URL against the site's `baseUrl`. |
1997
+ | `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. |
1908
1998
 
1909
1999
  `date` reads one word rather than parsing it: `{{ "now" | date("year") }}` is
1910
2000
  the year at the moment the page is rendered, in the site's own timezone. It is
@@ -0,0 +1,10 @@
1
+ {% extends "layouts/base.njk" %}
2
+ {% import "partials/kicker.njk" as kicker with context %}
3
+
4
+ {% block content %}
5
+ <article class="not-found">
6
+ {{ kicker.line("Deleted") }}
7
+ <h1 class="page-title">This content has been deleted.</h1>
8
+ <p>Go <a href="{{ "/" | url }}">home</a>, or <a href="{{ "/search/" | url }}">search the site</a>.</p>
9
+ </article>
10
+ {% endblock %}
@@ -153,6 +153,9 @@
153
153
  {% if webmention %}
154
154
  <link rel="webmention" href="{{ webmention | url }}">
155
155
  {% endif %}
156
+ {% if pingback %}
157
+ <link rel="pingback" href="{{ pingback | absoluteUrl }}">
158
+ {% endif %}
156
159
  {#- The site's search, for a browser to offer from its address bar
157
160
  (TASK-204). #}
158
161
  <link rel="search" type="application/opensearchdescription+xml" title="{{ site.title }}" href="{{ "/opensearch.xml" | url }}">
@@ -61,6 +61,13 @@
61
61
  {% endif %}
62
62
  </article>
63
63
 
64
+ {% if conversation %}
65
+ {% include "partials/conversation.njk" %}
66
+ {% endif %}
67
+ {% if commentForm %}
68
+ {% include "partials/comment-form.njk" %}
69
+ {% endif %}
70
+
64
71
  {# Only a page whose front matter says `contact: true` has one (TASK-56). #}
65
72
  {% if contactForm %}
66
73
  {% include "partials/contact-form.njk" %}
@@ -31,7 +31,7 @@
31
31
 
32
32
  {% block content %}
33
33
  {% set entryUrl = page.url or url %}
34
- <article class="blog-post h-entry"{{ language.attr(lang) }}>
34
+ <article class="blog-post {{ "h-event" if postType == "event" else "h-entry" }}"{{ language.attr(lang) }}>
35
35
  {#- A post with no name of its own (a note, or a reply without a title) is
36
36
  its words, so it opens on them in sight. Its page still needs one h1 to
37
37
  navigate by, so it gets one that says what it is, who wrote it and when,
@@ -39,21 +39,23 @@
39
39
  title and read the note as an article. -#}
40
40
  {% if named %}
41
41
  <header class="post-header">
42
- {{ kicker.line(postType | capitalize, false, false, categories) }}
42
+ {{ kicker.line("RSVP" if postType == "rsvp" else postType | capitalize, false, false, categories) }}
43
43
  <h1 class="p-name">{{ title }}</h1>
44
44
  {% if description %}
45
45
  <p class="post-deck p-summary">{{ description }}</p>
46
46
  {% endif %}
47
47
  </header>
48
+ {% include "partials/event.njk" %}
48
49
  {% include "partials/reply-context.njk" %}
49
50
  {% include "partials/citations.njk" %}
50
51
  {% else %}
51
52
  <h1 class="screen-reader-text">
52
- {{- postType | capitalize if postType in ["reply", "like", "repost", "bookmark", "read"] else "Note" }}
53
+ {{- "RSVP" if postType == "rsvp" else (postType | capitalize if postType in ["event", "reply", "like", "repost", "bookmark", "read"] else "Note") }}
53
54
  {%- if bioAuthor.name %} by {{ bioAuthor.name }}{% endif %}
54
55
  {%- if date %}, {{ date | date("readable", none, lang) }}{% endif -%}
55
56
  </h1>
56
- {{ kicker.line(postType | capitalize, false, false, categories) }}
57
+ {{ kicker.line("RSVP" if postType == "rsvp" else postType | capitalize, false, false, categories) }}
58
+ {% include "partials/event.njk" %}
57
59
  {% include "partials/reply-context.njk" %}
58
60
  {% include "partials/citations.njk" %}
59
61
  {% endif %}
@@ -69,6 +71,7 @@
69
71
  <section class="e-content">
70
72
  {#- Inside the content, so a parser that reads only the content of a
71
73
  read, as a webmention receiver does, has its sentence. -#}
74
+ {% include "partials/rsvp.njk" %}
72
75
  {% include "partials/read.njk" %}
73
76
  {{ content | safe }}
74
77
  <p class="entry-meta">
@@ -37,7 +37,7 @@
37
37
  <div class="search-results">
38
38
  {% for post in posts %}
39
39
  <article class="search-result h-entry"{{ language.attr(post.lang) }}>
40
- {{ kicker.line(("Page" if post.type == "page" else post.postType) | capitalize, post.date, lang=post.lang) }}
40
+ {{ kicker.line("Page" if post.type == "page" else ("RSVP" if post.postType == "rsvp" else post.postType | capitalize), post.date, lang=post.lang) }}
41
41
  {#- A post with no name of its own has none to claim, so its first words
42
42
  link it without being marked up as one. -#}
43
43
  <h2 class="feed-title{% if post.named %} p-name{% endif %}">
@@ -23,6 +23,11 @@
23
23
  `og:site_name`), printed after a middle dot as plain text: a site is not an
24
24
  author, so it is no `h-card`.
25
25
 
26
+ The author's `h-card` shows their photo before their name when the page
27
+ gave one (`author.photo.imgHtml`, an `img.u-photo` copied into the site's
28
+ uploads, TASK-199), and a fediverse author's handle after it as its
29
+ `p-nickname`, written `@user@host`.
30
+
26
31
  `thumb(url, picture)` is a cited page's thumbnail (TASK-252): a link to the
27
32
  page around `picture.imgHtml`, out of the tab order and hidden from assistive
28
33
  technology because the title beside it is the same link. A video's carries
@@ -52,7 +57,9 @@
52
57
 
53
58
  {% macro card(author, name) -%}
54
59
  <span class="p-author h-card">
60
+ {%- if author.photo %}{{ author.photo.imgHtml | safe }}{% endif -%}
55
61
  {%- if author.url %}<a class="u-url p-name" href="{{ author.url }}">{{ name }}</a>
56
62
  {%- else %}<span class="p-name">{{ name }}</span>{% endif -%}
63
+ {%- if author.handle %} <span class="p-nickname cite-handle">@{{ author.handle }}</span>{% endif -%}
57
64
  </span>
58
65
  {%- endmacro %}
@@ -3,11 +3,13 @@
3
3
  grouped by kind, and the replies as a thread, in the source design's markup
4
4
  (decision-16, TASK-84).
5
5
 
6
- Rendered from `layouts/post.njk` with the `conversation` on the context,
7
- which is only there when there is something in it, so a post nobody has
8
- answered renders none of this. A site replaces the whole section with a
9
- `partials/conversation.njk` of its own in the theme it wears; the shape it is
10
- handed is documented in the theme README under "The conversation".
6
+ Rendered from `layouts/post.njk` and `layouts/page.njk` with the
7
+ `conversation` on the context, which is only there when there is something
8
+ in it and, on a page, only while the page takes comments (TASK-196), so a
9
+ post nobody has answered renders none of this. A site replaces the whole
10
+ section with a `partials/conversation.njk` of its own in the theme it wears;
11
+ the shape it is handed is documented in the theme README under "The
12
+ conversation".
11
13
 
12
14
  The macros come first because Nunjucks defines one where it stands rather
13
15
  than hoisting it, and the two sections below call them.
@@ -80,6 +82,9 @@
80
82
  </div>
81
83
  </footer>
82
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 %}
83
88
  {{ reply.content | safe }}
84
89
  </div>
85
90
  {#
@@ -102,6 +107,21 @@
102
107
  </li>
103
108
  {% endmacro %}
104
109
 
110
+ {#
111
+ Who is coming to an event (TASK-200): each answer it was given, by a
112
+ webmention RSVP or a fediverse Accept, TentativeAccept or Reject, as a
113
+ facepile per answer in the order `conversation.rsvps` gives them, going
114
+ first. A post that is no event has no groups.
115
+ #}
116
+ {% set rsvpIcons = { yes: "✅", maybe: "🤔", interested: "⭐", no: "❌" } %}
117
+ {% if conversation.rsvps.length %}
118
+ <div class="reactions-section rsvps-section">
119
+ {%- for answer in conversation.rsvps %}
120
+ {{- group(answer.people, answer.label, "rsvp-" + answer.value, rsvpIcons[answer.value]) }}
121
+ {%- endfor %}
122
+ </div>
123
+ {% endif %}
124
+
105
125
  {#
106
126
  The reactions, above the thread. A like and a boost come from the fediverse
107
127
  and a `repost` from a webmention; the CMS has already put a repost among the
@@ -0,0 +1,27 @@
1
+ {#
2
+ When and where an event is (TASK-200): its `dt-start`, its `dt-end` when it
3
+ has one, and its `p-location`, the place's words or a link to join it
4
+ online. `layouts/post.njk` includes it inside the h-event with `event` on
5
+ the context. The times are the site's clock (decision-11), named with its
6
+ zone so a reader elsewhere can work out their own; the `datetime` is the
7
+ instant.
8
+ #}
9
+ {% if event %}
10
+ <dl class="event-details">
11
+ <dt>When</dt>
12
+ <dd>
13
+ <time class="dt-start" datetime="{{ event.start | date("iso") }}">{{ event.start | date("datetime", none, lang) }}</time>
14
+ {%- if event.end %} to <time class="dt-end" datetime="{{ event.end | date("iso") }}">{{ event.end | date("datetime", none, lang) }}</time>{% endif %}
15
+ </dd>
16
+ {% if event.location %}
17
+ <dt>Where</dt>
18
+ <dd>
19
+ {%- if event.location.kind == "virtual" -%}
20
+ Online at <a class="p-location" href="{{ event.location.url }}">{{ event.location.url | host }}</a>
21
+ {%- else -%}
22
+ <span class="p-location">{{ event.location.name }}</span>
23
+ {%- endif -%}
24
+ </dd>
25
+ {% endif %}
26
+ </dl>
27
+ {% endif %}
@@ -127,6 +127,34 @@
127
127
  {{- member("mainEntity", person) }}
128
128
  }{% endset %}
129
129
  {% set _ = nodes.push(profilePage) %}
130
+ {% elif isEntry and event %}
131
+ {% if event.location.kind == "virtual" %}
132
+ {% set eventLocation = { "@type": "VirtualLocation", "url": event.location.url } %}
133
+ {% set attendance = "https://schema.org/OnlineEventAttendanceMode" %}
134
+ {% elif event.location %}
135
+ {% set eventLocation = { "@type": "Place", "name": event.location.name, "address": event.location.name } %}
136
+ {% set attendance = "https://schema.org/OfflineEventAttendanceMode" %}
137
+ {% else %}
138
+ {% set eventLocation = "" %}
139
+ {% set attendance = "" %}
140
+ {% endif %}
141
+ {% set entry %}
142
+ {
143
+ "@type": "Event"
144
+ {{- member("@id", metaUrl + "#event") }}
145
+ {{- member("name", headline) }}
146
+ {{- member("url", metaUrl) }}
147
+ {{- member("mainEntityOfPage", metaUrl) }}
148
+ {{- member("startDate", event.start | date("iso")) }}
149
+ {{- member("endDate", event.end | date("iso") if event.end else "") }}
150
+ {{- member("location", eventLocation) }}
151
+ {{- member("eventAttendanceMode", attendance) }}
152
+ {{- member("eventStatus", "https://schema.org/EventScheduled") }}
153
+ {{- member("description", metaDescription) }}
154
+ {{- member("image", entryImage) }}
155
+ {{- member("organizer", person or publisher) }}
156
+ }{% endset %}
157
+ {% set _ = nodes.push(entry) %}
130
158
  {% elif isEntry %}
131
159
  {% set entry %}
132
160
  {
@@ -29,13 +29,14 @@
29
29
  {% endif %}
30
30
  <article class="feed-item h-entry"{{ language.attr(post.lang) }}>
31
31
  {% if post.named %}
32
- {{ kicker.line(post.postType | capitalize, post.date, false, post.categories, lang=post.lang) }}
32
+ {{ kicker.line("RSVP" if post.postType == "rsvp" else post.postType | capitalize, post.date, false, post.categories, lang=post.lang) }}
33
33
  {% else %}
34
- {{ kicker.line(post.postType | capitalize, post.date, post.url, [], post.label, lang=post.lang) }}
34
+ {{ kicker.line("RSVP" if post.postType == "rsvp" else post.postType | capitalize, post.date, post.url, [], post.label, lang=post.lang) }}
35
35
  {% endif %}
36
36
  {% set cited %}
37
37
  {% if post.inReplyTo %}
38
38
  {% set inReplyTo = post.inReplyTo %}
39
+ {% set rsvp = post.rsvp %}
39
40
  {% set replyContext = post.replyContext %}
40
41
  {% set lang = post.lang %}
41
42
  {% include "partials/reply-context.njk" %}
@@ -44,6 +45,8 @@
44
45
  {% include "partials/citations.njk" %}
45
46
  {% endset %}
46
47
  {% if not post.named %}{{ cited | safe }}{% endif %}
48
+ {% set rsvp = post.rsvp %}
49
+ {% include "partials/rsvp.njk" %}
47
50
  {% set read = post.read %}
48
51
  {% include "partials/read.njk" %}
49
52
  {% if post.photos.length %}
@@ -18,19 +18,39 @@
18
18
 
19
19
  `replyContext.picture`, when the target has one and the reply shows it, is
20
20
  drawn as a thumbnail beside the line, as `citations.njk` draws it (TASK-252).
21
+
22
+ An RSVP (`rsvp` on the context, TASK-198) says "RSVP to" rather than "In
23
+ reply to", and an event's `replyContext.start` and `replyContext.location`
24
+ follow its name as a `dt-start` and a `p-location`. `startDate` is the
25
+ instant to print and `startZone` the zone to print it in: "UTC" for a start
26
+ the event page wrote with no zone, which is the wall clock where the event
27
+ is, and nothing otherwise, so the site's zone applies. The `dt-start`
28
+ carries `start` as the page wrote it.
29
+
30
+ `replyContext.original`, when the target is a silo copy of a post that lists
31
+ the copy as its own (TASK-197), is the post cited: the `h-cite` links the
32
+ original and describes it, so its site finds the link when it is sent a
33
+ webmention. The copy follows the citation as a `u-in-reply-to` of its own,
34
+ outside the `h-cite`, so the silo can thread the reply too.
21
35
  #}
22
36
  {% import "partials/cited-page.njk" as cited %}
23
37
  {% if inReplyTo %}
24
38
  {% set picture = replyContext.picture %}
39
+ {% set answered = replyContext.original or inReplyTo %}
25
40
  <div class="reply-context cite u-in-reply-to h-cite{% if picture %} cite-with-thumb{% endif %}">
26
- {%- if picture %}{{ cited.thumb(inReplyTo, picture) }}{% endif %}
27
- <p class="cite-line small">In reply to
28
- {{- cited.page(inReplyTo, replyContext) }}
29
- {%- if replyContext.published %}, <time class="dt-published" datetime="{{ replyContext.published | date("iso") }}">{{ replyContext.published | date("readable", none, lang) }}</time>
41
+ {%- if picture %}{{ cited.thumb(answered, picture) }}{% endif %}
42
+ <p class="cite-line small">{{ "RSVP to" if rsvp else "In reply to" }}
43
+ {{- cited.page(answered, replyContext) }}
44
+ {%- if replyContext.startDate %}, <time class="dt-start" datetime="{{ replyContext.start }}">{{ replyContext.startDate | date("readable", replyContext.startZone, lang) }}</time>
45
+ {%- elif replyContext.published %}, <time class="dt-published" datetime="{{ replyContext.published | date("iso") }}">{{ replyContext.published | date("readable", none, lang) }}</time>
30
46
  {%- endif -%}
47
+ {%- if replyContext.location %}, <span class="p-location">{{ replyContext.location }}</span>{% endif -%}
31
48
  </p>
32
49
  {% if replyContext.text %}
33
50
  <blockquote class="cite-quote p-content">{{ replyContext.text }}</blockquote>
34
51
  {% endif %}
35
52
  </div>
53
+ {% if replyContext.original %}
54
+ <p class="cite-copy small">Also in reply to <a class="u-in-reply-to" href="{{ inReplyTo }}">its copy on {{ inReplyTo | host }}</a></p>
55
+ {% endif %}
36
56
  {% endif %}
@@ -0,0 +1,7 @@
1
+ {#
2
+ An RSVP's answer, the `p-rsvp` (TASK-198), as the line its content opens
3
+ with. An entry includes it with `rsvp` on the context; a listing sets it
4
+ from the entry it is printing. Nothing is printed for a post that is no
5
+ RSVP.
6
+ #}
7
+ {% if rsvp %}{{ rsvp.line | safe }}{% endif %}