@geekity/cms 0.4.0 → 0.6.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 (156) hide show
  1. package/README.md +246 -110
  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 +132 -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/edit.njk +25 -2
  8. package/admin/pages/users/list.njk +5 -2
  9. package/admin/static/admin.css +78 -3
  10. package/dist/access-log.d.ts +44 -0
  11. package/dist/access-log.d.ts.map +1 -0
  12. package/dist/access-log.js +71 -0
  13. package/dist/access-log.js.map +1 -0
  14. package/dist/admin/accounts.d.ts +10 -0
  15. package/dist/admin/accounts.d.ts.map +1 -1
  16. package/dist/admin/accounts.js +15 -3
  17. package/dist/admin/accounts.js.map +1 -1
  18. package/dist/admin/documents.d.ts +3 -13
  19. package/dist/admin/documents.d.ts.map +1 -1
  20. package/dist/admin/documents.js +2 -34
  21. package/dist/admin/documents.js.map +1 -1
  22. package/dist/admin/federation.d.ts +10 -0
  23. package/dist/admin/federation.d.ts.map +1 -1
  24. package/dist/admin/federation.js +10 -0
  25. package/dist/admin/federation.js.map +1 -1
  26. package/dist/admin/index.d.ts +3 -3
  27. package/dist/admin/index.d.ts.map +1 -1
  28. package/dist/admin/index.js +2 -2
  29. package/dist/admin/index.js.map +1 -1
  30. package/dist/admin/menu.d.ts.map +1 -1
  31. package/dist/admin/menu.js +19 -2
  32. package/dist/admin/menu.js.map +1 -1
  33. package/dist/admin/navigation.d.ts +155 -0
  34. package/dist/admin/navigation.d.ts.map +1 -0
  35. package/dist/admin/navigation.js +292 -0
  36. package/dist/admin/navigation.js.map +1 -0
  37. package/dist/admin/routes.d.ts.map +1 -1
  38. package/dist/admin/routes.js +9 -0
  39. package/dist/admin/routes.js.map +1 -1
  40. package/dist/admin/session.d.ts +17 -3
  41. package/dist/admin/session.d.ts.map +1 -1
  42. package/dist/admin/session.js +19 -5
  43. package/dist/admin/session.js.map +1 -1
  44. package/dist/admin/settings-federation.d.ts +10 -1
  45. package/dist/admin/settings-federation.d.ts.map +1 -1
  46. package/dist/admin/settings-federation.js +26 -6
  47. package/dist/admin/settings-federation.js.map +1 -1
  48. package/dist/admin/settings-page.d.ts +26 -9
  49. package/dist/admin/settings-page.d.ts.map +1 -1
  50. package/dist/admin/settings-page.js +18 -12
  51. package/dist/admin/settings-page.js.map +1 -1
  52. package/dist/admin/settings-pages.d.ts +14 -3
  53. package/dist/admin/settings-pages.d.ts.map +1 -1
  54. package/dist/admin/settings-pages.js +15 -5
  55. package/dist/admin/settings-pages.js.map +1 -1
  56. package/dist/admin/settings-reading.d.ts +5 -4
  57. package/dist/admin/settings-reading.d.ts.map +1 -1
  58. package/dist/admin/settings-reading.js +6 -5
  59. package/dist/admin/settings-reading.js.map +1 -1
  60. package/dist/admin/settings.d.ts +29 -15
  61. package/dist/admin/settings.d.ts.map +1 -1
  62. package/dist/admin/settings.js +28 -78
  63. package/dist/admin/settings.js.map +1 -1
  64. package/dist/admin/templates.d.ts +3 -1
  65. package/dist/admin/templates.d.ts.map +1 -1
  66. package/dist/admin/templates.js +3 -1
  67. package/dist/admin/templates.js.map +1 -1
  68. package/dist/admin/tools.d.ts +111 -0
  69. package/dist/admin/tools.d.ts.map +1 -0
  70. package/dist/admin/tools.js +182 -0
  71. package/dist/admin/tools.js.map +1 -0
  72. package/dist/admin/users.d.ts +24 -0
  73. package/dist/admin/users.d.ts.map +1 -1
  74. package/dist/admin/users.js +102 -30
  75. package/dist/admin/users.js.map +1 -1
  76. package/dist/cli.d.ts.map +1 -1
  77. package/dist/cli.js +6 -1
  78. package/dist/cli.js.map +1 -1
  79. package/dist/comments/form.d.ts +48 -0
  80. package/dist/comments/form.d.ts.map +1 -1
  81. package/dist/comments/form.js +15 -0
  82. package/dist/comments/form.js.map +1 -1
  83. package/dist/comments/index.d.ts +4 -3
  84. package/dist/comments/index.d.ts.map +1 -1
  85. package/dist/comments/index.js +2 -1
  86. package/dist/comments/index.js.map +1 -1
  87. package/dist/comments/routes.d.ts +3 -1
  88. package/dist/comments/routes.d.ts.map +1 -1
  89. package/dist/comments/routes.js +46 -11
  90. package/dist/comments/routes.js.map +1 -1
  91. package/dist/comments/submission.d.ts +37 -3
  92. package/dist/comments/submission.d.ts.map +1 -1
  93. package/dist/comments/submission.js +59 -20
  94. package/dist/comments/submission.js.map +1 -1
  95. package/dist/comments/viewer.d.ts +21 -0
  96. package/dist/comments/viewer.d.ts.map +1 -0
  97. package/dist/comments/viewer.js +45 -0
  98. package/dist/comments/viewer.js.map +1 -0
  99. package/dist/config.d.ts +35 -0
  100. package/dist/config.d.ts.map +1 -1
  101. package/dist/config.js +3 -0
  102. package/dist/config.js.map +1 -1
  103. package/dist/content/store.d.ts +18 -0
  104. package/dist/content/store.d.ts.map +1 -1
  105. package/dist/content/store.js +18 -0
  106. package/dist/content/store.js.map +1 -1
  107. package/dist/env.d.ts +12 -1
  108. package/dist/env.d.ts.map +1 -1
  109. package/dist/federation/mount.d.ts +4 -0
  110. package/dist/federation/mount.d.ts.map +1 -1
  111. package/dist/federation/mount.js +60 -15
  112. package/dist/federation/mount.js.map +1 -1
  113. package/dist/index.d.ts +5 -5
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +21 -4
  116. package/dist/index.js.map +1 -1
  117. package/dist/web/authors.d.ts +15 -1
  118. package/dist/web/authors.d.ts.map +1 -1
  119. package/dist/web/authors.js +9 -1
  120. package/dist/web/authors.js.map +1 -1
  121. package/dist/web/index.d.ts +4 -4
  122. package/dist/web/index.d.ts.map +1 -1
  123. package/dist/web/index.js +1 -1
  124. package/dist/web/index.js.map +1 -1
  125. package/dist/web/navigation.d.ts +235 -35
  126. package/dist/web/navigation.d.ts.map +1 -1
  127. package/dist/web/navigation.js +333 -57
  128. package/dist/web/navigation.js.map +1 -1
  129. package/dist/web/negotiate.d.ts +22 -0
  130. package/dist/web/negotiate.d.ts.map +1 -1
  131. package/dist/web/negotiate.js +25 -10
  132. package/dist/web/negotiate.js.map +1 -1
  133. package/dist/web/render.d.ts +16 -10
  134. package/dist/web/render.d.ts.map +1 -1
  135. package/dist/web/render.js +21 -14
  136. package/dist/web/render.js.map +1 -1
  137. package/dist/web/routes.d.ts.map +1 -1
  138. package/dist/web/routes.js +11 -2
  139. package/dist/web/routes.js.map +1 -1
  140. package/dist/web/themes.d.ts +21 -0
  141. package/dist/web/themes.d.ts.map +1 -1
  142. package/dist/web/themes.js +37 -0
  143. package/dist/web/themes.js.map +1 -1
  144. package/package.json +1 -1
  145. package/templates/site/content/_data/site.json +7 -0
  146. package/templates/site/content/pages/about.md +7 -0
  147. package/themes/default/README.md +215 -80
  148. package/themes/default/layouts/author.njk +3 -4
  149. package/themes/default/layouts/base.njk +38 -49
  150. package/themes/default/layouts/front-page.njk +1 -2
  151. package/themes/default/partials/bio.njk +20 -22
  152. package/themes/default/partials/comment-form.njk +31 -3
  153. package/themes/default/partials/menu.njk +34 -0
  154. package/themes/default/partials/search-form.njk +5 -0
  155. package/themes/default/static/style.css +54 -13
  156. 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,31 @@ 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
