@geekity/cms 0.4.0 → 0.5.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.
Files changed (143) hide show
  1. package/README.md +234 -108
  2. package/admin/pages/documents/editor.njk +0 -13
  3. package/admin/pages/{settings/federation.njk → federation/settings.njk} +7 -5
  4. package/admin/pages/navigation/menus.njk +129 -0
  5. package/admin/pages/settings/reading.njk +2 -10
  6. package/admin/pages/tools/content-index.njk +89 -0
  7. package/admin/pages/users/list.njk +5 -2
  8. package/admin/static/admin.css +60 -0
  9. package/dist/access-log.d.ts +44 -0
  10. package/dist/access-log.d.ts.map +1 -0
  11. package/dist/access-log.js +71 -0
  12. package/dist/access-log.js.map +1 -0
  13. package/dist/admin/documents.d.ts +3 -13
  14. package/dist/admin/documents.d.ts.map +1 -1
  15. package/dist/admin/documents.js +2 -34
  16. package/dist/admin/documents.js.map +1 -1
  17. package/dist/admin/federation.d.ts +10 -0
  18. package/dist/admin/federation.d.ts.map +1 -1
  19. package/dist/admin/federation.js +10 -0
  20. package/dist/admin/federation.js.map +1 -1
  21. package/dist/admin/index.d.ts +3 -3
  22. package/dist/admin/index.d.ts.map +1 -1
  23. package/dist/admin/index.js +2 -2
  24. package/dist/admin/index.js.map +1 -1
  25. package/dist/admin/menu.d.ts.map +1 -1
  26. package/dist/admin/menu.js +19 -2
  27. package/dist/admin/menu.js.map +1 -1
  28. package/dist/admin/navigation.d.ts +155 -0
  29. package/dist/admin/navigation.d.ts.map +1 -0
  30. package/dist/admin/navigation.js +292 -0
  31. package/dist/admin/navigation.js.map +1 -0
  32. package/dist/admin/routes.d.ts.map +1 -1
  33. package/dist/admin/routes.js +9 -0
  34. package/dist/admin/routes.js.map +1 -1
  35. package/dist/admin/session.d.ts +17 -3
  36. package/dist/admin/session.d.ts.map +1 -1
  37. package/dist/admin/session.js +19 -5
  38. package/dist/admin/session.js.map +1 -1
  39. package/dist/admin/settings-federation.d.ts +10 -1
  40. package/dist/admin/settings-federation.d.ts.map +1 -1
  41. package/dist/admin/settings-federation.js +26 -6
  42. package/dist/admin/settings-federation.js.map +1 -1
  43. package/dist/admin/settings-page.d.ts +26 -9
  44. package/dist/admin/settings-page.d.ts.map +1 -1
  45. package/dist/admin/settings-page.js +18 -12
  46. package/dist/admin/settings-page.js.map +1 -1
  47. package/dist/admin/settings-pages.d.ts +14 -3
  48. package/dist/admin/settings-pages.d.ts.map +1 -1
  49. package/dist/admin/settings-pages.js +15 -5
  50. package/dist/admin/settings-pages.js.map +1 -1
  51. package/dist/admin/settings-reading.d.ts +5 -4
  52. package/dist/admin/settings-reading.d.ts.map +1 -1
  53. package/dist/admin/settings-reading.js +6 -5
  54. package/dist/admin/settings-reading.js.map +1 -1
  55. package/dist/admin/settings.d.ts +29 -15
  56. package/dist/admin/settings.d.ts.map +1 -1
  57. package/dist/admin/settings.js +28 -78
  58. package/dist/admin/settings.js.map +1 -1
  59. package/dist/admin/templates.d.ts +3 -1
  60. package/dist/admin/templates.d.ts.map +1 -1
  61. package/dist/admin/templates.js +3 -1
  62. package/dist/admin/templates.js.map +1 -1
  63. package/dist/admin/tools.d.ts +111 -0
  64. package/dist/admin/tools.d.ts.map +1 -0
  65. package/dist/admin/tools.js +182 -0
  66. package/dist/admin/tools.js.map +1 -0
  67. package/dist/cli.d.ts.map +1 -1
  68. package/dist/cli.js +6 -1
  69. package/dist/cli.js.map +1 -1
  70. package/dist/comments/form.d.ts +48 -0
  71. package/dist/comments/form.d.ts.map +1 -1
  72. package/dist/comments/form.js +15 -0
  73. package/dist/comments/form.js.map +1 -1
  74. package/dist/comments/index.d.ts +4 -3
  75. package/dist/comments/index.d.ts.map +1 -1
  76. package/dist/comments/index.js +2 -1
  77. package/dist/comments/index.js.map +1 -1
  78. package/dist/comments/routes.d.ts +3 -1
  79. package/dist/comments/routes.d.ts.map +1 -1
  80. package/dist/comments/routes.js +46 -11
  81. package/dist/comments/routes.js.map +1 -1
  82. package/dist/comments/submission.d.ts +37 -3
  83. package/dist/comments/submission.d.ts.map +1 -1
  84. package/dist/comments/submission.js +59 -20
  85. package/dist/comments/submission.js.map +1 -1
  86. package/dist/comments/viewer.d.ts +21 -0
  87. package/dist/comments/viewer.d.ts.map +1 -0
  88. package/dist/comments/viewer.js +45 -0
  89. package/dist/comments/viewer.js.map +1 -0
  90. package/dist/config.d.ts +35 -0
  91. package/dist/config.d.ts.map +1 -1
  92. package/dist/config.js +3 -0
  93. package/dist/config.js.map +1 -1
  94. package/dist/content/store.d.ts +18 -0
  95. package/dist/content/store.d.ts.map +1 -1
  96. package/dist/content/store.js +18 -0
  97. package/dist/content/store.js.map +1 -1
  98. package/dist/env.d.ts +12 -1
  99. package/dist/env.d.ts.map +1 -1
  100. package/dist/federation/mount.d.ts +4 -0
  101. package/dist/federation/mount.d.ts.map +1 -1
  102. package/dist/federation/mount.js +44 -12
  103. package/dist/federation/mount.js.map +1 -1
  104. package/dist/index.d.ts +5 -5
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +21 -4
  107. package/dist/index.js.map +1 -1
  108. package/dist/web/index.d.ts +3 -3
  109. package/dist/web/index.d.ts.map +1 -1
  110. package/dist/web/index.js +1 -1
  111. package/dist/web/index.js.map +1 -1
  112. package/dist/web/navigation.d.ts +158 -36
  113. package/dist/web/navigation.d.ts.map +1 -1
  114. package/dist/web/navigation.js +250 -60
  115. package/dist/web/navigation.js.map +1 -1
  116. package/dist/web/negotiate.d.ts +22 -0
  117. package/dist/web/negotiate.d.ts.map +1 -1
  118. package/dist/web/negotiate.js +25 -10
  119. package/dist/web/negotiate.js.map +1 -1
  120. package/dist/web/render.d.ts +16 -10
  121. package/dist/web/render.d.ts.map +1 -1
  122. package/dist/web/render.js +21 -14
  123. package/dist/web/render.js.map +1 -1
  124. package/dist/web/routes.d.ts.map +1 -1
  125. package/dist/web/routes.js +11 -2
  126. package/dist/web/routes.js.map +1 -1
  127. package/dist/web/themes.d.ts +21 -0
  128. package/dist/web/themes.d.ts.map +1 -1
  129. package/dist/web/themes.js +37 -0
  130. package/dist/web/themes.js.map +1 -1
  131. package/package.json +1 -1
  132. package/templates/site/content/_data/site.json +7 -0
  133. package/templates/site/content/pages/about.md +7 -0
  134. package/themes/default/README.md +200 -80
  135. package/themes/default/layouts/author.njk +3 -4
  136. package/themes/default/layouts/base.njk +38 -49
  137. package/themes/default/layouts/front-page.njk +1 -2
  138. package/themes/default/partials/bio.njk +15 -21
  139. package/themes/default/partials/comment-form.njk +31 -3
  140. package/themes/default/partials/menu.njk +32 -0
  141. package/themes/default/partials/search-form.njk +5 -0
  142. package/themes/default/static/style.css +31 -8
  143. package/themes/default/theme.json +5 -1
