@geekity/cms 0.28.0 → 0.30.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 (195) hide show
  1. package/README.md +355 -53
  2. package/admin/pages/comments/all.njk +3 -1
  3. package/admin/pages/federation/followers.njk +8 -2
  4. package/dist/admin/comments.d.ts +2 -0
  5. package/dist/admin/comments.d.ts.map +1 -1
  6. package/dist/admin/comments.js +19 -49
  7. package/dist/admin/comments.js.map +1 -1
  8. package/dist/admin/documents.d.ts +5 -0
  9. package/dist/admin/documents.d.ts.map +1 -1
  10. package/dist/admin/documents.js +45 -17
  11. package/dist/admin/documents.js.map +1 -1
  12. package/dist/admin/federation.d.ts +21 -1
  13. package/dist/admin/federation.d.ts.map +1 -1
  14. package/dist/admin/federation.js +17 -7
  15. package/dist/admin/federation.js.map +1 -1
  16. package/dist/admin/store.d.ts +95 -14
  17. package/dist/admin/store.d.ts.map +1 -1
  18. package/dist/admin/store.js +206 -17
  19. package/dist/admin/store.js.map +1 -1
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +8 -1
  22. package/dist/cli.js.map +1 -1
  23. package/dist/comments/form.d.ts +3 -1
  24. package/dist/comments/form.d.ts.map +1 -1
  25. package/dist/comments/form.js +2 -1
  26. package/dist/comments/form.js.map +1 -1
  27. package/dist/comments/index.d.ts +3 -3
  28. package/dist/comments/index.d.ts.map +1 -1
  29. package/dist/comments/index.js +1 -1
  30. package/dist/comments/index.js.map +1 -1
  31. package/dist/comments/records.d.ts +56 -24
  32. package/dist/comments/records.d.ts.map +1 -1
  33. package/dist/comments/records.js +255 -31
  34. package/dist/comments/records.js.map +1 -1
  35. package/dist/comments/reply-notices.d.ts +42 -0
  36. package/dist/comments/reply-notices.d.ts.map +1 -0
  37. package/dist/comments/reply-notices.js +76 -0
  38. package/dist/comments/reply-notices.js.map +1 -0
  39. package/dist/comments/reply-post.d.ts +49 -0
  40. package/dist/comments/reply-post.d.ts.map +1 -0
  41. package/dist/comments/reply-post.js +66 -0
  42. package/dist/comments/reply-post.js.map +1 -0
  43. package/dist/comments/routes.d.ts +6 -1
  44. package/dist/comments/routes.d.ts.map +1 -1
  45. package/dist/comments/routes.js +49 -21
  46. package/dist/comments/routes.js.map +1 -1
  47. package/dist/comments/submission.d.ts +8 -25
  48. package/dist/comments/submission.d.ts.map +1 -1
  49. package/dist/comments/submission.js +19 -38
  50. package/dist/comments/submission.js.map +1 -1
  51. package/dist/comments/viewer.d.ts.map +1 -1
  52. package/dist/comments/viewer.js +1 -0
  53. package/dist/comments/viewer.js.map +1 -1
  54. package/dist/content/citation.d.ts +1 -0
  55. package/dist/content/citation.d.ts.map +1 -1
  56. package/dist/content/citation.js +2 -0
  57. package/dist/content/citation.js.map +1 -1
  58. package/dist/content/original.d.ts +6 -1
  59. package/dist/content/original.d.ts.map +1 -1
  60. package/dist/content/original.js +7 -4
  61. package/dist/content/original.js.map +1 -1
  62. package/dist/content/store.d.ts +43 -11
  63. package/dist/content/store.d.ts.map +1 -1
  64. package/dist/content/store.js +170 -10
  65. package/dist/content/store.js.map +1 -1
  66. package/dist/content/sync.d.ts +8 -0
  67. package/dist/content/sync.d.ts.map +1 -1
  68. package/dist/content/sync.js +164 -71
  69. package/dist/content/sync.js.map +1 -1
  70. package/dist/federation/article.d.ts.map +1 -1
  71. package/dist/federation/article.js +2 -1
  72. package/dist/federation/article.js.map +1 -1
  73. package/dist/federation/backfill.d.ts +114 -0
  74. package/dist/federation/backfill.d.ts.map +1 -0
  75. package/dist/federation/backfill.js +314 -0
  76. package/dist/federation/backfill.js.map +1 -0
  77. package/dist/federation/inbox.js +1 -1
  78. package/dist/federation/inbox.js.map +1 -1
  79. package/dist/federation/records.d.ts +13 -0
  80. package/dist/federation/records.d.ts.map +1 -1
  81. package/dist/federation/records.js +49 -10
  82. package/dist/federation/records.js.map +1 -1
  83. package/dist/federation/replies.d.ts +6 -0
  84. package/dist/federation/replies.d.ts.map +1 -1
  85. package/dist/federation/replies.js +18 -1
  86. package/dist/federation/replies.js.map +1 -1
  87. package/dist/index.d.ts +12 -4
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +81 -11
  90. package/dist/index.js.map +1 -1
  91. package/dist/init.d.ts +1 -1
  92. package/dist/init.d.ts.map +1 -1
  93. package/dist/init.js +1 -7
  94. package/dist/init.js.map +1 -1
  95. package/dist/plugin.d.ts +28 -2
  96. package/dist/plugin.d.ts.map +1 -1
  97. package/dist/plugin.js +10 -2
  98. package/dist/plugin.js.map +1 -1
  99. package/dist/plugins/site.d.ts.map +1 -1
  100. package/dist/plugins/site.js +7 -1
  101. package/dist/plugins/site.js.map +1 -1
  102. package/dist/web/context.d.ts +18 -3
  103. package/dist/web/context.d.ts.map +1 -1
  104. package/dist/web/context.js +17 -4
  105. package/dist/web/context.js.map +1 -1
  106. package/dist/web/conversation.d.ts +191 -28
  107. package/dist/web/conversation.d.ts.map +1 -1
  108. package/dist/web/conversation.js +670 -172
  109. package/dist/web/conversation.js.map +1 -1
  110. package/dist/web/documents.d.ts +0 -22
  111. package/dist/web/documents.d.ts.map +1 -1
  112. package/dist/web/documents.js +1 -34
  113. package/dist/web/documents.js.map +1 -1
  114. package/dist/web/feed-item.d.ts +12 -8
  115. package/dist/web/feed-item.d.ts.map +1 -1
  116. package/dist/web/feed-item.js +10 -10
  117. package/dist/web/feed-item.js.map +1 -1
  118. package/dist/web/feed-rss.d.ts.map +1 -1
  119. package/dist/web/feed-rss.js +34 -10
  120. package/dist/web/feed-rss.js.map +1 -1
  121. package/dist/web/feed-source.d.ts +33 -1
  122. package/dist/web/feed-source.d.ts.map +1 -1
  123. package/dist/web/feed-source.js +33 -0
  124. package/dist/web/feed-source.js.map +1 -1
  125. package/dist/web/feeds.d.ts +1 -1
  126. package/dist/web/feeds.d.ts.map +1 -1
  127. package/dist/web/feeds.js +7 -2
  128. package/dist/web/feeds.js.map +1 -1
  129. package/dist/web/guids.d.ts +47 -0
  130. package/dist/web/guids.d.ts.map +1 -0
  131. package/dist/web/guids.js +67 -0
  132. package/dist/web/guids.js.map +1 -0
  133. package/dist/web/index.d.ts +5 -6
  134. package/dist/web/index.d.ts.map +1 -1
  135. package/dist/web/index.js +3 -3
  136. package/dist/web/index.js.map +1 -1
  137. package/dist/web/redirects.d.ts +12 -0
  138. package/dist/web/redirects.d.ts.map +1 -1
  139. package/dist/web/redirects.js +16 -4
  140. package/dist/web/redirects.js.map +1 -1
  141. package/dist/web/render.d.ts +19 -9
  142. package/dist/web/render.d.ts.map +1 -1
  143. package/dist/web/render.js +44 -42
  144. package/dist/web/render.js.map +1 -1
  145. package/dist/web/routes.d.ts +2 -2
  146. package/dist/web/routes.d.ts.map +1 -1
  147. package/dist/web/routes.js +157 -84
  148. package/dist/web/routes.js.map +1 -1
  149. package/dist/webmention/index.d.ts +1 -1
  150. package/dist/webmention/index.d.ts.map +1 -1
  151. package/dist/webmention/index.js +1 -1
  152. package/dist/webmention/index.js.map +1 -1
  153. package/dist/webmention/links.d.ts +55 -0
  154. package/dist/webmention/links.d.ts.map +1 -1
  155. package/dist/webmention/links.js +162 -0
  156. package/dist/webmention/links.js.map +1 -1
  157. package/dist/webmention/microformats.d.ts +15 -0
  158. package/dist/webmention/microformats.d.ts.map +1 -1
  159. package/dist/webmention/microformats.js +63 -1
  160. package/dist/webmention/microformats.js.map +1 -1
  161. package/dist/webmention/pingback.js +2 -1
  162. package/dist/webmention/pingback.js.map +1 -1
  163. package/dist/webmention/receive.d.ts +5 -0
  164. package/dist/webmention/receive.d.ts.map +1 -1
  165. package/dist/webmention/receive.js +99 -3
  166. package/dist/webmention/receive.js.map +1 -1
  167. package/dist/webmention/reply-context.d.ts +15 -0
  168. package/dist/webmention/reply-context.d.ts.map +1 -1
  169. package/dist/webmention/reply-context.js +29 -0
  170. package/dist/webmention/reply-context.js.map +1 -1
  171. package/dist/webmention/reply-contexts.d.ts +6 -0
  172. package/dist/webmention/reply-contexts.d.ts.map +1 -1
  173. package/dist/webmention/reply-contexts.js +16 -10
  174. package/dist/webmention/reply-contexts.js.map +1 -1
  175. package/dist/webmention/routes.d.ts.map +1 -1
  176. package/dist/webmention/routes.js +2 -1
  177. package/dist/webmention/routes.js.map +1 -1
  178. package/dist/webmention/service.d.ts +30 -1
  179. package/dist/webmention/service.d.ts.map +1 -1
  180. package/dist/webmention/service.js +172 -32
  181. package/dist/webmention/service.js.map +1 -1
  182. package/package.json +1 -2
  183. package/themes/default/README.md +269 -95
  184. package/themes/default/layouts/base.njk +8 -0
  185. package/themes/default/layouts/comment.njk +90 -0
  186. package/themes/default/layouts/front-page.njk +9 -47
  187. package/themes/default/layouts/page.njk +2 -0
  188. package/themes/default/layouts/post.njk +14 -2
  189. package/themes/default/partials/backlinks.njk +19 -0
  190. package/themes/default/partials/cited-page.njk +3 -1
  191. package/themes/default/partials/comment-form.njk +10 -0
  192. package/themes/default/partials/comment.njk +79 -0
  193. package/themes/default/partials/conversation.njk +14 -59
  194. package/themes/default/src/style.css +42 -0
  195. package/themes/default/static/style.css +67 -0
