@geekity/cms 0.14.0 → 0.16.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 +270 -52
- package/admin/pages/documents/editor.njk +120 -0
- package/admin/pages/documents/syndication.njk +110 -0
- package/admin/pages/federation/followers.njk +7 -0
- package/admin/pages/media/library.njk +4 -3
- package/admin/pages/settings/general.njk +44 -0
- package/admin/static/admin.css +44 -0
- package/dist/admin/documents.d.ts +104 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +220 -55
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/enclosure-field.d.ts +91 -0
- package/dist/admin/enclosure-field.d.ts.map +1 -0
- package/dist/admin/enclosure-field.js +282 -0
- package/dist/admin/enclosure-field.js.map +1 -0
- package/dist/admin/federation.d.ts +15 -0
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +19 -0
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/media.d.ts +7 -2
- package/dist/admin/media.d.ts.map +1 -1
- package/dist/admin/media.js +4 -2
- package/dist/admin/media.js.map +1 -1
- package/dist/admin/menu.d.ts.map +1 -1
- package/dist/admin/menu.js +3 -0
- package/dist/admin/menu.js.map +1 -1
- package/dist/admin/photo-field.d.ts +39 -0
- package/dist/admin/photo-field.d.ts.map +1 -0
- package/dist/admin/photo-field.js +91 -0
- package/dist/admin/photo-field.js.map +1 -0
- package/dist/admin/preview.d.ts.map +1 -1
- package/dist/admin/preview.js +6 -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-general.d.ts.map +1 -1
- package/dist/admin/settings-general.js +36 -2
- package/dist/admin/settings-general.js.map +1 -1
- package/dist/admin/settings-page.d.ts.map +1 -1
- package/dist/admin/settings-page.js +1 -2
- package/dist/admin/settings-page.js.map +1 -1
- package/dist/admin/settings.d.ts +26 -0
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +88 -2
- package/dist/admin/settings.js.map +1 -1
- package/dist/admin/syndication-targets.d.ts +57 -0
- package/dist/admin/syndication-targets.d.ts.map +1 -0
- package/dist/admin/syndication-targets.js +266 -0
- package/dist/admin/syndication-targets.js.map +1 -0
- 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 +13 -2
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +23 -5
- package/dist/admin/uploads.js.map +1 -1
- package/dist/config.d.ts +17 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -7
- package/dist/config.js.map +1 -1
- package/dist/content/citation.d.ts +36 -0
- package/dist/content/citation.d.ts.map +1 -0
- package/dist/content/citation.js +31 -0
- package/dist/content/citation.js.map +1 -0
- package/dist/content/enclosure.d.ts +69 -0
- package/dist/content/enclosure.d.ts.map +1 -0
- package/dist/content/enclosure.js +165 -0
- package/dist/content/enclosure.js.map +1 -0
- package/dist/content/index.d.ts +5 -1
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +2 -0
- package/dist/content/index.js.map +1 -1
- package/dist/content/media.d.ts +15 -2
- package/dist/content/media.d.ts.map +1 -1
- package/dist/content/media.js +92 -9
- package/dist/content/media.js.map +1 -1
- package/dist/content/photo.d.ts +45 -0
- package/dist/content/photo.d.ts.map +1 -0
- package/dist/content/photo.js +55 -0
- package/dist/content/photo.js.map +1 -0
- package/dist/content/post-type.d.ts +20 -8
- package/dist/content/post-type.d.ts.map +1 -1
- package/dist/content/post-type.js +17 -0
- package/dist/content/post-type.js.map +1 -1
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +93 -10
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/citations.d.ts +38 -0
- package/dist/federation/citations.d.ts.map +1 -0
- package/dist/federation/citations.js +98 -0
- package/dist/federation/citations.js.map +1 -0
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +79 -31
- package/dist/federation/delivery.js.map +1 -1
- package/dist/images/alt-text.d.ts +6 -0
- package/dist/images/alt-text.d.ts.map +1 -1
- package/dist/images/alt-text.js +10 -0
- package/dist/images/alt-text.js.map +1 -1
- package/dist/images/icons.d.ts +5 -0
- package/dist/images/icons.d.ts.map +1 -1
- package/dist/images/icons.js +13 -8
- package/dist/images/icons.js.map +1 -1
- package/dist/images/paths.d.ts +1 -1
- package/dist/images/paths.d.ts.map +1 -1
- package/dist/images/paths.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -4
- package/dist/index.js.map +1 -1
- package/dist/indieauth/bearer.d.ts +6 -1
- package/dist/indieauth/bearer.d.ts.map +1 -1
- package/dist/indieauth/bearer.js +39 -12
- package/dist/indieauth/bearer.js.map +1 -1
- package/dist/indieauth/discovery.d.ts +9 -4
- package/dist/indieauth/discovery.d.ts.map +1 -1
- package/dist/indieauth/discovery.js +18 -7
- package/dist/indieauth/discovery.js.map +1 -1
- package/dist/micropub/create.d.ts +56 -0
- package/dist/micropub/create.d.ts.map +1 -0
- package/dist/micropub/create.js +200 -0
- package/dist/micropub/create.js.map +1 -0
- package/dist/micropub/endpoint.d.ts +21 -0
- package/dist/micropub/endpoint.d.ts.map +1 -0
- package/dist/micropub/endpoint.js +353 -0
- package/dist/micropub/endpoint.js.map +1 -0
- package/dist/micropub/media.d.ts +14 -0
- package/dist/micropub/media.d.ts.map +1 -0
- package/dist/micropub/media.js +93 -0
- package/dist/micropub/media.js.map +1 -0
- package/dist/micropub/update.d.ts +44 -0
- package/dist/micropub/update.d.ts.map +1 -0
- package/dist/micropub/update.js +133 -0
- package/dist/micropub/update.js.map +1 -0
- package/dist/web/assets.d.ts +14 -0
- package/dist/web/assets.d.ts.map +1 -1
- package/dist/web/assets.js +56 -1
- package/dist/web/assets.js.map +1 -1
- package/dist/web/context.d.ts +68 -3
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +56 -1
- package/dist/web/context.js.map +1 -1
- package/dist/web/feed-atom.d.ts.map +1 -1
- package/dist/web/feed-atom.js +20 -0
- package/dist/web/feed-atom.js.map +1 -1
- package/dist/web/feed-item.d.ts +19 -0
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +22 -0
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-json.d.ts +13 -0
- package/dist/web/feed-json.d.ts.map +1 -1
- package/dist/web/feed-json.js +15 -0
- package/dist/web/feed-json.js.map +1 -1
- package/dist/web/feed-rss.d.ts.map +1 -1
- package/dist/web/feed-rss.js +59 -2
- package/dist/web/feed-rss.js.map +1 -1
- package/dist/web/feed-xml.d.ts +7 -1
- package/dist/web/feed-xml.d.ts.map +1 -1
- package/dist/web/feed-xml.js +8 -2
- package/dist/web/feed-xml.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 +6 -0
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/index.d.ts +4 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +2 -0
- package/dist/web/index.js.map +1 -1
- package/dist/web/license.d.ts +94 -0
- package/dist/web/license.d.ts.map +1 -0
- package/dist/web/license.js +116 -0
- package/dist/web/license.js.map +1 -0
- package/dist/web/oembed.d.ts.map +1 -1
- package/dist/web/oembed.js +1 -2
- package/dist/web/oembed.js.map +1 -1
- package/dist/web/render.d.ts +15 -0
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +27 -2
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +10 -6
- package/dist/web/routes.js.map +1 -1
- package/dist/web/share-image.d.ts +7 -7
- package/dist/web/share-image.d.ts.map +1 -1
- package/dist/web/share-image.js +8 -7
- package/dist/web/share-image.js.map +1 -1
- package/dist/webmention/index.d.ts +2 -0
- package/dist/webmention/index.d.ts.map +1 -1
- package/dist/webmention/index.js +1 -0
- package/dist/webmention/index.js.map +1 -1
- package/dist/webmention/service.d.ts.map +1 -1
- package/dist/webmention/service.js +96 -18
- package/dist/webmention/service.js.map +1 -1
- package/dist/webmention/syndication.d.ts +135 -0
- package/dist/webmention/syndication.d.ts.map +1 -0
- package/dist/webmention/syndication.js +337 -0
- package/dist/webmention/syndication.js.map +1 -0
- package/package.json +1 -1
- package/themes/default/README.md +121 -37
- package/themes/default/layouts/base.njk +3 -4
- package/themes/default/layouts/post.njk +23 -6
- package/themes/default/partials/citations.njk +17 -0
- package/themes/default/partials/jsonld.njk +15 -6
- package/themes/default/partials/photos.njk +10 -0
- package/themes/default/partials/post-list.njk +7 -0
- package/themes/default/partials/recording.njk +41 -0
- package/themes/default/src/style.css +25 -0
- package/themes/default/static/style.css +41 -0
package/themes/default/README.md
CHANGED
|
@@ -25,6 +25,7 @@ themes/default/
|
|
|
25
25
|
tags.njk macros for tag and category links
|
|
26
26
|
kicker.njk the line above an entry naming its kind and date
|
|
27
27
|
reply-context.njk the citation of what a reply answers
|
|
28
|
+
citations.njk the citation of what a like, repost or bookmark cites
|
|
28
29
|
bio.njk who an entry is by, as an h-card
|
|
29
30
|
menu.njk one named menu, as a nav of links
|
|
30
31
|
feeds.njk macros for the feed links in <head>
|
|
@@ -207,7 +208,8 @@ it sits beside the link home. See [Navigation](#navigation).
|
|
|
207
208
|
**The footer** prints the copyright with the current year and the site
|
|
208
209
|
author's display name, `soloAuthor.name`, or the site title on a site with
|
|
209
210
|
several authors,
|
|
210
|
-
|
|
211
|
+
then the page's license, when it has one, as `<a rel="license">` with its name,
|
|
212
|
+
then `Published with Geekity`, and then `menus.footer` — the `footer` area this
|
|
211
213
|
theme declares, and the whole of what the footer links. A site that wants its
|
|
212
214
|
feed there types an `RSS | /feed/` line into it, the way the starter site does.
|
|
213
215
|
Nothing in the footer is read off an account: it used to hold one `rel="me"`
|
|
@@ -216,7 +218,9 @@ presented as the site's. An empty or missing footer menu prints no list at all,
|
|
|
216
218
|
and the copyright line stands on its own. The year is
|
|
217
219
|
`{{ "now" | date("year") }}` — `now` is the one word the `date` filter reads
|
|
218
220
|
rather than parses — so it is the year at the moment the page is rendered, in
|
|
219
|
-
the site's own timezone.
|
|
221
|
+
the site's own timezone. The license is `license`: a post's own from its
|
|
222
|
+
`license` front matter, else the site's from Settings > General, and nothing
|
|
223
|
+
on a site with none.
|
|
220
224
|
|
|
221
225
|
**The breadcrumb** opens `<main>` on a page with a place in the site's
|
|
222
226
|
hierarchy (TASK-150): a post filed under a category, and a category, tag or
|
|
@@ -230,8 +234,8 @@ two cannot disagree. Every other page has an empty trail and prints nothing. A
|
|
|
230
234
|
site that wants no visible breadcrumb empties the `breadcrumbs` block, and the
|
|
231
235
|
`BreadcrumbList` stays in the graph.
|
|
232
236
|
|
|
233
|
-
Webrings, badges
|
|
234
|
-
|
|
237
|
+
Webrings, badges and anything else that is markup rather than a link are
|
|
238
|
+
deliberately not in the package. They go in a site theme's `footer`
|
|
235
239
|
block:
|
|
236
240
|
|
|
237
241
|
```njk
|
|
@@ -331,8 +335,8 @@ is a microformats2 `h-entry`. An article, and a page:
|
|
|
331
335
|
<section class="e-content">
|
|
332
336
|
<p>The rendered body.</p>
|
|
333
337
|
<p class="entry-meta">
|
|
334
|
-
<a class="u-
|
|
335
|
-
|
|
338
|
+
<a class="u-syndication small" href="https://news.indieweb.org/en"
|
|
339
|
+
>IndieNews</a
|
|
336
340
|
>
|
|
337
341
|
<a href="/2026/09/hello/" class="u-url"
|
|
338
342
|
><time class="small dt-published" datetime="2026-09-02T09:00:00.000Z"
|
|
@@ -343,6 +347,13 @@ is a microformats2 `h-entry`. An article, and a page:
|
|
|
343
347
|
<time class="small dt-updated" datetime="2026-09-05T09:00:00.000Z"
|
|
344
348
|
>Updated 5 September 2026</time
|
|
345
349
|
>
|
|
350
|
+
<span aria-hidden="true">·</span>
|
|
351
|
+
<span class="small"
|
|
352
|
+
>Also on
|
|
353
|
+
<a class="u-syndication" href="https://news.indieweb.org/en/…"
|
|
354
|
+
>news.indieweb.org</a
|
|
355
|
+
></span
|
|
356
|
+
>
|
|
346
357
|
</p>
|
|
347
358
|
</section>
|
|
348
359
|
<p class="post-categories">…tags…</p>
|
|
@@ -366,10 +377,11 @@ permalink are part of the words, so a reader, or a fediverse peer reading the
|
|
|
366
377
|
`e-content`, takes them with the post. The permalink wraps the `dt-published`
|
|
367
378
|
time as a `u-url`, and a `dt-updated` follows it after a middle dot only when
|
|
368
379
|
the update happened on a different day in the site's own timezone; a typo fixed
|
|
369
|
-
an hour later is not news.
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
`
|
|
380
|
+
an hour later is not news. The line opens with a `u-syndication` link to each
|
|
381
|
+
syndication target the post selects (`syndicateTo`, TASK-155), which is what
|
|
382
|
+
IndieNews and Bridgy Publish look for on the page, and ends with the post's
|
|
383
|
+
copies elsewhere (`syndication`) after **Also on**. A page never prints either,
|
|
384
|
+
and its line is a `p.page-meta` rather than a `p.entry-meta`.
|
|
373
385
|
|
|
374
386
|
**Every post's page has one `h1`.** A post with a name of its own is headed by
|
|
375
387
|
it, as above. A note, or a reply without a title, opens on its words instead,
|
|
@@ -382,7 +394,7 @@ who wrote it and when, and then the kicker:
|
|
|
382
394
|
<p class="kicker"><span class="kicker-kind">Note</span></p>
|
|
383
395
|
```
|
|
384
396
|
|
|
385
|
-
It says `Reply` for
|
|
397
|
+
It says `Reply`, `Like`, `Repost` or `Bookmark` for those and leaves out the name or the date the post does
|
|
386
398
|
not have. It is not a `p-name`: a parser that found one would take the post
|
|
387
399
|
for an article. The headings under it step down one level at a time: the
|
|
388
400
|
conversation and the comment form are `h2`, and a refused form's error summary
|
|
@@ -395,17 +407,70 @@ far as they are known, and its excerpt as a `blockquote.cite-quote.p-content`.
|
|
|
395
407
|
An untitled reply prints it under the kicker; a titled reply prints it above
|
|
396
408
|
its header. The same partial cites a reply in a feed.
|
|
397
409
|
|
|
410
|
+
**A like, a repost or a bookmark cites what it cites** (TASK-169) with
|
|
411
|
+
`partials/citations.njk`, beside the reply context and drawn the same way: one
|
|
412
|
+
`div.reply-context.cite.h-cite` per entry of `citations`, classed
|
|
413
|
+
`u-repost-of`, `u-like-of` or `u-bookmark-of`, whose `p.cite-line` says
|
|
414
|
+
Reposted, Liked or Bookmarked and links the target as its `u-url`. Its kicker
|
|
415
|
+
and its hidden `h1` say Repost, Like or Bookmark. A listing cites each entry's
|
|
416
|
+
from the same partial.
|
|
417
|
+
|
|
398
418
|
The tags are printed under the words and inside the article, so that each link
|
|
399
419
|
is a `p-category` of this entry, as one `p.post-categories` from
|
|
400
420
|
`partials/tags.njk`. The stylesheet draws the `#` in front of each, so the
|
|
401
421
|
category a parser reads is the tag and not the hash.
|
|
402
422
|
|
|
423
|
+
**A post with a recording plays it** (TASK-213), from
|
|
424
|
+
`partials/recording.njk`, between the header and the `e-content`. The main file
|
|
425
|
+
is the browser's own player, `<audio controls>` or `<video controls>` as
|
|
426
|
+
`enclosure.player` says, with `preload="metadata"` so a reader who never presses
|
|
427
|
+
play downloads nothing, and a download link inside it for a browser that plays
|
|
428
|
+
neither. The `<source>` carries `u-audio` or `u-video`, so a microformats parser
|
|
429
|
+
finds the file on the entry. A video whose transcript is WebVTT also gets it as
|
|
430
|
+
a `<track kind="captions">`. Under the player a `figcaption` links the
|
|
431
|
+
transcript, as Captions for VTT or SRT and Transcript otherwise, and lists the
|
|
432
|
+
other versions by their title, else their media type. A post without a
|
|
433
|
+
recording, or whose front matter is missing what a player needs, prints none of
|
|
434
|
+
it.
|
|
435
|
+
|
|
436
|
+
**A post with photos shows them** (TASK-166), from `partials/photos.njk`, after
|
|
437
|
+
the recording and before the `e-content`, each in a `figure.post-photo`. Each is
|
|
438
|
+
the context's `photo.html`: an `<img class="u-photo">` with its alt text, so a
|
|
439
|
+
microformats parser finds every photo on the entry, and a `<picture>` with the
|
|
440
|
+
upload's resizes when it has them. The alt text is the post's own, else the
|
|
441
|
+
media library's. A listing prints them the same way above each entry, and the
|
|
442
|
+
JSON-LD `BlogPosting` lists each as an `ImageObject` with its absolute `url`
|
|
443
|
+
and its alt text as `caption`. The first photo is the post's share image when
|
|
444
|
+
its front matter names no `image`.
|
|
445
|
+
|
|
446
|
+
```html
|
|
447
|
+
<figure class="post-recording">
|
|
448
|
+
<audio controls preload="metadata" aria-label="Recording of Episode twelve">
|
|
449
|
+
<source
|
|
450
|
+
class="u-audio"
|
|
451
|
+
src="/uploads/2026/10/episode-12.mp3"
|
|
452
|
+
type="audio/mpeg"
|
|
453
|
+
/>
|
|
454
|
+
<a href="/uploads/2026/10/episode-12.mp3">Download the recording</a>
|
|
455
|
+
</audio>
|
|
456
|
+
<figcaption class="post-recording-links">
|
|
457
|
+
<p><a href="/uploads/2026/10/episode-12.vtt">Captions</a></p>
|
|
458
|
+
<p id="post-recording-versions">Other versions</p>
|
|
459
|
+
<ul aria-labelledby="post-recording-versions">
|
|
460
|
+
<li>
|
|
461
|
+
<a href="/uploads/2026/10/episode-12.mp4" type="video/mp4">Video</a>
|
|
462
|
+
</li>
|
|
463
|
+
</ul>
|
|
464
|
+
</figcaption>
|
|
465
|
+
</figure>
|
|
466
|
+
```
|
|
467
|
+
|
|
403
468
|
After the entry a post prints `nav.blog-post-nav`: the `previous` and `next`
|
|
404
469
|
posts as two cards, `rel="prev"` and `rel="next"`, each opening on a
|
|
405
470
|
`span.blog-post-nav-label`, and nothing at all at the ends of the archive; then
|
|
406
471
|
the conversation and the comment form. A page prints the contact form when its
|
|
407
|
-
front matter asked for one. A page has no neighbours, no tags and no
|
|
408
|
-
|
|
472
|
+
front matter asked for one. A page has no neighbours, no tags and no
|
|
473
|
+
syndication links, because none of those are things a page has.
|
|
409
474
|
|
|
410
475
|
### The bio
|
|
411
476
|
|
|
@@ -709,17 +774,28 @@ included. `og:url` is the canonical URL. An article also carries
|
|
|
709
774
|
publish date), `article:author` (the author's profile URL here, else their
|
|
710
775
|
name) and one `article:tag` per tag.
|
|
711
776
|
|
|
712
|
-
**The picture** is the
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
777
|
+
**The picture** is the first of these that the page has, on the context as
|
|
778
|
+
`shareImage`:
|
|
779
|
+
|
|
780
|
+
1. The `image` in the entry's front matter.
|
|
781
|
+
2. `site.avatar`, which only a hand edit of `site.json` sets.
|
|
782
|
+
3. The profile photo of the person the page is by: the writer of a post or page
|
|
783
|
+
that names an author, the user of an author archive, and the site's author
|
|
784
|
+
on a solo-author site's homepage and other pages about nobody in particular.
|
|
785
|
+
4. The site icon, from `icon` in `site.json`.
|
|
786
|
+
|
|
787
|
+
A page with none of these prints no `og:image` and no `twitter:image` rather
|
|
788
|
+
than an empty one. The picture always carries `og:image:alt` and
|
|
789
|
+
`twitter:image:alt`. The alt text is what the media library says about the
|
|
790
|
+
upload, else words for what it shows. For the entry's own picture, its
|
|
791
|
+
`imageAlt` front matter comes first, and the entry's title last. For the
|
|
792
|
+
avatar, the last resort is the site's author; for a photo, that person's
|
|
793
|
+
display name; for the icon, the site's title. `og:image:width` and
|
|
794
|
+
`og:image:height` are printed when the image's variants have been derived,
|
|
795
|
+
because the size is read from their sidecar rather than from the file.
|
|
796
|
+
`twitter:card` is `summary_large_image` for a picture at least 1200 pixels wide
|
|
797
|
+
and wider than it is tall. Everything else, a square photo included, is
|
|
798
|
+
`summary`, the small picture beside the words.
|
|
723
799
|
|
|
724
800
|
**The icons** come from the site's `icon` setting in `site.json`, else
|
|
725
801
|
`site.avatar`, through the derived images (decision-10). `icon` exists for a
|
|
@@ -778,7 +854,7 @@ IndieWeb. The graph holds:
|
|
|
778
854
|
`/search/?q={search_term_string}`. On a site whose author is a user, its
|
|
779
855
|
`publisher` and `about` both point at that user's Person, on every page. On
|
|
780
856
|
a site with several authors its `publisher` is the Organization below and it
|
|
781
|
-
has no `about`.
|
|
857
|
+
has no `about`. With a site license, `license` is its URL.
|
|
782
858
|
- `Organization`, on a site with several authors only: the site itself, named
|
|
783
859
|
for its title, at `{baseUrl}/#organization`. It publishes the `WebSite` and
|
|
784
860
|
every entry.
|
|
@@ -795,8 +871,9 @@ IndieWeb. The graph holds:
|
|
|
795
871
|
`author`, and the site's `publisher`: the site author's Person, or the
|
|
796
872
|
Organization. A post with no title is headed by the words its `<title>`
|
|
797
873
|
names it by, its `label`, cut on a word boundary to at most 110 characters.
|
|
798
|
-
|
|
799
|
-
|
|
874
|
+
Its `image` is the picture the Open Graph tags print, so the two always
|
|
875
|
+
agree (see [The head](#the-head)). Its `license` is the page's license URL,
|
|
876
|
+
the front matter's over the site's, and absent with none.
|
|
800
877
|
- `BreadcrumbList` wherever the page prints a breadcrumb (see
|
|
801
878
|
[The page shell](#the-page-shell)): one `ListItem` per crumb with its
|
|
802
879
|
`position`, `name` and absolute URL as `item`.
|
|
@@ -1055,16 +1132,18 @@ an Eleventy build needs few edits. It is part of the package's semver contract.
|
|
|
1055
1132
|
|
|
1056
1133
|
Every template gets:
|
|
1057
1134
|
|
|
1058
|
-
| Key
|
|
1059
|
-
|
|
|
1060
|
-
| `site`
|
|
1061
|
-
| `menus`
|
|
1062
|
-
| `siteAuthor`
|
|
1063
|
-
| `soloAuthor`
|
|
1064
|
-
| `icons`
|
|
1065
|
-
| `theme`
|
|
1066
|
-
| `shareImage`
|
|
1067
|
-
| `llmsTxt`
|
|
1135
|
+
| Key | What it holds |
|
|
1136
|
+
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1137
|
+
| `site` | `content/_data/site.json`, if the site has one, over the defaults `title` and `url`. Any key in the file is readable, so `site.tagline`, `site.author` and anything else a site adds are all available. |
|
|
1138
|
+
| `menus` | Every menu the site stores, by name, marked for this page: `menus.primary`, `menus.footer`, and any other name. See [Navigation](#navigation). |
|
|
1139
|
+
| `siteAuthor` | Who the page is by, as a profile. **Absent** when nobody matches. See [Bylines and author archives](#bylines-and-author-archives). |
|
|
1140
|
+
| `soloAuthor` | The site author's profile, on every page of a site whose Site author setting names a user. **Absent** on a site with several authors. See [The bio](#the-bio). |
|
|
1141
|
+
| `icons` | The site's icons, as `{ rel, sizes, type, href }`. Empty until the site has an icon or an avatar to derive them from. See [The head](#the-head). |
|
|
1142
|
+
| `theme` | The colours the theme declares, as `{ colorScheme, themeColor: { light, dark } }`, each absent when no theme declares it. See [The manifest](#the-manifest). |
|
|
1143
|
+
| `shareImage` | The picture a shared link shows, as `{ url, alt, size: { width, height }, card }`. `size` is absent when it is not known. **Absent** when there is no picture. See [The head](#the-head). |
|
|
1144
|
+
| `llmsTxt` | The path of `/llms.txt`, while the site serves it. **Absent** when the site has turned it off. The default theme links it from the front page only. |
|
|
1145
|
+
| `license` | The page's license as `{ name, url }`: the page's `license` front matter, else the site's. **Undefined** with none. It replaces the raw front matter value on the context. |
|
|
1146
|
+
| `siteLicense` | The site's own license as `{ name, url }`, whatever the page says. **Undefined** with none. |
|
|
1068
1147
|
|
|
1069
1148
|
A document — one post, one page, or one entry of a listing — adds:
|
|
1070
1149
|
|
|
@@ -1085,6 +1164,11 @@ A document — one post, one page, or one entry of a listing — adds:
|
|
|
1085
1164
|
| `type` | `post` or `page`. |
|
|
1086
1165
|
| `permalink`, `slug`, `draft`, `description` | Straight from the front matter. |
|
|
1087
1166
|
| `lang` | The `lang` front matter as a canonical tag, such as `fr-CA`. Absent when it names none or no tag. |
|
|
1167
|
+
| `enclosure` | The post's recording, parsed, with `player` (`audio` or `video`) and `transcript.captions`. Absent with none. |
|
|
1168
|
+
| `photos` | The post's photos, each `{ url, alt, html }`; `html` is the `img.u-photo`, responsive. Empty with none. |
|
|
1169
|
+
| `citations` | What it reposts, likes or bookmarks, each `{ property, url }` with `property` the mf2 name. Empty with none. |
|
|
1170
|
+
| `syndicateTo` | On a post's page, the syndication targets it selects, each `{ id, name, url }`. Empty with none. |
|
|
1171
|
+
| `syndication` | Its copies elsewhere, each `{ url, label }`: front matter `syndication`, and on its page the copies targets made. |
|
|
1088
1172
|
| `author` | Who wrote it, as a profile rather than a string. See [Bylines and author archives](#bylines-and-author-archives). |
|
|
1089
1173
|
| `activityStreams` | The post's ActivityPub object id, absolute. Only on a rendered published post. |
|
|
1090
1174
|
| `previous` | The published post before this one by date, as `{ title, url }`. Absent on the oldest post. |
|
|
@@ -40,9 +40,8 @@
|
|
|
40
40
|
|
|
41
41
|
`metaDescription` is the page's own description, else the entry's summary
|
|
42
42
|
— the very line the feeds publish — else the site's tagline. `metaImage` is
|
|
43
|
-
`shareImage.url
|
|
44
|
-
|
|
45
|
-
works out `shareImage` because its alt text and its size live in files a
|
|
43
|
+
`shareImage.url`; with none, nothing is printed rather than an empty tag.
|
|
44
|
+
The CMS works out `shareImage` because its alt text and its size live in files a
|
|
46
45
|
template cannot read. `isEntry` is a rendered post
|
|
47
46
|
or page, which is what Open Graph calls an article: a listing has
|
|
48
47
|
`pagination`, and the posts page is a listing carrying a page's front
|
|
@@ -281,7 +280,7 @@
|
|
|
281
280
|
|
|
282
281
|
{% block footer %}
|
|
283
282
|
<footer>
|
|
284
|
-
<p>© {{ "now" | date("year") }}, {{ soloAuthor.name or site.title }} · Published with Geekity</p>
|
|
283
|
+
<p>© {{ "now" | date("year") }}, {{ soloAuthor.name or site.title }}{% if license %} · <a rel="license" href="{{ license.url }}">{{ license.name }}</a>{% endif %} · Published with Geekity</p>
|
|
285
284
|
{#
|
|
286
285
|
The footer menu: the `footer` area this theme declares in its
|
|
287
286
|
`theme.json`, which a site fills in on the Navigation screen, and the
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
{#- A reply cites the post it answers, whose URL is also the link its
|
|
40
40
|
webmention points a receiver at. -#}
|
|
41
41
|
{% include "partials/reply-context.njk" %}
|
|
42
|
+
{% include "partials/citations.njk" %}
|
|
42
43
|
<header class="post-header">
|
|
43
44
|
{{ kicker.line(postType | capitalize, false, false, categories) }}
|
|
44
45
|
<h1 class="p-name">{{ title }}</h1>
|
|
@@ -48,22 +49,33 @@
|
|
|
48
49
|
</header>
|
|
49
50
|
{% else %}
|
|
50
51
|
<h1 class="screen-reader-text">
|
|
51
|
-
{{-
|
|
52
|
+
{{- postType | capitalize if postType in ["reply", "like", "repost", "bookmark"] else "Note" }}
|
|
52
53
|
{%- if bioAuthor.name %} by {{ bioAuthor.name }}{% endif %}
|
|
53
54
|
{%- if date %}, {{ date | date("readable", none, lang) }}{% endif -%}
|
|
54
55
|
</h1>
|
|
55
56
|
{{ kicker.line(postType | capitalize, false, false, categories) }}
|
|
56
57
|
{% include "partials/reply-context.njk" %}
|
|
58
|
+
{% include "partials/citations.njk" %}
|
|
59
|
+
{% endif %}
|
|
60
|
+
|
|
61
|
+
{% if enclosure %}
|
|
62
|
+
{% include "partials/recording.njk" %}
|
|
63
|
+
{% endif %}
|
|
64
|
+
|
|
65
|
+
{% if photos.length %}
|
|
66
|
+
{% include "partials/photos.njk" %}
|
|
57
67
|
{% endif %}
|
|
58
68
|
|
|
59
69
|
<section class="e-content">
|
|
60
70
|
{{ content | safe }}
|
|
61
71
|
<p class="entry-meta">
|
|
62
|
-
{#-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
{%-
|
|
72
|
+
{#- The line opens with a link to each syndication target the post
|
|
73
|
+
selects (TASK-155). It is inside the h-entry and marked
|
|
74
|
+
`u-syndication`, the markup IndieNews documents, and a target that
|
|
75
|
+
is sent a webmention checks the page for it. -#}
|
|
76
|
+
{%- for target in syndicateTo %}
|
|
77
|
+
<a class="u-syndication small" href="{{ target.url }}">{{ target.name }}</a>
|
|
78
|
+
{%- endfor %}
|
|
67
79
|
{%- if date %}
|
|
68
80
|
<a href="{{ entryUrl | url }}" class="u-url"><time class="small dt-published" datetime="{{ date | date("iso") }}">Published {{ date | date("readable", none, lang) }}</time></a>
|
|
69
81
|
{%- endif %}
|
|
@@ -71,6 +83,11 @@
|
|
|
71
83
|
a typo fixed an hour after publishing is not news. -#}
|
|
72
84
|
{%- if updated and (not date or (updated | date("html")) != (date | date("html"))) %}
|
|
73
85
|
{% if date %}<span aria-hidden="true">·</span> {% endif %}<time class="small dt-updated" datetime="{{ updated | date("iso") }}">Updated {{ updated | date("readable", none, lang) }}</time>
|
|
86
|
+
{%- endif %}
|
|
87
|
+
{#- The post's copies elsewhere: those its targets answered with and
|
|
88
|
+
those its front matter lists by hand. -#}
|
|
89
|
+
{%- if syndication.length %}
|
|
90
|
+
{% 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>
|
|
74
91
|
{%- endif -%}
|
|
75
92
|
</p>
|
|
76
93
|
</section>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{#
|
|
2
|
+
What a post reposts, likes or bookmarks (TASK-169), cited the way a reply
|
|
3
|
+
cites what it answers: each one an embedded `h-cite` under its own mf2
|
|
4
|
+
property, `u-repost-of`, `u-like-of` or `u-bookmark-of`, that always carries
|
|
5
|
+
the target's URL, so a parser reads what the post cites and the page its
|
|
6
|
+
webmention names links to it.
|
|
7
|
+
|
|
8
|
+
Drawn as the same citation block as `reply-context.njk` (doc-9). An entry
|
|
9
|
+
includes it with `citations` on the context, a list of `{ property, url }`;
|
|
10
|
+
a feed sets it from the entry it is printing.
|
|
11
|
+
#}
|
|
12
|
+
{% set citationVerbs = { "repost-of": "Reposted", "like-of": "Liked", "bookmark-of": "Bookmarked" } %}
|
|
13
|
+
{% for citation in citations %}
|
|
14
|
+
<div class="reply-context cite u-{{ citation.property }} h-cite">
|
|
15
|
+
<p class="cite-line small">{{ citationVerbs[citation.property] }} <a class="u-url" href="{{ citation.url }}">{{ citation.url }}</a></p>
|
|
16
|
+
</div>
|
|
17
|
+
{% endfor %}
|
|
@@ -54,12 +54,19 @@
|
|
|
54
54
|
{% set entryType = "BlogPosting" if type == "post" else "Article" %}
|
|
55
55
|
{#- A post with no title is headed by the words its <title> names it by,
|
|
56
56
|
within the 110 characters Google reads, cut where a word ends (TASK-201).
|
|
57
|
-
|
|
58
|
-
else the site icon: the original upload rather than a derived icon, which
|
|
59
|
-
at 180 pixels is too small for an article image. A page keeps its title
|
|
60
|
-
and its own picture. -#}
|
|
57
|
+
A page keeps its title. -#}
|
|
61
58
|
{% set headline = title or (label | truncate(109, false, "…") if type == "post" else "") %}
|
|
62
|
-
{
|
|
59
|
+
{#- A photo post's image is its photos, each an ImageObject captioned with
|
|
60
|
+
its alt text, in order (TASK-166); anything else's is the share image. -#}
|
|
61
|
+
{% set entryImage = metaImage | absoluteUrl if metaImage else "" %}
|
|
62
|
+
{% if photos and photos.length %}
|
|
63
|
+
{% set entryImage = [] %}
|
|
64
|
+
{% for photo in photos %}
|
|
65
|
+
{% set imageObject = { "@type": "ImageObject", "url": photo.url | absoluteUrl } %}
|
|
66
|
+
{% if photo.alt %}{% set imageObject = { "@type": "ImageObject", "url": photo.url | absoluteUrl, "caption": photo.alt } %}{% endif %}
|
|
67
|
+
{% set _ = entryImage.push(imageObject) %}
|
|
68
|
+
{% endfor %}
|
|
69
|
+
{% endif %}
|
|
63
70
|
{% set nodes = [] %}
|
|
64
71
|
{% set website %}
|
|
65
72
|
{
|
|
@@ -82,6 +89,7 @@
|
|
|
82
89
|
}) }}
|
|
83
90
|
{{- member("publisher", publisher) }}
|
|
84
91
|
{{- member("about", { "@id": soloId } if soloId else "") }}
|
|
92
|
+
{{- member("license", siteLicense.url if siteLicense else "") }}
|
|
85
93
|
}{% endset %}
|
|
86
94
|
{% set _ = nodes.push(website) %}
|
|
87
95
|
{% if not soloId %}
|
|
@@ -129,9 +137,10 @@
|
|
|
129
137
|
{{- member("datePublished", date | date("iso") if date else "") }}
|
|
130
138
|
{{- member("dateModified", (updated or date) | date("iso") if (updated or date) else "") }}
|
|
131
139
|
{{- member("description", metaDescription) }}
|
|
132
|
-
{{- member("image", entryImage
|
|
140
|
+
{{- member("image", entryImage) }}
|
|
133
141
|
{{- member("author", person) }}
|
|
134
142
|
{{- member("publisher", publisher) }}
|
|
143
|
+
{{- member("license", license.url if license else "") }}
|
|
135
144
|
}{% endset %}
|
|
136
145
|
{% set _ = nodes.push(entry) %}
|
|
137
146
|
{% endif %}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{#
|
|
2
|
+
A post's photos (TASK-166), each `photo.html`: an `img.u-photo` with its alt
|
|
3
|
+
text, so a microformats parser finds every photo on the h-entry, served as a
|
|
4
|
+
responsive picture when the upload has variants.
|
|
5
|
+
#}
|
|
6
|
+
<div class="post-photos">
|
|
7
|
+
{% for photo in photos %}
|
|
8
|
+
<figure class="post-photo">{{ photo.html | safe }}</figure>
|
|
9
|
+
{% endfor %}
|
|
10
|
+
</div>
|
|
@@ -39,6 +39,13 @@
|
|
|
39
39
|
{% set lang = post.lang %}
|
|
40
40
|
{% include "partials/reply-context.njk" %}
|
|
41
41
|
{% endif %}
|
|
42
|
+
{#- What it reposts, likes or bookmarks, from the partial an entry uses. -#}
|
|
43
|
+
{% set citations = post.citations %}
|
|
44
|
+
{% include "partials/citations.njk" %}
|
|
45
|
+
{% if post.photos.length %}
|
|
46
|
+
{% set photos = post.photos %}
|
|
47
|
+
{% include "partials/photos.njk" %}
|
|
48
|
+
{% endif %}
|
|
42
49
|
{% if not post.named %}
|
|
43
50
|
<div class="feed-excerpt e-content">{{ post.content | safe }}</div>
|
|
44
51
|
{% else %}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Timed captions in WebVTT go on a video as a track as well as in the links,
|
|
3
|
+
since that is the one format a browser shows over the picture. The source
|
|
4
|
+
carries `u-audio` or `u-video`, so a microformats parser finds the file on
|
|
5
|
+
the entry.
|
|
6
|
+
|
|
7
|
+
The `preload="metadata"` asks for the length and the first frame and no
|
|
8
|
+
more: a reader who never presses play does not download the episode.
|
|
9
|
+
#}
|
|
10
|
+
{% set recordingLabel = ("Recording of " ~ title) if named else "Recording" %}
|
|
11
|
+
<figure class="post-recording">
|
|
12
|
+
{% if enclosure.player == "video" %}
|
|
13
|
+
<video controls preload="metadata" aria-label="{{ recordingLabel }}">
|
|
14
|
+
<source class="u-video" src="{{ enclosure.url | url }}" type="{{ enclosure.type }}">
|
|
15
|
+
{% if enclosure.transcript and enclosure.transcript.type == "text/vtt" %}
|
|
16
|
+
<track kind="captions" src="{{ enclosure.transcript.url | url }}" srclang="{{ lang or (site.language | default("en")) }}" label="Captions" default>
|
|
17
|
+
{% endif %}
|
|
18
|
+
<a href="{{ enclosure.url | url }}">Download the video</a>
|
|
19
|
+
</video>
|
|
20
|
+
{% else %}
|
|
21
|
+
<audio controls preload="metadata" aria-label="{{ recordingLabel }}">
|
|
22
|
+
<source class="u-audio" src="{{ enclosure.url | url }}" type="{{ enclosure.type }}">
|
|
23
|
+
<a href="{{ enclosure.url | url }}">Download the recording</a>
|
|
24
|
+
</audio>
|
|
25
|
+
{% endif %}
|
|
26
|
+
{% if enclosure.alternates.length or enclosure.transcript %}
|
|
27
|
+
<figcaption class="post-recording-links">
|
|
28
|
+
{% if enclosure.transcript %}
|
|
29
|
+
<p><a href="{{ enclosure.transcript.url | url }}">{{ "Captions" if enclosure.transcript.captions else "Transcript" }}</a></p>
|
|
30
|
+
{% endif %}
|
|
31
|
+
{% if enclosure.alternates.length %}
|
|
32
|
+
<p id="post-recording-versions">Other versions</p>
|
|
33
|
+
<ul aria-labelledby="post-recording-versions">
|
|
34
|
+
{% for alternate in enclosure.alternates %}
|
|
35
|
+
<li><a href="{{ alternate.url | url }}" type="{{ alternate.type }}"{% if alternate.lang %} hreflang="{{ alternate.lang }}"{% endif %}>{{ alternate.title or alternate.type }}</a></li>
|
|
36
|
+
{% endfor %}
|
|
37
|
+
</ul>
|
|
38
|
+
{% endif %}
|
|
39
|
+
</figcaption>
|
|
40
|
+
{% endif %}
|
|
41
|
+
</figure>
|
|
@@ -660,6 +660,31 @@
|
|
|
660
660
|
@apply text-[1.08rem] leading-[1.65];
|
|
661
661
|
}
|
|
662
662
|
|
|
663
|
+
.post-recording {
|
|
664
|
+
@apply my-8;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.post-recording :is(audio, video) {
|
|
668
|
+
@apply block w-full;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.post-recording-links {
|
|
672
|
+
@apply mt-3 font-sans text-sm text-muted;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.post-recording-links ul {
|
|
676
|
+
@apply mt-1 flex flex-wrap gap-x-4 gap-y-1;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/* A post's photos (TASK-166), one under another at the column's width. */
|
|
680
|
+
.post-photos {
|
|
681
|
+
@apply my-8 grid gap-4;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.post-photo img {
|
|
685
|
+
@apply block h-auto w-full;
|
|
686
|
+
}
|
|
687
|
+
|
|
663
688
|
/* An untitled post is its words, set larger. It is the one entry whose
|
|
664
689
|
heading is the hidden one TASK-144 gave it. */
|
|
665
690
|
.blog-post > .screen-reader-text ~ .e-content {
|
|
@@ -1190,6 +1190,43 @@
|
|
|
1190
1190
|
line-height: 1.65;
|
|
1191
1191
|
}
|
|
1192
1192
|
|
|
1193
|
+
.post-recording {
|
|
1194
|
+
margin-block: calc(var(--spacing) * 8);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.post-recording :is(audio, video) {
|
|
1198
|
+
width: 100%;
|
|
1199
|
+
display: block;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
.post-recording-links {
|
|
1203
|
+
margin-top: calc(var(--spacing) * 3);
|
|
1204
|
+
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
1205
|
+
font-size: var(--text-sm);
|
|
1206
|
+
line-height: var(--tw-leading, var(--text-sm--line-height));
|
|
1207
|
+
color: var(--color-muted);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
.post-recording-links ul {
|
|
1211
|
+
margin-top: var(--spacing);
|
|
1212
|
+
column-gap: calc(var(--spacing) * 4);
|
|
1213
|
+
row-gap: var(--spacing);
|
|
1214
|
+
flex-wrap: wrap;
|
|
1215
|
+
display: flex;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
.post-photos {
|
|
1219
|
+
margin-block: calc(var(--spacing) * 8);
|
|
1220
|
+
gap: calc(var(--spacing) * 4);
|
|
1221
|
+
display: grid;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
.post-photo img {
|
|
1225
|
+
width: 100%;
|
|
1226
|
+
height: auto;
|
|
1227
|
+
display: block;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1193
1230
|
.blog-post > .screen-reader-text ~ .e-content {
|
|
1194
1231
|
--tw-leading: 1.55;
|
|
1195
1232
|
font-size: 1.3rem;
|
|
@@ -2124,6 +2161,10 @@
|
|
|
2124
2161
|
overflow: hidden;
|
|
2125
2162
|
}
|
|
2126
2163
|
|
|
2164
|
+
.capitalize {
|
|
2165
|
+
text-transform: capitalize;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2127
2168
|
.italic {
|
|
2128
2169
|
font-style: italic;
|
|
2129
2170
|
}
|