@@ -21,13 +21,14 @@ themes/default/
21
21
  post-list.njk the h-feed a listing is made of
22
22
  pagination.njk previous/next pager
23
23
  tags.njk macros for tag and category links
24
- bio.njk who an entry is by, as an h-card, with the site menu
24
+ bio.njk who an entry is by, as an h-card
25
+ menu.njk one named menu, as a nav of links
25
26
  feeds.njk macros for the feed links in <head>
26
27
  conversation.njk the replies, likes and boosts under a post
27
28
  comment-form.njk the form under a post that is taking comments
28
29
  contact-form.njk the form on a page whose front matter says contact: true
29
30
  archive.njk every post by month, on a page that says archive: true
30
- search-form.njk the search box, in the footer and on the search page
31
+ search-form.njk the search box, on the search page
31
32
  mail/
32
33
  test.*.njk the Send test email message
33
34
  password-reset.*.njk the forgot-password link
@@ -141,24 +142,36 @@ words above the posts; `layouts/home.njk` already does.
141
142
  skip link, one `.global-wrapper` at the 42rem measure, `.global-header`,
142
143
  `<main id="main">` and the footer, in that order.
143
144
 
144
- **The header has one rule.** On the front page it is the site title as
145
- `h1.main-heading`, linked home, with `site.tagline` in a paragraph under it; on
146
- every other page it is `a.header-link-home`, the site title small and linked
147
- home, and no tagline. The wrapper carries `data-is-root-path="true"` at `/` and
148
- nothing anywhere else, which is how the stylesheet tells the two apart. There is
149
- no navigation in the header — see [Navigation](#navigation).
145
+ **One home for each kind of link.** The header carries the site menu, on every
146
+ page. [The bio](#the-bio) carries the person whose page or post it is. The
147
+ footer carries the site's own links. The search box is on the search page and
148
+ nowhere else.
149
+
150
+ **The header has one rule, and the menu under it.** On the front page it is the
151
+ site title as `h1.main-heading`, linked home, with `site.tagline` in a
152
+ paragraph under it; on every other page it is `a.header-link-home`, the site
153
+ title small and linked home, and no tagline. The wrapper carries
154
+ `data-is-root-path="true"` at `/` and nothing anywhere else, which is how the
155
+ stylesheet tells the two apart. `menus.primary` is printed inside the
156
+ `.global-header` on both: at the root it is a line of its own under the
157
+ tagline, and everywhere else the stylesheet lays the header out as one line so
158
+ it sits beside the link home. See [Navigation](#navigation).
150
159
 
151
160
  **The footer** prints the copyright with the current year and `site.author`,
152
- `Published with Geekity`, and then one `ul.hlist` holding an RSS link to
153
- `/feed/` and one `rel="me"` link per entry of `siteAuthor.links`. A site whose
154
- `author` setting names nobody with an account here gets the line and the RSS
155
- link and no identity links, because `siteAuthor` is absent. The year is
161
+ `Published with Geekity`, and then `menus.footer` — the `footer` area this
162
+ theme declares, and the whole of what the footer links. A site that wants its
163
+ feed there types an `RSS | /feed/` line into it, the way the starter site does.
164
+ Nothing in the footer is read off an account: it used to hold one `rel="me"`
165
+ link per entry of `siteAuthor.links`, which was one nominated user's profile
166
+ presented as the site's. An empty or missing footer menu prints no list at all,
167
+ and the copyright line stands on its own. The year is
156
168
  `{{ "now" | date("year") }}` — `now` is the one word the `date` filter reads
157
169
  rather than parses — so it is the year at the moment the page is rendered, in
158
170
  the site's own timezone.
159
171
 
160
- Webrings, badges, a licence notice and anything else particular to one site are
161
- deliberately not in the package. They go in a site theme's `footer` block:
172
+ Webrings, badges, a licence notice and anything else that is markup rather than
173
+ a link are deliberately not in the package. They go in a site theme's `footer`
174
+ block:
162
175
 
163
176
  ```njk
164
177
  {% extends "layouts/base.njk" %}
@@ -244,25 +257,29 @@ meta line, and this is the whole credit.
244
257
  `bioAuthor` is the one thing to set, and nothing renders when it is absent.
245
258
  It is one of the profile objects the context already carries: `author` on a
246
259
  post, falling back to `siteAuthor`; `siteAuthor` on a page; the archive's
247
- person on an author archive. `layouts/base.njk` reads it too — see
248
- [Navigation](#navigation).
260
+ person on an author archive.
249
261
 
250
262
  What the bio prints: a round `u-photo` at 50px when they have an avatar,
251
263
  "Written by" and their name as a `p-name u-url` linked `rel="author me"` to
252
- their archive, then `p-job-title` and `p-locality` when the profile says. A
253
- name this site has no account for is printed unlinked, because the file still
254
- said somebody wrote this. Then the site menu.
264
+ their archive, then `p-job-title` and `p-locality` when the profile says, then
265
+ their `p-note` and their `rel="me"` links as a `ul.hlist.bio-links`. Each of
266
+ those is printed only when the profile says it, so a profile holding a name
267
+ alone prints a name alone. A name this site has no account for is printed
268
+ unlinked, because the file still said somebody wrote this.
269
+
270
+ **The person and only the person.** The site menu used to be printed here as
271
+ well, because the design had no header navigation; it is in the header on
272
+ every page now — see [Navigation](#navigation) — so the bio says nothing about
273
+ the site. The note and the links used to be behind a `bioProfile` switch that
274
+ an author archive set and an entry did not, on the grounds that the page
275
+ footer already carried the site's identity links. It carries nobody's, so the
276
+ switch is gone and the card is the same wherever it appears: at the top of
277
+ somebody's archive, and under each of their posts.
255
278
 
256
279
  `bioLead` is what the line opens with, `Written by` unless a layout sets
257
280
  another; `layouts/author.njk` sets `Posts by`, because the card there heads
258
281
  somebody's writing rather than crediting one piece of it.
259
282
 
260
- Set `bioProfile` as well and it also prints their `p-note` and their `rel="me"`
261
- links; `layouts/author.njk` does, because that page is about the person rather
262
- than about something they wrote. An entry leaves it unset: the page footer
263
- already prints the site's identity links, and the `rel="author me"` link leads
264
- to the archive where this person's own are.
265
-
266
283
  **A site that used `partials/byline.njk`** — `{{ byline.line(author) }}` from
267
284
  an overridden layout — either includes this partial instead or writes the line
268
285
  itself. It was four lines of markup, and the object behind it has not changed:
@@ -363,17 +380,17 @@ saying how many documents matched, a `div.search-results` of
363
380
  indexed with `<meta name="robots" content="noindex">` in its `head` block.
364
381
 
365
382
  The context is a listing's with two differences. `query` is the words searched
366
- for, trimmed, and an empty string on the page before a search; it is defined
367
- only on this page, which is how `layouts/base.njk` knows to leave the footer's
368
- search box off it. Each entry in `posts` carries a `snippet`: a few words of
383
+ for, trimmed, and an empty string on the page before a search. Each entry in
384
+ `posts` carries a `snippet`: a few words of
369
385
  HTML around the match, escaped, with every matched word in `<mark>`. Print it
370
386
  with `safe`. The results are best match first rather than newest first, and
371
387
  only what the public site would serve is ever among them.
372
388
 
373
389
  `partials/search-form.njk` is a `GET` form to `/search/` with the words in `q`,
374
- so it needs no JavaScript and a search is a URL. `layouts/base.njk` puts it in
375
- the footer of every other page. The search page takes the path `/search/` ahead
376
- of any document permalinked there.
390
+ so it needs no JavaScript and a search is a URL. This layout is the only thing
391
+ that includes it: the box used to be in the footer of every page as well, and
392
+ search is a menu item now — a `Search | /search/` line like any other. The
393
+ search page takes the path `/search/` ahead of any document permalinked there.
377
394
 
378
395
  ### The front page
379
396
 
@@ -393,8 +410,8 @@ It draws the page's own words and then what the site has been writing:
393
410
  3. `p.front-links`, a line of links to where the writing is. The posts page is
394
411
  linked by its own title when the site names one, and the search always is
395
412
  (TASK-22).
396
- 4. The bio, under a rule, exactly as an entry ends — so the site menu is there
397
- too, because that is where this design keeps it.
413
+ 4. The bio, under a rule, exactly as an entry ends: whoever the site's author
414
+ setting names, with their note and their own links.
398
415
 
399
416
  Two context keys are the front page's alone. `recentPosts` is the entries to
400
417
  list, in the same shape a listing's are: the posts of the current month when
@@ -691,7 +708,7 @@ Every template gets:
691
708
  | Key | What it holds |
692
709
  | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
693
710
  | `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. |
694
- | `menu` | The site menu for this page: a list of `{ label, url, current }`. See [Navigation](#navigation). |
711
+ | `menus` | Every menu the site stores, by name, marked for this page: `menus.primary`, `menus.footer`, and any other name. See [Navigation](#navigation). |
695
712
  | `siteAuthor` | Who the page is by, as a profile. **Absent** when nobody matches. See [Bylines and author archives](#bylines-and-author-archives). |
696
713
  | `icons` | The site's icons, as `{ rel, sizes, href }`. Empty until the site has an avatar to derive them from. See [The head](#the-head). |
697
714
 
@@ -774,17 +791,18 @@ the `partials/byline.njk` it used to have.
774
791
 
775
792
  `layouts/author.njk` is that person's archive, at `/author/{username}/`, with
776
793
  their pages at `/author/{username}/page/2/` and their three feeds under
777
- `/author/{username}/feed/`. It is headed with their name and then the bio with
778
- `bioProfile` set — the avatar, the note and the `rel="me"` links — and lists
779
- their published posts newest first. The source design has no author archive, so
794
+ `/author/{username}/feed/`. It is headed with their name and then the bio —
795
+ the avatar, the note and the `rel="me"` links, the same card that ends each of
796
+ their posts — and lists their published posts newest first. The source design has no author archive, so
780
797
  the heading is the CMS's own; the h-card under it is the one partial the theme
781
798
  has. A user with no profile still has one; they are called by their username.
782
799
 
783
800
  `siteAuthor` is the same object on a different question: not who wrote this
784
- document, but who the page in front of the reader is by. It is what the bio,
785
- the footer's `rel="me"` links and any structured data a theme emits should all
786
- read, so that what a reader sees and what a machine reads cannot drift apart.
787
- It resolves in this order:
801
+ document, but who the page in front of the reader is by. It is what the bio and
802
+ any structured data a theme emits should both read, so that what a reader sees
803
+ and what a machine reads cannot drift apart. The footer does not read it at
804
+ all: the site's links are `menus.footer`, typed rather than borrowed from an
805
+ account. It resolves in this order:
788
806
 
789
807
  - the document's own `author`, on a post or a page that names one;
790
808
  - the person whose archive it is, on an author archive;
@@ -795,8 +813,7 @@ It is **absent** when none of those name anybody this site has. The site
795
813
  setting is read more strictly than a byline is: a byline prints the name a file
796
814
  gives whether or not somebody answers to it, but a site author with no profile
797
815
  behind it has no picture, no bio and nowhere to link, so there is nothing to
798
- print and the key is not there. Write `{% if siteAuthor %}` around the bio and
799
- the identity links.
816
+ print and the key is not there. Write `{% if siteAuthor %}` around the bio.
800
817
 
801
818
  The URL is not only a page. Each user is an ActivityPub actor at that address,
802
819
  so it is the page a follower lands on when they click through from the
@@ -806,50 +823,113 @@ it.
806
823
 
807
824
  ## Navigation
808
825
 
809
- `menu` is the site menu, already in order and already knowing which of its items
810
- is the page being looked at. Every template gets it.
826
+ A site stores its menus by name in `content/_data/site.json`:
811
827
 
812
- The design has no header navigation: the menu is the horizontal list inside
813
- [the bio](#the-bio). So it is printed once, in one of two places. A page with a
814
- bio — an entry, an author archive — carries it there. A page without one — a
815
- listing, the 404, a layout of a site's own — gets it from the `footer` block of
816
- `layouts/base.njk`. What decides is `bioAuthor`: a layout that renders the bio
817
- sets it, and the footer then leaves the menu out. A layout that sets nothing
818
- keeps the footer menu, which is why an overridden `layouts/post.njk` does not
819
- lose the navigation by not having a bio.
828
+ ```json
829
+ {
830
+ "menus": {
831
+ "primary": [
832
+ { "label": "Home", "url": "/" },
833
+ { "label": "About", "url": "/about/" }
834
+ ],
835
+ "footer": [
836
+ { "label": "Colophon", "url": "/colophon/" },
837
+ { "label": "Mastodon", "url": "https://example.social/@me", "me": true }
838
+ ]
839
+ }
840
+ }
841
+ ```
820
842
 
821
- The markup is the same either way, and a layout that overrides `header` or
822
- `footer` writes it the same way:
843
+ Every template gets them as `menus`, keyed by the same names, already in order
844
+ and already knowing which item is the page being looked at:
823
845
 
824
846
  ```njk
825
- {% if menu.length %}
826
- <nav class="site-nav" aria-label="Site">
827
- <ul class="hlist">
828
- {% for item in menu %}
829
- <li><a href="{{ item.url | url }}"{% if item.current %} class="is-current" aria-current="page"{% endif %}>{{ item.label }}</a></li>
830
- {% endfor %}
831
- </ul>
832
- </nav>
833
- {% endif %}
847
+ {% for item in menus.footer %}
848
+ <a href="{{ item.url | url }}">{{ item.label }}</a>
849
+ {% endfor %}
834
850
  ```
835
851
 
836
- Each item is `{ label, url, current }`. `url` is a site-root path or an absolute
837
- URL for somewhere else, so put it through the `url` filter as above and a site
838
- served from a subdirectory still links correctly. `current` is true for the item
839
- whose path is the one being rendered, comparing without the trailing slash; an
840
- item pointing off the site is never current.
852
+ That is the whole interface, so a theme renders a menu this one has never heard
853
+ of by looping over the name a site stored it under.
854
+
855
+ ### The areas a theme declares
856
+
857
+ `theme.json` says where a theme renders a menu, so the Navigation screen can
858
+ offer those places and nowhere else. This theme declares two:
859
+
860
+ ```json
861
+ {
862
+ "name": "Default",
863
+ "kind": "site",
864
+ "areas": [
865
+ { "name": "primary", "label": "Site menu" },
866
+ { "name": "footer", "label": "Footer links" }
867
+ ]
868
+ }
869
+ ```
870
+
871
+ `name` is the key under `menus`, in `site.json` and on the context. `label` is
872
+ what a person choosing an area reads; leave it out and it falls back to the
873
+ name. Declare a third area and a site can fill it in; declare none and the
874
+ theme renders no menu.
875
+
876
+ The declaration is for the screen, not for the render. Every menu the site
877
+ stores is on the context whether or not a theme declared an area for it — a
878
+ menu nothing loops over is rendered nowhere and kept, which is what lets
879
+ somebody write the menu a theme will use before switching to that theme. An
880
+ `areas` that is missing, or that will not read, leaves the theme with none
881
+ rather than failing to load it.
841
882
 
842
- The list is the `navigation` setting first, in the order the settings screen
843
- names it, and then every published page whose front matter says
844
- `navigation: true`, ordered by `navigationOrder` and then by title. A page that
845
- names no order sorts after every page that does.
883
+ ### One item
846
884
 
847
- It is called `menu` rather than `navigation` because `navigation` is the
848
- front-matter key a page opts in with, and a document's own front matter goes on
849
- top of the globals exactly as Eleventy's data cascade does. The setting is
850
- mirrored to `navigation` in `content/_data/site.json`, so an Eleventy build of
851
- the same content renders the same menu; `docs/eleventy.config.example.js` builds
852
- it as `collections.menu`.
885
+ Each item is `{ label, url, current }`, plus `me: true` on a link that should
886
+ carry `rel="me"`. `url` is a site-root path or an absolute URL for somewhere
887
+ else, so put it through the `url` filter and a site served from a subdirectory
888
+ still links correctly. `current` is true for the item whose path is the one
889
+ being rendered, comparing without the trailing slash; an item pointing off the
890
+ site is never current. `me` is for the IndieWeb's identity check: Mastodon
891
+ verifies a link on a profile by looking for a `rel="me"` link back, so a footer
892
+ link to a profile marked `me` is what makes the tick appear.
893
+
894
+ ### Where this theme prints them
895
+
896
+ `partials/menu.njk` holds the markup, as one macro:
897
+
898
+ ```njk
899
+ {% import "partials/menu.njk" as nav %}
900
+ {{ nav.list(menus.footer, "Footer") }}
901
+ ```
902
+
903
+ The second argument is the `aria-label`, which is what tells a reader on a
904
+ screen reader which of a page's menus this one is. Nothing is printed for a
905
+ menu with nothing in it.
906
+
907
+ `menus.primary` is printed once, in `header.global-header`, on every page
908
+ (TASK-105). At the root, where the header is the site title with the tagline
909
+ under it, the menu is a line of its own under the tagline; everywhere else,
910
+ where the header is the small link home, the stylesheet lays the header out as
911
+ one line and the menu sits beside it. Nothing decides between two places any
912
+ more: a reader looks in the same spot whatever they are reading, and a layout
913
+ that overrides the `content` block cannot lose the navigation by not printing
914
+ a bio.
915
+
916
+ `menus.footer` is printed in the `footer` block of `layouts/base.njk`, under
917
+ the copyright line, and is the whole of what the footer links — a feed, a
918
+ colophon, a webring, a profile marked `me`. The footer reads nothing off an
919
+ account: it used to print one `rel="me"` link per entry of `siteAuthor.links`,
920
+ which meant one nominated user's links stood for the site and nobody else's
921
+ appeared at all.
922
+
923
+ `menus.primary` is the whole of the site menu, in the order the Navigation
924
+ screen names it. Both areas are declared in this theme's `theme.json`, which is
925
+ what puts them on `/admin/navigation` with those labels; a theme that renders a
926
+ third menu declares a third area. A page cannot put itself in a menu; a page
927
+ that should be linked — including the one a site serves as its front page,
928
+ typed `Home | /` — is typed into the box there.
929
+
930
+ Because the menus live in `site.json`, an Eleventy build of the same content
931
+ renders the same ones; `docs/eleventy.config.example.js` builds them as
932
+ `collections.menus`.
853
933
 
854
934
  ## Feeds
855
935
 
@@ -1084,7 +1164,7 @@ that has CSS for the WordPress theme can bring it.
1084
1164
  | Key | What it holds |
1085
1165
  | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1086
1166
  | `action` | Where the form posts. One fixed path; the post travels as a field. |
1087
- | `fields` | The name each field is submitted under: `post`, `name`, `email`, `url`, `body`, `inReplyTo`, `trap`, `loaded`, `notify`. Use these rather than typing the names. |
1167
+ | `fields` | The name each field is submitted under: `post`, `name`, `email`, `url`, `body`, `inReplyTo`, `trap`, `loaded`, `notify`, `csrf`. Use these rather than typing the names. |
1088
1168
  | `post` | The post's slug, for the hidden field. |
1089
1169
  | `loaded` | When this form was rendered, in epoch milliseconds, for the hidden field. A submission that comes back too fast is refused. |
1090
1170
  | `values` | What is in the fields: empty on a fresh form, what was typed on a refused one. |
@@ -1098,6 +1178,46 @@ technology, and give it `tabindex="-1"` and `autocomplete="off"`. A submission
1098
1178
  that filled it is dropped. **Do not** remove it from a replacement partial —
1099
1179
  it is one of three things standing between the site and a spam queue.
1100
1180
 
1181
+ ### When somebody is signed in
1182
+
1183
+ Two more keys are on `commentForm` when — and only when — the request carries a
1184
+ valid session for this site's admin:
1185
+
1186
+ | Key | What it holds |
1187
+ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
1188
+ | `signedInAs` | Who the comment will be posted as: `name`, which is their display name or their username, and `url`, which is their author archive. |
1189
+ | `csrfToken` | The value for a hidden `fields.csrf`, without which the submission is refused. |
1190
+
1191
+ **A replacement partial has to draw both forms**, because the site draws this
1192
+ one for its own author and the stranger's one for everybody else:
1193
+
1194
+ ```njk
1195
+ {% if commentForm.signedInAs %}
1196
+ <input type="hidden" name="{{ commentForm.fields.csrf }}" value="{{ commentForm.csrfToken }}" />
1197
+ <p class="comment-signed-in">
1198
+ Commenting as <a href="{{ commentForm.signedInAs.url }}">{{ commentForm.signedInAs.name }}</a>.
1199
+ </p>
1200
+ {% else %}
1201
+ {# fields.loaded, fields.trap, and the name, email and website boxes #}
1202
+ {% endif %}
1203
+ ```
1204
+
1205
+ The signed-in branch renders **no** name, email or website box, **no**
1206
+ `fields.trap` and **no** `fields.loaded`: the site already knows all three
1207
+ answers, and a session is better evidence that this is a person than a honeypot
1208
+ or a stopwatch. It renders `fields.csrf` instead, because this is the one form
1209
+ on the public site that acts on somebody's behalf — without the token, a page
1210
+ on another site could make them post under their own name. The comment box, the
1211
+ reply-to field and the notify box are the same in both branches.
1212
+
1213
+ The account's email address is **never** on the context. The comment is stored
1214
+ with it, exactly as a stranger's is, and it is no more printable than theirs.
1215
+
1216
+ A page carrying this form is one reader's page rather than the post, so the CMS
1217
+ answers it `Cache-Control: private, no-store` and with no ETag. A theme does
1218
+ not have to do anything about that, but it is why printing somebody's name into
1219
+ a page is safe here.
1220
+
1101
1221
  Two more keys travel beside it, both from the URL rather than from the post:
1102
1222
 
1103
1223
  | Key | What it holds |
@@ -9,14 +9,13 @@
9
9
 
10
10
  The source design has no author archive, so there is nothing to port: the
11
11
  heading is the CMS's own. What it does not keep is an h-card of its own.
12
- `partials/bio.njk` is the one the theme has, and `bioProfile` asks it for the
13
- whole profile — the note and the `rel="me"` links — because this page is
14
- about that person rather than about something they wrote.
12
+ `partials/bio.njk` is the one the theme has, and it prints the whole profile
13
+ — the note and the `rel="me"` links — wherever it is, so this page is headed
14
+ with the same card that ends each of their posts.
15
15
  #}
16
16
  {% extends "layouts/base.njk" %}
17
17
 
18
18
  {% set bioAuthor = author %}
19
- {% set bioProfile = true %}
20
19
  {% set bioLead = "Posts by" %}
21
20
 
22
21
  {% block title %}{{ author.name }}{% if pagination.pageNumber > 0 %}, page {{ pagination.pageNumber + 1 }}{% endif %} &middot; {{ site.title }}{% endblock %}
@@ -3,25 +3,26 @@
3
3
 
4
4
  The shell of the andrewshell.org design (decision-16): one wrapper at the
5
5
  measure, a big site title and tagline on the front page and a small link home
6
- everywhere else, and a footer holding the copyright, the colophon, the feed
7
- and whoever the site is by. There is no navigation in the header — the site
8
- menu belongs with the bio under an entry — and `data-is-root-path` on the
9
- wrapper is how the stylesheet tells the front page from every other one.
6
+ everywhere else, and a footer holding the copyright, the colophon and the
7
+ site's own links. `data-is-root-path` on the wrapper is how the stylesheet
8
+ tells the front page from every other one.
9
+
10
+ One home for each kind of link (TASK-105). The header carries the site menu,
11
+ on every page. `partials/bio.njk` carries the person whose page or post it
12
+ is. The footer carries `menus.footer`, the site's own list, and nothing read
13
+ off an account. The search form is on the search page and nowhere else.
10
14
 
11
15
  Blocks a site can override without copying the whole file:
12
16
  title the contents of <title>
13
17
  head the cards, the icons and the structured data, at the end of
14
18
  <head>. Call `super()` from an override that only adds to it.
15
19
  alternates <link rel="alternate"> tags, where the feeds go
16
- header the site header
20
+ header the site header, with the site menu
17
21
  content the page body
18
- footer the site footer, with the search box
22
+ footer the site footer
19
23
  scripts scripts at the end of <body>
20
-
21
- One variable crosses between a layout and this file: `bioAuthor`, whoever an
22
- entry's bio credits. A layout that prints `partials/bio.njk` sets it, and the
23
- footer here leaves the site menu out because the bio is carrying it.
24
24
  #}{% import "partials/feeds.njk" as feeds %}
25
+ {%- import "partials/menu.njk" as nav %}
25
26
  {#- Whether this is the front page, which is the one page the header and the
26
27
  stylesheet treat differently. `page.url` is the URL being served rather
27
28
  than the document's own permalink, so a static homepage under the Reading
@@ -107,6 +108,16 @@
107
108
  <a class="screen-reader-text" href="#main">Skip to content</a>
108
109
  <div class="global-wrapper"{% if atRoot %} data-is-root-path="true"{% endif %}>
109
110
  {% block header %}
111
+ {#
112
+ The header, and the site menu inside it on every page (TASK-105): one
113
+ place a reader looks for the navigation, whatever they are reading.
114
+
115
+ The header is two shapes and the menu sits differently in each. At the
116
+ root it is the site title and the tagline, and the menu is a line of
117
+ its own under them; everywhere else it is the small link home, and the
118
+ menu is beside it on that line. The stylesheet does the beside part,
119
+ off `data-is-root-path` on the wrapper.
120
+ #}
110
121
  <header class="global-header">
111
122
  {% if atRoot %}
112
123
  <h1 class="main-heading">
@@ -118,6 +129,7 @@
118
129
  {% else %}
119
130
  <a class="header-link-home" href="{{ "/" | url }}">{{ site.title }}</a>
120
131
  {% endif %}
132
+ {{ nav.list(menus.primary, "Site") }}
121
133
  </header>
122
134
  {% endblock %}
123
135
 
@@ -129,47 +141,24 @@
129
141
  <footer>
130
142
  <p>&copy; {{ "now" | date("year") }}{% if site.author %}, {{ site.author }}{% endif %} &middot; Published with Geekity</p>
131
143
  {#
132
- The feed and whoever the site is by, as one horizontal list. The links
133
- come from the site author's profile (decision-16), so what a reader
134
- sees and what a consumer of `rel="me"` reads are the same list, and a
135
- site whose author setting names nobody with an account here simply has
136
- none. Webrings, badges and anything else particular to one site are
137
- deliberately not here: they go in a site theme's `footer` override.
138
- #}
139
- <ul class="hlist">
140
- <li><a href="{{ "/feed/" | url }}">RSS</a></li>
141
- {% if siteAuthor.links %}
142
- {% for link in siteAuthor.links %}
143
- <li><a rel="me" href="{{ link.href }}">{{ link.label }}</a></li>
144
- {% endfor %}
145
- {% endif %}
146
- </ul>
147
- {#
148
- The site menu, on a page that has no bio to carry it.
144
+ The footer menu: the `footer` area this theme declares in its
145
+ `theme.json`, which a site fills in on the Navigation screen, and the
146
+ whole of what the footer links (TASK-105). It is the site's own list
147
+ — the feed, a colophon, a webring, a Mastodon link it wants verified
148
+ — rather than anything read off an account: one nominated user's
149
+ profile links presented as the site's were nobody's to give, and on a
150
+ site with more than one person nobody else's appeared at all. An item
151
+ marked `me` carries `rel="me"`, which is what a site moving a
152
+ verified profile link in here needs.
149
153
 
150
- The design has no header navigation: the menu is the horizontal list
151
- inside the bio (TASK-83), which an entry and an author archive print
152
- and a listing, a 404 or a layout of a site's own does not. So a
153
- layout that renders a bio sets `bioAuthor`, and this prints the menu
154
- only where nothing else did — the menu is on every page, once.
155
- #}
156
- {#
157
- The search box, on every page but the one that already has it at the
158
- top: `query` is on the context of the search page alone, empty or
159
- not, so its being defined is what says this is that page.
154
+ Nothing is printed until a site has filled the area in: an empty
155
+ footer menu is no list rather than an empty one, and the copyright
156
+ line above stands on its own.
157
+
158
+ Webrings, badges and anything else that is markup rather than a link
159
+ go in a site theme's `footer` override.
160
160
  #}
161
- {% if query is not defined %}
162
- {% include "partials/search-form.njk" %}
163
- {% endif %}
164
- {% if menu.length and not bioAuthor %}
165
- <nav class="site-nav" aria-label="Site">
166
- <ul class="hlist">
167
- {% for item in menu %}
168
- <li><a href="{{ item.url | url }}"{% if item.current %} class="is-current" aria-current="page"{% endif %}>{{ item.label }}</a></li>
169
- {% endfor %}
170
- </ul>
171
- </nav>
172
- {% endif %}
161
+ {{ nav.list(menus.footer, "Footer") }}
173
162
  </footer>
174
163
  {% endblock %}
175
164
  </div>
@@ -15,8 +15,7 @@
15
15
  #}
16
16
  {% extends "layouts/base.njk" %}
17
17
 
18
- {#- Who the bio credits, and what tells `layouts/base.njk` the site menu is in
19
- the bio rather than in the page footer. -#}
18
+ {#- Who the bio credits: the person the site's author setting names. -#}
20
19
  {% set bioAuthor = siteAuthor %}
21
20
 
22
21
  {% block content %}
@@ -4,10 +4,14 @@
4
4
 
5
5
  It replaces `partials/byline.njk`, which printed a name in a meta line. This
6
6
  is the whole credit — a round photo, "Written by" linked to their archive
7
- with `rel="author me"`, what they do, where they are — and under it the site
8
- menu, because the design has no navigation in the header and this is where it
9
- reads it. The menu is here and not in the page footer whenever a page has a
10
- bio; `layouts/base.njk` prints it in the footer on the pages that have none.
7
+ with `rel="author me"`, what they do, where they are, what they wrote about
8
+ themselves and the links they call their own.
9
+
10
+ The person and only the person (TASK-105). The site menu used to be printed
11
+ here as well, because the design had no header navigation; it is in
12
+ `header.global-header` on every page now, so this card says nothing about
13
+ the site and a reader looking for the way around never has to find the bio
14
+ first.
11
15
 
12
16
  Set `bioAuthor` before including it and nothing renders when that is absent:
13
17
 
@@ -25,11 +29,11 @@
25
29
  otherwise. An author archive sets "Posts by", because the card there heads a
26
30
  list of somebody's writing rather than crediting one piece of it.
27
31
 
28
- `bioProfile` is the second switch. Set it and the bio also prints their
29
- `p-note` and their `rel="me"` links, which is what an author archive wants —
30
- the page is about them. An entry leaves it unset: the page footer already
31
- prints the site's identity links, and the `rel="author me"` link above leads
32
- to the archive where this person's own are.
32
+ The `p-note` and the `rel="me"` links are printed wherever the card is, which
33
+ is what `bioProfile` used to ask for on an author archive alone. An entry
34
+ left that switch unset because the page footer carried the site's identity
35
+ links; it carries nobody's now, so somebody's links belong under what they
36
+ wrote rather than one page further on.
33
37
  #}
34
38
  {% if bioAuthor %}
35
39
  <div class="bio p-author h-card">
@@ -51,21 +55,11 @@
51
55
  {%- if bioAuthor.location %} from <span class="p-locality">{{ bioAuthor.location }}</span>{% endif %}.
52
56
  </p>
53
57
 
54
- {%- if bioProfile and bioAuthor.bio %}
58
+ {%- if bioAuthor.bio %}
55
59
  <p class="bio-note p-note">{{ bioAuthor.bio }}</p>
56
60
  {%- endif %}
57
61
 
58
- {%- if menu.length %}
59
- <nav class="site-nav" aria-label="Site">
60
- <ul class="hlist">
61
- {%- for item in menu %}
62
- <li><a href="{{ item.url | url }}"{% if item.current %} class="is-current" aria-current="page"{% endif %}>{{ item.label }}</a></li>
63
- {%- endfor %}
64
- </ul>
65
- </nav>
66
- {%- endif %}
67
-
68
- {%- if bioProfile and bioAuthor.links.length %}
62
+ {%- if bioAuthor.links.length %}
69
63
  <ul class="hlist bio-links">
70
64
  {%- for link in bioAuthor.links %}
71
65
  <li><a class="u-url" rel="me" href="{{ link.href }}">{{ link.label }}</a></li>