266
+ link's `rel` is on it ready to print, `me` first and then whatever else the
267
+ Links box typed. Each of
268
+ those is printed only when the profile says it, so a profile holding a name
269
+ alone prints a name alone. A name this site has no account for is printed
270
+ unlinked, because the file still said somebody wrote this.
271
+
272
+ **The person and only the person.** The site menu used to be printed here as
273
+ well, because the design had no header navigation; it is in the header on
274
+ every page now — see [Navigation](#navigation) — so the bio says nothing about
275
+ the site. The note and the links used to be behind a `bioProfile` switch that
276
+ an author archive set and an entry did not, on the grounds that the page
277
+ footer already carried the site's identity links. It carries nobody's, so the
278
+ switch is gone and the card is the same wherever it appears: at the top of
279
+ somebody's archive, and under each of their posts.
255
280
 
256
281
  `bioLead` is what the line opens with, `Written by` unless a layout sets
257
282
  another; `layouts/author.njk` sets `Posts by`, because the card there heads
258
283
  somebody's writing rather than crediting one piece of it.
259
284
 
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
285
  **A site that used `partials/byline.njk`** — `{{ byline.line(author) }}` from
267
286
  an overridden layout — either includes this partial instead or writes the line
268
287
  itself. It was four lines of markup, and the object behind it has not changed:
@@ -363,17 +382,17 @@ saying how many documents matched, a `div.search-results` of
363
382
  indexed with `<meta name="robots" content="noindex">` in its `head` block.
364
383
 
365
384
  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
385
+ for, trimmed, and an empty string on the page before a search. Each entry in
386
+ `posts` carries a `snippet`: a few words of
369
387
  HTML around the match, escaped, with every matched word in `<mark>`. Print it
370
388
  with `safe`. The results are best match first rather than newest first, and
371
389
  only what the public site would serve is ever among them.
372
390
 
373
391
  `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.
392
+ so it needs no JavaScript and a search is a URL. This layout is the only thing
393
+ that includes it: the box used to be in the footer of every page as well, and
394
+ search is a menu item now — a `Search | /search/` line like any other. The
395
+ search page takes the path `/search/` ahead of any document permalinked there.
377
396
 
378
397
  ### The front page
379
398
 
@@ -393,8 +412,8 @@ It draws the page's own words and then what the site has been writing:
393
412
  3. `p.front-links`, a line of links to where the writing is. The posts page is
394
413
  linked by its own title when the site names one, and the search always is
395
414
  (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.
415
+ 4. The bio, under a rule, exactly as an entry ends: whoever the site's author
416
+ setting names, with their note and their own links.
398
417
 
399
418
  Two context keys are the front page's alone. `recentPosts` is the entries to
400
419
  list, in the same shape a listing's are: the posts of the current month when
@@ -691,7 +710,7 @@ Every template gets:
691
710
  | Key | What it holds |
692
711
  | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
693
712
  | `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). |