@@ -0,0 +1,90 @@
1
+ {#
2
+ A native comment on a page of its own (TASK-318), at `/comment/{key}/`: 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 not step.author.username %} 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 }}"{% if not comment.author.username %} rel="nofollow ugc noopener noreferrer"{% endif %}>{{ 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
@@ -118,8 +119,18 @@
118
119
  {% include "partials/bio.njk" %}
119
120
  </footer>
120
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 %}
121
130
  </article>
122
131
 
132
+ {% include "partials/backlinks.njk" %}
133
+
123
134
  {# The post either side of this one, as two cards. Each is on the context only
124
135
  when there is one, so the ends of the archive draw one card and a site of a
125
136
  single post draws none. #}
@@ -138,6 +149,7 @@
138
149
 
139
150
  {# Only a post that has been replied to, liked or boosted has one of these. #}
140
151
  {% if conversation %}
152
+ {% set conversationPart = "reactions" if postType == "reply" else "all" %}
141
153
  {% include "partials/conversation.njk" %}
142
154
  {% endif %}
143
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 %}
@@ -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.key }}" 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.key }}" 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 not reply.author.username %} 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 key 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.key }}#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 | urlencode }}#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
 
@@ -873,6 +873,26 @@
873
873
  @apply target;
874
874
  }
