@geekity/cms 0.27.0 → 0.28.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 +168 -33
- package/admin/layouts/shell.njk +6 -0
- package/admin/pages/documents/editor.njk +4 -0
- 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/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/editor-actions.js +3 -1
- package/dist/admin/editor-actions.js.map +1 -1
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +2 -0
- 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 +4 -2
- package/dist/admin/store.d.ts.map +1 -1
- package/dist/admin/store.js +1 -1
- 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 +76 -10
- package/dist/cli.js.map +1 -1
- package/dist/comments/index.d.ts +1 -1
- 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 +1 -0
- package/dist/comments/records.d.ts.map +1 -1
- package/dist/comments/records.js +39 -10
- package/dist/comments/records.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/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 +3 -0
- package/dist/content/original.d.ts.map +1 -0
- package/dist/content/original.js +12 -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/sync.d.ts.map +1 -1
- package/dist/content/sync.js +27 -5
- 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/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 +13 -3
- 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/relays.d.ts.map +1 -1
- package/dist/federation/relays.js +16 -1
- package/dist/federation/relays.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -4
- 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/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 +7 -0
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +7 -4
- package/dist/web/context.js.map +1 -1
- package/dist/web/feed-item.d.ts +7 -6
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +5 -4
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-rss.d.ts +2 -2
- package/dist/web/feed-rss.js +2 -2
- package/dist/web/redirects.d.ts +17 -10
- package/dist/web/redirects.d.ts.map +1 -1
- package/dist/web/redirects.js +83 -49
- package/dist/web/redirects.js.map +1 -1
- package/dist/webmention/service.d.ts +1 -1
- package/dist/webmention/service.d.ts.map +1 -1
- package/dist/webmention/service.js +12 -2
- package/dist/webmention/service.js.map +1 -1
- package/package.json +1 -1
- package/themes/default/README.md +152 -1
- package/themes/default/layouts/base.njk +11 -2
- package/themes/default/layouts/post.njk +8 -2
- package/themes/default/partials/body-end.njk +1 -0
- package/themes/default/partials/conversation.njk +1 -1
- package/themes/default/partials/head-end.njk +1 -0
- package/themes/default/src/style.css +4 -0
- package/themes/default/static/style.css +8 -0
package/themes/default/README.md
CHANGED
|
@@ -38,6 +38,8 @@ themes/default/
|
|
|
38
38
|
contact-form.njk the form on a page whose front matter says contact: true
|
|
39
39
|
archive.njk every post by month, on a page that says archive: true
|
|
40
40
|
search-form.njk the search box, on the search page
|
|
41
|
+
head-end.njk empty: what a site adds to the end of every <head>
|
|
42
|
+
body-end.njk empty: what a site adds to the end of every <body>
|
|
41
43
|
mail/
|
|
42
44
|
test.*.njk the Send test email message
|
|
43
45
|
password-reset.*.njk the forgot-password link
|
|
@@ -187,6 +189,94 @@ until it writes one. On the posts page the page's own front matter and rendered
|
|
|
187
189
|
body are on the context beside the listing, so `{{ content | safe }}` prints its
|
|
188
190
|
words above the posts; `layouts/home.njk` already does.
|
|
189
191
|
|
|
192
|
+
## Adding to every page
|
|
193
|
+
|
|
194
|
+
Analytics, a site verification tag or a chat widget goes on every page, and
|
|
195
|
+
none of them needs a layout copied. `layouts/base.njk` includes two partials
|
|
196
|
+
that this theme ships empty:
|
|
197
|
+
|
|
198
|
+
| Partial | Printed |
|
|
199
|
+
| ----------------------- | -------------------------------------------------- |
|
|
200
|
+
| `partials/head-end.njk` | last in `<head>`, after the cards and the JSON-LD |
|
|
201
|
+
| `partials/body-end.njk` | last in `<body>`, after the footer and the scripts |
|
|
202
|
+
|
|
203
|
+
A site theme that holds one of them, and nothing else but its `theme.json`,
|
|
204
|
+
adds that markup to every public HTML page: posts, pages, listings, search, the
|
|
205
|
+
front page, and the 404, 410, 500 and 503 pages. Every other template still
|
|
206
|
+
comes from this directory, so the site keeps receiving updates to them, and
|
|
207
|
+
the two partial names stay as they are. The includes sit outside every block,
|
|
208
|
+
so a layout that overrides `head` or `scripts` without `super()` keeps them.
|
|
209
|
+
|
|
210
|
+
They are part of the HTML page and nothing else. The Markdown, plain text and
|
|
211
|
+
JSON representations of a page, the feeds, `llms.txt`, mail and the admin do
|
|
212
|
+
not print them. The editor's preview renders the post or page layout in the
|
|
213
|
+
admin, so the context there carries `editorPreview: true` and `layouts/base.njk`
|
|
214
|
+
leaves both partials out. A theme that replaces `layouts/base.njk` and includes
|
|
215
|
+
them itself should test the same key.
|
|
216
|
+
|
|
217
|
+
Umami and Google Analytics 4, in `themes/analytics/`:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
themes/analytics/
|
|
221
|
+
theme.json
|
|
222
|
+
partials/head-end.njk
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{ "name": "Analytics", "kind": "site" }
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```njk
|
|
230
|
+
<script defer src="https://cloud.umami.is/script.js" data-website-id="your-website-id"></script>
|
|
231
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
|
|
232
|
+
<script>
|
|
233
|
+
window.dataLayer = window.dataLayer || [];
|
|
234
|
+
function gtag(){dataLayer.push(arguments);}
|
|
235
|
+
gtag('js', new Date());
|
|
236
|
+
gtag('config', 'G-XXXXXXXXXX');
|
|
237
|
+
</script>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Then wear the theme, either with Activate on **Appearance > Themes** or with
|
|
241
|
+
`"theme": "analytics"` in `content/_data/site.json`. A site that already wears
|
|
242
|
+
a theme of its own puts the partial in that theme instead.
|
|
243
|
+
|
|
244
|
+
The partial is a template, rendered with the page's context, so it can read a
|
|
245
|
+
value out of `site.json`, as in `data-website-id="{{ site.umamiId }}"`.
|
|
246
|
+
|
|
247
|
+
**In Docker.** The image reads themes from `/site/themes`. Mount the site's
|
|
248
|
+
`themes/` directory there, read-only, which is the commented line in
|
|
249
|
+
`deploy/compose.yaml`:
|
|
250
|
+
|
|
251
|
+
```yaml
|
|
252
|
+
volumes:
|
|
253
|
+
- ./themes:/site/themes:ro
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
No custom image is needed. A change to the partial shows on the next request.
|
|
257
|
+
|
|
258
|
+
**Under a Content-Security-Policy.** The default headers set only
|
|
259
|
+
`frame-ancestors`, so they restrict nothing a page loads, and the inline gtag
|
|
260
|
+
configuration runs as it is. A site that sets a policy of its own with
|
|
261
|
+
`securityHeaders` must allow what the snippets load and where they send data:
|
|
262
|
+
|
|
263
|
+
| Directive | Umami | Google Analytics 4 |
|
|
264
|
+
| ------------- | -------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
265
|
+
| `script-src` | `https://cloud.umami.is` | `https://*.googletagmanager.com`, and the inline configuration (see below) |
|
|
266
|
+
| `connect-src` | `https://gateway.umami.is` | `https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com` |
|
|
267
|
+
| `img-src` | none | `https://*.google-analytics.com https://*.googletagmanager.com` |
|
|
268
|
+
|
|
269
|
+
The inline `<script>` that configures gtag needs one of these:
|
|
270
|
+
|
|
271
|
+
- Move it to a file in the theme, `themes/analytics/static/gtag.js`, and load
|
|
272
|
+
it with `<script src="{{ "gtag.js" | asset }}"></script>`. It is then served
|
|
273
|
+
from the site, and `script-src 'self'` covers it.
|
|
274
|
+
- Allow its hash, `'sha256-…'`, in `script-src`. The hash covers every byte
|
|
275
|
+
between the tags, so it changes when the measurement ID or the whitespace
|
|
276
|
+
changes.
|
|
277
|
+
- Allow `'unsafe-inline'` in `script-src`. This also allows any injected
|
|
278
|
+
inline script, so prefer one of the other two.
|
|
279
|
+
|
|
190
280
|
## The page shell
|
|
191
281
|
|
|
192
282
|
`layouts/base.njk` is the page shell: the skip link, one `.global-wrapper` at the
|
|
@@ -387,6 +477,17 @@ IndieNews and Bridgy Publish look for on the page, and ends with the post's
|
|
|
387
477
|
copies elsewhere (`syndication`) after **Also on**. A page never prints either,
|
|
388
478
|
and its line is a `p.page-meta` rather than a `p.entry-meta`.
|
|
389
479
|
|
|
480
|
+
**A post first published elsewhere says so.** A post whose front matter names
|
|
481
|
+
its original with `canonical_href`, an absolute http or https URL such as a
|
|
482
|
+
Substack essay's, has it on the context as `original` (TASK-293). The line
|
|
483
|
+
then reads **Originally published at** and links the original's host as a
|
|
484
|
+
second `u-url` of the h-entry, after the permalink's. It is no
|
|
485
|
+
`u-syndication`, because the original is not a copy of this post, and
|
|
486
|
+
original-post-discovery reads a copy's off-site `u-url` as its original. The
|
|
487
|
+
same URL is the page's `rel="canonical"` and its `og:url`, so search engines
|
|
488
|
+
credit the original. A value that is not an absolute http or https URL is
|
|
489
|
+
ignored, and `geekity sync` names the file.
|
|
490
|
+
|
|
390
491
|
**Every post's page has one `h1`.** A post with a name of its own is headed by
|
|
391
492
|
it, as above. So is a post with a title and no words, such as a titled like,
|
|
392
493
|
repost or bookmark (TASK-256): its header comes first and its citation after
|
|
@@ -919,7 +1020,8 @@ mode gets a dark canvas rather than a white flash while the stylesheet loads.
|
|
|
919
1020
|
**The card.** `og:title` is the page's title, or the site's on the front page;
|
|
920
1021
|
`og:site_name` is always the site's. `og:type` is `article` on a rendered post
|
|
921
1022
|
or page and `website` everywhere else, a listing carrying a page's front matter
|
|
922
|
-
included. `og:url` is the canonical URL
|
|
1023
|
+
included. `og:url` is the canonical URL: the page's own, or the `original` a
|
|
1024
|
+
post first published elsewhere names. An article also carries
|
|
923
1025
|
`article:published_time`, `article:modified_time` (the `updated` date, else the
|
|
924
1026
|
publish date), `article:author` (the author's profile URL here, else their
|
|
925
1027
|
name) and one `article:tag` per tag.
|
|
@@ -1220,6 +1322,54 @@ the `+` and the `-` in the text carry the meaning as well as the colour does.
|
|
|
1220
1322
|
{% block scripts %}{% endblock %}
|
|
1221
1323
|
```
|
|
1222
1324
|
|
|
1325
|
+
### Videos
|
|
1326
|
+
|
|
1327
|
+
A YouTube or Vimeo URL on a line of its own in a post plays as the video
|
|
1328
|
+
(decision-34; doc-2 lists the addresses it knows). The core renders it into the
|
|
1329
|
+
body, so it reaches a theme inside `content` as:
|
|
1330
|
+
|
|
1331
|
+
```html
|
|
1332
|
+
<figure class="video-embed video-embed-youtube">
|
|
1333
|
+
<iframe
|
|
1334
|
+
src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
|
|
1335
|
+
width="560"
|
|
1336
|
+
height="315"
|
|
1337
|
+
title="YouTube video"
|
|
1338
|
+
loading="lazy"
|
|
1339
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
1340
|
+
referrerpolicy="strict-origin-when-cross-origin"
|
|
1341
|
+
allowfullscreen
|
|
1342
|
+
></iframe>
|
|
1343
|
+
<figcaption>
|
|
1344
|
+
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
|
1345
|
+
>https://www.youtube.com/watch?v=dQw4w9WgXcQ</a
|
|
1346
|
+
>
|
|
1347
|
+
</figcaption>
|
|
1348
|
+
</figure>
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
It is one line in the real output. A Vimeo video is the same with
|
|
1352
|
+
`video-embed-vimeo`, a `https://player.vimeo.com/video/ID?dnt=1` player and the
|
|
1353
|
+
title `Vimeo video`. The core sets no style on it: with nothing from the theme
|
|
1354
|
+
the player is the providers' standard 560 by 315.
|
|
1355
|
+
|
|
1356
|
+
**Paper** makes it responsive. `.video-embed iframe` takes the full width of
|
|
1357
|
+
the text column at 16:9, with the rounded corners an image has, and the
|
|
1358
|
+
figcaption takes the muted caption style every figure in a post has. A theme
|
|
1359
|
+
that ships its own `static/style.css` writes that rule itself:
|
|
1360
|
+
|
|
1361
|
+
```css
|
|
1362
|
+
.video-embed iframe {
|
|
1363
|
+
width: 100%;
|
|
1364
|
+
height: auto;
|
|
1365
|
+
aspect-ratio: 16 / 9;
|
|
1366
|
+
}
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
The figcaption is the link a feed reader, a fediverse server or the embed view
|
|
1370
|
+
keeps when it drops the iframe. A theme that would rather not show it on the
|
|
1371
|
+
page can hide `.video-embed figcaption`; the feeds carry it either way.
|
|
1372
|
+
|
|
1223
1373
|
## Mail templates
|
|
1224
1374
|
|
|
1225
1375
|
The messages the CMS sends live under `mail/` and resolve the same way, so a
|
|
@@ -1328,6 +1478,7 @@ A document — one post, one page, or one entry of a listing — adds:
|
|
|
1328
1478
|
| `event` | An event's `{ start, end, location }`: `Date`s, and `{ kind: "place", name }` or `{ kind: "virtual", url }`. |
|
|
1329
1479
|
| `syndicateTo` | On a post's page, the syndication targets it selects, each `{ id, name, url }`. Empty with none. |
|
|
1330
1480
|
| `syndication` | Its copies elsewhere, each `{ url, label }`: front matter `syndication`, and on its page the copies targets made. |
|
|
1481
|
+
| `original` | Where it was first published, as `{ url, label }`: front matter `canonical_href`. Absent unless http or https. |
|
|
1331
1482
|
| `author` | Who wrote it, as a profile rather than a string. See [Bylines and author archives](#bylines-and-author-archives). |
|
|
1332
1483
|
| `activityStreams` | The post's ActivityPub object id, absolute. Only on a rendered published post. |
|
|
1333
1484
|
| `previous` | The published post before this one by date, as `{ title, url }`. Absent on the oldest post. |
|
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
content the page body
|
|
29
29
|
footer the site footer
|
|
30
30
|
scripts scripts at the end of <body>
|
|
31
|
+
|
|
32
|
+
Two partials, empty here, are included outside every block so that no layout
|
|
33
|
+
can drop them: `partials/head-end.njk` last in <head> and
|
|
34
|
+
`partials/body-end.njk` last in <body>. A site theme fills either one to add
|
|
35
|
+
analytics or a widget to every page without overriding a layout. The
|
|
36
|
+
editor's preview in the admin sets `editorPreview` and leaves both out.
|
|
31
37
|
#}{% import "partials/feeds.njk" as feeds %}
|
|
32
38
|
{%- import "partials/menu.njk" as nav %}
|
|
33
39
|
{#- Whether this is the front page, which is the one page the header and the
|
|
@@ -53,6 +59,7 @@
|
|
|
53
59
|
{% set metaUrl = (page.url or "/") | absoluteUrl %}
|
|
54
60
|
{% set metaImage = shareImage.url if shareImage else "" %}
|
|
55
61
|
{% set isEntry = (type == "post" or type == "page") and not pagination %}
|
|
62
|
+
{% set canonicalUrl = original.url if (isEntry and original) else metaUrl %}
|
|
56
63
|
{#- An author archive is the only page that is about a person: a listing whose
|
|
57
64
|
`author` is whose page this is, rather than the posts page's writer. -#}
|
|
58
65
|
{% set isAuthorArchive = pagination and author.url and url and (author.url in url) %}
|
|
@@ -102,7 +109,7 @@
|
|
|
102
109
|
{% endif %}{% if noindex %}
|
|
103
110
|
<meta name="robots" content="noindex">
|
|
104
111
|
{% endif %}
|
|
105
|
-
<link rel="canonical" href="{{
|
|
112
|
+
<link rel="canonical" href="{{ canonicalUrl }}">
|
|
106
113
|
<link rel="stylesheet" href="{{ "style.css" | asset }}">
|
|
107
114
|
{% block speculationRules %}
|
|
108
115
|
{#
|
|
@@ -188,7 +195,7 @@
|
|
|
188
195
|
{% if metaDescription %}
|
|
189
196
|
<meta property="og:description" content="{{ metaDescription }}">
|
|
190
197
|
{% endif %}
|
|
191
|
-
<meta property="og:url" content="{{
|
|
198
|
+
<meta property="og:url" content="{{ canonicalUrl }}">
|
|
192
199
|
<meta property="og:type" content="{% if isEntry %}article{% else %}website{% endif %}">
|
|
193
200
|
<meta property="og:site_name" content="{{ site.title }}">
|
|
194
201
|
{% if shareImage %}
|
|
@@ -246,6 +253,7 @@
|
|
|
246
253
|
<link rel="manifest" href="{{ "/manifest.webmanifest" | url }}">
|
|
247
254
|
{% include "partials/jsonld.njk" %}
|
|
248
255
|
{% endblock %}
|
|
256
|
+
{% if not editorPreview %}{% include "partials/head-end.njk" %}{% endif %}
|
|
249
257
|
</head>
|
|
250
258
|
<body>
|
|
251
259
|
<a class="screen-reader-text" href="#main">Skip to content</a>
|
|
@@ -331,5 +339,6 @@
|
|
|
331
339
|
<script src="{{ "highlight.js" | asset }}" defer></script>
|
|
332
340
|
{% endif %}
|
|
333
341
|
{% endblock %}
|
|
342
|
+
{% if not editorPreview %}{% include "partials/body-end.njk" %}{% endif %}
|
|
334
343
|
</body>
|
|
335
344
|
</html>
|
|
@@ -90,13 +90,19 @@
|
|
|
90
90
|
{%- if updated and (not date or (updated | date("html")) != (date | date("html"))) %}
|
|
91
91
|
{% if date %}<span aria-hidden="true">·</span> {% endif %}<time class="small dt-updated" datetime="{{ updated | date("iso") }}">Updated {{ updated | date("readable", none, lang) }}</time>
|
|
92
92
|
{%- endif %}
|
|
93
|
+
{#- A second `u-url` rather than a `u-syndication`: the original is not
|
|
94
|
+
a copy of this post, and original-post-discovery reads a copy's
|
|
95
|
+
off-site `u-url` as the original. -#}
|
|
96
|
+
{%- if original %}
|
|
97
|
+
{% if date or updated %} <span aria-hidden="true">·</span> {% endif %}<span class="small">Originally published at <a class="u-url" href="{{ original.url }}">{{ original.label }}</a></span>
|
|
98
|
+
{%- endif %}
|
|
93
99
|
{#- The post's copies elsewhere: those its targets answered with and
|
|
94
100
|
those its front matter lists by hand. -#}
|
|
95
101
|
{%- if syndication.length %}
|
|
96
|
-
{% if date or updated %} <span aria-hidden="true">·</span> {% endif %}<span class="small">Also on {% for copy in syndication %}<a class="u-syndication" href="{{ copy.url }}">{{ copy.label }}</a>{% if not loop.last %}, {% endif %}{% endfor %}</span>
|
|
102
|
+
{% if date or updated or original %} <span aria-hidden="true">·</span> {% endif %}<span class="small">Also on {% for copy in syndication %}<a class="u-syndication" href="{{ copy.url }}">{{ copy.label }}</a>{% if not loop.last %}, {% endif %}{% endfor %}</span>
|
|
97
103
|
{%- endif -%}
|
|
98
104
|
{%- if location %}
|
|
99
|
-
{% if date or updated or syndication.length %} <span aria-hidden="true">·</span> {% endif %}<span class="small">{% include "partials/location.njk" %}</span>
|
|
105
|
+
{% if date or updated or original or syndication.length %} <span aria-hidden="true">·</span> {% endif %}<span class="small">{% include "partials/location.njk" %}</span>
|
|
100
106
|
{%- endif -%}
|
|
101
107
|
</p>
|
|
102
108
|
</section>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{#- Printed last in <body> by layouts/base.njk. Empty here; a site theme fills it. -#}
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
is answered on the page that sent it, so neither gets such a link.
|
|
95
95
|
#}
|
|
96
96
|
{% if commentForm and reply.source == "comment" %}
|
|
97
|
-
<div class="reply"><a class="comment-reply-link" href="{{ page.url }}?reply_to={{ reply.id }}#respond">Reply</a></div>
|
|
97
|
+
<div class="reply"><a class="comment-reply-link" href="{{ page.url }}?reply_to={{ reply.id | urlencode }}#respond">Reply</a></div>
|
|
98
98
|
{% endif %}
|
|
99
99
|
</article>
|
|
100
100
|
{% if reply.replies.length %}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{#- Printed last in <head> by layouts/base.njk. Empty here; a site theme fills it. -#}
|
|
@@ -404,6 +404,10 @@
|
|
|
404
404
|
@apply h-auto max-w-full rounded-md;
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
:is(.e-content, .page-body) .video-embed iframe {
|
|
408
|
+
@apply aspect-video h-auto w-full rounded-md;
|
|
409
|
+
}
|
|
410
|
+
|
|
407
411
|
:is(.e-content, .page-body) figcaption {
|
|
408
412
|
@apply mt-2 font-sans text-sm text-muted;
|
|
409
413
|
}
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
--radius-xs: .125rem;
|
|
67
67
|
--radius-sm: .25rem;
|
|
68
68
|
--radius-md: .375rem;
|
|
69
|
+
--aspect-video: 16 / 9;
|
|
69
70
|
--default-transition-duration: .15s;
|
|
70
71
|
--default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
|
|
71
72
|
--default-font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
@@ -840,6 +841,13 @@
|
|
|
840
841
|
height: auto;
|
|
841
842
|
}
|
|
842
843
|
|
|
844
|
+
:is(.e-content, .page-body) .video-embed iframe {
|
|
845
|
+
aspect-ratio: var(--aspect-video);
|
|
846
|
+
border-radius: var(--radius-md);
|
|
847
|
+
width: 100%;
|
|
848
|
+
height: auto;
|
|
849
|
+
}
|
|
850
|
+
|
|
843
851
|
:is(.e-content, .page-body) figcaption {
|
|
844
852
|
margin-top: calc(var(--spacing) * 2);
|
|
845
853
|
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|