@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.
- package/README.md +469 -84
- package/admin/layouts/shell.njk +6 -0
- package/admin/pages/comments/all.njk +3 -1
- package/admin/pages/documents/editor.njk +4 -0
- package/admin/pages/federation/followers.njk +8 -2
- package/admin/pages/plugins/installed.njk +1 -1
- package/admin/pages/tools/dev-mode.njk +47 -0
- package/admin/static/admin.css +8 -0
- package/admin/static/editor-actions.js +13 -9
- package/admin/static/editor.js +31 -17
- package/dist/admin/comments.d.ts +2 -0
- package/dist/admin/comments.d.ts.map +1 -1
- package/dist/admin/comments.js +19 -49
- package/dist/admin/comments.js.map +1 -1
- package/dist/admin/dev-mode.d.ts +9 -0
- package/dist/admin/dev-mode.d.ts.map +1 -0
- package/dist/admin/dev-mode.js +42 -0
- package/dist/admin/dev-mode.js.map +1 -0
- package/dist/admin/documents.d.ts +5 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +45 -17
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/editor-actions.js +3 -1
- package/dist/admin/editor-actions.js.map +1 -1
- package/dist/admin/federation.d.ts +21 -1
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +19 -7
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/menu.d.ts.map +1 -1
- package/dist/admin/menu.js +2 -0
- package/dist/admin/menu.js.map +1 -1
- package/dist/admin/own-paragraph.d.ts +7 -0
- package/dist/admin/own-paragraph.d.ts.map +1 -0
- package/dist/admin/own-paragraph.js +9 -0
- package/dist/admin/own-paragraph.js.map +1 -0
- package/dist/admin/plugins.d.ts.map +1 -1
- package/dist/admin/plugins.js +19 -8
- package/dist/admin/plugins.js.map +1 -1
- package/dist/admin/preview.d.ts.map +1 -1
- package/dist/admin/preview.js +4 -1
- package/dist/admin/preview.js.map +1 -1
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +4 -0
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/settings-email.d.ts.map +1 -1
- package/dist/admin/settings-email.js +4 -0
- package/dist/admin/settings-email.js.map +1 -1
- package/dist/admin/store.d.ts +68 -12
- package/dist/admin/store.d.ts.map +1 -1
- package/dist/admin/store.js +167 -18
- package/dist/admin/store.js.map +1 -1
- package/dist/admin/templates.d.ts +1 -0
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +1 -0
- package/dist/admin/templates.js.map +1 -1
- package/dist/admin/uploads.d.ts +7 -1
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +33 -30
- package/dist/admin/uploads.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +78 -12
- package/dist/cli.js.map +1 -1
- package/dist/comments/form.d.ts +3 -1
- package/dist/comments/form.d.ts.map +1 -1
- package/dist/comments/form.js +2 -1
- package/dist/comments/form.js.map +1 -1
- package/dist/comments/index.d.ts +2 -2
- package/dist/comments/index.d.ts.map +1 -1
- package/dist/comments/index.js +1 -1
- package/dist/comments/index.js.map +1 -1
- package/dist/comments/records.d.ts +25 -20
- package/dist/comments/records.d.ts.map +1 -1
- package/dist/comments/records.js +113 -33
- package/dist/comments/records.js.map +1 -1
- package/dist/comments/reply-notices.d.ts +42 -0
- package/dist/comments/reply-notices.d.ts.map +1 -0
- package/dist/comments/reply-notices.js +76 -0
- package/dist/comments/reply-notices.js.map +1 -0
- package/dist/comments/reply-post.d.ts +49 -0
- package/dist/comments/reply-post.d.ts.map +1 -0
- package/dist/comments/reply-post.js +66 -0
- package/dist/comments/reply-post.js.map +1 -0
- package/dist/comments/routes.d.ts +6 -1
- package/dist/comments/routes.d.ts.map +1 -1
- package/dist/comments/routes.js +49 -21
- package/dist/comments/routes.js.map +1 -1
- package/dist/comments/submission.d.ts +8 -25
- package/dist/comments/submission.d.ts.map +1 -1
- package/dist/comments/submission.js +19 -38
- package/dist/comments/submission.js.map +1 -1
- package/dist/comments/viewer.d.ts.map +1 -1
- package/dist/comments/viewer.js +1 -0
- package/dist/comments/viewer.js.map +1 -1
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/content/citation.d.ts +1 -0
- package/dist/content/citation.d.ts.map +1 -1
- package/dist/content/citation.js +2 -0
- package/dist/content/citation.js.map +1 -1
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +33 -1
- package/dist/content/markdown.js.map +1 -1
- package/dist/content/migrated.d.ts +6 -0
- package/dist/content/migrated.d.ts.map +1 -0
- package/dist/content/migrated.js +12 -0
- package/dist/content/migrated.js.map +1 -0
- package/dist/content/original.d.ts +8 -0
- package/dist/content/original.d.ts.map +1 -0
- package/dist/content/original.js +15 -0
- package/dist/content/original.js.map +1 -0
- package/dist/content/parser.d.ts +5 -0
- package/dist/content/parser.d.ts.map +1 -1
- package/dist/content/parser.js +1 -1
- package/dist/content/parser.js.map +1 -1
- package/dist/content/schedule.d.ts +7 -0
- package/dist/content/schedule.d.ts.map +1 -1
- package/dist/content/schedule.js +11 -2
- package/dist/content/schedule.js.map +1 -1
- package/dist/content/store.d.ts +43 -11
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +170 -10
- package/dist/content/store.js.map +1 -1
- package/dist/content/sync.d.ts +8 -0
- package/dist/content/sync.d.ts.map +1 -1
- package/dist/content/sync.js +172 -57
- package/dist/content/sync.js.map +1 -1
- package/dist/content/video.d.ts +12 -0
- package/dist/content/video.d.ts.map +1 -0
- package/dist/content/video.js +91 -0
- package/dist/content/video.js.map +1 -0
- package/dist/dev-mode.d.ts +28 -0
- package/dist/dev-mode.d.ts.map +1 -0
- package/dist/dev-mode.js +64 -0
- package/dist/dev-mode.js.map +1 -0
- package/dist/drain.d.ts +9 -3
- package/dist/drain.d.ts.map +1 -1
- package/dist/drain.js +24 -3
- package/dist/drain.js.map +1 -1
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +2 -1
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/backfill.d.ts +114 -0
- package/dist/federation/backfill.d.ts.map +1 -0
- package/dist/federation/backfill.js +312 -0
- package/dist/federation/backfill.js.map +1 -0
- package/dist/federation/delivery.d.ts +2 -0
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +46 -16
- package/dist/federation/delivery.js.map +1 -1
- package/dist/federation/inbox.d.ts.map +1 -1
- package/dist/federation/inbox.js +14 -4
- package/dist/federation/inbox.js.map +1 -1
- package/dist/federation/mount.d.ts.map +1 -1
- package/dist/federation/mount.js +8 -4
- package/dist/federation/mount.js.map +1 -1
- package/dist/federation/records.d.ts +13 -0
- package/dist/federation/records.d.ts.map +1 -1
- package/dist/federation/records.js +49 -10
- package/dist/federation/records.js.map +1 -1
- package/dist/federation/relays.d.ts.map +1 -1
- package/dist/federation/relays.js +16 -1
- package/dist/federation/relays.js.map +1 -1
- package/dist/federation/replies.d.ts +6 -0
- package/dist/federation/replies.d.ts.map +1 -1
- package/dist/federation/replies.js +18 -1
- package/dist/federation/replies.js.map +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -14
- package/dist/index.js.map +1 -1
- package/dist/indexnow.d.ts +1 -1
- package/dist/indexnow.d.ts.map +1 -1
- package/dist/indexnow.js +7 -0
- package/dist/indexnow.js.map +1 -1
- package/dist/init.d.ts +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +1 -7
- package/dist/init.js.map +1 -1
- package/dist/mail/service.d.ts +3 -1
- package/dist/mail/service.d.ts.map +1 -1
- package/dist/mail/service.js +17 -0
- package/dist/mail/service.js.map +1 -1
- package/dist/notify.d.ts +1 -1
- package/dist/notify.d.ts.map +1 -1
- package/dist/notify.js +7 -0
- package/dist/notify.js.map +1 -1
- package/dist/plugin.d.ts +67 -4
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/plugins/folder.d.ts +2 -0
- package/dist/plugins/folder.d.ts.map +1 -1
- package/dist/plugins/folder.js +26 -5
- package/dist/plugins/folder.js.map +1 -1
- package/dist/plugins/manage.d.ts +8 -6
- package/dist/plugins/manage.d.ts.map +1 -1
- package/dist/plugins/manage.js +1 -4
- package/dist/plugins/manage.js.map +1 -1
- package/dist/plugins/site.d.ts.map +1 -1
- package/dist/plugins/site.js +56 -0
- package/dist/plugins/site.js.map +1 -1
- package/dist/supervisor/control.d.ts +15 -0
- package/dist/supervisor/control.d.ts.map +1 -0
- package/dist/supervisor/control.js +89 -0
- package/dist/supervisor/control.js.map +1 -0
- package/dist/supervisor/primary.d.ts +18 -4
- package/dist/supervisor/primary.d.ts.map +1 -1
- package/dist/supervisor/primary.js +128 -16
- package/dist/supervisor/primary.js.map +1 -1
- package/dist/supervisor/protocol.d.ts +12 -0
- package/dist/supervisor/protocol.d.ts.map +1 -1
- package/dist/supervisor/protocol.js +21 -2
- package/dist/supervisor/protocol.js.map +1 -1
- package/dist/supervisor/supervision.d.ts +1 -0
- package/dist/supervisor/supervision.d.ts.map +1 -1
- package/dist/supervisor/supervision.js +14 -1
- package/dist/supervisor/supervision.js.map +1 -1
- package/dist/supervisor/worker.d.ts.map +1 -1
- package/dist/supervisor/worker.js +31 -6
- package/dist/supervisor/worker.js.map +1 -1
- package/dist/web/context.d.ts +22 -0
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +20 -4
- package/dist/web/context.js.map +1 -1
- package/dist/web/conversation.d.ts +155 -16
- package/dist/web/conversation.d.ts.map +1 -1
- package/dist/web/conversation.js +602 -135
- package/dist/web/conversation.js.map +1 -1
- package/dist/web/documents.d.ts +0 -22
- package/dist/web/documents.d.ts.map +1 -1
- package/dist/web/documents.js +1 -34
- package/dist/web/documents.js.map +1 -1
- package/dist/web/feed-item.d.ts +19 -14
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +10 -9
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-rss.d.ts +2 -2
- package/dist/web/feed-rss.d.ts.map +1 -1
- package/dist/web/feed-rss.js +36 -12
- package/dist/web/feed-rss.js.map +1 -1
- package/dist/web/feed-source.d.ts +30 -1
- package/dist/web/feed-source.d.ts.map +1 -1
- package/dist/web/feed-source.js +25 -0
- package/dist/web/feed-source.js.map +1 -1
- package/dist/web/feeds.d.ts +1 -1
- package/dist/web/feeds.d.ts.map +1 -1
- package/dist/web/feeds.js +7 -2
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/guids.d.ts +59 -0
- package/dist/web/guids.d.ts.map +1 -0
- package/dist/web/guids.js +79 -0
- package/dist/web/guids.js.map +1 -0
- package/dist/web/index.d.ts +5 -6
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +3 -3
- package/dist/web/index.js.map +1 -1
- package/dist/web/redirects.d.ts +29 -10
- package/dist/web/redirects.d.ts.map +1 -1
- package/dist/web/redirects.js +99 -53
- package/dist/web/redirects.js.map +1 -1
- package/dist/web/render.d.ts +19 -9
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +44 -42
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts +2 -2
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +94 -31
- package/dist/web/routes.js.map +1 -1
- package/dist/webmention/index.d.ts +1 -1
- package/dist/webmention/index.d.ts.map +1 -1
- package/dist/webmention/index.js +1 -1
- package/dist/webmention/index.js.map +1 -1
- package/dist/webmention/links.d.ts +55 -0
- package/dist/webmention/links.d.ts.map +1 -1
- package/dist/webmention/links.js +162 -0
- package/dist/webmention/links.js.map +1 -1
- package/dist/webmention/microformats.d.ts +15 -0
- package/dist/webmention/microformats.d.ts.map +1 -1
- package/dist/webmention/microformats.js +63 -1
- package/dist/webmention/microformats.js.map +1 -1
- package/dist/webmention/pingback.js +2 -1
- package/dist/webmention/pingback.js.map +1 -1
- package/dist/webmention/receive.d.ts +5 -0
- package/dist/webmention/receive.d.ts.map +1 -1
- package/dist/webmention/receive.js +99 -3
- package/dist/webmention/receive.js.map +1 -1
- package/dist/webmention/reply-context.d.ts +15 -0
- package/dist/webmention/reply-context.d.ts.map +1 -1
- package/dist/webmention/reply-context.js +29 -0
- package/dist/webmention/reply-context.js.map +1 -1
- package/dist/webmention/reply-contexts.d.ts +6 -0
- package/dist/webmention/reply-contexts.d.ts.map +1 -1
- package/dist/webmention/reply-contexts.js +16 -10
- package/dist/webmention/reply-contexts.js.map +1 -1
- package/dist/webmention/routes.d.ts.map +1 -1
- package/dist/webmention/routes.js +2 -1
- package/dist/webmention/routes.js.map +1 -1
- package/dist/webmention/service.d.ts +31 -2
- package/dist/webmention/service.d.ts.map +1 -1
- package/dist/webmention/service.js +183 -33
- package/dist/webmention/service.js.map +1 -1
- package/package.json +1 -2
- package/themes/default/README.md +390 -75
- package/themes/default/layouts/base.njk +19 -2
- package/themes/default/layouts/comment.njk +90 -0
- package/themes/default/layouts/front-page.njk +9 -47
- package/themes/default/layouts/page.njk +2 -0
- package/themes/default/layouts/post.njk +22 -4
- package/themes/default/partials/backlinks.njk +19 -0
- package/themes/default/partials/body-end.njk +1 -0
- package/themes/default/partials/cited-page.njk +3 -1
- package/themes/default/partials/comment-form.njk +10 -0
- package/themes/default/partials/comment.njk +79 -0
- package/themes/default/partials/conversation.njk +14 -59
- package/themes/default/partials/head-end.njk +1 -0
- package/themes/default/src/style.css +46 -0
- package/themes/default/static/style.css +75 -0
package/themes/default/README.md
CHANGED
|
@@ -20,6 +20,7 @@ themes/default/
|
|
|
20
20
|
410.njk what was at this URL was deleted
|
|
21
21
|
500.njk the request failed on the server
|
|
22
22
|
503.njk the site is in maintenance mode
|
|
23
|
+
comment.njk a native comment on a page of its own
|
|
23
24
|
partials/
|
|
24
25
|
post-list.njk the h-feed a listing is made of
|
|
25
26
|
pagination.njk previous/next pager
|
|
@@ -34,10 +35,14 @@ themes/default/
|
|
|
34
35
|
menu.njk one named menu, as a nav of links
|
|
35
36
|
feeds.njk macros for the feed links in <head>
|
|
36
37
|
conversation.njk the replies, likes and boosts under a post or an open page
|
|
38
|
+
comment.njk one reply and its answers, as the thread and a comment page print it
|
|
37
39
|
comment-form.njk the form under a post or page that is taking comments
|
|
38
40
|
contact-form.njk the form on a page whose front matter says contact: true
|
|
39
41
|
archive.njk every post by month, on a page that says archive: true
|
|
42
|
+
backlinks.njk the site's own posts and pages that link to this one
|
|
40
43
|
search-form.njk the search box, on the search page
|
|
44
|
+
head-end.njk empty: what a site adds to the end of every <head>
|
|
45
|
+
body-end.njk empty: what a site adds to the end of every <body>
|
|
41
46
|
mail/
|
|
42
47
|
test.*.njk the Send test email message
|
|
43
48
|
password-reset.*.njk the forgot-password link
|
|
@@ -187,6 +192,94 @@ until it writes one. On the posts page the page's own front matter and rendered
|
|
|
187
192
|
body are on the context beside the listing, so `{{ content | safe }}` prints its
|
|
188
193
|
words above the posts; `layouts/home.njk` already does.
|
|
189
194
|
|
|
195
|
+
## Adding to every page
|
|
196
|
+
|
|
197
|
+
Analytics, a site verification tag or a chat widget goes on every page, and
|
|
198
|
+
none of them needs a layout copied. `layouts/base.njk` includes two partials
|
|
199
|
+
that this theme ships empty:
|
|
200
|
+
|
|
201
|
+
| Partial | Printed |
|
|
202
|
+
| ----------------------- | -------------------------------------------------- |
|
|
203
|
+
| `partials/head-end.njk` | last in `<head>`, after the cards and the JSON-LD |
|
|
204
|
+
| `partials/body-end.njk` | last in `<body>`, after the footer and the scripts |
|
|
205
|
+
|
|
206
|
+
A site theme that holds one of them, and nothing else but its `theme.json`,
|
|
207
|
+
adds that markup to every public HTML page: posts, pages, listings, search, the
|
|
208
|
+
front page, and the 404, 410, 500 and 503 pages. Every other template still
|
|
209
|
+
comes from this directory, so the site keeps receiving updates to them, and
|
|
210
|
+
the two partial names stay as they are. The includes sit outside every block,
|
|
211
|
+
so a layout that overrides `head` or `scripts` without `super()` keeps them.
|
|
212
|
+
|
|
213
|
+
They are part of the HTML page and nothing else. The Markdown, plain text and
|
|
214
|
+
JSON representations of a page, the feeds, `llms.txt`, mail and the admin do
|
|
215
|
+
not print them. The editor's preview renders the post or page layout in the
|
|
216
|
+
admin, so the context there carries `editorPreview: true` and `layouts/base.njk`
|
|
217
|
+
leaves both partials out. A theme that replaces `layouts/base.njk` and includes
|
|
218
|
+
them itself should test the same key.
|
|
219
|
+
|
|
220
|
+
Umami and Google Analytics 4, in `themes/analytics/`:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
themes/analytics/
|
|
224
|
+
theme.json
|
|
225
|
+
partials/head-end.njk
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{ "name": "Analytics", "kind": "site" }
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```njk
|
|
233
|
+
<script defer src="https://cloud.umami.is/script.js" data-website-id="your-website-id"></script>
|
|
234
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
|
|
235
|
+
<script>
|
|
236
|
+
window.dataLayer = window.dataLayer || [];
|
|
237
|
+
function gtag(){dataLayer.push(arguments);}
|
|
238
|
+
gtag('js', new Date());
|
|
239
|
+
gtag('config', 'G-XXXXXXXXXX');
|
|
240
|
+
</script>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Then wear the theme, either with Activate on **Appearance > Themes** or with
|
|
244
|
+
`"theme": "analytics"` in `content/_data/site.json`. A site that already wears
|
|
245
|
+
a theme of its own puts the partial in that theme instead.
|
|
246
|
+
|
|
247
|
+
The partial is a template, rendered with the page's context, so it can read a
|
|
248
|
+
value out of `site.json`, as in `data-website-id="{{ site.umamiId }}"`.
|
|
249
|
+
|
|
250
|
+
**In Docker.** The image reads themes from `/site/themes`. Mount the site's
|
|
251
|
+
`themes/` directory there, read-only, which is the commented line in
|
|
252
|
+
`deploy/compose.yaml`:
|
|
253
|
+
|
|
254
|
+
```yaml
|
|
255
|
+
volumes:
|
|
256
|
+
- ./themes:/site/themes:ro
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
No custom image is needed. A change to the partial shows on the next request.
|
|
260
|
+
|
|
261
|
+
**Under a Content-Security-Policy.** The default headers set only
|
|
262
|
+
`frame-ancestors`, so they restrict nothing a page loads, and the inline gtag
|
|
263
|
+
configuration runs as it is. A site that sets a policy of its own with
|
|
264
|
+
`securityHeaders` must allow what the snippets load and where they send data:
|
|
265
|
+
|
|
266
|
+
| Directive | Umami | Google Analytics 4 |
|
|
267
|
+
| ------------- | -------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
268
|
+
| `script-src` | `https://cloud.umami.is` | `https://*.googletagmanager.com`, and the inline configuration (see below) |
|
|
269
|
+
| `connect-src` | `https://gateway.umami.is` | `https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com` |
|
|
270
|
+
| `img-src` | none | `https://*.google-analytics.com https://*.googletagmanager.com` |
|
|
271
|
+
|
|
272
|
+
The inline `<script>` that configures gtag needs one of these:
|
|
273
|
+
|
|
274
|
+
- Move it to a file in the theme, `themes/analytics/static/gtag.js`, and load
|
|
275
|
+
it with `<script src="{{ "gtag.js" | asset }}"></script>`. It is then served
|
|
276
|
+
from the site, and `script-src 'self'` covers it.
|
|
277
|
+
- Allow its hash, `'sha256-…'`, in `script-src`. The hash covers every byte
|
|
278
|
+
between the tags, so it changes when the measurement ID or the whitespace
|
|
279
|
+
changes.
|
|
280
|
+
- Allow `'unsafe-inline'` in `script-src`. This also allows any injected
|
|
281
|
+
inline script, so prefer one of the other two.
|
|
282
|
+
|
|
190
283
|
## The page shell
|
|
191
284
|
|
|
192
285
|
`layouts/base.njk` is the page shell: the skip link, one `.global-wrapper` at the
|
|
@@ -387,6 +480,21 @@ IndieNews and Bridgy Publish look for on the page, and ends with the post's
|
|
|
387
480
|
copies elsewhere (`syndication`) after **Also on**. A page never prints either,
|
|
388
481
|
and its line is a `p.page-meta` rather than a `p.entry-meta`.
|
|
389
482
|
|
|
483
|
+
**A post first published elsewhere says so.** A post whose front matter names
|
|
484
|
+
its original with `canonical_href`, an absolute http or https URL such as a
|
|
485
|
+
Substack essay's, has it on the context as `original` (TASK-293). The line
|
|
486
|
+
then reads **Originally published at** and links the original as a second
|
|
487
|
+
`u-url` of the h-entry, after the permalink's. The link says the publication's
|
|
488
|
+
name when the front matter gives one in `canonical_name`, such as `Smarter
|
|
489
|
+
Engineers` for a guest post on someone else's Substack (TASK-316), and the
|
|
490
|
+
original's host otherwise. It is no
|
|
491
|
+
`u-syndication`, because the original is not a copy of this post, and
|
|
492
|
+
original-post-discovery reads a copy's off-site `u-url` as its original. The
|
|
493
|
+
same URL is the page's `rel="canonical"` and its `og:url`, so search engines
|
|
494
|
+
credit the original. A value that is not an absolute http or https URL is
|
|
495
|
+
ignored, and `geekity sync` names the file. So is a `canonical_name` with no
|
|
496
|
+
valid `canonical_href` beside it.
|
|
497
|
+
|
|
390
498
|
**Every post's page has one `h1`.** A post with a name of its own is headed by
|
|
391
499
|
it, as above. So is a post with a title and no words, such as a titled like,
|
|
392
500
|
repost or bookmark (TASK-256): its header comes first and its citation after
|
|
@@ -470,7 +578,8 @@ a `p-author h-card`, when the target was read when the post was saved
|
|
|
470
578
|
provider's known oEmbed endpoint, asked before its page, which is read only
|
|
471
579
|
when the endpoint names nothing (TASK-251, TASK-253). Any other target's oEmbed
|
|
472
580
|
title comes from the endpoint its page links. A title that is only a site suffix such as "- YouTube" is
|
|
473
|
-
no title.
|
|
581
|
+
no title. A comment on this site (`replyContext.comment`, TASK-326) says "a
|
|
582
|
+
comment". With only an author it says "a post", and with neither "a page on"
|
|
474
583
|
the target's host, never the bare URL, which can run to hundreds of
|
|
475
584
|
characters: "Bookmarked a page on example.com" (TASK-255). Its kicker
|
|
476
585
|
and its hidden `h1` say Repost, Like or Bookmark. It is placed as the reply
|
|
@@ -605,13 +714,50 @@ its front matter names no `image`.
|
|
|
605
714
|
</figure>
|
|
606
715
|
```
|
|
607
716
|
|
|
608
|
-
After the entry a post prints `nav.blog-post-nav`: the
|
|
609
|
-
posts as two cards, `rel="prev"` and `rel="next"`, each
|
|
610
|
-
`span.blog-post-nav-label`, and nothing at all at the ends of the
|
|
611
|
-
the conversation and the comment form. A page prints
|
|
612
|
-
|
|
613
|
-
matter asked for one. A page has
|
|
614
|
-
syndication links, because none of those are
|
|
717
|
+
After the entry a post prints its backlinks, then `nav.blog-post-nav`: the
|
|
718
|
+
`previous` and `next` posts as two cards, `rel="prev"` and `rel="next"`, each
|
|
719
|
+
opening on a `span.blog-post-nav-label`, and nothing at all at the ends of the
|
|
720
|
+
archive; then the conversation and the comment form. A page prints its
|
|
721
|
+
backlinks too, then the conversation and the comment form when it takes
|
|
722
|
+
comments, and the contact form when its front matter asked for one. A page has
|
|
723
|
+
no neighbours, no tags and no syndication links, because none of those are
|
|
724
|
+
things a page has.
|
|
725
|
+
|
|
726
|
+
### Backlinks
|
|
727
|
+
|
|
728
|
+
A link from one of the site's posts or pages to another sends no webmention.
|
|
729
|
+
The CMS records it instead, and the linked document's context carries
|
|
730
|
+
`backlinks`: the listed posts and pages whose body links to it, newest first,
|
|
731
|
+
each as `{ title, url, date }`. `title` is the linking document's title, or a
|
|
732
|
+
note's first words. `date` is absent for a page that has none. A link counts
|
|
733
|
+
whether it is absolute or relative, with or without its trailing slash, a query
|
|
734
|
+
or a fragment, and through a `redirect_from` of the target. A link to `/`
|
|
735
|
+
counts for the page the Reading setting makes the homepage. A link to a URL the
|
|
736
|
+
site answers with a redirect counts for the document the redirect leads to,
|
|
737
|
+
whether the redirect is a `redirect_from` or an entry in `_data/redirects.json`
|
|
738
|
+
or `_data/redirects/*.json`, `/?p=123` included. A link with a query names the
|
|
739
|
+
query's redirect only when the query matches the redirect's source exactly, as
|
|
740
|
+
a request does, so `/?p=123&ref=feed` is a link to `/`. A document that links
|
|
741
|
+
to itself is not its own backlink. Drafts, future-dated, unlisted,
|
|
742
|
+
private and trashed documents never appear. `backlinks` is always a list, and
|
|
743
|
+
it is empty when nothing links to the document.
|
|
744
|
+
|
|
745
|
+
`partials/backlinks.njk` prints a `section.backlinks` under the entry, apart
|
|
746
|
+
from the conversation: an `h2` that says "Linked from", then an `ol` of links,
|
|
747
|
+
each with its date in a `<time>`. It prints nothing for an empty list, so
|
|
748
|
+
`layouts/post.njk` and `layouts/page.njk` include it with no condition. The
|
|
749
|
+
front page has `backlinks` on its context too, but `layouts/front-page.njk`
|
|
750
|
+
does not print them, because most of a site links home. A site theme that wants
|
|
751
|
+
them there adds the same include to its own `layouts/front-page.njk`:
|
|
752
|
+
|
|
753
|
+
```njk
|
|
754
|
+
{% include "partials/backlinks.njk" %}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
A site theme moves the list by including the partial somewhere else in its own
|
|
758
|
+
layout, and leaves it out by overriding a layout without the include. To change
|
|
759
|
+
the markup, add a `partials/backlinks.njk` to the site theme. That file replaces
|
|
760
|
+
this one, as any other template is replaced.
|
|
615
761
|
|
|
616
762
|
### The bio
|
|
617
763
|
|
|
@@ -640,16 +786,19 @@ claims: the name is linked `rel="author"` alone and `me` comes off every
|
|
|
640
786
|
link.
|
|
641
787
|
|
|
642
788
|
**The homepage** speaks for the site's author only on a site whose Site author
|
|
643
|
-
setting names a user, which `soloAuthor` on the context says. Then
|
|
644
|
-
`layouts/
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
`
|
|
789
|
+
setting names a user, which `soloAuthor` on the context says. Then
|
|
790
|
+
`layouts/base.njk` puts that person's `rel="me"` claims in the head of `/` as
|
|
791
|
+
`<link rel="me">` elements, one for their author archive and one for each
|
|
792
|
+
profile link, whichever layout draws the page (decision-45). Their author
|
|
793
|
+
archive's link home carries `rel="me"` back, so the homepage and the archive
|
|
794
|
+
name each other. `layouts/home.njk` at `/` also ends with that person's card,
|
|
795
|
+
with the same claims, and sets `bioHome` to the homepage's absolute URL, which
|
|
796
|
+
puts a `data.u-url.u-uid` holding it first in the card, so a parser running the
|
|
649
797
|
[representative h-card algorithm](https://microformats.org/wiki/representative-h-card-parsing)
|
|
650
798
|
on the homepage finds this card as the site's (TASK-193). The archive stays a
|
|
651
|
-
second `u-url`. No other page sets `bioHome`.
|
|
652
|
-
|
|
799
|
+
second `u-url`. No other page sets `bioHome`. A static front page prints no
|
|
800
|
+
card: see [The front page](#the-front-page) for how a site puts one back. On a
|
|
801
|
+
site with several authors neither homepage prints a card or makes a claim. Each of
|
|
653
802
|
those is printed only when the profile says it, so a profile holding a name
|
|
654
803
|
alone prints a name alone. A name this site has no account for is printed
|
|
655
804
|
unlinked, because the file still said somebody wrote this.
|
|
@@ -685,8 +834,8 @@ byline under the title rather than a bio in the footer, so it writes one.
|
|
|
685
834
|
### A listing
|
|
686
835
|
|
|
687
836
|
`partials/post-list.njk` is the feed every listing is made of (the home page,
|
|
688
|
-
the posts page, a tag, category or author archive, and
|
|
689
|
-
|
|
837
|
+
the posts page, a tag, category or author archive, and a front page that
|
|
838
|
+
lists `newestPosts`) and it is a microformats2 `h-feed` of `h-entry` items, each opening on
|
|
690
839
|
its kicker:
|
|
691
840
|
|
|
692
841
|
```html
|
|
@@ -833,25 +982,69 @@ name a homepage (`homepage` in `content/_data/site.json`). It is that page read
|
|
|
833
982
|
somewhere else: the same document, the same context, with `page.url` saying `/`
|
|
834
983
|
rather than the permalink that redirects there.
|
|
835
984
|
|
|
836
|
-
It draws the page's own
|
|
837
|
-
|
|
838
|
-
1. `
|
|
839
|
-
heads the root path with the site title
|
|
840
|
-
|
|
841
|
-
as the site rather than as a page somebody
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
list
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
985
|
+
It draws the page's own title and words, and nothing else (decision-44):
|
|
986
|
+
|
|
987
|
+
1. `h2.section-title`, the page's title. It is an `h2` because
|
|
988
|
+
`layouts/base.njk` heads the root path with the site title as the `h1`.
|
|
989
|
+
2. `div.page-body.e-content`, the rendered body. There is no Published line,
|
|
990
|
+
because a front page is read as the site rather than as a page somebody
|
|
991
|
+
wrote on a Tuesday.
|
|
992
|
+
3. The contact form, when the page's front matter says `contact: true`.
|
|
993
|
+
|
|
994
|
+
It lists no posts, prints no line of links and prints no bio. A site that wants
|
|
995
|
+
any of them on its front page overrides `layouts/front-page.njk` in its own
|
|
996
|
+
theme. Two context keys are the front page's alone, for that override:
|
|
997
|
+
|
|
998
|
+
- `newestPosts(count)` is a function. It returns the newest `count` published
|
|
999
|
+
posts as entries, in the same shape a listing's `posts` are, so
|
|
1000
|
+
`partials/post-list.njk` prints them. Drafts, future-dated, unlisted and
|
|
1001
|
+
trashed posts are left out. The theme chooses the count. The query runs only
|
|
1002
|
+
when a template calls it, so a front page that lists nothing costs nothing.
|
|
1003
|
+
A count that is not a whole number of at least 1 is an error.
|
|
1004
|
+
- `postsPage` is `{ title, url }` for the page carrying the listing, and is
|
|
1005
|
+
**absent** when the site names none, because a link to a listing that has no
|
|
1006
|
+
URL is a link to nothing.
|
|
1007
|
+
|
|
1008
|
+
`newestPosts` replaces `recentPosts`, which held this month's posts when there
|
|
1009
|
+
were at least five and the five newest otherwise. An override that read
|
|
1010
|
+
`recentPosts` calls `newestPosts(5)` instead. This override draws the page's
|
|
1011
|
+
words, the five newest posts, a line of links and the site author's card as
|
|
1012
|
+
the site's representative h-card:
|
|
1013
|
+
|
|
1014
|
+
```njk
|
|
1015
|
+
{% extends "layouts/base.njk" %}
|
|
1016
|
+
|
|
1017
|
+
{% block content %}
|
|
1018
|
+
<div class="page-body e-content">
|
|
1019
|
+
{{ content | safe }}
|
|
1020
|
+
</div>
|
|
1021
|
+
|
|
1022
|
+
<h2 class="section-title">Recent Posts</h2>
|
|
1023
|
+
{% set posts = newestPosts(5) %}
|
|
1024
|
+
{% set feedHeading = 3 %}
|
|
1025
|
+
{% include "partials/post-list.njk" %}
|
|
1026
|
+
|
|
1027
|
+
<p class="front-links">
|
|
1028
|
+
{% if postsPage %}
|
|
1029
|
+
<a href="{{ postsPage.url | url }}">{{ postsPage.title }}<span aria-hidden="true"> →</span></a>
|
|
1030
|
+
{% endif %}
|
|
1031
|
+
<a href="{{ "/search/" | url }}">Search<span aria-hidden="true"> →</span></a>
|
|
1032
|
+
</p>
|
|
1033
|
+
|
|
1034
|
+
{% if soloAuthor %}
|
|
1035
|
+
{% set bioAuthor = soloAuthor %}
|
|
1036
|
+
{% set bioHome = "/" | absoluteUrl %}
|
|
1037
|
+
<hr>
|
|
1038
|
+
<footer>
|
|
1039
|
+
{% include "partials/bio.njk" %}
|
|
1040
|
+
</footer>
|
|
1041
|
+
{% endif %}
|
|
1042
|
+
{% endblock %}
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
The stylesheet still styles `h2.section-title` and `p.front-links`, so an
|
|
1046
|
+
override can use both. Whatever the override prints, the solo author's
|
|
1047
|
+
`rel="me"` claims stay in the head of `/` (decision-45).
|
|
855
1048
|
|
|
856
1049
|
### An archive page
|
|
857
1050
|
|
|
@@ -919,7 +1112,8 @@ mode gets a dark canvas rather than a white flash while the stylesheet loads.
|
|
|
919
1112
|
**The card.** `og:title` is the page's title, or the site's on the front page;
|
|
920
1113
|
`og:site_name` is always the site's. `og:type` is `article` on a rendered post
|
|
921
1114
|
or page and `website` everywhere else, a listing carrying a page's front matter
|
|
922
|
-
included. `og:url` is the canonical URL
|
|
1115
|
+
included. `og:url` is the canonical URL: the page's own, or the `original` a
|
|
1116
|
+
post first published elsewhere names. An article also carries
|
|
923
1117
|
`article:published_time`, `article:modified_time` (the `updated` date, else the
|
|
924
1118
|
publish date), `article:author` (the author's profile URL here, else their
|
|
925
1119
|
name) and one `article:tag` per tag.
|
|
@@ -1220,6 +1414,54 @@ the `+` and the `-` in the text carry the meaning as well as the colour does.
|
|
|
1220
1414
|
{% block scripts %}{% endblock %}
|
|
1221
1415
|
```
|
|
1222
1416
|
|
|
1417
|
+
### Videos
|
|
1418
|
+
|
|
1419
|
+
A YouTube or Vimeo URL on a line of its own in a post plays as the video
|
|
1420
|
+
(decision-34; doc-2 lists the addresses it knows). The core renders it into the
|
|
1421
|
+
body, so it reaches a theme inside `content` as:
|
|
1422
|
+
|
|
1423
|
+
```html
|
|
1424
|
+
<figure class="video-embed video-embed-youtube">
|
|
1425
|
+
<iframe
|
|
1426
|
+
src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
|
|
1427
|
+
width="560"
|
|
1428
|
+
height="315"
|
|
1429
|
+
title="YouTube video"
|
|
1430
|
+
loading="lazy"
|
|
1431
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
1432
|
+
referrerpolicy="strict-origin-when-cross-origin"
|
|
1433
|
+
allowfullscreen
|
|
1434
|
+
></iframe>
|
|
1435
|
+
<figcaption>
|
|
1436
|
+
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
|
1437
|
+
>https://www.youtube.com/watch?v=dQw4w9WgXcQ</a
|
|
1438
|
+
>
|
|
1439
|
+
</figcaption>
|
|
1440
|
+
</figure>
|
|
1441
|
+
```
|
|
1442
|
+
|
|
1443
|
+
It is one line in the real output. A Vimeo video is the same with
|
|
1444
|
+
`video-embed-vimeo`, a `https://player.vimeo.com/video/ID?dnt=1` player and the
|
|
1445
|
+
title `Vimeo video`. The core sets no style on it: with nothing from the theme
|
|
1446
|
+
the player is the providers' standard 560 by 315.
|
|
1447
|
+
|
|
1448
|
+
**Paper** makes it responsive. `.video-embed iframe` takes the full width of
|
|
1449
|
+
the text column at 16:9, with the rounded corners an image has, and the
|
|
1450
|
+
figcaption takes the muted caption style every figure in a post has. A theme
|
|
1451
|
+
that ships its own `static/style.css` writes that rule itself:
|
|
1452
|
+
|
|
1453
|
+
```css
|
|
1454
|
+
.video-embed iframe {
|
|
1455
|
+
width: 100%;
|
|
1456
|
+
height: auto;
|
|
1457
|
+
aspect-ratio: 16 / 9;
|
|
1458
|
+
}
|
|
1459
|
+
```
|
|
1460
|
+
|
|
1461
|
+
The figcaption is the link a feed reader, a fediverse server or the embed view
|
|
1462
|
+
keeps when it drops the iframe. A theme that would rather not show it on the
|
|
1463
|
+
page can hide `.video-embed figcaption`; the feeds carry it either way.
|
|
1464
|
+
|
|
1223
1465
|
## Mail templates
|
|
1224
1466
|
|
|
1225
1467
|
The messages the CMS sends live under `mail/` and resolve the same way, so a
|
|
@@ -1328,11 +1570,13 @@ A document — one post, one page, or one entry of a listing — adds:
|
|
|
1328
1570
|
| `event` | An event's `{ start, end, location }`: `Date`s, and `{ kind: "place", name }` or `{ kind: "virtual", url }`. |
|
|
1329
1571
|
| `syndicateTo` | On a post's page, the syndication targets it selects, each `{ id, name, url }`. Empty with none. |
|
|
1330
1572
|
| `syndication` | Its copies elsewhere, each `{ url, label }`: front matter `syndication`, and on its page the copies targets made. |
|
|
1573
|
+
| `original` | Where it was first published, `{ url, label }`: `canonical_href`, named by `canonical_name` or its host. |
|
|
1331
1574
|
| `author` | Who wrote it, as a profile rather than a string. See [Bylines and author archives](#bylines-and-author-archives). |
|
|
1332
1575
|
| `activityStreams` | The post's ActivityPub object id, absolute. Only on a rendered published post. |
|
|
1333
1576
|
| `previous` | The published post before this one by date, as `{ title, url }`. Absent on the oldest post. |
|
|
1334
1577
|
| `next` | The published post after it. Absent on the newest post, and on a page. |
|
|
1335
|
-
| `
|
|
1578
|
+
| `backlinks` | The site's listed posts and pages that link to it, newest first, each `{ title, url, date }`. Empty with none. |
|
|
1579
|
+
| `newestPosts` | A function, front page only: `newestPosts(5)` is the five newest published posts, as entries. |
|
|
1336
1580
|
| `postsPage` | The page carrying the listing, as `{ title, url }`, on the front page only. Absent when the site names none. |
|
|
1337
1581
|
| `archiveMonths` | Every published post as `{ month, posts }`, newest month first. Only on a page that says `archive: true`. |
|
|
1338
1582
|
| `webmention` | Where a webmention about this page is sent. Only on a rendered document, and only while the site takes them. |
|
|
@@ -1574,9 +1818,9 @@ as well as the site's — on an author archive that root is `author.url`.
|
|
|
1574
1818
|
|
|
1575
1819
|
`layouts/base.njk` fills the `alternates` block with `feedLinks` over `/` and
|
|
1576
1820
|
then the site's comments feed, so every page that extends it advertises all
|
|
1577
|
-
four. On a published post
|
|
1578
|
-
`commentsFeed` on the context
|
|
1579
|
-
object:
|
|
1821
|
+
four. On a published post, and on a page that takes comments, it adds that
|
|
1822
|
+
document's own comments feed, from `commentsFeed` on the context. On a
|
|
1823
|
+
published post it also adds a link pointing at the post's ActivityPub object:
|
|
1580
1824
|
|
|
1581
1825
|
```html
|
|
1582
1826
|
<link
|
|
@@ -1589,9 +1833,10 @@ object:
|
|
|
1589
1833
|
A post's object id is its permalink, so that link usually points at the page it
|
|
1590
1834
|
is on: it says the URL answers ActivityStreams as well as HTML. A post migrated
|
|
1591
1835
|
from elsewhere keeps the id its file names, and the link points there instead.
|
|
1592
|
-
It comes from `activityStreams`, which
|
|
1593
|
-
|
|
1594
|
-
does not call `super()` has to
|
|
1836
|
+
It comes from `activityStreams`, which is only on the context of a rendered
|
|
1837
|
+
published post, as `commentsFeed` is only on a document that has a comments
|
|
1838
|
+
feed, so a layout that overrides the block and does not call `super()` has to
|
|
1839
|
+
emit both itself. They are on the context rather
|
|
1595
1840
|
than in `post.njk` so that a site which overrides that layout, as the demo
|
|
1596
1841
|
does, keeps them.
|
|
1597
1842
|
|
|
@@ -1678,15 +1923,27 @@ section — a `div.reactions-section` of the likes, the boosts and the mentions
|
|
|
1678
1923
|
as facepiles grouped by kind, and then the thread as
|
|
1679
1924
|
`div#comments.comments-area` — and a site replaces it with a
|
|
1680
1925
|
`partials/conversation.njk` of its own in the theme it wears, exactly as it
|
|
1681
|
-
replaces any other template. It defines
|
|
1682
|
-
`group(items, label, kind, icon, source)
|
|
1683
|
-
|
|
1926
|
+
replaces any other template. It defines two macros, `face(item, icon, href)`
|
|
1927
|
+
and `group(items, label, kind, icon, source)`, and a layout that wants to place
|
|
1928
|
+
the pieces itself can import them:
|
|
1684
1929
|
|
|
1685
1930
|
```njk
|
|
1686
|
-
{% import "partials/conversation.njk" as
|
|
1687
|
-
{{
|
|
1931
|
+
{% import "partials/conversation.njk" as reactions with context %}
|
|
1932
|
+
{{ reactions.group(conversation.likes, "Likes", "p-like", "❤️") }}
|
|
1688
1933
|
```
|
|
1689
1934
|
|
|
1935
|
+
One reply and everything under it is `comment(reply, classes)` in
|
|
1936
|
+
`partials/comment.njk`, which the thread and a comment's own page both import.
|
|
1937
|
+
`classes` is the reply's microformat: `h-entry`, the default, under a post, and
|
|
1938
|
+
`p-comment h-cite` on a comment's page, where the comment is the one entry.
|
|
1939
|
+
|
|
1940
|
+
A reply post prints its replies inside its own `h-entry`, as `p-comment
|
|
1941
|
+
h-cite`s, so the page it answers finds them there when it is sent a salmention
|
|
1942
|
+
(TASK-320). `layouts/post.njk` includes `partials/conversation.njk` twice for
|
|
1943
|
+
one, setting `conversationPart`: `"replies"` inside the entry for the thread,
|
|
1944
|
+
and `"reactions"` below it for the facepiles, whose links are not the entry's.
|
|
1945
|
+
Left unset, the partial prints the whole section.
|
|
1946
|
+
|
|
1690
1947
|
### The shape
|
|
1691
1948
|
|
|
1692
1949
|
The conversation is deliberately **not** spelled in ActivityPub's vocabulary.
|
|
@@ -1714,26 +1971,35 @@ Each entry — a reply, a like or a boost — is:
|
|
|
1714
1971
|
| Key | What it holds |
|
|
1715
1972
|
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1716
1973
|
| `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.
|
|
1974
|
+
| `source` | `"activitypub"` for a fediverse reply, `"comment"` for one left on the page, `"webmention"` for another page linking here, `"post"` for a reply post a user of the site wrote (its `url` is the post's permalink). |
|
|
1718
1975
|
| `kind` | `"reply"`, `"like"`, `"boost"`, `"repost"` or `"mention"`. A `repost` comes from a webmention; a `mention` is a webmention or an approved fediverse quote. |
|
|
1719
1976
|
| `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
1977
|
| `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
1978
|
| `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
1979
|
| `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
1980
|
| `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
|
|
1981
|
+
| `url` | Where it can be read: the remote note's `url` for a fediverse reply or quote, the source page for a webmention, and `/comment/{id}/`, its own page, for a native comment (see [A comment's own page](#a-comments-own-page)). |
|
|
1725
1982
|
| `content` | What it says, **already sanitised**, so print it with `\| safe`. Empty for a like or a boost. |
|
|
1726
1983
|
| `published` | A `Date`: when it was published, or when it arrived if it did not say. Use the `date` filter. |
|
|
1727
1984
|
| `inReplyTo` | What it answers — the post's ActivityPub id, or another reply's — and `null` for a reaction. |
|
|
1728
1985
|
| `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.
|
|
1986
|
+
| `replies` | The replies to this one, oldest first, nested as deep as the site has seen. One may be a placeholder: see below. |
|
|
1730
1987
|
| `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. |
|
|
1731
1988
|
|
|
1732
|
-
|
|
1989
|
+
Four rules decide what is in the thread, and they are the CMS's rather than a
|
|
1733
1990
|
theme's: a reply whose author deleted it is gone, and its own answers move up to
|
|
1734
|
-
whatever it was answering; a
|
|
1735
|
-
|
|
1736
|
-
with is
|
|
1991
|
+
whatever it was answering; a comment a reader may not see (waiting for a
|
|
1992
|
+
moderator, spam or deleted) that has a visible reply under it is a placeholder,
|
|
1993
|
+
and one with none is not there at all; a like is counted once per actor and
|
|
1994
|
+
disappears when that actor undoes it; and a note answering something this post
|
|
1995
|
+
has nothing to do with is left out.
|
|
1996
|
+
|
|
1997
|
+
A placeholder in `replies` is `{ id, withheld: true, replies }`: the hidden
|
|
1998
|
+
comment's id and the replies under it, and nothing that names its author or
|
|
1999
|
+
says what it said. Test `reply.withheld` before printing an author. A pending
|
|
2000
|
+
or spam comment's placeholder sits where the comment would; a deleted one's
|
|
2001
|
+
under the post, since nothing is known of what it answered. `counts.replies`
|
|
2002
|
+
and the comments feeds count and carry the visible replies only.
|
|
1737
2003
|
|
|
1738
2004
|
`content` is safe to print whatever produced it, and safe for different reasons.
|
|
1739
2005
|
A fediverse reply is HTML somebody else's server composed, rebuilt from an
|
|
@@ -1749,7 +2015,8 @@ The packaged partial draws each entry as the source design does: an
|
|
|
1749
2015
|
`comment-{{ reply.source }}` class, holding an `article.comment-body` whose
|
|
1750
2016
|
`footer.comment-meta` is the author as a `.comment-author.vcard.p-author.h-card`
|
|
1751
2017
|
(their avatar, or the first letter of their name in a `span.comment-avatar-empty`
|
|
1752
|
-
when there is none
|
|
2018
|
+
when there is none, and their link marked `rel="nofollow ugc"` unless the entry
|
|
2019
|
+
is a reply post, whose author is a user of the site) and the permalink as a `.comment-metadata` link around a
|
|
1753
2020
|
`time.dt-published`, with a `span.comment-source` saying "via webmention" or
|
|
1754
2021
|
"via the fediverse" when it came that way, then the words in
|
|
1755
2022
|
`div.comment-content.e-content`. Answers nest in an
|
|
@@ -1757,7 +2024,10 @@ when there is none) and the permalink as a `.comment-metadata` link around a
|
|
|
1757
2024
|
ones written here — `source == "comment"` — when the post is still open; it
|
|
1758
2025
|
carries the comment's id to the form as `?reply_to=`, so threading needs no
|
|
1759
2026
|
JavaScript. A fediverse reply is answered on the server that holds it and a
|
|
1760
|
-
webmention on the page that sent it, so neither gets one.
|
|
2027
|
+
webmention on the page that sent it, so neither gets one. A placeholder is an
|
|
2028
|
+
`li.comment.comment-placeholder` with the hidden comment's anchor, a
|
|
2029
|
+
`p.comment-withheld` printing `withheld()` ("This comment is no longer
|
|
2030
|
+
shown."), and its replies in an `ol.children`; it is no microformat of its own.
|
|
1761
2031
|
|
|
1762
2032
|
Above the thread, `group()` draws one `div.reaction-group` per kind that has
|
|
1763
2033
|
anything — `p-like`, `p-repost` and `p-mention`, the source theme's classes — as
|
|
@@ -1786,7 +2056,37 @@ An Eleventy build of the same content gets the same thing from the same files:
|
|
|
1786
2056
|
|
|
1787
2057
|
and then loops over exactly the keys above. The slug is what names the post's
|
|
1788
2058
|
comment file under `content/_data/comments/`; the format is documented in
|
|
1789
|
-
backlog doc-6.
|
|
2059
|
+
backlog doc-6. Two things differ: a native comment's `url` is its
|
|
2060
|
+
`#comment-{id}` anchor, because a static build writes no comment pages, and
|
|
2061
|
+
the site's own reply posts (`source: "post"`) are not in the thread.
|
|
2062
|
+
|
|
2063
|
+
### A comment's own page
|
|
2064
|
+
|
|
2065
|
+
A comment left through the form has a page of its own at `/comment/{id}/`,
|
|
2066
|
+
with the id percent-encoded, which is its `url` everywhere: the thread's
|
|
2067
|
+
permalink, the comments feeds and what another site fetches when it replies
|
|
2068
|
+
to the comment. The anchor `#comment-{id}` on the post stays, so an old link
|
|
2069
|
+
still scrolls to it. A webmention and a fediverse reply have no such page:
|
|
2070
|
+
their `url` is where they came from.
|
|
2071
|
+
|
|
2072
|
+
`layouts/comment.njk` draws it, with the comment as the page's one `h-entry`.
|
|
2073
|
+
It answers only while the post shows its conversation, and is `noindex` and on
|
|
2074
|
+
no list: not the sitemap, the post feeds or search. Its context:
|
|
2075
|
+
|
|
2076
|
+
| Key | What it holds |
|
|
2077
|
+
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2078
|
+
| `comment` | The comment, an entry of the shape above, its `replies` nested at every depth by the thread's own rules. |
|
|
2079
|
+
| `ancestors` | What it answers, from the top-level comment down to its parent. An ancestor a reader may not see (waiting for a moderator, spam, deleted, withdrawn) is `null`, so the chain keeps its place. |
|
|
2080
|
+
| `post` | The post it is on, as a listing entry is: `label`, `url`, `date`, `summary`, `author` and the rest. |
|
|
2081
|
+
| `threadUrl` | The comment's anchor on the post, for a link back to the whole conversation. |
|
|
2082
|
+
|
|
2083
|
+
The packaged layout prints the chain above the comment as nested
|
|
2084
|
+
`u-in-reply-to h-cite`s: the comment cites its parent, which cites its own,
|
|
2085
|
+
down to the post, which is printed first, so a parser and a reader meet the
|
|
2086
|
+
thread in the same order. A `null` ancestor is a cite with no author and no
|
|
2087
|
+
words, printing the same `withheld()` the thread's placeholder does. The replies follow inside the
|
|
2088
|
+
entry as `comment(reply, "p-comment h-cite")`, with no Reply links, and a
|
|
2089
|
+
link to `threadUrl` ends the page.
|
|
1790
2090
|
|
|
1791
2091
|
### Avatars
|
|
1792
2092
|
|
|
@@ -1834,17 +2134,17 @@ in a `div.field-row`, side by side on a wide screen, in both forms. On a site
|
|
|
1834
2134
|
that takes webmentions a `p.comment-help` under the heading says a reader can
|
|
1835
2135
|
reply from their own site instead.
|
|
1836
2136
|
|
|
1837
|
-
| Key | What it holds
|
|
1838
|
-
| -------------------------- |
|
|
1839
|
-
| `action` | Where the form posts. One fixed path; the post travels as a field.
|
|
1840
|
-
| `fields` | The name each field is submitted under: `post`, `name`, `email`, `url`, `body`, `inReplyTo`, `trap`, `loaded`, `notify`, `csrf`. Use these rather than typing the names.
|
|
1841
|
-
| `post` | The post's slug, for the hidden field.
|
|
1842
|
-
| `loaded` | When this form was rendered, in epoch milliseconds, for the hidden field. A submission that comes back too fast is refused.
|
|
1843
|
-
| `values` | What is in the fields: empty on a fresh form, what was typed on a refused one.
|
|
1844
|
-
| `problems` | One message per field a person has to put right — `name`, `email`, `url`, `body`.
|
|
1845
|
-
| `error` | A message about the submission as a whole, when there is one.
|
|
1846
|
-
| `notifiable` | Whether to offer `fields.notify`, the "email me when somebody replies" box. False on a site that sends no mail, where the box would promise a message nothing could send.
|
|
1847
|
-
| `nameLength`, `bodyLength` | The `maxlength` for the two fields that have one.
|
|
2137
|
+
| Key | What it holds |
|
|
2138
|
+
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2139
|
+
| `action` | Where the form posts. One fixed path; the post travels as a field. |
|
|
2140
|
+
| `fields` | The name each field is submitted under: `post`, `name`, `email`, `url`, `body`, `inReplyTo`, `trap`, `loaded`, `notify`, `csrf`, `listed`. Use these rather than typing the names. |
|
|
2141
|
+
| `post` | The post's slug, for the hidden field. |
|
|
2142
|
+
| `loaded` | When this form was rendered, in epoch milliseconds, for the hidden field. A submission that comes back too fast is refused. |
|
|
2143
|
+
| `values` | What is in the fields: empty on a fresh form, what was typed on a refused one. |
|
|
2144
|
+
| `problems` | One message per field a person has to put right — `name`, `email`, `url`, `body`. |
|
|
2145
|
+
| `error` | A message about the submission as a whole, when there is one. |
|
|
2146
|
+
| `notifiable` | Whether to offer `fields.notify`, the "email me when somebody replies" box. False on a site that sends no mail, where the box would promise a message nothing could send. |
|
|
2147
|
+
| `nameLength`, `bodyLength` | The `maxlength` for the two fields that have one. |
|
|
1848
2148
|
|
|
1849
2149
|
`fields.trap` is a honeypot: render it, hide it from sight and from assistive
|
|
1850
2150
|
technology, and give it `tabindex="-1"` and `autocomplete="off"`. A submission
|
|
@@ -1873,10 +2173,10 @@ The contact form below keeps the same contract.
|
|
|
1873
2173
|
Two more keys are on `commentForm` when — and only when — the request carries a
|
|
1874
2174
|
valid session for this site's admin:
|
|
1875
2175
|
|
|
1876
|
-
| Key | What it holds
|
|
1877
|
-
| ------------ |
|
|
1878
|
-
| `signedInAs` | Who the
|
|
1879
|
-
| `csrfToken` | The value for a hidden `fields.csrf`, without which the submission is refused.
|
|
2176
|
+
| Key | What it holds |
|
|
2177
|
+
| ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
2178
|
+
| `signedInAs` | Who the reply will be posted as: `name`, which is their display name or their username, and `url`, which is their author archive. |
|
|
2179
|
+
| `csrfToken` | The value for a hidden `fields.csrf`, without which the submission is refused. |
|
|
1880
2180
|
|
|
1881
2181
|
**A replacement partial has to draw both forms**, because the site draws this
|
|
1882
2182
|
one for its own author and the stranger's one for everybody else:
|
|
@@ -1887,6 +2187,10 @@ one for its own author and the stranger's one for everybody else:
|
|
|
1887
2187
|
<p class="comment-signed-in">
|
|
1888
2188
|
Commenting as <a href="{{ commentForm.signedInAs.url }}">{{ commentForm.signedInAs.name }}</a>.
|
|
1889
2189
|
</p>
|
|
2190
|
+
<p class="comment-check">
|
|
2191
|
+
<input id="comment-listed" name="{{ commentForm.fields.listed }}" type="checkbox" value="1"{% if commentForm.values.listed %} checked{% endif %} />
|
|
2192
|
+
<label for="comment-listed">Include in posts and feeds</label>
|
|
2193
|
+
</p>
|
|
1890
2194
|
{% else %}
|
|
1891
2195
|
{# fields.loaded, fields.trap, and the name, email and website boxes #}
|
|
1892
2196
|
{% endif %}
|
|
@@ -1900,6 +2204,17 @@ on the public site that acts on somebody's behalf — without the token, a page
|
|
|
1900
2204
|
on another site could make them post under their own name. The comment box, the
|
|
1901
2205
|
reply-to field and the notify box are the same in both branches.
|
|
1902
2206
|
|
|
2207
|
+
What the signed-in branch posts is a reply post rather than a comment (CMS
|
|
2208
|
+
README, "Replying while signed in"). It adds `fields.listed`, a checkbox
|
|
2209
|
+
labelled "Include in posts and feeds" and unchecked by default: unchecked
|
|
2210
|
+
publishes the reply post unlisted, checked publishes it public. The stranger's
|
|
2211
|
+
branch never draws it, and the CMS ignores it from a stranger.
|
|
2212
|
+
|
|
2213
|
+
A Reply link, `?reply_to={id}#respond`, goes on every entry of the thread for
|
|
2214
|
+
somebody signed in, and on `source == "comment"` entries only for a stranger.
|
|
2215
|
+
`partials/comment.njk` asks `reply.source == "comment" or
|
|
2216
|
+
commentForm.signedInAs`.
|
|
2217
|
+
|
|
1903
2218
|
The account's email address is **never** on the context. The comment is stored
|
|
1904
2219
|
with it, exactly as a stranger's is, and it is no more printable than theirs.
|
|
1905
2220
|
|