875
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
+
876
896
  /* Pagination: two words, the newer at the start and the older at the end. */
877
897
  .pagination {
878
898
  @apply mt-12 flex flex-wrap gap-4 font-sans text-sm;
@@ -1027,6 +1047,28 @@
1027
1047
  @apply mt-8 font-sans text-sm text-muted italic;
1028
1048
  }
1029
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
+
1030
1072
  /* The comment form under an open post and the contact form on a page that
1031
1073
  asked for one, which wears the same class. */
1032
1074
  .comment-respond {
@@ -1619,6 +1619,50 @@
1619
1619
  display: inline-flex;
1620
1620
  }
1621
1621
 
1622
+ .backlinks {
1623
+ margin-top: calc(var(--spacing) * 12);
1624
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
1625
+ font-size: var(--text-sm);
1626
+ line-height: var(--tw-leading, var(--text-sm--line-height));
1627
+ }
1628
+
1629
+ .backlinks-title {
1630
+ margin-bottom: calc(var(--spacing) * 2);
1631
+ --tw-font-weight: var(--font-weight-normal);
1632
+ font-size: .72rem;
1633
+ font-weight: var(--font-weight-normal);
1634
+ --tw-tracking: .14em;
1635
+ letter-spacing: .14em;
1636
+ color: var(--color-muted);
1637
+ text-transform: uppercase;
1638
+ }
1639
+
1640
+ .backlinks ol {
1641
+ list-style-type: none;
1642
+ }
1643
+
1644
+ :where(.backlinks ol > :not(:last-child)) {
1645
+ --tw-space-y-reverse: 0;
1646
+ margin-block-start: calc(var(--spacing) * var(--tw-space-y-reverse));
1647
+ margin-block-end: calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)));
1648
+ }
1649
+
1650
+ .backlinks ol {
1651
+ padding: 0;
1652
+ }
1653
+
1654
+ .backlinks a {
1655
+ align-items: center;
1656
+ min-block-size: 24px;
1657
+ min-inline-size: 24px;
1658
+ display: inline-flex;
1659
+ }
1660
+
1661
+ .backlinks time {
1662
+ color: var(--color-muted);
1663
+ margin-inline-start: var(--spacing);
1664
+ }
1665
+
1622
1666
  .pagination {
1623
1667
  margin-top: calc(var(--spacing) * 12);
1624
1668
  gap: calc(var(--spacing) * 4);
@@ -2028,6 +2072,29 @@
2028
2072
  font-style: italic;
2029
2073
  }
2030
2074
 
2075
+ .comment-thread {
2076
+ margin-bottom: calc(var(--spacing) * 8);
2077
+ }
2078
+
2079
+ .comment-step > .comment-step {
2080
+ margin-bottom: calc(var(--spacing) * 4);
2081
+ }
2082
+
2083
+ .comment-withheld {
2084
+ font-style: italic;
2085
+ }
2086
+
2087
+ .comment-page .comments-area {
2088
+ margin-top: calc(var(--spacing) * 10);
2089
+ }
2090
+
2091
+ .comment-back {
2092
+ margin-top: calc(var(--spacing) * 10);
2093
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
2094
+ font-size: var(--text-sm);
2095
+ line-height: var(--tw-leading, var(--text-sm--line-height));
2096
+ }
2097
+
2031
2098
  .comment-respond {
2032
2099
  margin-top: calc(var(--spacing) * 12);
2033
2100
  border-top-style: var(--tw-border-style);