@geekity/cms 0.9.0 → 0.11.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 (190) hide show
  1. package/README.md +86 -24
  2. package/admin/components/admin-bar.njk +26 -0
  3. package/admin/components/fields.njk +55 -12
  4. package/admin/components/public-admin-bar.njk +41 -0
  5. package/admin/layouts/base.njk +6 -1
  6. package/admin/layouts/settings-page.njk +7 -2
  7. package/admin/layouts/shell.njk +9 -20
  8. package/admin/pages/account/forgot.njk +1 -1
  9. package/admin/pages/account/login.njk +1 -1
  10. package/admin/pages/account/reset.njk +4 -2
  11. package/admin/pages/account/setup.njk +1 -1
  12. package/admin/pages/dashboard/home.njk +1 -0
  13. package/admin/pages/documents/list.njk +1 -0
  14. package/admin/pages/documents/taxonomy.njk +1 -0
  15. package/admin/pages/federation/followers.njk +3 -0
  16. package/admin/pages/federation/settings.njk +5 -0
  17. package/admin/pages/media/library.njk +39 -0
  18. package/admin/pages/navigation/menus.njk +6 -1
  19. package/admin/pages/settings/discussion.njk +7 -0
  20. package/admin/pages/settings/email.njk +10 -0
  21. package/admin/pages/settings/general.njk +7 -0
  22. package/admin/pages/settings/permalinks.njk +6 -0
  23. package/admin/pages/settings/reading.njk +7 -0
  24. package/admin/pages/tools/content-index.njk +1 -0
  25. package/admin/pages/tools/personal-data.njk +5 -0
  26. package/admin/pages/users/edit.njk +7 -5
  27. package/admin/pages/users/list.njk +1 -0
  28. package/admin/pages/users/new.njk +5 -1
  29. package/admin/static/admin-bar.css +153 -0
  30. package/admin/static/admin.css +95 -96
  31. package/dist/admin/comments.js +2 -1
  32. package/dist/admin/comments.js.map +1 -1
  33. package/dist/admin/documents.d.ts.map +1 -1
  34. package/dist/admin/documents.js +18 -0
  35. package/dist/admin/documents.js.map +1 -1
  36. package/dist/admin/index.d.ts +1 -1
  37. package/dist/admin/index.d.ts.map +1 -1
  38. package/dist/admin/index.js.map +1 -1
  39. package/dist/admin/media.d.ts +13 -2
  40. package/dist/admin/media.d.ts.map +1 -1
  41. package/dist/admin/media.js +58 -3
  42. package/dist/admin/media.js.map +1 -1
  43. package/dist/admin/personal-data.d.ts.map +1 -1
  44. package/dist/admin/personal-data.js +5 -1
  45. package/dist/admin/personal-data.js.map +1 -1
  46. package/dist/admin/preview.d.ts.map +1 -1
  47. package/dist/admin/preview.js +1 -1
  48. package/dist/admin/preview.js.map +1 -1
  49. package/dist/admin/routes.d.ts.map +1 -1
  50. package/dist/admin/routes.js +2 -1
  51. package/dist/admin/routes.js.map +1 -1
  52. package/dist/admin/store.d.ts +36 -1
  53. package/dist/admin/store.d.ts.map +1 -1
  54. package/dist/admin/store.js +63 -1
  55. package/dist/admin/store.js.map +1 -1
  56. package/dist/admin/templates.d.ts +1 -0
  57. package/dist/admin/templates.d.ts.map +1 -1
  58. package/dist/admin/templates.js +1 -0
  59. package/dist/admin/templates.js.map +1 -1
  60. package/dist/admin/tools.d.ts +2 -0
  61. package/dist/admin/tools.d.ts.map +1 -1
  62. package/dist/admin/tools.js +3 -2
  63. package/dist/admin/tools.js.map +1 -1
  64. package/dist/admin/uploads.d.ts +5 -2
  65. package/dist/admin/uploads.d.ts.map +1 -1
  66. package/dist/admin/uploads.js +6 -2
  67. package/dist/admin/uploads.js.map +1 -1
  68. package/dist/comments/index.d.ts +2 -2
  69. package/dist/comments/index.d.ts.map +1 -1
  70. package/dist/comments/index.js +1 -1
  71. package/dist/comments/index.js.map +1 -1
  72. package/dist/comments/records.d.ts +43 -2
  73. package/dist/comments/records.d.ts.map +1 -1
  74. package/dist/comments/records.js +201 -54
  75. package/dist/comments/records.js.map +1 -1
  76. package/dist/comments/routes.js +1 -1
  77. package/dist/comments/routes.js.map +1 -1
  78. package/dist/comments/viewer.d.ts +13 -0
  79. package/dist/comments/viewer.d.ts.map +1 -1
  80. package/dist/comments/viewer.js +13 -1
  81. package/dist/comments/viewer.js.map +1 -1
  82. package/dist/config.d.ts +21 -0
  83. package/dist/config.d.ts.map +1 -1
  84. package/dist/config.js +2 -0
  85. package/dist/config.js.map +1 -1
  86. package/dist/env.d.ts +19 -0
  87. package/dist/env.d.ts.map +1 -1
  88. package/dist/federation/article.d.ts.map +1 -1
  89. package/dist/federation/article.js +37 -1
  90. package/dist/federation/article.js.map +1 -1
  91. package/dist/federation/delivery.d.ts +3 -1
  92. package/dist/federation/delivery.d.ts.map +1 -1
  93. package/dist/federation/delivery.js +6 -1
  94. package/dist/federation/delivery.js.map +1 -1
  95. package/dist/federation/federation.d.ts +3 -0
  96. package/dist/federation/federation.d.ts.map +1 -1
  97. package/dist/federation/federation.js.map +1 -1
  98. package/dist/federation/inbox.d.ts +7 -5
  99. package/dist/federation/inbox.d.ts.map +1 -1
  100. package/dist/federation/inbox.js +15 -32
  101. package/dist/federation/inbox.js.map +1 -1
  102. package/dist/federation/index.d.ts +2 -0
  103. package/dist/federation/index.d.ts.map +1 -1
  104. package/dist/federation/index.js +1 -0
  105. package/dist/federation/index.js.map +1 -1
  106. package/dist/federation/mount.js +6 -1
  107. package/dist/federation/mount.js.map +1 -1
  108. package/dist/federation/profiles.d.ts +95 -0
  109. package/dist/federation/profiles.d.ts.map +1 -0
  110. package/dist/federation/profiles.js +171 -0
  111. package/dist/federation/profiles.js.map +1 -0
  112. package/dist/federation/relays.d.ts +3 -1
  113. package/dist/federation/relays.d.ts.map +1 -1
  114. package/dist/federation/relays.js +6 -1
  115. package/dist/federation/relays.js.map +1 -1
  116. package/dist/federation/replies.d.ts +13 -4
  117. package/dist/federation/replies.d.ts.map +1 -1
  118. package/dist/federation/replies.js +25 -6
  119. package/dist/federation/replies.js.map +1 -1
  120. package/dist/images/alt-text.d.ts +51 -0
  121. package/dist/images/alt-text.d.ts.map +1 -0
  122. package/dist/images/alt-text.js +113 -0
  123. package/dist/images/alt-text.js.map +1 -0
  124. package/dist/images/icons.d.ts.map +1 -1
  125. package/dist/images/icons.js +3 -2
  126. package/dist/images/icons.js.map +1 -1
  127. package/dist/images/index.d.ts +2 -2
  128. package/dist/images/index.d.ts.map +1 -1
  129. package/dist/images/markup.d.ts +26 -4
  130. package/dist/images/markup.d.ts.map +1 -1
  131. package/dist/images/markup.js +36 -15
  132. package/dist/images/markup.js.map +1 -1
  133. package/dist/images/paths.d.ts +11 -0
  134. package/dist/images/paths.d.ts.map +1 -1
  135. package/dist/images/paths.js +17 -1
  136. package/dist/images/paths.js.map +1 -1
  137. package/dist/images/variants.d.ts +13 -3
  138. package/dist/images/variants.d.ts.map +1 -1
  139. package/dist/images/variants.js +13 -11
  140. package/dist/images/variants.js.map +1 -1
  141. package/dist/index.d.ts +11 -5
  142. package/dist/index.d.ts.map +1 -1
  143. package/dist/index.js +50 -11
  144. package/dist/index.js.map +1 -1
  145. package/dist/notifications/routes.js +1 -1
  146. package/dist/notifications/routes.js.map +1 -1
  147. package/dist/privacy/erase.d.ts.map +1 -1
  148. package/dist/privacy/erase.js +2 -3
  149. package/dist/privacy/erase.js.map +1 -1
  150. package/dist/privacy/retention.js +1 -1
  151. package/dist/privacy/retention.js.map +1 -1
  152. package/dist/web/admin-bar.d.ts +21 -0
  153. package/dist/web/admin-bar.d.ts.map +1 -0
  154. package/dist/web/admin-bar.js +145 -0
  155. package/dist/web/admin-bar.js.map +1 -0
  156. package/dist/web/assets.d.ts +29 -1
  157. package/dist/web/assets.d.ts.map +1 -1
  158. package/dist/web/assets.js +48 -3
  159. package/dist/web/assets.js.map +1 -1
  160. package/dist/web/compression.d.ts +19 -0
  161. package/dist/web/compression.d.ts.map +1 -0
  162. package/dist/web/compression.js +160 -0
  163. package/dist/web/compression.js.map +1 -0
  164. package/dist/web/context.d.ts +5 -1
  165. package/dist/web/context.d.ts.map +1 -1
  166. package/dist/web/context.js +5 -2
  167. package/dist/web/context.js.map +1 -1
  168. package/dist/web/conversation.d.ts.map +1 -1
  169. package/dist/web/conversation.js +13 -12
  170. package/dist/web/conversation.js.map +1 -1
  171. package/dist/web/render.d.ts +27 -0
  172. package/dist/web/render.d.ts.map +1 -1
  173. package/dist/web/render.js +77 -38
  174. package/dist/web/render.js.map +1 -1
  175. package/dist/web/routes.d.ts.map +1 -1
  176. package/dist/web/routes.js +63 -28
  177. package/dist/web/routes.js.map +1 -1
  178. package/dist/web/templates.d.ts.map +1 -1
  179. package/dist/web/templates.js +9 -2
  180. package/dist/web/templates.js.map +1 -1
  181. package/dist/webmention/service.d.ts.map +1 -1
  182. package/dist/webmention/service.js +5 -1
  183. package/dist/webmention/service.js.map +1 -1
  184. package/package.json +8 -8
  185. package/themes/default/README.md +176 -19
  186. package/themes/default/layouts/base.njk +51 -10
  187. package/themes/default/layouts/post.njk +10 -1
  188. package/themes/default/partials/comment-form.njk +33 -14
  189. package/themes/default/partials/contact-form.njk +32 -14
  190. package/themes/default/static/style.css +109 -24
