@geekity/cms 0.14.0 → 0.15.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 +163 -52
- package/admin/pages/documents/editor.njk +69 -0
- package/admin/pages/media/library.njk +4 -3
- package/admin/pages/settings/general.njk +44 -0
- package/admin/static/admin.css +33 -0
- package/dist/admin/documents.d.ts +3 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +38 -5
- 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/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/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/uploads.d.ts +7 -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/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 +3 -1
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +1 -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/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +35 -6
- package/dist/federation/article.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 +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- 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 +26 -0
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +15 -0
- 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.map +1 -1
- package/dist/web/render.js +11 -2
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +8 -4
- 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/package.json +1 -1
- package/themes/default/README.md +81 -28
- package/themes/default/layouts/base.njk +3 -4
- package/themes/default/layouts/post.njk +4 -0
- package/themes/default/partials/jsonld.njk +4 -6
- package/themes/default/partials/recording.njk +41 -0
- package/themes/default/src/style.css +16 -0
- package/themes/default/static/style.css +25 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"share-image.d.ts","sourceRoot":"","sources":["../../src/web/share-image.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6FAA6F;IAC7F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAChF,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,qBAAqB,CAAC;CAClD;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC
|
|
1
|
+
{"version":3,"file":"share-image.d.ts","sourceRoot":"","sources":["../../src/web/share-image.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6FAA6F;IAC7F,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAChF,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,qBAAqB,CAAC;CAClD;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;IACf,qCAAqC;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC1C,GAAG,UAAU,GAAG,SAAS,CAuBzB"}
|
package/dist/web/share-image.js
CHANGED
|
@@ -7,13 +7,11 @@ import { siteImageRecord, uploadPath } from '../images/markup.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export const LARGE_CARD_MIN_WIDTH = 1200;
|
|
9
9
|
/**
|
|
10
|
-
* The page's share image, or `undefined` when it has none
|
|
11
|
-
* `image`, else the site's avatar.
|
|
10
|
+
* The page's share image, or `undefined` when it has none.
|
|
12
11
|
*
|
|
13
12
|
* The alt text is the front matter's `imageAlt` for the entry's own picture,
|
|
14
13
|
* because one picture can need describing differently in two posts; else what
|
|
15
|
-
* the media library says about the upload
|
|
16
|
-
* picture and the site's author for the avatar, which is a picture of them.
|
|
14
|
+
* the media library says about the upload.
|
|
17
15
|
*
|
|
18
16
|
* The size is read off the variant sidecar, never off the file, so a render
|
|
19
17
|
* never opens an image (decision-10). A picture with no sidecar has no size
|
|
@@ -21,14 +19,17 @@ export const LARGE_CARD_MIN_WIDTH = 1200;
|
|
|
21
19
|
*/
|
|
22
20
|
export function shareImage(input) {
|
|
23
21
|
const own = nonEmpty(input.image);
|
|
24
|
-
const
|
|
25
|
-
|
|
22
|
+
const chosen = own === undefined
|
|
23
|
+
? input.fallbacks.find((fallback) => nonEmpty(fallback.url) !== undefined)
|
|
24
|
+
: { url: own, describedAs: input.title };
|
|
25
|
+
const url = nonEmpty(chosen?.url);
|
|
26
|
+
if (chosen === undefined || url === undefined)
|
|
26
27
|
return undefined;
|
|
27
28
|
const source = uploadPath(url);
|
|
28
29
|
const described = source === undefined ? undefined : readAltTexts(input.config.contentDir).get(source);
|
|
29
30
|
const alt = (own === undefined ? undefined : nonEmpty(input.imageAlt)) ??
|
|
30
31
|
(described?.kind === 'described' ? nonEmpty(described.text) : undefined) ??
|
|
31
|
-
|
|
32
|
+
chosen.describedAs;
|
|
32
33
|
const record = source === undefined ? undefined : siteImageRecord(input.config, source);
|
|
33
34
|
const size = record === undefined ? undefined : { width: record.width, height: record.height };
|
|
34
35
|
const large = size !== undefined && size.width >= LARGE_CARD_MIN_WIDTH && size.width > size.height;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"share-image.js","sourceRoot":"","sources":["../../src/web/share-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAmBlE;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"share-image.js","sourceRoot":"","sources":["../../src/web/share-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAmBlE;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAOzC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,KAS1B;IACC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,MAAM,GACV,GAAG,KAAK,SAAS;QACf,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;QAC1E,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IAC7C,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAClC,IAAI,MAAM,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAEhE,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,SAAS,GACb,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvF,MAAM,GAAG,GACP,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC1D,CAAC,SAAS,EAAE,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,MAAM,CAAC,WAAW,CAAC;IAErB,MAAM,MAAM,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxF,MAAM,IAAI,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAC/F,MAAM,KAAK,GACT,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,IAAI,oBAAoB,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;IAEvF,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;AAC7E,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC9C,CAAC"}
|
package/package.json
CHANGED
package/themes/default/README.md
CHANGED
|
@@ -207,7 +207,8 @@ it sits beside the link home. See [Navigation](#navigation).
|
|
|
207
207
|
**The footer** prints the copyright with the current year and the site
|
|
208
208
|
author's display name, `soloAuthor.name`, or the site title on a site with
|
|
209
209
|
several authors,
|
|
210
|
-
|
|
210
|
+
then the page's license, when it has one, as `<a rel="license">` with its name,
|
|
211
|
+
then `Published with Geekity`, and then `menus.footer` — the `footer` area this
|
|
211
212
|
theme declares, and the whole of what the footer links. A site that wants its
|
|
212
213
|
feed there types an `RSS | /feed/` line into it, the way the starter site does.
|
|
213
214
|
Nothing in the footer is read off an account: it used to hold one `rel="me"`
|
|
@@ -216,7 +217,9 @@ presented as the site's. An empty or missing footer menu prints no list at all,
|
|
|
216
217
|
and the copyright line stands on its own. The year is
|
|
217
218
|
`{{ "now" | date("year") }}` — `now` is the one word the `date` filter reads
|
|
218
219
|
rather than parses — so it is the year at the moment the page is rendered, in
|
|
219
|
-
the site's own timezone.
|
|
220
|
+
the site's own timezone. The license is `license`: a post's own from its
|
|
221
|
+
`license` front matter, else the site's from Settings > General, and nothing
|
|
222
|
+
on a site with none.
|
|
220
223
|
|
|
221
224
|
**The breadcrumb** opens `<main>` on a page with a place in the site's
|
|
222
225
|
hierarchy (TASK-150): a post filed under a category, and a category, tag or
|
|
@@ -230,8 +233,8 @@ two cannot disagree. Every other page has an empty trail and prints nothing. A
|
|
|
230
233
|
site that wants no visible breadcrumb empties the `breadcrumbs` block, and the
|
|
231
234
|
`BreadcrumbList` stays in the graph.
|
|
232
235
|
|
|
233
|
-
Webrings, badges
|
|
234
|
-
|
|
236
|
+
Webrings, badges and anything else that is markup rather than a link are
|
|
237
|
+
deliberately not in the package. They go in a site theme's `footer`
|
|
235
238
|
block:
|
|
236
239
|
|
|
237
240
|
```njk
|
|
@@ -400,6 +403,41 @@ is a `p-category` of this entry, as one `p.post-categories` from
|
|
|
400
403
|
`partials/tags.njk`. The stylesheet draws the `#` in front of each, so the
|
|
401
404
|
category a parser reads is the tag and not the hash.
|
|
402
405
|
|
|
406
|
+
**A post with a recording plays it** (TASK-213), from
|
|
407
|
+
`partials/recording.njk`, between the header and the `e-content`. The main file
|
|
408
|
+
is the browser's own player, `<audio controls>` or `<video controls>` as
|
|
409
|
+
`enclosure.player` says, with `preload="metadata"` so a reader who never presses
|
|
410
|
+
play downloads nothing, and a download link inside it for a browser that plays
|
|
411
|
+
neither. The `<source>` carries `u-audio` or `u-video`, so a microformats parser
|
|
412
|
+
finds the file on the entry. A video whose transcript is WebVTT also gets it as
|
|
413
|
+
a `<track kind="captions">`. Under the player a `figcaption` links the
|
|
414
|
+
transcript, as Captions for VTT or SRT and Transcript otherwise, and lists the
|
|
415
|
+
other versions by their title, else their media type. A post without a
|
|
416
|
+
recording, or whose front matter is missing what a player needs, prints none of
|
|
417
|
+
it.
|
|
418
|
+
|
|
419
|
+
```html
|
|
420
|
+
<figure class="post-recording">
|
|
421
|
+
<audio controls preload="metadata" aria-label="Recording of Episode twelve">
|
|
422
|
+
<source
|
|
423
|
+
class="u-audio"
|
|
424
|
+
src="/uploads/2026/10/episode-12.mp3"
|
|
425
|
+
type="audio/mpeg"
|
|
426
|
+
/>
|
|
427
|
+
<a href="/uploads/2026/10/episode-12.mp3">Download the recording</a>
|
|
428
|
+
</audio>
|
|
429
|
+
<figcaption class="post-recording-links">
|
|
430
|
+
<p><a href="/uploads/2026/10/episode-12.vtt">Captions</a></p>
|
|
431
|
+
<p id="post-recording-versions">Other versions</p>
|
|
432
|
+
<ul aria-labelledby="post-recording-versions">
|
|
433
|
+
<li>
|
|
434
|
+
<a href="/uploads/2026/10/episode-12.mp4" type="video/mp4">Video</a>
|
|
435
|
+
</li>
|
|
436
|
+
</ul>
|
|
437
|
+
</figcaption>
|
|
438
|
+
</figure>
|
|
439
|
+
```
|
|
440
|
+
|
|
403
441
|
After the entry a post prints `nav.blog-post-nav`: the `previous` and `next`
|
|
404
442
|
posts as two cards, `rel="prev"` and `rel="next"`, each opening on a
|
|
405
443
|
`span.blog-post-nav-label`, and nothing at all at the ends of the archive; then
|
|
@@ -709,17 +747,28 @@ included. `og:url` is the canonical URL. An article also carries
|
|
|
709
747
|
publish date), `article:author` (the author's profile URL here, else their
|
|
710
748
|
name) and one `article:tag` per tag.
|
|
711
749
|
|
|
712
|
-
**The picture** is the
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
750
|
+
**The picture** is the first of these that the page has, on the context as
|
|
751
|
+
`shareImage`:
|
|
752
|
+
|
|
753
|
+
1. The `image` in the entry's front matter.
|
|
754
|
+
2. `site.avatar`, which only a hand edit of `site.json` sets.
|
|
755
|
+
3. The profile photo of the person the page is by: the writer of a post or page
|
|
756
|
+
that names an author, the user of an author archive, and the site's author
|
|
757
|
+
on a solo-author site's homepage and other pages about nobody in particular.
|
|
758
|
+
4. The site icon, from `icon` in `site.json`.
|
|
759
|
+
|
|
760
|
+
A page with none of these prints no `og:image` and no `twitter:image` rather
|
|
761
|
+
than an empty one. The picture always carries `og:image:alt` and
|
|
762
|
+
`twitter:image:alt`. The alt text is what the media library says about the
|
|
763
|
+
upload, else words for what it shows. For the entry's own picture, its
|
|
764
|
+
`imageAlt` front matter comes first, and the entry's title last. For the
|
|
765
|
+
avatar, the last resort is the site's author; for a photo, that person's
|
|
766
|
+
display name; for the icon, the site's title. `og:image:width` and
|
|
767
|
+
`og:image:height` are printed when the image's variants have been derived,
|
|
768
|
+
because the size is read from their sidecar rather than from the file.
|
|
769
|
+
`twitter:card` is `summary_large_image` for a picture at least 1200 pixels wide
|
|
770
|
+
and wider than it is tall. Everything else, a square photo included, is
|
|
771
|
+
`summary`, the small picture beside the words.
|
|
723
772
|
|
|
724
773
|
**The icons** come from the site's `icon` setting in `site.json`, else
|
|
725
774
|
`site.avatar`, through the derived images (decision-10). `icon` exists for a
|
|
@@ -778,7 +827,7 @@ IndieWeb. The graph holds:
|
|
|
778
827
|
`/search/?q={search_term_string}`. On a site whose author is a user, its
|
|
779
828
|
`publisher` and `about` both point at that user's Person, on every page. On
|
|
780
829
|
a site with several authors its `publisher` is the Organization below and it
|
|
781
|
-
has no `about`.
|
|
830
|
+
has no `about`. With a site license, `license` is its URL.
|
|
782
831
|
- `Organization`, on a site with several authors only: the site itself, named
|
|
783
832
|
for its title, at `{baseUrl}/#organization`. It publishes the `WebSite` and
|
|
784
833
|
every entry.
|
|
@@ -795,8 +844,9 @@ IndieWeb. The graph holds:
|
|
|
795
844
|
`author`, and the site's `publisher`: the site author's Person, or the
|
|
796
845
|
Organization. A post with no title is headed by the words its `<title>`
|
|
797
846
|
names it by, its `label`, cut on a word boundary to at most 110 characters.
|
|
798
|
-
|
|
799
|
-
|
|
847
|
+
Its `image` is the picture the Open Graph tags print, so the two always
|
|
848
|
+
agree (see [The head](#the-head)). Its `license` is the page's license URL,
|
|
849
|
+
the front matter's over the site's, and absent with none.
|
|
800
850
|
- `BreadcrumbList` wherever the page prints a breadcrumb (see
|
|
801
851
|
[The page shell](#the-page-shell)): one `ListItem` per crumb with its
|
|
802
852
|
`position`, `name` and absolute URL as `item`.
|
|
@@ -1055,16 +1105,18 @@ an Eleventy build needs few edits. It is part of the package's semver contract.
|
|
|
1055
1105
|
|
|
1056
1106
|
Every template gets:
|
|
1057
1107
|
|
|
1058
|
-
| Key
|
|
1059
|
-
|
|
|
1060
|
-
| `site`
|
|
1061
|
-
| `menus`
|
|
1062
|
-
| `siteAuthor`
|
|
1063
|
-
| `soloAuthor`
|
|
1064
|
-
| `icons`
|
|
1065
|
-
| `theme`
|
|
1066
|
-
| `shareImage`
|
|
1067
|
-
| `llmsTxt`
|
|
1108
|
+
| Key | What it holds |
|
|
1109
|
+
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1110
|
+
| `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. |
|
|
1111
|
+
| `menus` | Every menu the site stores, by name, marked for this page: `menus.primary`, `menus.footer`, and any other name. See [Navigation](#navigation). |
|
|
1112
|
+
| `siteAuthor` | Who the page is by, as a profile. **Absent** when nobody matches. See [Bylines and author archives](#bylines-and-author-archives). |
|
|
1113
|
+
| `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). |
|
|
1114
|
+
| `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). |
|
|
1115
|
+
| `theme` | The colours the theme declares, as `{ colorScheme, themeColor: { light, dark } }`, each absent when no theme declares it. See [The manifest](#the-manifest). |
|
|
1116
|
+
| `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). |
|
|
1117
|
+
| `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. |
|
|
1118
|
+
| `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. |
|
|
1119
|
+
| `siteLicense` | The site's own license as `{ name, url }`, whatever the page says. **Undefined** with none. |
|
|
1068
1120
|
|
|
1069
1121
|
A document — one post, one page, or one entry of a listing — adds:
|
|
1070
1122
|
|
|
@@ -1085,6 +1137,7 @@ A document — one post, one page, or one entry of a listing — adds:
|
|
|
1085
1137
|
| `type` | `post` or `page`. |
|
|
1086
1138
|
| `permalink`, `slug`, `draft`, `description` | Straight from the front matter. |
|
|
1087
1139
|
| `lang` | The `lang` front matter as a canonical tag, such as `fr-CA`. Absent when it names none or no tag. |
|
|
1140
|
+
| `enclosure` | The post's recording, parsed, with `player` (`audio` or `video`) and `transcript.captions`. Absent with none. |
|
|
1088
1141
|
| `author` | Who wrote it, as a profile rather than a string. See [Bylines and author archives](#bylines-and-author-archives). |
|
|
1089
1142
|
| `activityStreams` | The post's ActivityPub object id, absolute. Only on a rendered published post. |
|
|
1090
1143
|
| `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
|
|
@@ -54,12 +54,8 @@
|
|
|
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
|
-
{% set entryImage = metaImage or (siteAuthor.avatar or site.icon if type == "post" else "") %}
|
|
63
59
|
{% set nodes = [] %}
|
|
64
60
|
{% set website %}
|
|
65
61
|
{
|
|
@@ -82,6 +78,7 @@
|
|
|
82
78
|
}) }}
|
|
83
79
|
{{- member("publisher", publisher) }}
|
|
84
80
|
{{- member("about", { "@id": soloId } if soloId else "") }}
|
|
81
|
+
{{- member("license", siteLicense.url if siteLicense else "") }}
|
|
85
82
|
}{% endset %}
|
|
86
83
|
{% set _ = nodes.push(website) %}
|
|
87
84
|
{% if not soloId %}
|
|
@@ -129,9 +126,10 @@
|
|
|
129
126
|
{{- member("datePublished", date | date("iso") if date else "") }}
|
|
130
127
|
{{- member("dateModified", (updated or date) | date("iso") if (updated or date) else "") }}
|
|
131
128
|
{{- member("description", metaDescription) }}
|
|
132
|
-
{{- member("image",
|
|
129
|
+
{{- member("image", metaImage | absoluteUrl if metaImage else "") }}
|
|
133
130
|
{{- member("author", person) }}
|
|
134
131
|
{{- member("publisher", publisher) }}
|
|
132
|
+
{{- member("license", license.url if license else "") }}
|
|
135
133
|
}{% endset %}
|
|
136
134
|
{% set _ = nodes.push(entry) %}
|
|
137
135
|
{% endif %}
|
|
@@ -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,22 @@
|
|
|
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
|
+
|
|
663
679
|
/* An untitled post is its words, set larger. It is the one entry whose
|
|
664
680
|
heading is the hidden one TASK-144 gave it. */
|
|
665
681
|
.blog-post > .screen-reader-text ~ .e-content {
|
|
@@ -1190,6 +1190,31 @@
|
|
|
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
|
+
|
|
1193
1218
|
.blog-post > .screen-reader-text ~ .e-content {
|
|
1194
1219
|
--tw-leading: 1.55;
|
|
1195
1220
|
font-size: 1.3rem;
|