@geekity/cms 0.12.0 → 0.14.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 +188 -9
- package/admin/pages/account/login.njk +3 -0
- package/admin/pages/documents/editor.njk +20 -0
- package/admin/pages/indieauth/consent.njk +51 -0
- package/admin/pages/indieauth/refused.njk +11 -0
- package/admin/pages/settings/general.njk +12 -2
- package/admin/pages/settings/reading.njk +93 -0
- package/admin/pages/users/apps.njk +74 -0
- package/admin/pages/users/edit.njk +12 -0
- package/admin/static/admin.css +27 -0
- package/dist/admin/accounts.d.ts +3 -0
- package/dist/admin/accounts.d.ts.map +1 -1
- package/dist/admin/accounts.js +5 -0
- package/dist/admin/accounts.js.map +1 -1
- package/dist/admin/documents.d.ts +4 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +45 -5
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/federation.js +2 -2
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/headers.d.ts +9 -2
- package/dist/admin/headers.d.ts.map +1 -1
- package/dist/admin/headers.js +16 -7
- package/dist/admin/headers.js.map +1 -1
- package/dist/admin/menu.d.ts.map +1 -1
- package/dist/admin/menu.js +2 -1
- package/dist/admin/menu.js.map +1 -1
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +48 -5
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/settings-general.d.ts.map +1 -1
- package/dist/admin/settings-general.js +28 -3
- package/dist/admin/settings-general.js.map +1 -1
- package/dist/admin/settings-reading.d.ts.map +1 -1
- package/dist/admin/settings-reading.js +15 -1
- package/dist/admin/settings-reading.js.map +1 -1
- package/dist/admin/settings.d.ts +83 -4
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +166 -4
- package/dist/admin/settings.js.map +1 -1
- package/dist/admin/templates.d.ts +3 -0
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +3 -0
- package/dist/admin/templates.js.map +1 -1
- package/dist/admin/users.d.ts +8 -0
- package/dist/admin/users.d.ts.map +1 -1
- package/dist/admin/users.js +9 -0
- package/dist/admin/users.js.map +1 -1
- package/dist/config.d.ts +34 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/content/pinned.d.ts +26 -0
- package/dist/content/pinned.d.ts.map +1 -0
- package/dist/content/pinned.js +42 -0
- package/dist/content/pinned.js.map +1 -0
- package/dist/content/store.d.ts +7 -0
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +20 -0
- package/dist/content/store.js.map +1 -1
- package/dist/env.d.ts +21 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/federation/actor.d.ts +15 -0
- package/dist/federation/actor.d.ts.map +1 -1
- package/dist/federation/actor.js +94 -3
- package/dist/federation/actor.js.map +1 -1
- package/dist/federation/article.d.ts +13 -1
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +45 -6
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +36 -4
- package/dist/federation/delivery.js.map +1 -1
- package/dist/federation/federation.d.ts.map +1 -1
- package/dist/federation/federation.js +24 -2
- package/dist/federation/federation.js.map +1 -1
- package/dist/federation/mount.js +2 -2
- package/dist/federation/mount.js.map +1 -1
- package/dist/federation/paths.d.ts +30 -0
- package/dist/federation/paths.d.ts.map +1 -1
- package/dist/federation/paths.js +36 -0
- package/dist/federation/paths.js.map +1 -1
- package/dist/images/icons.d.ts +44 -25
- package/dist/images/icons.d.ts.map +1 -1
- package/dist/images/icons.js +205 -60
- package/dist/images/icons.js.map +1 -1
- package/dist/images/index.d.ts +2 -2
- package/dist/images/index.d.ts.map +1 -1
- package/dist/images/index.js +1 -1
- package/dist/images/index.js.map +1 -1
- package/dist/images/markup.d.ts +14 -0
- package/dist/images/markup.d.ts.map +1 -1
- package/dist/images/markup.js +16 -11
- package/dist/images/markup.js.map +1 -1
- package/dist/images/variants.d.ts +11 -0
- package/dist/images/variants.d.ts.map +1 -1
- package/dist/images/variants.js +24 -4
- package/dist/images/variants.js.map +1 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -2
- package/dist/index.js.map +1 -1
- package/dist/indexnow.d.ts +34 -0
- package/dist/indexnow.d.ts.map +1 -0
- package/dist/indexnow.js +141 -0
- package/dist/indexnow.js.map +1 -0
- package/dist/indieauth/bearer.d.ts +39 -0
- package/dist/indieauth/bearer.d.ts.map +1 -0
- package/dist/indieauth/bearer.js +63 -0
- package/dist/indieauth/bearer.js.map +1 -0
- package/dist/indieauth/client.d.ts +61 -0
- package/dist/indieauth/client.d.ts.map +1 -0
- package/dist/indieauth/client.js +139 -0
- package/dist/indieauth/client.js.map +1 -0
- package/dist/indieauth/connected-apps.d.ts +23 -0
- package/dist/indieauth/connected-apps.d.ts.map +1 -0
- package/dist/indieauth/connected-apps.js +73 -0
- package/dist/indieauth/connected-apps.js.map +1 -0
- package/dist/indieauth/consent.d.ts +28 -0
- package/dist/indieauth/consent.d.ts.map +1 -0
- package/dist/indieauth/consent.js +185 -0
- package/dist/indieauth/consent.js.map +1 -0
- package/dist/indieauth/discovery.d.ts +87 -0
- package/dist/indieauth/discovery.d.ts.map +1 -0
- package/dist/indieauth/discovery.js +127 -0
- package/dist/indieauth/discovery.js.map +1 -0
- package/dist/indieauth/grants.d.ts +57 -0
- package/dist/indieauth/grants.d.ts.map +1 -0
- package/dist/indieauth/grants.js +35 -0
- package/dist/indieauth/grants.js.map +1 -0
- package/dist/indieauth/identity.d.ts +36 -0
- package/dist/indieauth/identity.d.ts.map +1 -0
- package/dist/indieauth/identity.js +77 -0
- package/dist/indieauth/identity.js.map +1 -0
- package/dist/indieauth/redeem.d.ts +47 -0
- package/dist/indieauth/redeem.d.ts.map +1 -0
- package/dist/indieauth/redeem.js +69 -0
- package/dist/indieauth/redeem.js.map +1 -0
- package/dist/indieauth/request.d.ts +47 -0
- package/dist/indieauth/request.d.ts.map +1 -0
- package/dist/indieauth/request.js +104 -0
- package/dist/indieauth/request.js.map +1 -0
- package/dist/indieauth/token.d.ts +19 -0
- package/dist/indieauth/token.d.ts.map +1 -0
- package/dist/indieauth/token.js +110 -0
- package/dist/indieauth/token.js.map +1 -0
- package/dist/indieauth/tokens.d.ts +131 -0
- package/dist/indieauth/tokens.d.ts.map +1 -0
- package/dist/indieauth/tokens.js +205 -0
- package/dist/indieauth/tokens.js.map +1 -0
- package/dist/web/admin-bar.d.ts +3 -1
- package/dist/web/admin-bar.d.ts.map +1 -1
- package/dist/web/admin-bar.js +5 -1
- package/dist/web/admin-bar.js.map +1 -1
- package/dist/web/archive.d.ts +1 -1
- package/dist/web/archive.d.ts.map +1 -1
- package/dist/web/archive.js +3 -2
- package/dist/web/archive.js.map +1 -1
- package/dist/web/authors.d.ts +19 -0
- package/dist/web/authors.d.ts.map +1 -1
- package/dist/web/authors.js +25 -0
- package/dist/web/authors.js.map +1 -1
- package/dist/web/context.d.ts +25 -0
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +18 -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 +5 -2
- package/dist/web/feed-atom.js.map +1 -1
- package/dist/web/feed-item.d.ts +24 -6
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +16 -8
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-json.d.ts +6 -2
- package/dist/web/feed-json.d.ts.map +1 -1
- package/dist/web/feed-json.js +6 -2
- 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 +17 -4
- package/dist/web/feed-rss.js.map +1 -1
- package/dist/web/feed-source.d.ts +31 -0
- package/dist/web/feed-source.d.ts.map +1 -1
- package/dist/web/feed-source.js +31 -0
- package/dist/web/feed-source.js.map +1 -1
- package/dist/web/feed-xml.d.ts +2 -0
- package/dist/web/feed-xml.d.ts.map +1 -1
- package/dist/web/feed-xml.js +2 -0
- package/dist/web/feed-xml.js.map +1 -1
- package/dist/web/feeds.d.ts.map +1 -1
- package/dist/web/feeds.js +15 -3
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/index.d.ts +4 -2
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +3 -2
- package/dist/web/index.js.map +1 -1
- package/dist/web/indexnow.d.ts +9 -0
- package/dist/web/indexnow.d.ts.map +1 -0
- package/dist/web/indexnow.js +27 -0
- package/dist/web/indexnow.js.map +1 -0
- package/dist/web/llms.d.ts +58 -0
- package/dist/web/llms.d.ts.map +1 -0
- package/dist/web/llms.js +91 -0
- package/dist/web/llms.js.map +1 -0
- package/dist/web/locale.d.ts +41 -0
- package/dist/web/locale.d.ts.map +1 -0
- package/dist/web/locale.js +56 -0
- package/dist/web/locale.js.map +1 -0
- package/dist/web/manifest.d.ts +50 -0
- package/dist/web/manifest.d.ts.map +1 -0
- package/dist/web/manifest.js +56 -0
- package/dist/web/manifest.js.map +1 -0
- package/dist/web/negotiate.d.ts.map +1 -1
- package/dist/web/negotiate.js +6 -0
- package/dist/web/negotiate.js.map +1 -1
- package/dist/web/oembed.d.ts +104 -0
- package/dist/web/oembed.d.ts.map +1 -0
- package/dist/web/oembed.js +290 -0
- package/dist/web/oembed.js.map +1 -0
- package/dist/web/opensearch.d.ts +35 -0
- package/dist/web/opensearch.d.ts.map +1 -0
- package/dist/web/opensearch.js +65 -0
- package/dist/web/opensearch.js.map +1 -0
- package/dist/web/render.d.ts +6 -1
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +40 -10
- package/dist/web/render.js.map +1 -1
- package/dist/web/robots.d.ts +89 -0
- package/dist/web/robots.d.ts.map +1 -0
- package/dist/web/robots.js +259 -0
- package/dist/web/robots.js.map +1 -0
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +179 -8
- package/dist/web/routes.js.map +1 -1
- package/dist/web/share-image.d.ts +51 -0
- package/dist/web/share-image.d.ts.map +1 -0
- package/dist/web/share-image.js +43 -0
- package/dist/web/share-image.js.map +1 -0
- package/dist/web/sitemap.d.ts +0 -26
- package/dist/web/sitemap.d.ts.map +1 -1
- package/dist/web/sitemap.js +0 -44
- package/dist/web/sitemap.js.map +1 -1
- package/dist/web/templates.d.ts +16 -9
- package/dist/web/templates.d.ts.map +1 -1
- package/dist/web/templates.js +88 -55
- package/dist/web/templates.js.map +1 -1
- package/dist/web/themes.d.ts +33 -0
- package/dist/web/themes.d.ts.map +1 -1
- package/dist/web/themes.js +57 -0
- package/dist/web/themes.js.map +1 -1
- package/dist/webmention/discovery.d.ts +4 -2
- package/dist/webmention/discovery.d.ts.map +1 -1
- package/dist/webmention/discovery.js +14 -8
- package/dist/webmention/discovery.js.map +1 -1
- package/dist/webmention/fetch-public.d.ts +2 -0
- package/dist/webmention/fetch-public.d.ts.map +1 -1
- package/dist/webmention/fetch-public.js +1 -1
- package/dist/webmention/fetch-public.js.map +1 -1
- package/package.json +2 -1
- package/templates/site/content/_data/site.json +0 -1
- package/themes/default/README.md +174 -43
- package/themes/default/layouts/base.njk +116 -17
- package/themes/default/layouts/front-page.njk +7 -1
- package/themes/default/layouts/home.njk +11 -0
- package/themes/default/layouts/page.njk +4 -3
- package/themes/default/layouts/post.njk +5 -4
- package/themes/default/layouts/search.njk +4 -5
- package/themes/default/partials/archive.njk +3 -1
- package/themes/default/partials/bio.njk +16 -2
- package/themes/default/partials/breadcrumbs.njk +20 -0
- package/themes/default/partials/conversation.njk +4 -1
- package/themes/default/partials/jsonld.njk +49 -9
- package/themes/default/partials/kicker.njk +6 -4
- package/themes/default/partials/lang.njk +7 -0
- package/themes/default/partials/post-list.njk +5 -3
- package/themes/default/partials/reply-context.njk +4 -2
- package/themes/default/src/style.css +14 -0
- package/themes/default/static/style.css +57 -0
- package/themes/default/theme.json +2 -0
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
adds to it.
|
|
24
24
|
alternates <link rel="alternate"> tags, where the feeds go
|
|
25
25
|
header the site header, with the site menu
|
|
26
|
+
breadcrumbs the trail at the top of <main>; empty it to print none
|
|
27
|
+
(the JSON-LD keeps its BreadcrumbList)
|
|
26
28
|
content the page body
|
|
27
29
|
footer the site footer
|
|
28
30
|
scripts scripts at the end of <body>
|
|
@@ -38,8 +40,10 @@
|
|
|
38
40
|
|
|
39
41
|
`metaDescription` is the page's own description, else the entry's summary
|
|
40
42
|
— the very line the feeds publish — else the site's tagline. `metaImage` is
|
|
41
|
-
the picture the front matter names, else the site's
|
|
42
|
-
nothing is printed rather than an empty tag.
|
|
43
|
+
`shareImage.url`: the picture the front matter names, else the site's
|
|
44
|
+
avatar; with neither, nothing is printed rather than an empty tag. The CMS
|
|
45
|
+
works out `shareImage` because its alt text and its size live in files a
|
|
46
|
+
template cannot read. `isEntry` is a rendered post
|
|
43
47
|
or page, which is what Open Graph calls an article: a listing has
|
|
44
48
|
`pagination`, and the posts page is a listing carrying a page's front
|
|
45
49
|
matter, so it is a website like every other listing. -#}
|
|
@@ -48,13 +52,51 @@
|
|
|
48
52
|
{% set pageTitle = title or label %}
|
|
49
53
|
{% set metaTitle = pageTitle or site.title %}
|
|
50
54
|
{% set metaUrl = (page.url or "/") | absoluteUrl %}
|
|
51
|
-
{% set metaImage =
|
|
55
|
+
{% set metaImage = shareImage.url if shareImage else "" %}
|
|
52
56
|
{% set isEntry = (type == "post" or type == "page") and not pagination %}
|
|
57
|
+
{#- An author archive is the only page that is about a person: a listing whose
|
|
58
|
+
`author` is whose page this is, rather than the posts page's writer. -#}
|
|
59
|
+
{% set isAuthorArchive = pagination and author.url and url and (author.url in url) %}
|
|
60
|
+
{#- Where this page sits, as `{ name, url }` from the front page down to it
|
|
61
|
+
(TASK-150): a post under its first category, and an archive under the
|
|
62
|
+
front page, with a crumb of its own past page one. The visible breadcrumb
|
|
63
|
+
and the JSON-LD both print this list, so they cannot say different things.
|
|
64
|
+
Every other page has no hierarchy worth showing and an empty trail. -#}
|
|
65
|
+
{% set crumbs = [] %}
|
|
66
|
+
{% if pagination and category %}
|
|
67
|
+
{% set _ = crumbs.push({ "name": category, "url": "/" + (site.categoryBase or "category") + "/" + (category | urlencode) + "/" }) %}
|
|
68
|
+
{% elif pagination and tag %}
|
|
69
|
+
{% set _ = crumbs.push({ "name": tag, "url": "/" + (site.tagBase or "tag") + "/" + (tag | urlencode) + "/" }) %}
|
|
70
|
+
{% elif isAuthorArchive %}
|
|
71
|
+
{% set _ = crumbs.push({ "name": author.name, "url": author.url }) %}
|
|
72
|
+
{% elif type == "post" and isEntry and categories.length %}
|
|
73
|
+
{% set _ = crumbs.push({ "name": categories[0], "url": "/" + (site.categoryBase or "category") + "/" + (categories[0] | urlencode) + "/" }) %}
|
|
74
|
+
{% set _ = crumbs.push({ "name": pageTitle, "url": page.url or url }) %}
|
|
75
|
+
{% endif %}
|
|
76
|
+
{% if crumbs.length and pagination.pageNumber > 0 %}
|
|
77
|
+
{% set _ = crumbs.push({ "name": "Page " + (pagination.pageNumber + 1), "url": page.url or url }) %}
|
|
78
|
+
{% endif %}
|
|
79
|
+
{% set breadcrumbs = [{ "name": "Home", "url": "/" }].concat(crumbs) if crumbs.length else [] %}
|
|
53
80
|
<!doctype html>
|
|
54
81
|
<html lang="{{ site.language | default("en") }}">
|
|
55
82
|
<head>
|
|
56
83
|
<meta charset="utf-8">
|
|
57
84
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
85
|
+
{#
|
|
86
|
+
The colours the theme's `theme.json` declares (TASK-146). The colour
|
|
87
|
+
scheme goes before the stylesheet, so a reader in dark mode gets a dark
|
|
88
|
+
canvas while the stylesheet is still on its way rather than a white
|
|
89
|
+
flash. The theme colour tints the browser around the page, one per
|
|
90
|
+
scheme, and is the page's own background so the two meet without a seam.
|
|
91
|
+
#}
|
|
92
|
+
{% if theme.colorScheme %}
|
|
93
|
+
<meta name="color-scheme" content="{{ theme.colorScheme }}">
|
|
94
|
+
{% endif %}
|
|
95
|
+
{% for scheme in ["light", "dark"] %}
|
|
96
|
+
{% if theme.themeColor[scheme] %}
|
|
97
|
+
<meta name="theme-color" content="{{ theme.themeColor[scheme] }}" media="(prefers-color-scheme: {{ scheme }})">
|
|
98
|
+
{% endif %}
|
|
99
|
+
{% endfor %}
|
|
58
100
|
<title>{% block title %}{% if pageTitle %}{{ pageTitle }} · {% endif %}{{ site.title }}{% endblock %}</title>
|
|
59
101
|
{% if metaDescription %}
|
|
60
102
|
<meta name="description" content="{{ metaDescription }}">
|
|
@@ -110,13 +152,33 @@
|
|
|
110
152
|
{% if webmention %}
|
|
111
153
|
<link rel="webmention" href="{{ webmention | url }}">
|
|
112
154
|
{% endif %}
|
|
155
|
+
{#- The site's search, for a browser to offer from its address bar
|
|
156
|
+
(TASK-204). #}
|
|
157
|
+
<link rel="search" type="application/opensearchdescription+xml" title="{{ site.title }}" href="{{ "/opensearch.xml" | url }}">
|
|
158
|
+
{#- Where a tool that is pasted this post's URL asks for its card
|
|
159
|
+
(TASK-205), as JSON and as XML. #}
|
|
160
|
+
{% if isEntry %}
|
|
161
|
+
{% set oembed = "/_geekity/oembed?url=" + (metaUrl | urlencode) + "&format=" %}
|
|
162
|
+
<link rel="alternate" type="application/json+oembed" href="{{ (oembed + "json") | absoluteUrl }}" title="{{ metaTitle }}">
|
|
163
|
+
<link rel="alternate" type="text/xml+oembed" href="{{ (oembed + "xml") | absoluteUrl }}" title="{{ metaTitle }}">
|
|
164
|
+
{% endif %}
|
|
165
|
+
{#- The site's index for language models (TASK-149), from the front page,
|
|
166
|
+
which is where a tool that found the site looks for it. #}
|
|
167
|
+
{% if llmsTxt and atRoot %}
|
|
168
|
+
<link rel="describedby" type="text/markdown" href="{{ llmsTxt | url }}">
|
|
169
|
+
{% endif %}
|
|
113
170
|
{% block head %}
|
|
114
171
|
{#
|
|
115
172
|
What a link to this page looks like when somebody posts it: the same
|
|
116
173
|
title, description and picture to Open Graph and to a Twitter card,
|
|
117
|
-
which is the pair every reader of this markup understands. The
|
|
118
|
-
always
|
|
119
|
-
|
|
174
|
+
which is the pair every reader of this markup understands. The image
|
|
175
|
+
always carries its alt text, and its size when the CMS knows it. The
|
|
176
|
+
card is `summary_large_image` only for a picture wide enough to fill
|
|
177
|
+
one, and `summary` — a small picture beside the words — for the site's
|
|
178
|
+
avatar and anything else.
|
|
179
|
+
|
|
180
|
+
An entry is an article, with its times, its author and its tags: the
|
|
181
|
+
author is their profile here when they have one, else their name.
|
|
120
182
|
#}
|
|
121
183
|
<meta property="og:title" content="{{ metaTitle }}">
|
|
122
184
|
{% if metaDescription %}
|
|
@@ -125,25 +187,59 @@
|
|
|
125
187
|
<meta property="og:url" content="{{ metaUrl }}">
|
|
126
188
|
<meta property="og:type" content="{% if isEntry %}article{% else %}website{% endif %}">
|
|
127
189
|
<meta property="og:site_name" content="{{ site.title }}">
|
|
128
|
-
{% if
|
|
129
|
-
<meta property="og:image" content="{{
|
|
190
|
+
{% if shareImage %}
|
|
191
|
+
<meta property="og:image" content="{{ shareImage.url | absoluteUrl }}">
|
|
192
|
+
<meta property="og:image:alt" content="{{ shareImage.alt }}">
|
|
193
|
+
{% if shareImage.size %}
|
|
194
|
+
<meta property="og:image:width" content="{{ shareImage.size.width }}">
|
|
195
|
+
<meta property="og:image:height" content="{{ shareImage.size.height }}">
|
|
196
|
+
{% endif %}
|
|
197
|
+
{% endif %}
|
|
198
|
+
{% if isEntry %}
|
|
199
|
+
{% if date %}
|
|
200
|
+
<meta property="article:published_time" content="{{ date | date("iso") }}">
|
|
201
|
+
{% endif %}
|
|
202
|
+
{% if updated or date %}
|
|
203
|
+
<meta property="article:modified_time" content="{{ (updated or date) | date("iso") }}">
|
|
204
|
+
{% endif %}
|
|
205
|
+
{% if siteAuthor.url %}
|
|
206
|
+
<meta property="article:author" content="{{ siteAuthor.url | absoluteUrl }}">
|
|
207
|
+
{% elif siteAuthor.name or author.name or author %}
|
|
208
|
+
<meta property="article:author" content="{{ siteAuthor.name or author.name or author }}">
|
|
209
|
+
{% endif %}
|
|
210
|
+
{% for tag in tags %}
|
|
211
|
+
<meta property="article:tag" content="{{ tag }}">
|
|
212
|
+
{% endfor %}
|
|
213
|
+
{% endif %}
|
|
214
|
+
{#
|
|
215
|
+
Who a Mastodon link preview credits, with a follow button (TASK-202):
|
|
216
|
+
the user's own account here, on an entry by them and on the homepage of
|
|
217
|
+
a site one person writes. Mastodon shows it only when that account lists
|
|
218
|
+
this site's domain under author attribution.
|
|
219
|
+
#}
|
|
220
|
+
{% set creator = siteAuthor if isEntry else (soloAuthor if atRoot) %}
|
|
221
|
+
{% if creator.username %}
|
|
222
|
+
<meta name="fediverse:creator" content="{{ creator.username | fediverseHandle }}">
|
|
130
223
|
{% endif %}
|
|
131
|
-
<meta name="twitter:card" content="summary">
|
|
224
|
+
<meta name="twitter:card" content="{{ shareImage.card if shareImage else "summary" }}">
|
|
132
225
|
<meta name="twitter:title" content="{{ metaTitle }}">
|
|
133
226
|
{% if metaDescription %}
|
|
134
227
|
<meta name="twitter:description" content="{{ metaDescription }}">
|
|
135
228
|
{% endif %}
|
|
136
|
-
{% if
|
|
137
|
-
<meta name="twitter:image" content="{{
|
|
229
|
+
{% if shareImage %}
|
|
230
|
+
<meta name="twitter:image" content="{{ shareImage.url | absoluteUrl }}">
|
|
231
|
+
<meta name="twitter:image:alt" content="{{ shareImage.alt }}">
|
|
138
232
|
{% endif %}
|
|
139
233
|
{#
|
|
140
|
-
The site's icons, derived from its
|
|
141
|
-
lines print nothing — until a site has one,
|
|
142
|
-
links no icon rather than
|
|
234
|
+
The site's icons, derived from its `icon` setting, else its avatar.
|
|
235
|
+
`icons` is empty — and these lines print nothing — until a site has one,
|
|
236
|
+
so a site with neither links no icon rather than links that answer 404.
|
|
237
|
+
An SVG icon has every size, so it carries no `sizes`.
|
|
143
238
|
#}
|
|
144
239
|
{% for icon in icons %}
|
|
145
|
-
<link rel="{{ icon.rel }}" type="
|
|
240
|
+
<link rel="{{ icon.rel }}" type="{{ icon.type }}"{% if icon.sizes %} sizes="{{ icon.sizes }}"{% endif %} href="{{ icon.href | url }}">
|
|
146
241
|
{% endfor %}
|
|
242
|
+
<link rel="manifest" href="{{ "/manifest.webmanifest" | url }}">
|
|
147
243
|
{% include "partials/jsonld.njk" %}
|
|
148
244
|
{% endblock %}
|
|
149
245
|
</head>
|
|
@@ -170,19 +266,22 @@
|
|
|
170
266
|
<p class="site-tagline">{{ site.tagline }}</p>
|
|
171
267
|
{% endif %}
|
|
172
268
|
{% else %}
|
|
173
|
-
|
|
269
|
+
{#- The solo author's own archive claims the homepage back, so the
|
|
270
|
+
two URLs are provably one person (TASK-180). -#}
|
|
271
|
+
<a class="header-link-home"{% if isAuthorArchive and soloAuthor.url == author.url %} rel="me"{% endif %} href="{{ "/" | url }}">{{ site.title }}</a>
|
|
174
272
|
{% endif %}
|
|
175
273
|
{{ nav.list(menus.primary, "Site") }}
|
|
176
274
|
</header>
|
|
177
275
|
{% endblock %}
|
|
178
276
|
|
|
179
277
|
<main id="main">
|
|
278
|
+
{% block breadcrumbs %}{% include "partials/breadcrumbs.njk" %}{% endblock %}
|
|
180
279
|
{% block content %}{% endblock %}
|
|
181
280
|
</main>
|
|
182
281
|
|
|
183
282
|
{% block footer %}
|
|
184
283
|
<footer>
|
|
185
|
-
<p>© {{ "now" | date("year") }}{
|
|
284
|
+
<p>© {{ "now" | date("year") }}, {{ soloAuthor.name or site.title }} · Published with Geekity</p>
|
|
186
285
|
{#
|
|
187
286
|
The footer menu: the `footer` area this theme declares in its
|
|
188
287
|
`theme.json`, which a site fills in on the Navigation screen, and the
|
|
@@ -10,13 +10,19 @@
|
|
|
10
10
|
over the same feed a listing is made of, headed at 3 so the outline never
|
|
11
11
|
skips a level, then the line of links, then the bio.
|
|
12
12
|
|
|
13
|
+
The bio is the site's author's on a solo author blog, with the `rel="me"`
|
|
14
|
+
claims that make this page their identity (TASK-180). On any other site it is
|
|
15
|
+
whoever the page is by, and it claims nothing: the homepage speaks for nobody.
|
|
16
|
+
|
|
13
17
|
A theme that wants a different front page replaces this file; a site that
|
|
14
18
|
wants no front page of its own sets no homepage, and `/` is the listing.
|
|
15
19
|
#}
|
|
16
20
|
{% extends "layouts/base.njk" %}
|
|
17
21
|
|
|
18
22
|
{#- Who the bio credits: the person the site's author setting names. -#}
|
|
19
|
-
{% set bioAuthor = siteAuthor %}
|
|
23
|
+
{% set bioAuthor = soloAuthor or siteAuthor %}
|
|
24
|
+
{% set bioRelMe = true if soloAuthor else false %}
|
|
25
|
+
{% set bioHome = "/" | absoluteUrl if soloAuthor else "" %}
|
|
20
26
|
|
|
21
27
|
{% block content %}
|
|
22
28
|
<div class="page-body e-content">
|
|
@@ -22,4 +22,15 @@
|
|
|
22
22
|
{% endif %}
|
|
23
23
|
{% include "partials/post-list.njk" %}
|
|
24
24
|
{% include "partials/pagination.njk" %}
|
|
25
|
+
{#- On a solo author blog the homepage speaks for the site's author, so it
|
|
26
|
+
ends with their card, as the front page does (TASK-180). Any other
|
|
27
|
+
listing, and the homepage of any other site, is nobody's. -#}
|
|
28
|
+
{% if atRoot and soloAuthor %}
|
|
29
|
+
{% set bioAuthor = soloAuthor %}
|
|
30
|
+
{% set bioHome = "/" | absoluteUrl %}
|
|
31
|
+
<hr>
|
|
32
|
+
<footer>
|
|
33
|
+
{% include "partials/bio.njk" %}
|
|
34
|
+
</footer>
|
|
35
|
+
{% endif %}
|
|
25
36
|
{% endblock %}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#}
|
|
15
15
|
{% extends "layouts/base.njk" %}
|
|
16
16
|
{% import "partials/kicker.njk" as kicker with context %}
|
|
17
|
+
{% import "partials/lang.njk" as language with context %}
|
|
17
18
|
|
|
18
19
|
{#- Who the bio credits, and what tells `layouts/base.njk` the site menu is in
|
|
19
20
|
the bio rather than in the page footer. -#}
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
|
|
22
23
|
{% block content %}
|
|
23
24
|
{% set entryUrl = page.url or url %}
|
|
24
|
-
<article class="blog-post h-entry">
|
|
25
|
+
<article class="blog-post h-entry"{{ language.attr(lang) }}>
|
|
25
26
|
<header class="post-header">
|
|
26
27
|
{{ kicker.line("Page") }}
|
|
27
28
|
<h1 class="p-name">{{ title }}</h1>
|
|
@@ -35,10 +36,10 @@
|
|
|
35
36
|
deciding twice whether there is anything to say. -#}
|
|
36
37
|
<p class="page-meta">
|
|
37
38
|
{%- if date %}
|
|
38
|
-
<a href="{{ entryUrl | url }}" class="u-url"><time class="small dt-published" datetime="{{ date | date("iso") }}">Published {{ date | date }}</time></a>
|
|
39
|
+
<a href="{{ entryUrl | url }}" class="u-url"><time class="small dt-published" datetime="{{ date | date("iso") }}">Published {{ date | date("readable", none, lang) }}</time></a>
|
|
39
40
|
{%- endif %}
|
|
40
41
|
{%- if updated and (not date or (updated | date("html")) != (date | date("html"))) %}
|
|
41
|
-
{% if date %}<span aria-hidden="true">·</span> {% endif %}<time class="small dt-updated" datetime="{{ updated | date("iso") }}">Updated {{ updated | date }}</time>
|
|
42
|
+
{% if date %}<span aria-hidden="true">·</span> {% endif %}<time class="small dt-updated" datetime="{{ updated | date("iso") }}">Updated {{ updated | date("readable", none, lang) }}</time>
|
|
42
43
|
{%- endif -%}
|
|
43
44
|
</p>
|
|
44
45
|
</section>
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
{% extends "layouts/base.njk" %}
|
|
18
18
|
{% import "partials/tags.njk" as taxonomy with context %}
|
|
19
19
|
{% import "partials/kicker.njk" as kicker with context %}
|
|
20
|
+
{% import "partials/lang.njk" as language with context %}
|
|
20
21
|
|
|
21
22
|
{#- Who the bio credits: the person the file names, and otherwise whoever the
|
|
22
23
|
site is by, which is what the source theme's bio reads. A post naming
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
|
|
29
30
|
{% block content %}
|
|
30
31
|
{% set entryUrl = page.url or url %}
|
|
31
|
-
<article class="blog-post h-entry">
|
|
32
|
+
<article class="blog-post h-entry"{{ language.attr(lang) }}>
|
|
32
33
|
{#- A post with no name of its own (a note, or a reply without a title) is
|
|
33
34
|
its words, so it opens on them in sight. Its page still needs one h1 to
|
|
34
35
|
navigate by, so it gets one that says what it is, who wrote it and when,
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
<h1 class="screen-reader-text">
|
|
50
51
|
{{- "Reply" if postType == "reply" else "Note" }}
|
|
51
52
|
{%- if bioAuthor.name %} by {{ bioAuthor.name }}{% endif %}
|
|
52
|
-
{%- if date %}, {{ date | date }}{% endif -%}
|
|
53
|
+
{%- if date %}, {{ date | date("readable", none, lang) }}{% endif -%}
|
|
53
54
|
</h1>
|
|
54
55
|
{{ kicker.line(postType | capitalize, false, false, categories) }}
|
|
55
56
|
{% include "partials/reply-context.njk" %}
|
|
@@ -64,12 +65,12 @@
|
|
|
64
65
|
<a class="u-category small" href="https://news.indieweb.org/en">#indienews</a>
|
|
65
66
|
{%- endif %}
|
|
66
67
|
{%- if date %}
|
|
67
|
-
<a href="{{ entryUrl | url }}" class="u-url"><time class="small dt-published" datetime="{{ date | date("iso") }}">Published {{ date | date }}</time></a>
|
|
68
|
+
<a href="{{ entryUrl | url }}" class="u-url"><time class="small dt-published" datetime="{{ date | date("iso") }}">Published {{ date | date("readable", none, lang) }}</time></a>
|
|
68
69
|
{%- endif %}
|
|
69
70
|
{#- Updated only when it is a different day in the site's own timezone:
|
|
70
71
|
a typo fixed an hour after publishing is not news. -#}
|
|
71
72
|
{%- if updated and (not date or (updated | date("html")) != (date | date("html"))) %}
|
|
72
|
-
{% if date %}<span aria-hidden="true">·</span> {% endif %}<time class="small dt-updated" datetime="{{ updated | date("iso") }}">Updated {{ updated | date }}</time>
|
|
73
|
+
{% if date %}<span aria-hidden="true">·</span> {% endif %}<time class="small dt-updated" datetime="{{ updated | date("iso") }}">Updated {{ updated | date("readable", none, lang) }}</time>
|
|
73
74
|
{%- endif -%}
|
|
74
75
|
</p>
|
|
75
76
|
</section>
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
{% endblock %}
|
|
22
22
|
|
|
23
23
|
{% import "partials/kicker.njk" as kicker with context %}
|
|
24
|
+
{% import "partials/lang.njk" as language with context %}
|
|
24
25
|
{% block content %}
|
|
25
26
|
<h1 class="page-title">Search</h1>
|
|
26
27
|
{% include "partials/search-form.njk" %}
|
|
@@ -28,17 +29,15 @@
|
|
|
28
29
|
<p class="search-summary" role="status">
|
|
29
30
|
{%- if pagination.total == 0 -%}
|
|
30
31
|
Nothing found for “{{ query }}”.
|
|
31
|
-
{%- elif pagination.total == 1 -%}
|
|
32
|
-
One result for “{{ query }}”.
|
|
33
32
|
{%- else -%}
|
|
34
|
-
{{ pagination.total }}
|
|
33
|
+
{{ pagination.total | plural({ one: "One result", other: "# results" }, "en") }} for “{{ query }}”.
|
|
35
34
|
{%- endif -%}
|
|
36
35
|
</p>
|
|
37
36
|
{% if posts.length %}
|
|
38
37
|
<div class="search-results">
|
|
39
38
|
{% for post in posts %}
|
|
40
|
-
<article class="search-result h-entry">
|
|
41
|
-
{{ kicker.line(("Page" if post.type == "page" else post.postType) | capitalize, post.date) }}
|
|
39
|
+
<article class="search-result h-entry"{{ language.attr(post.lang) }}>
|
|
40
|
+
{{ kicker.line(("Page" if post.type == "page" else post.postType) | capitalize, post.date, lang=post.lang) }}
|
|
42
41
|
{#- A post with no name of its own has none to claim, so its first words
|
|
43
42
|
link it without being marked up as one. -#}
|
|
44
43
|
<h2 class="feed-title{% if post.named %} p-name{% endif %}">
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
rather than an index.
|
|
15
15
|
#}
|
|
16
16
|
{% if archiveMonths.length %}
|
|
17
|
-
|
|
17
|
+
{#- The month headings are in the site's locale, so a page in another
|
|
18
|
+
language hands the section back to the site's language. -#}
|
|
19
|
+
<section class="archive"{% if lang and lang != (site.language | default("en")) %} lang="{{ site.language | default("en") }}"{% endif %}>
|
|
18
20
|
{% for group in archiveMonths %}
|
|
19
21
|
<h2>{{ group.month }}</h2>
|
|
20
22
|
<ol class="list-none">
|
|
@@ -33,6 +33,17 @@
|
|
|
33
33
|
Links box typed it — followed by whatever else that line carried, so this
|
|
34
34
|
prints one attribute rather than one of its own beside one of theirs.
|
|
35
35
|
|
|
36
|
+
`bioRelMe` set to `false` keeps the card and drops its claims: the name is
|
|
37
|
+
linked `rel="author"` alone and `me` comes off every profile link. The
|
|
38
|
+
homepage of a site that is not a solo author blog sets it (TASK-180), because
|
|
39
|
+
that page speaks for nobody even when it shows the site author's card.
|
|
40
|
+
|
|
41
|
+
`bioHome` set to the homepage's absolute URL makes the card the site's
|
|
42
|
+
representative h-card (TASK-193): its first `u-url` and its `u-uid` are that
|
|
43
|
+
URL, which is what a parser reading the homepage looks for to learn who the
|
|
44
|
+
site is. The name keeps its link to the archive, as a second `u-url`. Only
|
|
45
|
+
the homepage of a solo author blog sets it; every other card is unchanged.
|
|
46
|
+
|
|
36
47
|
The `p-note` and the `rel="me"` links are printed wherever the card is, which
|
|
37
48
|
is what `bioProfile` used to ask for on an author archive alone. An entry
|
|
38
49
|
left that switch unset because the page footer carried the site's identity
|
|
@@ -40,6 +51,7 @@
|
|
|
40
51
|
wrote rather than one page further on.
|
|
41
52
|
#}
|
|
42
53
|
{% if bioAuthor %}
|
|
54
|
+
{%- set bioClaims = bioRelMe !== false %}
|
|
43
55
|
<div class="bio p-author h-card">
|
|
44
56
|
{%- if bioAuthor.avatar %}
|
|
45
57
|
<div class="bio-avatar">
|
|
@@ -49,9 +61,10 @@
|
|
|
49
61
|
|
|
50
62
|
<div>
|
|
51
63
|
<p>
|
|
64
|
+
{%- if bioHome %}<data class="u-url u-uid" value="{{ bioHome }}"></data>{% endif %}
|
|
52
65
|
{{ bioLead | default("Written by") }}
|
|
53
66
|
{%- if bioAuthor.url %}
|
|
54
|
-
<a class="u-url p-name" rel="author me" href="{{ bioAuthor.url | url }}">{{ bioAuthor.name }}</a>
|
|
67
|
+
<a class="u-url p-name" rel="author{% if bioClaims %} me{% endif %}" href="{{ bioAuthor.url | url }}">{{ bioAuthor.name }}</a>
|
|
55
68
|
{%- else %}
|
|
56
69
|
<strong class="p-name">{{ bioAuthor.name }}</strong>
|
|
57
70
|
{%- endif %}
|
|
@@ -66,7 +79,8 @@
|
|
|
66
79
|
{%- if bioAuthor.links.length %}
|
|
67
80
|
<ul class="hlist bio-links">
|
|
68
81
|
{%- for link in bioAuthor.links %}
|
|
69
|
-
|
|
82
|
+
{%- set linkRel = link.rel if bioClaims else (link.rel.split(" ") | reject("equalto", "me") | join(" ")) %}
|
|
83
|
+
<li><a class="u-url"{% if linkRel %} rel="{{ linkRel }}"{% endif %} href="{{ link.href }}">{{ link.label }}</a></li>
|
|
70
84
|
{%- endfor %}
|
|
71
85
|
</ul>
|
|
72
86
|
{%- endif %}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{#
|
|
2
|
+
The trail from the front page down to this one (TASK-150), printed from
|
|
3
|
+
`breadcrumbs`, the list `layouts/base.njk` builds and `partials/jsonld.njk`
|
|
4
|
+
prints as a BreadcrumbList. Every crumb but the last is a link; the last is
|
|
5
|
+
the page the reader is on, so it is text marked as the current page. The
|
|
6
|
+
separators are decoration and a screen reader skips them: the list already
|
|
7
|
+
says there are steps.
|
|
8
|
+
#}
|
|
9
|
+
{% if breadcrumbs.length %}
|
|
10
|
+
<nav class="breadcrumbs" aria-label="Breadcrumb">
|
|
11
|
+
<ol class="hlist">
|
|
12
|
+
{% for crumb in breadcrumbs %}
|
|
13
|
+
<li>
|
|
14
|
+
{%- if not loop.first %}<span class="breadcrumbs-sep" aria-hidden="true">›</span>{% endif -%}
|
|
15
|
+
{%- if loop.last %}<span aria-current="page">{{ crumb.name }}</span>{% else %}<a href="{{ crumb.url | url }}">{{ crumb.name }}</a>{% endif -%}
|
|
16
|
+
</li>
|
|
17
|
+
{% endfor %}
|
|
18
|
+
</ol>
|
|
19
|
+
</nav>
|
|
20
|
+
{% endif %}
|
|
@@ -120,6 +120,9 @@
|
|
|
120
120
|
And the thread. The title counts the whole of it rather than the top level,
|
|
121
121
|
which is what `counts.replies` is. It does not quote the post's title: the
|
|
122
122
|
post is the page it sits under, and a note has no title to quote (TASK-186).
|
|
123
|
+
The count goes through `plural` with "en" because the words are English: a
|
|
124
|
+
theme that translates them drops the "en" and gives the forms its language
|
|
125
|
+
has, so the site's locale picks between them.
|
|
123
126
|
|
|
124
127
|
"Comments are closed." is printed under a post that has been answered and is
|
|
125
128
|
no longer taking comments. That a post is closed is what the absence of
|
|
@@ -130,7 +133,7 @@
|
|
|
130
133
|
{% if conversation.replies.length %}
|
|
131
134
|
<div id="comments" class="comments-area">
|
|
132
135
|
<h2 class="comments-title">
|
|
133
|
-
{
|
|
136
|
+
{{- conversation.counts.replies | plural({ one: "One reply", other: "# replies" }, "en") -}}
|
|
134
137
|
</h2>
|
|
135
138
|
|
|
136
139
|
<ol class="comment-list">
|
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
`siteAuthor`: the site's own author on a page about nobody in particular, the
|
|
13
13
|
writer on a post, the archive's user on an author archive. Identity comes
|
|
14
14
|
from a user profile, so a name that answers to no account here has no Person
|
|
15
|
-
at all, and the author
|
|
15
|
+
at all, and the author reference goes with it.
|
|
16
|
+
|
|
17
|
+
Who publishes the site follows the Site author setting (TASK-192). A site
|
|
18
|
+
whose author is a user is published by that user's Person and is about
|
|
19
|
+
them, on every page. A site with several authors is published by an
|
|
20
|
+
Organization named for the site and is about nobody.
|
|
16
21
|
|
|
17
22
|
How it is printed: every value goes through `dump`, which is JSON.stringify,
|
|
18
23
|
with every `<` escaped to its `\u003C` form so nothing in a title or a
|
|
@@ -34,6 +39,10 @@
|
|
|
34
39
|
{#- What the author, publisher and mainEntity members hold: a reference to the
|
|
35
40
|
Person rather than a second copy of them. -#}
|
|
36
41
|
{% set person = { "@id": personId } if personId else "" %}
|
|
42
|
+
{#- Who publishes the site and everything on it: the site author, else the
|
|
43
|
+
site itself. -#}
|
|
44
|
+
{% set soloId = (soloAuthor.url | absoluteUrl) + "#person" if soloAuthor.url else "" %}
|
|
45
|
+
{% set publisher = { "@id": soloId or home + "#organization" } %}
|
|
37
46
|
{#- `sameAs` is everywhere else this person is: the profile links they listed,
|
|
38
47
|
and their actor id, which asserts that the schema.org Person and the
|
|
39
48
|
fediverse actor are one identity — decision-14 makes their archive URL the
|
|
@@ -42,10 +51,15 @@
|
|
|
42
51
|
{% for link in siteAuthor.links %}{% set _ = sameAs.push(link.href) %}{% endfor %}
|
|
43
52
|
{% if personUrl %}{% set _ = sameAs.push(personUrl) %}{% endif %}
|
|
44
53
|
{% set address = { "@type": "PostalAddress", "addressLocality": siteAuthor.location } if siteAuthor.location else "" %}
|
|
45
|
-
{#- An author archive is the only page that is about a person: a listing whose
|
|
46
|
-
`author` is whose page this is. -#}
|
|
47
|
-
{% set isProfilePage = pagination and author.url and url and (author.url in url) %}
|
|
48
54
|
{% set entryType = "BlogPosting" if type == "post" else "Article" %}
|
|
55
|
+
{#- A post with no title is headed by the words its <title> names it by,
|
|
56
|
+
within the 110 characters Google reads, cut where a word ends (TASK-201).
|
|
57
|
+
Its picture is its own, else the site's avatar, else its author's photo,
|
|
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. -#}
|
|
61
|
+
{% 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 "") %}
|
|
49
63
|
{% set nodes = [] %}
|
|
50
64
|
{% set website %}
|
|
51
65
|
{
|
|
@@ -66,9 +80,20 @@
|
|
|
66
80
|
},
|
|
67
81
|
"query-input": "required name=search_term_string"
|
|
68
82
|
}) }}
|
|
69
|
-
{{- member("publisher",
|
|
83
|
+
{{- member("publisher", publisher) }}
|
|
84
|
+
{{- member("about", { "@id": soloId } if soloId else "") }}
|
|
70
85
|
}{% endset %}
|
|
71
86
|
{% set _ = nodes.push(website) %}
|
|
87
|
+
{% if not soloId %}
|
|
88
|
+
{% set organization %}
|
|
89
|
+
{
|
|
90
|
+
"@type": "Organization"
|
|
91
|
+
{{- member("@id", publisher["@id"]) }}
|
|
92
|
+
{{- member("name", site.title) }}
|
|
93
|
+
{{- member("url", home) }}
|
|
94
|
+
}{% endset %}
|
|
95
|
+
{% set _ = nodes.push(organization) %}
|
|
96
|
+
{% endif %}
|
|
72
97
|
{% if personId %}
|
|
73
98
|
{% set profile %}
|
|
74
99
|
{
|
|
@@ -84,7 +109,7 @@
|
|
|
84
109
|
}{% endset %}
|
|
85
110
|
{% set _ = nodes.push(profile) %}
|
|
86
111
|
{% endif %}
|
|
87
|
-
{% if
|
|
112
|
+
{% if isAuthorArchive %}
|
|
88
113
|
{% set profilePage %}
|
|
89
114
|
{
|
|
90
115
|
"@type": "ProfilePage"
|
|
@@ -98,18 +123,33 @@
|
|
|
98
123
|
{% set entry %}
|
|
99
124
|
{
|
|
100
125
|
"@type": {{ entryType | dump | safe }}
|
|
101
|
-
{{- member("headline",
|
|
126
|
+
{{- member("headline", headline) }}
|
|
102
127
|
{{- member("url", metaUrl) }}
|
|
103
128
|
{{- member("mainEntityOfPage", metaUrl) }}
|
|
104
129
|
{{- member("datePublished", date | date("iso") if date else "") }}
|
|
105
130
|
{{- member("dateModified", (updated or date) | date("iso") if (updated or date) else "") }}
|
|
106
131
|
{{- member("description", metaDescription) }}
|
|
107
|
-
{{- member("image",
|
|
132
|
+
{{- member("image", entryImage | absoluteUrl if entryImage else "") }}
|
|
108
133
|
{{- member("author", person) }}
|
|
109
|
-
{{- member("publisher",
|
|
134
|
+
{{- member("publisher", publisher) }}
|
|
110
135
|
}{% endset %}
|
|
111
136
|
{% set _ = nodes.push(entry) %}
|
|
112
137
|
{% endif %}
|
|
138
|
+
{#- Where the page sits, from the trail `layouts/base.njk` builds and the
|
|
139
|
+
visible breadcrumb prints (TASK-150). -#}
|
|
140
|
+
{% if breadcrumbs.length %}
|
|
141
|
+
{% set trail = [] %}
|
|
142
|
+
{% for crumb in breadcrumbs %}
|
|
143
|
+
{% set _ = trail.push({ "@type": "ListItem", "position": loop.index, "name": crumb.name, "item": crumb.url | absoluteUrl }) %}
|
|
144
|
+
{% endfor %}
|
|
145
|
+
{% set breadcrumbList %}
|
|
146
|
+
{
|
|
147
|
+
"@type": "BreadcrumbList"
|
|
148
|
+
{{- member("@id", metaUrl + "#breadcrumb") }}
|
|
149
|
+
{{- member("itemListElement", trail) }}
|
|
150
|
+
}{% endset %}
|
|
151
|
+
{% set _ = nodes.push(breadcrumbList) %}
|
|
152
|
+
{% endif %}
|
|
113
153
|
<script type="application/ld+json">
|
|
114
154
|
{
|
|
115
155
|
"@context": "https://schema.org",
|
|
@@ -14,17 +14,19 @@
|
|
|
14
14
|
an error page. `date` is the entry's `dt-published`, printed long. With
|
|
15
15
|
`href` the date is the entry's `u-url`, which is how a feed links an entry
|
|
16
16
|
that has no title to link; an entry with no date links `label` instead.
|
|
17
|
-
`categories` are printed as `p-category` links.
|
|
17
|
+
`categories` are printed as `p-category` links. `lang` is the entry's own
|
|
18
|
+
language when it names one, so its date is written in that language rather
|
|
19
|
+
than the site's (TASK-189); a listing passes `lang=post.lang`.
|
|
18
20
|
#}
|
|
19
|
-
{% macro line(kind, date=false, href=false, categories=[], label="") %}
|
|
21
|
+
{% macro line(kind, date=false, href=false, categories=[], label="", lang=false) %}
|
|
20
22
|
<p class="kicker">
|
|
21
23
|
<span class="kicker-kind">{{ kind }}</span>
|
|
22
24
|
{%- if href %}
|
|
23
25
|
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
24
|
-
<a href="{{ href | url }}" class="u-url">{% if date %}<time class="feed-date dt-published" datetime="{{ date | date("iso") }}">{{ date | date }}</time>{% else %}{{ label }}{% endif %}</a>
|
|
26
|
+
<a href="{{ href | url }}" class="u-url">{% if date %}<time class="feed-date dt-published" datetime="{{ date | date("iso") }}">{{ date | date("readable", none, lang) }}</time>{% else %}{{ label }}{% endif %}</a>
|
|
25
27
|
{%- elif date %}
|
|
26
28
|
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
27
|
-
<time class="feed-date dt-published" datetime="{{ date | date("iso") }}">{{ date | date }}</time>
|
|
29
|
+
<time class="feed-date dt-published" datetime="{{ date | date("iso") }}">{{ date | date("readable", none, lang) }}</time>
|
|
28
30
|
{%- endif %}
|
|
29
31
|
{%- for category in categories %}
|
|
30
32
|
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{#- The `lang` attribute of an article written in a language other than the
|
|
2
|
+
site's (TASK-154), with its leading space, and nothing for one in the
|
|
3
|
+
site's own: the `<html lang>` already names that. Imported with context,
|
|
4
|
+
since the site's language is what it compares against. -#}
|
|
5
|
+
{% macro attr(tag) -%}
|
|
6
|
+
{%- if tag and tag != (site.language | default("en")) %} lang="{{ tag }}"{% endif -%}
|
|
7
|
+
{%- endmacro %}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
never skips a level.
|
|
19
19
|
#}
|
|
20
20
|
{% import "partials/kicker.njk" as kicker with context %}
|
|
21
|
+
{% import "partials/lang.njk" as language with context %}
|
|
21
22
|
{% set feedTitleTag = "h" + (feedHeading | default(2)) %}
|
|
22
23
|
{% if posts.length %}
|
|
23
24
|
<div class="feed h-feed">
|
|
@@ -25,16 +26,17 @@
|
|
|
25
26
|
{% if not loop.first %}
|
|
26
27
|
<hr class="feed-separator">
|
|
27
28
|
{% endif %}
|
|
28
|
-
<article class="feed-item h-entry">
|
|
29
|
+
<article class="feed-item h-entry"{{ language.attr(post.lang) }}>
|
|
29
30
|
{% if post.named %}
|
|
30
|
-
{{ kicker.line(post.postType | capitalize, post.date, false, post.categories) }}
|
|
31
|
+
{{ kicker.line(post.postType | capitalize, post.date, false, post.categories, lang=post.lang) }}
|
|
31
32
|
{% else %}
|
|
32
|
-
{{ kicker.line(post.postType | capitalize, post.date, post.url, [], post.label) }}
|
|
33
|
+
{{ kicker.line(post.postType | capitalize, post.date, post.url, [], post.label, lang=post.lang) }}
|
|
33
34
|
{% endif %}
|
|
34
35
|
{#- What a reply answers, from the same partial an entry cites it with. -#}
|
|
35
36
|
{% if post.inReplyTo %}
|
|
36
37
|
{% set inReplyTo = post.inReplyTo %}
|
|
37
38
|
{% set replyContext = post.replyContext %}
|
|
39
|
+
{% set lang = post.lang %}
|
|
38
40
|
{% include "partials/reply-context.njk" %}
|
|
39
41
|
{% endif %}
|
|
40
42
|
{% if not post.named %}
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
or synced: its name, a short excerpt, its author and its date, each only when
|
|
13
13
|
it said so. It is somebody else's words, so every piece is printed through
|
|
14
14
|
autoescaping and none of it with `safe`. With a name, the name is the link;
|
|
15
|
-
with only an author, "a post" is; with neither, the bare URL.
|
|
15
|
+
with only an author, "a post" is; with neither, the bare URL. Its date is
|
|
16
|
+
written in `lang`, the language of the entry it sits in (TASK-189), so a
|
|
17
|
+
listing sets `lang` from each entry as it sets `replyContext`.
|
|
16
18
|
#}
|
|
17
19
|
{% if inReplyTo %}
|
|
18
20
|
<div class="reply-context cite u-in-reply-to h-cite">
|
|
@@ -26,7 +28,7 @@
|
|
|
26
28
|
{%- else %}<span class="p-name">{{ replyContext.author.name }}</span>{% endif -%}
|
|
27
29
|
</span>
|
|
28
30
|
{%- endif %}
|
|
29
|
-
{%- if replyContext.published %}, <time class="dt-published" datetime="{{ replyContext.published | date("iso") }}">{{ replyContext.published | date }}</time>
|
|
31
|
+
{%- if replyContext.published %}, <time class="dt-published" datetime="{{ replyContext.published | date("iso") }}">{{ replyContext.published | date("readable", none, lang) }}</time>
|
|
30
32
|
{%- endif -%}
|
|
31
33
|
</p>
|
|
32
34
|
{% if replyContext.text %}
|