@@ -60,9 +60,11 @@
60
60
  <p class="admin-empty">Nothing has been uploaded yet.</p>
61
61
  {% else %}
62
62
  <table class="admin-list admin-media">
63
+ <caption class="admin-visually-hidden">{{ heading }}</caption>
63
64
  <thead>
64
65
  <tr>
65
66
  <th scope="col">File</th>
67
+ <th scope="col">Alt text</th>
66
68
  <th scope="col">Uploaded</th>
67
69
  <th scope="col">Size</th>
68
70
  <th scope="col">Link</th>
@@ -83,6 +85,43 @@
83
85
  {% endif %}
84
86
  <a href="{{ file.url }}">{{ file.path }}</a>
85
87
  </td>
88
+ <td class="admin-media-alt">
89
+ {% if file.image %}
90
+ <form method="post" action="{{ altUrl }}">
91
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
92
+ <input type="hidden" name="{{ fields.path }}" value="{{ file.path }}" />
93
+ <input type="hidden" name="{{ fields.page }}" value="{{ page }}" />
94
+ <label class="admin-visually-hidden" for="media-alt-{{ loop.index }}">
95
+ Alt text for {{ file.name }}
96
+ </label>
97
+ <input
98
+ id="media-alt-{{ loop.index }}"
99
+ name="{{ fields.alt }}"
100
+ type="text"
101
+ value="{% if file.alt and file.alt.kind == 'described' %}{{ file.alt.text }}{% endif %}"
102
+ aria-describedby="media-alt-state-{{ loop.index }}"
103
+ />
104
+ <span class="admin-check">
105
+ <input
106
+ id="media-decorative-{{ loop.index }}"
107
+ name="{{ fields.decorative }}"
108
+ type="checkbox"
109
+ value="1"
110
+ {% if file.alt and file.alt.kind == 'decorative' %}checked{% endif %}
111
+ />
112
+ <label for="media-decorative-{{ loop.index }}">Decorative</label>
113
+ </span>
114
+ <button type="submit">Save</button>
115
+ <span id="media-alt-state-{{ loop.index }}" class="admin-status{% if not file.alt %} admin-status-missing{% endif %}">
116
+ {%- if not file.alt %}Missing: describe it, or mark it decorative
117
+ {%- elif file.alt.kind == 'decorative' %}Decorative: inserted with an empty alt
118
+ {%- else %}Offered when it is inserted{% endif -%}
119
+ </span>
120
+ </form>
121
+ {% else %}
122
+ <span class="admin-status">Not an image</span>
123
+ {% endif %}
124
+ </td>
86
125
  <td>{{ file.modified | date('html') }}</td>