713
+ | `menus` | Every menu the site stores, by name, marked for this page: `menus.primary`, `menus.footer`, and any other name. See [Navigation](#navigation). |
695
714
  | `siteAuthor` | Who the page is by, as a profile. **Absent** when nobody matches. See [Bylines and author archives](#bylines-and-author-archives). |
696
715
  | `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
716
 
@@ -774,17 +793,18 @@ the `partials/byline.njk` it used to have.
774
793
 
775
794
  `layouts/author.njk` is that person's archive, at `/author/{username}/`, with
776
795
  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
796
+ `/author/{username}/feed/`. It is headed with their name and then the bio —
797
+ the avatar, the note and the `rel="me"` links, the same card that ends each of
798
+ their posts — and lists their published posts newest first. The source design has no author archive, so
780
799
  the heading is the CMS's own; the h-card under it is the one partial the theme
781
800
  has. A user with no profile still has one; they are called by their username.
782
801
 
783
802
  `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:
803
+ document, but who the page in front of the reader is by. It is what the bio and
804
+ any structured data a theme emits should both read, so that what a reader sees
805
+ and what a machine reads cannot drift apart. The footer does not read it at
806
+ all: the site's links are `menus.footer`, typed rather than borrowed from an
807
+ account. It resolves in this order:
788
808
 
789
809
  - the document's own `author`, on a post or a page that names one;
790
810
  - the person whose archive it is, on an author archive;
@@ -795,8 +815,7 @@ It is **absent** when none of those name anybody this site has. The site
795
815
  setting is read more strictly than a byline is: a byline prints the name a file
796
816
  gives whether or not somebody answers to it, but a site author with no profile
797
817
  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.
818
+ print and the key is not there. Write `{% if siteAuthor %}` around the bio.
800
819
 
801
820
  The URL is not only a page. Each user is an ActivityPub actor at that address,
802
821
  so it is the page a follower lands on when they click through from the
@@ -806,50 +825,126 @@ it.
806
825
 
807
826
  ## Navigation
808
827
 
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.
828
+ A site stores its menus by name in `content/_data/site.json`:
811
829
 
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.
830
+ ```json
831
+ {
832
+ "menus": {
833
+ "primary": [
834
+ { "label": "Home", "url": "/" },
835
+ { "label": "About", "url": "/about/" }
836
+ ],
837
+ "footer": [
838
+ { "label": "Colophon", "url": "/colophon/" },
839
+ { "label": "Mastodon", "url": "https://example.social/@me", "rel": "me" }
840
+ ]
841
+ }
842
+ }
843
+ ```
820
844
 
821
- The markup is the same either way, and a layout that overrides `header` or
822
- `footer` writes it the same way:
845
+ Every template gets them as `menus`, keyed by the same names, already in order
846
+ and already knowing which item is the page being looked at:
823
847
 
824
848
  ```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 %}
849
+ {% for item in menus.footer %}
850
+ <a href="{{ item.url | url }}">{{ item.label }}</a>
851
+ {% endfor %}
834
852
  ```
835
853
 
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.
854
+ That is the whole interface, so a theme renders a menu this one has never heard
855
+ of by looping over the name a site stored it under.
856
+
857
+ ### The areas a theme declares
858
+
859
+ `theme.json` says where a theme renders a menu, so the Navigation screen can
860
+ offer those places and nowhere else. This theme declares two:
861
+
862
+ ```json
863
+ {
864
+ "name": "Default",
865
+ "kind": "site",
866
+ "areas": [
867
+ { "name": "primary", "label": "Site menu" },
868
+ { "name": "footer", "label": "Footer links" }
869
+ ]
870
+ }
871
+ ```
872
+
873
+ `name` is the key under `menus`, in `site.json` and on the context. `label` is
874
+ what a person choosing an area reads; leave it out and it falls back to the
875
+ name. Declare a third area and a site can fill it in; declare none and the
876
+ theme renders no menu.
877
+
878
+ The declaration is for the screen, not for the render. Every menu the site
879
+ stores is on the context whether or not a theme declared an area for it — a
880
+ menu nothing loops over is rendered nowhere and kept, which is what lets
881
+ somebody write the menu a theme will use before switching to that theme. An
882
+ `areas` that is missing, or that will not read, leaves the theme with none
883
+ rather than failing to load it.
841
884
 
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.
885
+ ### One item
846
886
 
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`.
887
+ Each item is `{ label, url, current }`, plus `rel` on a link that carries `rel`
888
+ values. `url` is a site-root path or an absolute URL for somewhere else, so put
889
+ it through the `url` filter and a site served from a subdirectory still links
890
+ correctly. `current` is true for the item whose path is the one being rendered,
891
+ comparing without the trailing slash; an item pointing off the site is never
892
+ current. `rel` is one string holding every value the line carried, already
893
+ lower case and each said once — print it as the whole attribute,
894
+ `rel="{{ item.rel }}"`, rather than building one of your own. `me` is the value
895
+ most menus have, for the IndieWeb's identity check: Mastodon verifies a link on
896
+ a profile by looking for a `rel="me"` link back, so a footer link to a profile
897
+ marked `me` is what makes the tick appear.
898
+
899
+ ### Where this theme prints them
900
+
901
+ `partials/menu.njk` holds the markup, as one macro:
902
+
903
+ ```njk
904
+ {% import "partials/menu.njk" as nav %}
905
+ {{ nav.list(menus.footer, "Footer") }}
906
+ ```
907
+
908
+ The second argument is the `aria-label`, which is what tells a reader on a
909
+ screen reader which of a page's menus this one is. Nothing is printed for a
910
+ menu with nothing in it.
911
+
912
+ `menus.primary` is printed once, in `header.global-header`, on every page
913
+ (TASK-105). At the root, where the header is the site title with the tagline
914
+ under it, the menu is a line of its own under the tagline; everywhere else,
915
+ where the header is the small link home, the stylesheet lays the header out as
916
+ one line and the menu sits beside it. Nothing decides between two places any
917
+ more: a reader looks in the same spot whatever they are reading, and a layout
918
+ that overrides the `content` block cannot lose the navigation by not printing
919
+ a bio.
920
+
921
+ `menus.footer` is printed in the `footer` block of `layouts/base.njk`, under
922
+ the copyright line, and is the whole of what the footer links — a feed, a
923
+ colophon, a webring, a profile marked `me`. The footer reads nothing off an
924
+ account: it used to print one `rel="me"` link per entry of `siteAuthor.links`,
925
+ which meant one nominated user's links stood for the site and nobody else's
926
+ appeared at all.
927
+
928
+ Every menu reads the same: a link in one is the colour, the underline and the
929
+ size of a link in the page's prose, in the sans heading font a `nav` is set in
930
+ (TASK-111). A menu is navigation somebody is meant to use rather than a
931
+ footnote, so none of them is set smaller than the words around it, and the same
932
+ goes for the `rel="me"` links `partials/bio.njk` prints under a post. The two
933
+ links that read as plain text instead are the site title and
934
+ `a.header-link-home`, which are the page saying where it is rather than
935
+ somewhere to go, and the stylesheet names those two rather than reaching for
936
+ every link that happens to sit in a `<header>`.
937
+
938
+ `menus.primary` is the whole of the site menu, in the order the Navigation
939
+ screen names it. Both areas are declared in this theme's `theme.json`, which is
940
+ what puts them on `/admin/navigation` with those labels; a theme that renders a
941
+ third menu declares a third area. A page cannot put itself in a menu; a page
942
+ that should be linked — including the one a site serves as its front page,
943
+ typed `Home | /` — is typed into the box there.
944
+
945
+ Because the menus live in `site.json`, an Eleventy build of the same content
946
+ renders the same ones; `docs/eleventy.config.example.js` builds them as
947
+ `collections.menus`.
853
948
 
854
949
  ## Feeds
855
950
 
@@ -1084,7 +1179,7 @@ that has CSS for the WordPress theme can bring it.
1084
1179
  | Key | What it holds |
1085
1180
  | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1086
1181
  | `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. |
1182
+ | `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
1183
  | `post` | The post's slug, for the hidden field. |
1089
1184
  | `loaded` | When this form was rendered, in epoch milliseconds, for the hidden field. A submission that comes back too fast is refused. |
1090
1185
  | `values` | What is in the fields: empty on a fresh form, what was typed on a refused one. |
@@ -1098,6 +1193,46 @@ technology, and give it `tabindex="-1"` and `autocomplete="off"`. A submission
1098
1193
  that filled it is dropped. **Do not** remove it from a replacement partial —
1099
1194
  it is one of three things standing between the site and a spam queue.
1100
1195
 
1196
+ ### When somebody is signed in
1197
+
1198
+ Two more keys are on `commentForm` when — and only when — the request carries a
1199
+ valid session for this site's admin:
1200
+
1201
+ | Key | What it holds |
1202
+ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
1203
+ | `signedInAs` | Who the comment will be posted as: `name`, which is their display name or their username, and `url`, which is their author archive. |
1204
+ | `csrfToken` | The value for a hidden `fields.csrf`, without which the submission is refused. |
1205
+
1206
+ **A replacement partial has to draw both forms**, because the site draws this
1207
+ one for its own author and the stranger's one for everybody else:
1208
+
1209
+ ```njk
1210
+ {% if commentForm.signedInAs %}
1211
+ <input type="hidden" name="{{ commentForm.fields.csrf }}" value="{{ commentForm.csrfToken }}" />
1212
+ <p class="comment-signed-in">
1213
+ Commenting as <a href="{{ commentForm.signedInAs.url }}">{{ commentForm.signedInAs.name }}</a>.
1214
+ </p>
1215
+ {% else %}
1216
+ {# fields.loaded, fields.trap, and the name, email and website boxes #}
1217
+ {% endif %}
1218
+ ```
1219
+
1220
+ The signed-in branch renders **no** name, email or website box, **no**
1221
+ `fields.trap` and **no** `fields.loaded`: the site already knows all three
1222
+ answers, and a session is better evidence that this is a person than a honeypot
1223
+ or a stopwatch. It renders `fields.csrf` instead, because this is the one form
1224
+ on the public site that acts on somebody's behalf — without the token, a page
1225
+ on another site could make them post under their own name. The comment box, the
1226
+ reply-to field and the notify box are the same in both branches.
1227
+
1228
+ The account's email address is **never** on the context. The comment is stored
1229
+ with it, exactly as a stranger's is, and it is no more printable than theirs.
1230
+
1231
+ A page carrying this form is one reader's page rather than the post, so the CMS
1232
+ answers it `Cache-Control: private, no-store` and with no ETag. A theme does
1233
+ not have to do anything about that, but it is why printing somebody's name into
1234
+ a page is safe here.
1235
+
1101
1236
  Two more keys travel beside it, both from the URL rather than from the post:
1102
1237
 
1103
1238
  | 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 %}