87
126
  <td>{{ file.bytes }}</td>
88
127
  <td class="admin-media-links">
@@ -60,7 +60,12 @@
60
60
  <h1>{{ heading }}</h1>
61
61
 
62
62
  {% if hasProblems %}
63
- <p class="admin-field-error">Nothing was saved. See below.</p>
63
+ {% call field.summary('Nothing was saved. Fix the box marked below and try again.') %}
64
+ {% for menu in areas.concat(others) %}
65
+ {{ field.problem('menu-' + menu.name, menu.problem) }}
66
+ {% endfor %}
67
+ {{ field.problem('menu-name', nameProblem) }}
68
+ {% endcall %}
64
69
  {% endif %}
65
70
 
66
71
  <h2>Where {{ themeName }} puts a menu</h2>
@@ -13,6 +13,13 @@
13
13
  {%- endif -%}
14
14
  {% endmacro %}
15
15
 
16
+ {% block settingsProblems %}
17
+ {{ field.problem('settings-comments-close', problems.commentsCloseAfterDays) }}
18
+ {{ field.problem('settings-comment-email-retention', problems.commentEmailRetentionDays) }}
19
+ {{ field.problem('settings-address-hash-retention', problems.addressHashRetentionDays) }}
20
+ {{ field.problem('settings-contact-message-retention', problems.contactMessageRetentionDays) }}
21
+ {% endblock %}
22
+
16
23
  {% block settingsFields %}
17
24
  <h2>Comments</h2>
18
25
 
@@ -3,6 +3,16 @@
3
3
  {% extends "layouts/settings-page.njk" %}
4
4
  {% import "components/fields.njk" as field %}
5
5
 
6
+ {% block settingsProblems %}
7
+ {{ field.problem('settings-mail-provider', problems.mailProvider) }}
8
+ {{ field.problem('settings-mail-from-address', problems.mailFromAddress) }}
9
+ {{ field.problem('settings-mail-reply-to', problems.mailReplyTo) }}
10
+ {{ field.problem('settings-contact-email', problems.contactEmail) }}
11
+ {{ field.problem('settings-security-contacts', problems.securityContacts) }}
12
+ {{ field.problem('settings-security-policy', problems.securityPolicy) }}
13
+ {{ field.problem('settings-security-languages', problems.securityLanguages) }}
14
+ {% endblock %}
15
+
6
16
  {% block settingsFields %}
7
17
  {% set providerHint %}
8
18
  The key or password each one needs is a credential and is saved below,
@@ -2,6 +2,13 @@
2
2
  {% extends "layouts/settings-page.njk" %}
3
3
  {% import "components/fields.njk" as field %}
4
4
 
5
+ {% block settingsProblems %}
6
+ {{ field.problem('settings-title', problems.title) }}
7
+ {{ field.problem('settings-base-url', problems.baseUrl) }}
8
+ {{ field.problem('settings-timezone', problems.timezone) }}
9
+ {{ field.problem('settings-language', problems.language) }}
10
+ {% endblock %}
11
+
5
12
  {% block settingsFields %}
6
13
  {{ field.text('settings-title', fields.title, 'Title', form.title,
7
14
  error=problems.title, required=true, autofocus=true) }}
@@ -3,6 +3,11 @@
3
3
  {% extends "layouts/settings-page.njk" %}
4
4
  {% import "components/fields.njk" as field %}
5
5
 
6
+ {% block settingsProblems %}
7
+ {{ field.problem('settings-tag-base', problems.tagBase) }}
8
+ {{ field.problem('settings-category-base', problems.categoryBase) }}
9
+ {% endblock %}
10
+
6
11
  {% block settingsFields %}
7
12
  {% set tagBaseHint %}The tag archives live at <code>/{{ form.tagBase }}/{tag}/</code>. One path segment, no slashes.{% endset %}
8
13
  {{ field.text('settings-tag-base', fields.tagBase, 'Tag base', form.tagBase,
@@ -22,6 +27,7 @@
22
27
 
23
28
  {% if taxonomyRedirects.length %}
24
29
  <table class="admin-list">
30
+ <caption class="admin-visually-hidden">Archive redirects</caption>
25
31
  <thead>
26
32
  <tr><th scope="col">Taxonomy</th><th scope="col">Was</th><th scope="col">Is now</th></tr>
27
33
  </thead>
@@ -4,6 +4,13 @@
4
4
  {% extends "layouts/settings-page.njk" %}
5
5
  {% import "components/fields.njk" as field %}
6
6
 
7
+ {% block settingsProblems %}
8
+ {{ field.problem('settings-homepage', problems.homepage) }}
9
+ {{ field.problem('settings-posts-page', problems.postsPage) }}
10
+ {{ field.problem('settings-posts-per-page', problems.postsPerPage) }}
11
+ {{ field.problem('settings-notify-server', problems.notifyServer) }}
12
+ {% endblock %}
13
+
7
14
  {% block settingsFields %}
8
15
  {# WordPress's "Your homepage displays": the empty option is Your latest
9
16
  posts, so the choice and the page it needs are one control rather than a
@@ -20,6 +20,7 @@
20
20
  </p>
21
21
 
22
22
  <table class="admin-list">
23
+ <caption class="admin-visually-hidden">What the index holds</caption>
23
24
  <thead>
24
25
  <tr>
25
26
  <th scope="col">What the index holds</th>
@@ -49,6 +49,11 @@
49
49
  {% endif %}
50
50
  </form>
51
51
  {% else %}
52
+ {% if problem %}
53
+ {% call field.summary('Nothing was looked up.') %}
54
+ {{ field.problem('personal-data-email', problem) }}
55
+ {% endcall %}
56
+ {% endif %}
52
57
  <form class="admin-settings" method="post" action="{{ actionUrl }}">
53
58
  <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
54
59
  {{ field.text('personal-data-email', fields.email, 'Email address', email,
@@ -80,10 +80,9 @@
80
80
  </p>
81
81
 
82
82
  {% if profileProblems.links %}
83
- <p class="admin-error">
84
- Nothing was saved. Fix the Links box below and save again; every other
85
- box here still holds what you typed.
86
- </p>
83
+ {% call field.summary('Nothing was saved. Fix the Links box below and save again; every other box here still holds what you typed.') %}
84
+ {{ field.problem('profile-links', profileProblems.links) }}
85
+ {% endcall %}
87
86
  {% endif %}
88
87
 
89
88
  {{ field.text('profile-display-name', fields.displayName, 'Display name',
@@ -219,7 +218,10 @@
219
218
  <h2>Change your password</h2>
220
219
 
221
220
  {% if passwordProblems.currentPassword or passwordProblems.newPassword %}
222
- <p class="admin-error">Your password was not changed. Fix the fields marked below and try again.</p>
221
+ {% call field.summary('Your password was not changed. Fix the fields marked below and try again.') %}
222
+ {{ field.problem('current-password', passwordProblems.currentPassword) }}
223
+ {{ field.problem('new-password', passwordProblems.newPassword) }}
224
+ {% endcall %}
223
225
  {% endif %}
224
226
 
225
227
  {{ field.password('current-password', fields.currentPassword, 'Current password',
@@ -13,6 +13,7 @@
13
13
  <h1>Users <a class="admin-button admin-button-small" href="{{ addUserUrl }}">Add new</a></h1>
14
14
 
15
15
  <table class="admin-list">
16
+ <caption class="admin-visually-hidden">Users</caption>
16
17
  <thead>
17
18
  <tr>
18
19
  <th scope="col">Username</th>
@@ -16,7 +16,11 @@
16
16
  <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
17
17
 
18
18
  {% if addProblems.username or addProblems.password or addProblems.email %}
19
- <p class="admin-error">Nobody was added. Fix the fields marked below and try again.</p>
19
+ {% call field.summary('Nobody was added. Fix the fields marked below and try again.') %}
20
+ {{ field.problem('add-username', addProblems.username) }}
21
+ {{ field.problem('add-email', addProblems.email) }}
22
+ {{ field.problem('add-password', addProblems.password) }}
23
+ {% endcall %}
20
24
  {% endif %}
21
25
 
22
26
  {{ field.text('add-username', fields.username, 'Username', addForm.username,
@@ -0,0 +1,153 @@
1
+ /*
2
+ * The admin bar: the dark strip across the top of every admin screen, and of
3
+ * every public page a signed-in user reads (TASK-183).
4
+ *
5
+ * Self-contained, because on the public site it is inlined into the bar's
6
+ * shadow root, under a host reset to `all: initial`: nothing here may lean on
7
+ * admin.css or on whatever theme the site wears. Sizes are in pixels for the
8
+ * same reason, since a `rem` is the theme's root font size. The colours are
9
+ * declared on `:host` there and on `:root` in the admin, where admin.css
10
+ * reads them too.
11
+ */
12
+
13
+ :root,
14
+ :host {
15
+ --admin-bar: #1d2327;
16
+ --admin-nav: #2c3338;
17
+ --admin-nav-text: #f0f0f1;
18
+ --admin-focus-on-dark: #fff;
19
+ }
20
+
21
+ .admin-bar,
22
+ .admin-bar * {
23
+ box-sizing: border-box;
24
+ }
25
+
26
+ /* Site name on the left, the shortcuts beside it, who you are on the right. */
27
+
28
+ .admin-bar {
29
+ display: flex;
30
+ flex-wrap: wrap;
31
+ align-items: center;
32
+ gap: 0 16px;
33
+ padding: 0 16px;
34
+ min-height: 40px;
35
+ background: var(--admin-bar);
36
+ color: var(--admin-nav-text);
37
+ font:
38
+ 14px/40px -apple-system,
39
+ BlinkMacSystemFont,
40
+ 'Segoe UI',
41
+ Roboto,
42
+ sans-serif;
43
+ anchor-name: --admin-bar;
44
+ }
45
+
46
+ .admin-bar a {
47
+ color: var(--admin-nav-text);
48
+ text-decoration: none;
49
+ white-space: nowrap;
50
+ }
51
+
52
+ .admin-bar a:hover,
53
+ .admin-bar a:focus {
54
+ color: #72aee6;
55
+ }
56
+
57
+ /* The ring where the keyboard is, white on the dark bar. Here rather than in
58
+ admin.css because on the public site this is the only stylesheet the bar
59
+ has. */
60
+
61
+ .admin-bar :focus-visible {
62
+ outline: 2px solid var(--admin-focus-on-dark);
63
+ outline-offset: 2px;
64
+ }
65
+
66
+ .admin-bar-site {
67
+ font-weight: 600;
68
+ }
69
+
70
+ .admin-bar-spacer {
71
+ flex: 1;
72
+ }
73
+
74
+ .admin-bar-account {
75
+ padding: 0;
76
+ border: 0;
77
+ background: none;
78
+ color: var(--admin-nav-text);
79
+ font: inherit;
80
+ text-align: right;
81
+ cursor: pointer;
82
+ anchor-name: --admin-account;
83
+ }
84
+
85
+ /* On a phone the bar wraps, and the greeting keeps to the right of its line
86
+ so the menu under it opens inside the screen. */
87
+
88
+ .admin-bar-spacer + .admin-bar-account {
89
+ margin-left: auto;
90
+ }
91
+
92
+ .admin-bar-account:hover,
93
+ .admin-bar-account:focus-visible,
94
+ .admin-bar-account:popover-open {
95
+ color: #72aee6;
96
+ }
97
+
98
+ .admin-bar-account::after {
99
+ content: ' \25BE' / '';
100
+ }
101
+
102
+ /* The menu is in the top layer, so it is placed rather than laid out: under
103
+ the greeting where anchor positioning exists, at the bar's right edge
104
+ where it does not. */
105
+
106
+ .admin-account-menu {
107
+ inset: 40px 0 auto auto;
108
+ margin: 0;
109
+ min-width: 160px;
110
+ padding: 4px 0;
111
+ border: 0;
112
+ background: var(--admin-nav);
113
+ color: var(--admin-nav-text);
114
+ line-height: 1.6;
115
+ box-shadow: 0 3px 5px rgb(0 0 0 / 20%);
116
+ }
117
+
118
+ @supports (top: anchor(--admin-bar bottom)) {
119
+ .admin-account-menu {
120
+ inset: anchor(--admin-bar bottom) anchor(--admin-account right) auto auto;
121
+ }
122
+ }
123
+
124
+ .admin-account-menu a,
125
+ .admin-account-menu button[type='submit'] {
126
+ display: block;
127
+ box-sizing: border-box;
128
+ width: 100%;
129
+ margin: 0;
130
+ padding: 8px 16px;
131
+ border: 0;
132
+ border-radius: 0;
133
+ background: none;
134
+ color: var(--admin-nav-text);
135
+ font: inherit;
136
+ text-align: left;
137
+ text-decoration: none;
138
+ cursor: pointer;
139
+ }
140
+
141
+ /* A menu entry fills the menu's width, so its ring is drawn inside it. */
142
+
143
+ .admin-bar .admin-account-menu :focus-visible {
144
+ outline-offset: -2px;
145
+ }
146
+
147
+ .admin-account-menu a:hover,
148
+ .admin-account-menu a:focus-visible,
149
+ .admin-account-menu button[type='submit']:hover,
150
+ .admin-account-menu button[type='submit']:focus-visible {
151
+ background: var(--admin-bar);
152
+ color: #72aee6;
153
+ }
@@ -8,10 +8,10 @@
8
8
  */
9
9
 
10
10
  :root {
11
- --admin-bar: #1d2327;
12
- --admin-nav: #2c3338;
13
- --admin-nav-text: #f0f0f1;
11
+ /* --admin-bar, --admin-nav and --admin-nav-text are in admin-bar.css. */
14
12
  --admin-nav-current: #2271b1;
13
+ --admin-nav-open: #1d2327;
14
+ --admin-nav-children: #32373c;
15
15
  --admin-field: #f0f0f1;
16
16
  --admin-surface: #fff;
17
17
  --admin-border: #c3c4c7;
@@ -20,7 +20,12 @@
20
20
  --admin-link: #2271b1;
21
21
  --admin-notice: #00a32a;
22
22
  --admin-error: #d63638;
23
+ --admin-warning: #dba617;
23
24
  --admin-unread: #f0f6fc;
25
+ /* The focus ring on the light field and surfaces; --admin-focus-on-dark,
26
+ for the bar and the menu column, is in admin-bar.css. Both are held to 3:1
27
+ against what they are drawn on by src/admin/keyboard.test.ts. */
28
+ --admin-focus: #2271b1;
24
29
  --admin-nav-width: 11rem;
25
30
  }
26
31
 
@@ -44,103 +49,33 @@ a {
44
49
  color: var(--admin-link);
45
50
  }
46
51
 
47
- /* The top bar: site name on the left, who you are on the right. */
52
+ /* Every control says where the keyboard is, in one colour that holds 3:1
53
+ against the field and the surfaces, rather than the browser's own ring,
54
+ which is faint on grey. Set off by 2px so it is drawn on the background
55
+ around the control, whatever colour the control itself is. */
48
56
 
49
- .admin-bar {
50
- display: flex;
51
- align-items: center;
52
- gap: 1rem;
53
- padding: 0 1rem;
54
- min-height: 2.5rem;
55
- background: var(--admin-bar);
56
- color: var(--admin-nav-text);
57
- anchor-name: --admin-bar;
58
- }
59
-
60
- .admin-bar a {
61
- color: var(--admin-nav-text);
62
- text-decoration: none;
63
- white-space: nowrap;
57
+ :focus-visible {
58
+ outline: 2px solid var(--admin-focus);
59
+ outline-offset: 2px;
64
60
  }
65
61
 
66
- .admin-bar a:hover,
67
- .admin-bar a:focus {
68
- color: #72aee6;
69
- }
62
+ /* Hidden until a keyboard reaches it, then drawn over the bar. */
70
63
 
71
- .admin-bar-site {
64
+ .admin-skip-link {
65
+ position: absolute;
66
+ top: 0;
67
+ left: 0;
68
+ z-index: 1;
69
+ padding: 0.6rem 0.9rem;
70
+ background: var(--admin-surface);
71
+ color: var(--admin-link);
72
72
  font-weight: 600;
73
+ transform: translateY(-120%);
73
74
  }
74
75
 
75
- .admin-bar-spacer {
76
- flex: 1;
77
- }
78
-
79
- .admin-bar-account {
80
- padding: 0;
81
- border: 0;
82
- background: none;
83
- color: var(--admin-nav-text);
84
- font: inherit;
85
- text-align: right;
86
- cursor: pointer;
87
- anchor-name: --admin-account;
88
- }
89
-
90
- .admin-bar-account:hover,
91
- .admin-bar-account:focus-visible,
92
- .admin-bar-account:popover-open {
93
- color: #72aee6;
94
- }
95
-
96
- .admin-bar-account::after {
97
- content: ' \25BE' / '';
98
- }
99
-
100
- /* The menu is in the top layer, so it is placed rather than laid out: under
101
- the greeting where anchor positioning exists, at the bar's right edge
102
- where it does not. */
103
-
104
- .admin-account-menu {
105
- inset: 2.5rem 0 auto auto;
106
- margin: 0;
107
- min-width: 10rem;
108
- padding: 0.25rem 0;
109
- border: 0;
110
- background: var(--admin-nav);
111
- color: var(--admin-nav-text);
112
- box-shadow: 0 3px 5px rgb(0 0 0 / 20%);
113
- }
114
-
115
- @supports (top: anchor(--admin-bar bottom)) {
116
- .admin-account-menu {
117
- inset: anchor(--admin-bar bottom) anchor(--admin-account right) auto auto;
118
- }
119
- }
120
-
121
- .admin-account-menu a,
122
- .admin-account-menu button[type='submit'] {
123
- display: block;
124
- box-sizing: border-box;
125
- width: 100%;
126
- margin: 0;
127
- padding: 0.5rem 1rem;
128
- border: 0;
129
- border-radius: 0;
130
- background: none;
131
- color: var(--admin-nav-text);
132
- font: inherit;
133
- text-align: left;
134
- text-decoration: none;
135
- cursor: pointer;
136
- }
137
-
138
- .admin-account-menu a:hover,
139
- .admin-account-menu a:focus-visible,
140
- .admin-account-menu button[type='submit']:hover,
141
- .admin-account-menu button[type='submit']:focus-visible {
142
- background: var(--admin-bar);
143
- color: #72aee6;
76
+ .admin-skip-link:focus {
77
+ transform: none;
78
+ outline-offset: -4px;
144
79
  }
145
80
 
146
81
  /* The shell: navigation column beside the screen itself. */
@@ -170,11 +105,18 @@ a {
170
105
  }
171
106
 
172
107
  .admin-nav a:hover,
173
- .admin-nav a:focus {
108
+ .admin-nav a:focus-visible {
174
109
  background: var(--admin-bar);
175
110
  color: #72aee6;
176
111
  }
177
112
 
113
+ /* Each entry fills the column, so its ring is drawn inside it. */
114
+
115
+ .admin-nav a:focus-visible {
116
+ outline-color: var(--admin-focus-on-dark);
117
+ outline-offset: -2px;
118
+ }
119
+
178
120
  .admin-nav a[aria-current='page'] {
179
121
  background: var(--admin-nav-current);
180
122
  color: #fff;
@@ -193,12 +135,12 @@ a {
193
135
  }
194
136
 
195
137
  .admin-nav-open > .admin-nav-heading {
196
- background: #1d2327;
138
+ background: var(--admin-nav-open);
197
139
  color: #fff;
198
140
  }
199
141
 
200
142
  .admin-nav-children {
201
- background: #32373c;
143
+ background: var(--admin-nav-children);
202
144
  font-size: 0.95em;
203
145
  }
204
146
 
@@ -283,6 +225,12 @@ h2 {
283
225
  border-left-color: var(--admin-error);
284
226
  }
285
227
 
228
+ /* Done, but with something still to fix: a post published with an image
229
+ nobody has described. */
230
+ .admin-flash-warning {
231
+ border-left-color: var(--admin-warning);
232
+ }
233
+
286
234
  /* Panels, the box every group of things on the dashboard sits in. */
287
235
 
288
236
  .admin-panels {
@@ -336,6 +284,12 @@ h2 {
336
284
  border-collapse: collapse;
337
285
  }
338
286
 
287
+ .admin-list caption:not(.admin-visually-hidden) {
288
+ padding: 0.4rem 0;
289
+ text-align: left;
290
+ font-weight: 600;
291
+ }
292
+
339
293
  .admin-list th {
340
294
  text-align: left;
341
295
  color: var(--admin-muted);
@@ -430,6 +384,25 @@ button[type='submit']:hover,
430
384
  border-left: 4px solid var(--admin-error);
431
385
  }
432
386
 
387
+ /* A refused form's summary: what went wrong as its heading, and a link to
388
+ each field to put right. It takes focus on load, so the outline says
389
+ where the keyboard now is. */
390
+
391
+ .admin-error-summary h2 {
392
+ margin: 0;
393
+ font-size: 1em;
394
+ }
395
+
396
+ .admin-error-summary ul {
397
+ margin: 0.4rem 0 0;
398
+ padding-left: 1.25rem;
399
+ }
400
+
401
+ .admin-error-summary:focus {
402
+ outline: 2px solid var(--admin-error);
403
+ outline-offset: 2px;
404
+ }
405
+
433
406
  .admin-note {
434
407
  color: var(--admin-muted);
435
408
  }
@@ -994,6 +967,32 @@ button[type='submit']:hover,
994
967
  min-width: 18rem;
995
968
  }
996
969
 
970
+ /* An image's alt text, edited in its row. The state underneath says what
971
+ inserting it will do, and a missing one reads as a problem. */
972
+
973
+ .admin-media-alt {
974
+ min-width: 16rem;
975
+ }
976
+
977
+ .admin-media-alt form {
978
+ display: flex;
979
+ flex-wrap: wrap;
980
+ align-items: center;
981
+ gap: 0.3rem 0.6rem;
982
+ }
983
+
984
+ .admin-media-alt input[type='text'] {
985
+ flex: 1 1 100%;
986
+ }
987
+
988
+ .admin-media-alt .admin-status {
989
+ flex: 1 1 100%;
990
+ }
991
+
992
+ .admin-status-missing {
993
+ color: var(--admin-error);
994
+ }
995
+
997
996
  /* The copy control: a readonly field that works on its own, and a button that
998
997
  copy.js reveals. The field is monospaced because what is in it is a URL or a
999
998
  line of Markdown, and both are read character by character. */
@@ -208,7 +208,8 @@ function removedNote(redacted) {
208
208
  }
209
209
  /** The files and the index this request writes through. */
210
210
  function recordsOf(c) {
211
- return { admin: c.var.admin, contentDir: c.var.config.contentDir };
211
+ const { contentDir, dataDir } = c.var.config;
212
+ return { admin: c.var.admin, contentDir, dataDir };
212
213
  }
213
214
  /** One stored comment as the screen shows it. */
214
215
  function commentRow(c, comment) {