@geekity/cms 0.21.0 → 0.23.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 (302) hide show
  1. package/README.md +42 -4
  2. package/admin/components/admin-bar.njk +27 -6
  3. package/admin/components/alert.njk +28 -0
  4. package/admin/components/badge.njk +58 -0
  5. package/admin/components/button.njk +56 -0
  6. package/admin/components/card.njk +40 -0
  7. package/admin/components/collapse.njk +36 -0
  8. package/admin/components/copy.njk +20 -0
  9. package/admin/components/dropdown.njk +32 -0
  10. package/admin/components/fields.njk +81 -45
  11. package/admin/components/flash.njk +12 -5
  12. package/admin/components/menu.njk +51 -0
  13. package/admin/components/navbar.njk +23 -0
  14. package/admin/components/pagination.njk +47 -0
  15. package/admin/components/public-admin-bar.njk +4 -41
  16. package/admin/components/stat.njk +35 -0
  17. package/admin/components/table.njk +74 -0
  18. package/admin/components/tabs.njk +33 -0
  19. package/admin/components/theme-choice.njk +18 -0
  20. package/admin/layouts/base.njk +9 -16
  21. package/admin/layouts/settings-page.njk +15 -10
  22. package/admin/layouts/shell.njk +23 -31
  23. package/admin/pages/account/forgot.njk +39 -33
  24. package/admin/pages/account/login.njk +22 -16
  25. package/admin/pages/account/reset.njk +34 -25
  26. package/admin/pages/account/setup.njk +21 -15
  27. package/admin/pages/appearance/themes.njk +50 -44
  28. package/admin/pages/comments/all.njk +88 -106
  29. package/admin/pages/dashboard/home.njk +50 -62
  30. package/admin/pages/documents/conflict.njk +29 -24
  31. package/admin/pages/documents/editor.njk +173 -275
  32. package/admin/pages/documents/list.njk +60 -69
  33. package/admin/pages/documents/syndication.njk +98 -60
  34. package/admin/pages/documents/taxonomy.njk +36 -32
  35. package/admin/pages/error.njk +8 -5
  36. package/admin/pages/federation/followers.njk +91 -97
  37. package/admin/pages/federation/settings.njk +10 -11
  38. package/admin/pages/indieauth/consent.njk +60 -44
  39. package/admin/pages/indieauth/refused.njk +6 -3
  40. package/admin/pages/media/library.njk +86 -107
  41. package/admin/pages/messages/all.njk +71 -77
  42. package/admin/pages/navigation/menus.njk +90 -85
  43. package/admin/pages/placeholder.njk +13 -8
  44. package/admin/pages/settings/discussion.njk +30 -29
  45. package/admin/pages/settings/email.njk +26 -28
  46. package/admin/pages/settings/general.njk +10 -4
  47. package/admin/pages/settings/permalinks.njk +8 -9
  48. package/admin/pages/settings/privacy.njk +3 -3
  49. package/admin/pages/settings/reading.njk +3 -3
  50. package/admin/pages/tools/content-index.njk +79 -73
  51. package/admin/pages/tools/personal-data.njk +61 -55
  52. package/admin/pages/users/activity-entry.njk +12 -12
  53. package/admin/pages/users/activity.njk +13 -19
  54. package/admin/pages/users/apps.njk +112 -100
  55. package/admin/pages/users/edit.njk +65 -58
  56. package/admin/pages/users/list.njk +26 -24
  57. package/admin/pages/users/new.njk +11 -7
  58. package/admin/src/admin.css +174 -0
  59. package/admin/static/admin-bar.css +33 -31
  60. package/admin/static/admin-bar.js +22 -0
  61. package/admin/static/admin.css +7388 -1195
  62. package/admin/static/copy.js +1 -1
  63. package/admin/static/editor.js +18 -18
  64. package/dist/admin/accounts.d.ts +20 -0
  65. package/dist/admin/accounts.d.ts.map +1 -1
  66. package/dist/admin/accounts.js +25 -0
  67. package/dist/admin/accounts.js.map +1 -1
  68. package/dist/admin/admin-theme.d.ts +157 -0
  69. package/dist/admin/admin-theme.d.ts.map +1 -0
  70. package/dist/admin/admin-theme.js +52 -0
  71. package/dist/admin/admin-theme.js.map +1 -0
  72. package/dist/admin/assets.d.ts +4 -3
  73. package/dist/admin/assets.d.ts.map +1 -1
  74. package/dist/admin/assets.js +4 -3
  75. package/dist/admin/assets.js.map +1 -1
  76. package/dist/admin/comments.js +2 -2
  77. package/dist/admin/comments.js.map +1 -1
  78. package/dist/admin/documents.d.ts +36 -7
  79. package/dist/admin/documents.d.ts.map +1 -1
  80. package/dist/admin/documents.js +141 -30
  81. package/dist/admin/documents.js.map +1 -1
  82. package/dist/admin/editor-layout.d.ts +1 -1
  83. package/dist/admin/editor-layout.d.ts.map +1 -1
  84. package/dist/admin/editor-layout.js +6 -1
  85. package/dist/admin/editor-layout.js.map +1 -1
  86. package/dist/admin/event-field.d.ts +26 -0
  87. package/dist/admin/event-field.d.ts.map +1 -0
  88. package/dist/admin/event-field.js +74 -0
  89. package/dist/admin/event-field.js.map +1 -0
  90. package/dist/admin/federation.js +3 -3
  91. package/dist/admin/federation.js.map +1 -1
  92. package/dist/admin/headers.js +1 -1
  93. package/dist/admin/headers.js.map +1 -1
  94. package/dist/admin/media.js +2 -2
  95. package/dist/admin/media.js.map +1 -1
  96. package/dist/admin/preview.d.ts.map +1 -1
  97. package/dist/admin/preview.js +16 -1
  98. package/dist/admin/preview.js.map +1 -1
  99. package/dist/admin/routes.d.ts.map +1 -1
  100. package/dist/admin/routes.js +17 -8
  101. package/dist/admin/routes.js.map +1 -1
  102. package/dist/admin/store.d.ts +9 -0
  103. package/dist/admin/store.d.ts.map +1 -1
  104. package/dist/admin/store.js +16 -5
  105. package/dist/admin/store.js.map +1 -1
  106. package/dist/admin/templates.d.ts +6 -3
  107. package/dist/admin/templates.d.ts.map +1 -1
  108. package/dist/admin/templates.js +25 -3
  109. package/dist/admin/templates.js.map +1 -1
  110. package/dist/admin/users.d.ts +4 -0
  111. package/dist/admin/users.d.ts.map +1 -1
  112. package/dist/admin/users.js +37 -1
  113. package/dist/admin/users.js.map +1 -1
  114. package/dist/avatars/avatars.js +1 -1
  115. package/dist/avatars/avatars.js.map +1 -1
  116. package/dist/comments/policy.d.ts +10 -0
  117. package/dist/comments/policy.d.ts.map +1 -1
  118. package/dist/comments/policy.js +12 -0
  119. package/dist/comments/policy.js.map +1 -1
  120. package/dist/comments/records.d.ts +5 -1
  121. package/dist/comments/records.d.ts.map +1 -1
  122. package/dist/comments/records.js +16 -2
  123. package/dist/comments/records.js.map +1 -1
  124. package/dist/content/event.d.ts +40 -0
  125. package/dist/content/event.d.ts.map +1 -0
  126. package/dist/content/event.js +51 -0
  127. package/dist/content/event.js.map +1 -0
  128. package/dist/content/handles.d.ts +22 -0
  129. package/dist/content/handles.d.ts.map +1 -0
  130. package/dist/content/handles.js +68 -0
  131. package/dist/content/handles.js.map +1 -0
  132. package/dist/content/markdown.d.ts +8 -2
  133. package/dist/content/markdown.d.ts.map +1 -1
  134. package/dist/content/markdown.js +55 -5
  135. package/dist/content/markdown.js.map +1 -1
  136. package/dist/content/parser.d.ts +3 -0
  137. package/dist/content/parser.d.ts.map +1 -1
  138. package/dist/content/parser.js +1 -1
  139. package/dist/content/parser.js.map +1 -1
  140. package/dist/content/post-type.d.ts +12 -8
  141. package/dist/content/post-type.d.ts.map +1 -1
  142. package/dist/content/post-type.js +19 -4
  143. package/dist/content/post-type.js.map +1 -1
  144. package/dist/content/rsvp.d.ts +29 -0
  145. package/dist/content/rsvp.d.ts.map +1 -0
  146. package/dist/content/rsvp.js +49 -0
  147. package/dist/content/rsvp.js.map +1 -0
  148. package/dist/content/save.d.ts +2 -1
  149. package/dist/content/save.d.ts.map +1 -1
  150. package/dist/content/save.js +10 -3
  151. package/dist/content/save.js.map +1 -1
  152. package/dist/content/store.d.ts +11 -4
  153. package/dist/content/store.d.ts.map +1 -1
  154. package/dist/content/store.js +15 -4
  155. package/dist/content/store.js.map +1 -1
  156. package/dist/content/sync.d.ts.map +1 -1
  157. package/dist/content/sync.js +7 -6
  158. package/dist/content/sync.js.map +1 -1
  159. package/dist/env.d.ts +3 -0
  160. package/dist/env.d.ts.map +1 -1
  161. package/dist/federation/article.d.ts +9 -3
  162. package/dist/federation/article.d.ts.map +1 -1
  163. package/dist/federation/article.js +90 -34
  164. package/dist/federation/article.js.map +1 -1
  165. package/dist/federation/cited-post.d.ts +13 -0
  166. package/dist/federation/cited-post.d.ts.map +1 -0
  167. package/dist/federation/cited-post.js +75 -0
  168. package/dist/federation/cited-post.js.map +1 -0
  169. package/dist/federation/delivery.d.ts.map +1 -1
  170. package/dist/federation/delivery.js +31 -2
  171. package/dist/federation/delivery.js.map +1 -1
  172. package/dist/federation/handles.d.ts +28 -0
  173. package/dist/federation/handles.d.ts.map +1 -0
  174. package/dist/federation/handles.js +65 -0
  175. package/dist/federation/handles.js.map +1 -0
  176. package/dist/federation/inbox.d.ts +2 -1
  177. package/dist/federation/inbox.d.ts.map +1 -1
  178. package/dist/federation/inbox.js +11 -3
  179. package/dist/federation/inbox.js.map +1 -1
  180. package/dist/federation/mount.d.ts.map +1 -1
  181. package/dist/federation/mount.js +18 -2
  182. package/dist/federation/mount.js.map +1 -1
  183. package/dist/federation/profiles.d.ts.map +1 -1
  184. package/dist/federation/profiles.js +2 -8
  185. package/dist/federation/profiles.js.map +1 -1
  186. package/dist/federation/site-loaders.d.ts +15 -0
  187. package/dist/federation/site-loaders.d.ts.map +1 -0
  188. package/dist/federation/site-loaders.js +19 -0
  189. package/dist/federation/site-loaders.js.map +1 -0
  190. package/dist/index.d.ts.map +1 -1
  191. package/dist/index.js +23 -1
  192. package/dist/index.js.map +1 -1
  193. package/dist/indieauth/client.js +2 -2
  194. package/dist/indieauth/client.js.map +1 -1
  195. package/dist/micropub/create.d.ts +2 -1
  196. package/dist/micropub/create.d.ts.map +1 -1
  197. package/dist/micropub/create.js +57 -8
  198. package/dist/micropub/create.js.map +1 -1
  199. package/dist/micropub/endpoint.d.ts.map +1 -1
  200. package/dist/micropub/endpoint.js +12 -4
  201. package/dist/micropub/endpoint.js.map +1 -1
  202. package/dist/micropub/update.d.ts +3 -0
  203. package/dist/micropub/update.d.ts.map +1 -1
  204. package/dist/micropub/update.js +29 -4
  205. package/dist/micropub/update.js.map +1 -1
  206. package/dist/web/admin-bar.d.ts +4 -4
  207. package/dist/web/admin-bar.d.ts.map +1 -1
  208. package/dist/web/admin-bar.js +11 -34
  209. package/dist/web/admin-bar.js.map +1 -1
  210. package/dist/web/context.d.ts +32 -4
  211. package/dist/web/context.d.ts.map +1 -1
  212. package/dist/web/context.js +28 -1
  213. package/dist/web/context.js.map +1 -1
  214. package/dist/web/conversation.d.ts +26 -1
  215. package/dist/web/conversation.d.ts.map +1 -1
  216. package/dist/web/conversation.js +67 -10
  217. package/dist/web/conversation.js.map +1 -1
  218. package/dist/web/documents.d.ts +12 -0
  219. package/dist/web/documents.d.ts.map +1 -1
  220. package/dist/web/documents.js +20 -0
  221. package/dist/web/documents.js.map +1 -1
  222. package/dist/web/feed-item.d.ts.map +1 -1
  223. package/dist/web/feed-item.js +8 -3
  224. package/dist/web/feed-item.js.map +1 -1
  225. package/dist/web/render.d.ts +3 -0
  226. package/dist/web/render.d.ts.map +1 -1
  227. package/dist/web/render.js +39 -6
  228. package/dist/web/render.js.map +1 -1
  229. package/dist/web/routes.d.ts.map +1 -1
  230. package/dist/web/routes.js +16 -3
  231. package/dist/web/routes.js.map +1 -1
  232. package/dist/web/templates.d.ts +4 -1
  233. package/dist/web/templates.d.ts.map +1 -1
  234. package/dist/web/templates.js +11 -0
  235. package/dist/web/templates.js.map +1 -1
  236. package/dist/webmention/cited-picture.d.ts +11 -4
  237. package/dist/webmention/cited-picture.d.ts.map +1 -1
  238. package/dist/webmention/cited-picture.js +22 -13
  239. package/dist/webmention/cited-picture.js.map +1 -1
  240. package/dist/webmention/cited-start.d.ts +10 -0
  241. package/dist/webmention/cited-start.d.ts.map +1 -0
  242. package/dist/webmention/cited-start.js +21 -0
  243. package/dist/webmention/cited-start.js.map +1 -0
  244. package/dist/webmention/fetch-public.d.ts +2 -2
  245. package/dist/webmention/fetch-public.d.ts.map +1 -1
  246. package/dist/webmention/fetch-public.js +1 -1
  247. package/dist/webmention/fetch-public.js.map +1 -1
  248. package/dist/webmention/html.d.ts +2 -0
  249. package/dist/webmention/html.d.ts.map +1 -1
  250. package/dist/webmention/html.js +6 -0
  251. package/dist/webmention/html.js.map +1 -1
  252. package/dist/webmention/index.d.ts +2 -1
  253. package/dist/webmention/index.d.ts.map +1 -1
  254. package/dist/webmention/index.js +1 -0
  255. package/dist/webmention/index.js.map +1 -1
  256. package/dist/webmention/microformats.d.ts +25 -2
  257. package/dist/webmention/microformats.d.ts.map +1 -1
  258. package/dist/webmention/microformats.js +38 -4
  259. package/dist/webmention/microformats.js.map +1 -1
  260. package/dist/webmention/pingback.d.ts +42 -0
  261. package/dist/webmention/pingback.d.ts.map +1 -0
  262. package/dist/webmention/pingback.js +229 -0
  263. package/dist/webmention/pingback.js.map +1 -0
  264. package/dist/webmention/receive.d.ts +20 -2
  265. package/dist/webmention/receive.d.ts.map +1 -1
  266. package/dist/webmention/receive.js +25 -16
  267. package/dist/webmention/receive.js.map +1 -1
  268. package/dist/webmention/reply-context.d.ts +65 -18
  269. package/dist/webmention/reply-context.d.ts.map +1 -1
  270. package/dist/webmention/reply-context.js +403 -99
  271. package/dist/webmention/reply-context.js.map +1 -1
  272. package/dist/webmention/reply-contexts.d.ts +3 -1
  273. package/dist/webmention/reply-contexts.d.ts.map +1 -1
  274. package/dist/webmention/reply-contexts.js +53 -17
  275. package/dist/webmention/reply-contexts.js.map +1 -1
  276. package/dist/webmention/routes.d.ts.map +1 -1
  277. package/dist/webmention/routes.js +2 -0
  278. package/dist/webmention/routes.js.map +1 -1
  279. package/dist/webmention/service.d.ts +11 -0
  280. package/dist/webmention/service.d.ts.map +1 -1
  281. package/dist/webmention/service.js +27 -6
  282. package/dist/webmention/service.js.map +1 -1
  283. package/dist/webmention/syndication.d.ts +6 -0
  284. package/dist/webmention/syndication.d.ts.map +1 -1
  285. package/dist/webmention/syndication.js +12 -0
  286. package/dist/webmention/syndication.js.map +1 -1
  287. package/package.json +10 -6
  288. package/themes/default/README.md +130 -38
  289. package/themes/default/layouts/410.njk +10 -0
  290. package/themes/default/layouts/base.njk +3 -0
  291. package/themes/default/layouts/page.njk +7 -0
  292. package/themes/default/layouts/post.njk +7 -4
  293. package/themes/default/layouts/search.njk +1 -1
  294. package/themes/default/partials/cited-page.njk +7 -0
  295. package/themes/default/partials/conversation.njk +25 -5
  296. package/themes/default/partials/event.njk +27 -0
  297. package/themes/default/partials/jsonld.njk +28 -0
  298. package/themes/default/partials/post-list.njk +5 -2
  299. package/themes/default/partials/reply-context.njk +24 -4
  300. package/themes/default/partials/rsvp.njk +7 -0
  301. package/themes/default/src/style.css +20 -0
  302. package/themes/default/static/style.css +35 -0
@@ -1,5 +1,12 @@
1
- {# Messages queued by the request that redirected here. They were cleared as
2
- they were read, so this is the only page that shows them. #}
3
- {% for entry in flash %}
4
- <p class="admin-flash admin-flash-{{ entry.kind }}" role="status">{{ entry.message }}</p>
5
- {% endfor %}
1
+ {# Messages queued by the request that redirected here, one alert each in the
2
+ colour of its kind. They were cleared as they were read, so this is the only
3
+ page that shows them. #}
4
+ {% from "components/alert.njk" import alert %}
5
+ {% set KINDS = { notice: 'success', warning: 'warning', error: 'error' } %}
6
+ {% if flash and flash.length %}
7
+ <div class="mb-6 flex flex-col gap-2">
8
+ {% for entry in flash %}
9
+ {% call alert(color=KINDS[entry.kind], polite=true) %}<span>{{ entry.message }}</span>{% endcall %}
10
+ {% endfor %}
11
+ </div>
12
+ {% endif %}
@@ -0,0 +1,51 @@
1
+ {# A menu: a list of links, down the side or across the top.
2
+
3
+ `menu(items, …)` emits
4
+
5
+ <ul class="menu w-full">
6
+ <li>
7
+ <a class="{RING}" href="…">Posts</a>
8
+ <ul>
9
+ <li><a class="{RING} menu-active" href="…" aria-current="page">All posts</a></li>
10
+ </ul>
11
+ </li>
12
+ </ul>
13
+
14
+ Each item is `{ label, url, current, open, children }`, as
15
+ `src/admin/menu.ts` builds it: `current` marks the screen being looked at
16
+ with `menu-active` and `aria-current`, and an item's `children` are listed
17
+ only while it is `open`. Labels are escaped. No `<nav>` is emitted; the
18
+ screen wraps the menu in one where it is a landmark.
19
+
20
+ `{RING}` is the focus-ring classes on every link: DaisyUI's menu sets
21
+ `outline-style: none` on `:focus-visible` and skips `.menu-active`.
22
+
23
+ Modifiers:
24
+
25
+ direction vertical (the default, filling its column), horizontal, or
26
+ responsive (down on a phone, across from `lg` up)
27
+ size xs, sm, md, lg, xl
28
+
29
+ A value outside its set is a render error. #}
30
+
31
+ {% set DIRECTIONS = {
32
+ vertical: 'w-full',
33
+ horizontal: 'menu-horizontal',
34
+ responsive: 'menu-vertical lg:menu-horizontal'
35
+ } %}
36
+ {% set SIZES = { xs: 'menu-xs', sm: 'menu-sm', md: 'menu-md', lg: 'menu-lg', xl: 'menu-xl' } %}
37
+
38
+ {% macro _items(items) -%}
39
+ {% for item in items %}
40
+ <li>
41
+ <a class="focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-base-content{% if item.current %} menu-active{% endif %}" href="{{ item.url }}"{% if item.current %} aria-current="page"{% endif %}>{{ item.label }}</a>
42
+ {% if item.open and item.children.length %}
43
+ <ul>{{ _items(item.children) }}</ul>
44
+ {% endif %}
45
+ </li>
46
+ {% endfor %}
47
+ {%- endmacro %}
48
+
49
+ {% macro menu(items, direction='vertical', size=none) -%}
50
+ <ul class="menu{{ DIRECTIONS | modifier(direction) }}{{ SIZES | modifier(size) }}">{{ _items(items) }}</ul>
51
+ {%- endmacro %}
@@ -0,0 +1,23 @@
1
+ {# A navbar, the bar across the top of a page.
2
+
3
+ `{% call navbar() %}…{% endcall %}` emits
4
+ `<div class="navbar bg-base-100 shadow-sm">` around its body. Inside it,
5
+ `navbarStart()`, `navbarCenter()` and `navbarEnd()`, each optional, take a
6
+ body through `{% call %}` and emit `<div class="navbar-start">`,
7
+ `navbar-center` and `navbar-end`. Every body is markup. #}
8
+
9
+ {% macro navbar() -%}
10
+ <div class="navbar bg-base-100 shadow-sm">{{ caller() }}</div>
11
+ {%- endmacro %}
12
+
13
+ {% macro navbarStart() -%}
14
+ <div class="navbar-start">{{ caller() }}</div>
15
+ {%- endmacro %}
16
+
17
+ {% macro navbarCenter() -%}
18
+ <div class="navbar-center">{{ caller() }}</div>
19
+ {%- endmacro %}
20
+
21
+ {% macro navbarEnd() -%}
22
+ <div class="navbar-end">{{ caller() }}</div>
23
+ {%- endmacro %}
@@ -0,0 +1,47 @@
1
+ {# Pagination: newer, where you are, and older, joined into one group of
2
+ buttons.
3
+
4
+ `pagination(page, pages=…, previousUrl=…, nextUrl=…)` emits
5
+
6
+ <nav aria-label="Pages">
7
+ <div class="join">
8
+ <a class="join-item btn btn-sm" rel="prev" href="…">Newer</a>
9
+ <span class="join-item btn btn-sm btn-active" aria-current="page">Page 2 of 5</span>
10
+ <a class="join-item btn btn-sm" rel="next" href="…">Older</a>
11
+ </div>
12
+ </nav>
13
+
14
+ An end with nowhere to go is a `btn-disabled` span. `pages` is left out of
15
+ the middle when it is not given. With neither a newer nor an older page it
16
+ draws nothing.
17
+
18
+ `previousLabel`, `nextLabel` and `label` change the words; all three are
19
+ escaped. #}
20
+
21
+ {% macro _end(url, rel, text) -%}
22
+ {% if url %}
23
+ <a class="join-item btn btn-sm" rel="{{ rel }}" href="{{ url }}">{{ text }}</a>
24
+ {% else %}
25
+ <span class="join-item btn btn-sm btn-disabled" aria-disabled="true">{{ text }}</span>
26
+ {% endif %}
27
+ {%- endmacro %}
28
+
29
+ {% macro pagination(
30
+ page,
31
+ pages=none,
32
+ previousUrl='',
33
+ nextUrl='',
34
+ previousLabel='Newer',
35
+ nextLabel='Older',
36
+ label='Pages'
37
+ ) -%}
38
+ {% if previousUrl or nextUrl %}
39
+ <nav aria-label="{{ label }}">
40
+ <div class="join">
41
+ {{ _end(previousUrl, 'prev', previousLabel) }}
42
+ <span class="join-item btn btn-sm btn-active" aria-current="page">Page {{ page }}{% if pages %} of {{ pages }}{% endif %}</span>
43
+ {{ _end(nextUrl, 'next', nextLabel) }}
44
+ </div>
45
+ </nav>
46
+ {% endif %}
47
+ {%- endmacro %}
@@ -1,43 +1,6 @@
1
- {# The admin bar as the public site draws it for a signed-in user (TASK-183),
2
- put straight after the theme's <body> tag.
3
-
4
- Whatever theme the site wears has never heard of it, so it keeps to a shadow
5
- root: no selector of the theme's reaches the markup inside, and the
6
- stylesheet inside reaches nothing outside. The stylesheet is inlined rather
7
- than linked so the bar is never drawn unstyled; the public site has no
8
- content CSP to refuse it.
9
-
10
- The host is fixed to the top of the window, as WordPress's is. In normal
11
- flow it sat inside whatever padding or margin the theme gave <body>, so it
12
- stopped short of the window's edges. Out of flow, nothing the theme does to
13
- <body> moves it, and the inline reset keeps the theme's rules for the host
14
- element itself, a `body > *` or a grid on <body>, from restyling it; only an
15
- `!important` rule can, which is also how a theme that insists hides it. The
16
- page is pushed down from outside, by a stylesheet the CMS puts in <head>
17
- (OFFSET_STYLE in src/web/admin-bar.ts) reading --geekity-admin-bar-height.
18
-
19
- The script does two things. It measures the bar and sets that height on
20
- <html>, so a bar that wraps to two lines on a phone, or one a theme has
21
- hidden, pushes the page down by what it really is. And it gives focus back
22
- to the greeting when the menu closes with focus inside it: Chrome does that
23
- for a popover in the light DOM, not from inside a shadow root, where the
24
- focus is simply lost. A click elsewhere has already moved focus by then,
25
- and keeps it. #}
26
- <geekity-admin-bar style="all: initial; display: block; position: fixed; top: 0; left: 0; right: 0; z-index: 99999"><template shadowrootmode="open"><style>{% include "static/admin-bar.css" %}</style>{% include "components/admin-bar.njk" %}<script>
27
- (() => {
28
- const host = document.querySelector('geekity-admin-bar');
29
- const root = host.shadowRoot;
30
- new ResizeObserver(() => {
31
- const height = host.getBoundingClientRect().height;
32
- document.documentElement.style.setProperty('--geekity-admin-bar-height', `${height}px`);
33
- }).observe(host);
34
- const menu = root.getElementById('admin-account-menu');
35
- menu?.addEventListener('beforetoggle', (event) => {
36
- if (event.newState === 'closed' && menu.contains(root.activeElement)) {
37
- setTimeout(() => root.querySelector('.admin-bar-account').focus());
38
- }
39
- });
40
- })();
41
- </script></template></geekity-admin-bar>{% if notice %}
1
+ {# The admin bar as the public site includes it (TASK-183, decision-30),
2
+ straight after the theme's <body> tag, with the unpublished notice under
3
+ it. #}
4
+ {% include "components/admin-bar.njk" %}{% if notice %}
42
5
  <p class="geekity-unpublished-notice" style="all: initial; display: block; box-sizing: border-box; margin: 0; padding: 8px 16px; background: #fcf9e8; color: #1d2327; border-bottom: 1px solid #dba617; font: 14px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif">{{ notice }}</p>
43
6
  {%- endif %}
@@ -0,0 +1,35 @@
1
+ {# Stats, numbers set out in a row.
2
+
3
+ `{% call stats(…) %}…{% endcall %}` emits
4
+ `<div class="stats bg-base-100 shadow">` around its body, and
5
+ `stat(title, value, desc='', href='')` inside it emits one
6
+ `<div class="stat">` holding a `stat-title`, a `stat-value` and, when it is
7
+ given one, a `stat-desc`. Title, value and description are escaped.
8
+
9
+ Given an `href`, the stat is an `<a class="stat">` instead, so the whole
10
+ stat is the link and its name is the title and the value together; the
11
+ value is drawn in the primary colour to say it leads somewhere.
12
+
13
+ `direction` is horizontal, vertical, or responsive (a column on a phone, a
14
+ row from `lg` up); left out, the stats lie in a row. Any other value is a
15
+ render error. #}
16
+
17
+ {% set DIRECTIONS = {
18
+ horizontal: 'stats-horizontal',
19
+ vertical: 'stats-vertical',
20
+ responsive: 'stats-vertical lg:stats-horizontal'
21
+ } %}
22
+
23
+ {% macro stats(direction=none) -%}
24
+ <div class="stats bg-base-100 shadow{{ DIRECTIONS | modifier(direction) }}">{{ caller() }}</div>
25
+ {%- endmacro %}
26
+
27
+ {% macro stat(title, value, desc='', href='') -%}
28
+ {% if href %}<a class="stat hover:bg-base-200" href="{{ href }}">{% else %}<div class="stat">{% endif %}
29
+ <div class="stat-title">{{ title }}</div>
30
+ <div class="stat-value{% if href %} text-primary{% endif %}">{{ value }}</div>
31
+ {% if desc %}
32
+ <div class="stat-desc">{{ desc }}</div>
33
+ {% endif %}
34
+ {% if href %}</a>{% else %}</div>{% endif %}
35
+ {%- endmacro %}
@@ -0,0 +1,74 @@
1
+ {# A table on a surface of its own, scrolling sideways inside it instead of
2
+ widening the page.
3
+
4
+ `{% call table(caption, …) %}<thead>…</thead><tbody>…</tbody>{% endcall %}`
5
+ emits
6
+
7
+ <div class="max-w-full overflow-x-auto contain-inline-size rounded-box bg-base-100 p-2 shadow-sm">
8
+ <table class="table table-sm">
9
+ <caption class="sr-only">caption</caption>
10
+ body
11
+ </table>
12
+ </div>
13
+
14
+ The caption is required and escaped. The body is the rows.
15
+
16
+ `contain-inline-size` keeps the table's intrinsic width from sizing the
17
+ card or grid cell around it.
18
+
19
+ Modifiers:
20
+
21
+ size xs, sm (the default), md, lg, xl
22
+ zebra true stripes the rows
23
+ pinRows true keeps the head and foot in view as the rows scroll
24
+ pinCols true keeps the `th` cells of each row in view
25
+ inCard true leaves off the surface, for a table already in a card's body
26
+
27
+ A size outside its set is a render error.
28
+
29
+ Rows that stack below lg instead of scrolling the table sideways. Each
30
+ row is a `{% call row() %}`. Its first cell is written out and ends with
31
+ `rowToggle(name)`; every other cell is
32
+ `{% call cell(label, nowrap) %}value{% endcall %}`, and every heading
33
+ after the first is `heading(label)`, with the same label:
34
+
35
+ <tr class="group …">
36
+ <td>title, row actions <details><summary>Details of name</summary></details></td>
37
+ <td data-label="Author" class="…">ada</td>
38
+ </tr>
39
+
40
+ From lg up the row is a table row and the toggle is not drawn. Below lg
41
+ the row shows its first cell with the toggle at its end, and opening the
42
+ toggle shows each other cell under it as a line of label then value. #}
43
+
44
+ {% set SIZES = { xs: 'table-xs', sm: 'table-sm', md: 'table-md', lg: 'table-lg', xl: 'table-xl' } %}
45
+
46
+ {% macro table(caption, size='sm', zebra=false, pinRows=false, pinCols=false, inCard=false) -%}
47
+ <div class="max-w-full overflow-x-auto contain-inline-size{% if not inCard %} rounded-box bg-base-100 p-2 shadow-sm {%- endif %}">
48
+ <table class="table{{ SIZES | modifier(size) }}{% if zebra %} table-zebra {%- endif %}{% if pinRows %} table-pin-rows {%- endif %}{% if pinCols %} table-pin-cols {%- endif %}">
49
+ <caption class="sr-only">{{ caption }}</caption>
50
+ {{ caller() }}
51
+ </table>
52
+ </div>
53
+ {%- endmacro %}
54
+
55
+ {% macro row() -%}
56
+ <tr class="group *:align-top max-lg:relative max-lg:block max-lg:pe-10 max-lg:not-last:border-b max-lg:border-base-content/5 max-lg:*:border-b-0">{{ caller() }}</tr>
57
+ {%- endmacro %}
58
+
59
+ {% macro heading(label) -%}
60
+ <th scope="col" class="max-lg:hidden">{{ label }}</th>
61
+ {%- endmacro %}
62
+
63
+ {% macro cell(label, nowrap=false) -%}
64
+ <td data-label="{{ label }}" class="{% if nowrap %}whitespace-nowrap {% endif %}max-lg:hidden max-lg:gap-2 max-lg:py-1 max-lg:group-has-open:flex max-lg:before:w-24 max-lg:before:shrink-0 max-lg:before:font-semibold max-lg:before:text-base-content/60 max-lg:before:content-[attr(data-label)]">{{ caller() }}</td>
65
+ {%- endmacro %}
66
+
67
+ {% macro rowToggle(name) -%}
68
+ <details class="absolute end-1 top-1 lg:hidden">
69
+ <summary class="btn btn-ghost btn-sm btn-square">
70
+ <span class="sr-only">Details of {{ name }}</span>
71
+ <span aria-hidden="true" class="-mt-1 size-2 rotate-45 border-e-2 border-b-2 transition-transform group-has-open:mt-1 group-has-open:rotate-225"></span>
72
+ </summary>
73
+ </details>
74
+ {%- endmacro %}
@@ -0,0 +1,33 @@
1
+ {# Tabs: a row of links between views of one screen.
2
+
3
+ `tabs(items, label, …)` emits
4
+
5
+ <nav class="tabs tabs-box" aria-label="label">
6
+ <a class="tab tab-active" href="…" aria-current="page">All (3)</a>
7
+ <a class="tab" href="…">Spam (0)</a>
8
+ </nav>
9
+
10
+ Each item is `{ label, url, current, count }`: `current` marks the view
11
+ being looked at, and `count`, when there is one, is printed after the
12
+ label. Labels are escaped.
13
+
14
+ No `role="tablist"` or `role="tab"`: those ARIA roles promise panels and
15
+ arrow keys, and these are plain links.
16
+
17
+ Modifiers:
18
+
19
+ style box (the default, a segmented control), border, lift
20
+ size xs, sm, md, lg, xl
21
+
22
+ A value outside its set is a render error. #}
23
+
24
+ {% set STYLES = { box: 'tabs-box', border: 'tabs-border', lift: 'tabs-lift' } %}
25
+ {% set SIZES = { xs: 'tabs-xs', sm: 'tabs-sm', md: 'tabs-md', lg: 'tabs-lg', xl: 'tabs-xl' } %}
26
+
27
+ {% macro tabs(items, label, style='box', size=none) -%}
28
+ <nav class="tabs{{ STYLES | modifier(style) }}{{ SIZES | modifier(size) }}" aria-label="{{ label }}">
29
+ {% for item in items %}
30
+ <a class="tab{% if item.current %} tab-active{% endif %}" href="{{ item.url }}"{% if item.current %} aria-current="page"{% endif %}>{{ item.label }}{% if item.count is defined %} ({{ item.count }}){% endif %}</a>
31
+ {% endfor %}
32
+ </nav>
33
+ {%- endmacro %}
@@ -0,0 +1,18 @@
1
+ {% import "components/fields.njk" as field %}
2
+ {% from "components/card.njk" import card, cardActions %}
3
+ {% from "components/button.njk" import button %}
4
+
5
+ {% call card(title='Theme') %}
6
+ <form method="post" action="{{ userThemeUrl }}">
7
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
8
+ {% call field.select('admin-theme', fields.adminTheme, 'Draw the admin in',
9
+ hint='Only you see it. Follow the system draws light or dark as your device is set.') %}
10
+ {% for choice in themeChoices %}
11
+ <option value="{{ choice.value }}"{% if choice.chosen %} selected{% endif %}>{{ choice.label }}</option>
12
+ {% endfor %}
13
+ {% endcall %}
14
+ {% call cardActions() %}
15
+ {{ button('Save theme', type='submit', color='primary') }}
16
+ {% endcall %}
17
+ </form>
18
+ {% endcall %}
@@ -1,29 +1,22 @@
1
- {# The admin's base layout. It lives outside the theme search path on purpose:
2
- a site's theme/ can override any public template, and must never be able to
3
- shadow the login form.
4
-
5
- This is the bare page: head, stylesheet and one centred column. Screens that
6
- need the navigation extend layouts/shell.njk, which extends this and wraps
7
- the same content block in the admin chrome. Login and setup extend this one
8
- directly, because a visitor who cannot log in yet has nowhere to navigate. #}
1
+ {# The admin's base layout, the bare page: head, the compiled stylesheet and
2
+ one column. `dataTheme` is the signed-in user's theme; with none, the light
3
+ and dark defaults follow the system. #}
9
4
  <!doctype html>
10
- <html lang="en">
5
+ <html lang="en"{% if dataTheme %} data-theme="{{ dataTheme }}"{% endif %}>
11
6
  <head>
12
7
  <meta charset="utf-8" />
13
8
  <meta name="viewport" content="width=device-width, initial-scale=1" />
14
9
  <title>{% block title %}Admin{% endblock %}{% if site.title %} · {{ site.title }}{% endif %}</title>
15
10
  <meta name="robots" content="noindex, nofollow" />
16
- <link rel="stylesheet" href="{{ assetPrefix }}admin-bar.css" />
17
11
  <link rel="stylesheet" href="{{ assetPrefix }}admin.css" />
18
12
  </head>
19
- <body>
20
- {# First on every screen, so a keyboard gets past the bar and the menu
21
- in one key. It moves no focus of its own: a refused form's summary
22
- still takes focus on load (TASK-142). #}
23
- <a class="admin-skip-link" href="#main">Skip to main content</a>
13
+ <body class="min-h-screen bg-base-200 text-base-content">
14
+ {# First on every screen, so a keyboard gets past the bar and the menu in
15
+ one key. #}
16
+ <a class="fixed top-(--geekity-admin-bar-height) left-0 z-50 -translate-y-full bg-base-100 p-3 font-semibold focus:translate-y-0" href="#main">Skip to main content</a>
24
17
  {% block chrome %}{% endblock %}
25
18
  {% block main %}
26
- <main id="main" class="admin-plain">
19
+ <main id="main" class="mx-auto flex min-h-screen max-w-md flex-col justify-center p-6">
27
20
  {% include "components/flash.njk" %}
28
21
  {% block content %}{% endblock %}
29
22
  </main>
@@ -1,31 +1,36 @@
1
1
  {# What every settings page is: a heading, one form that saves the fields that
2
- page carries, and whatever panels sit under it. A page fills in the two
2
+ page carries, and whatever panels sit under it. A page fills in the three
3
3
  blocks, and names in `settingsProblems` each field a save can refuse, so
4
4
  the summary at the top links to it; nothing about the chrome is written six
5
5
  times. #}
6
6
  {% extends "layouts/shell.njk" %}
7
7
  {% import "components/fields.njk" as field %}
8
+ {% from "components/button.njk" import button %}
8
9
 
9
10
  {% block title %}{{ settingsLabel }} settings{% endblock %}
10
11
 
11
12
  {% block content %}
12
- <h1>{{ settingsLabel }}</h1>
13
+ <h1 class="mb-6 text-2xl font-bold">{{ settingsLabel }}</h1>
13
14
 
14
15
  {% if hasProblems %}
15
- {% call field.summary('Nothing was saved. Fix the fields marked below and try again.') %}
16
- {% block settingsProblems %}{% endblock %}
17
- {% endcall %}
16
+ <div class="mb-6 max-w-2xl">
17
+ {% call field.summary('Nothing was saved. Fix the fields marked below and try again.') %}
18
+ {% block settingsProblems %}{% endblock %}
19
+ {% endcall %}
20
+ </div>
18
21
  {% endif %}
19
22
 
20
- <form class="admin-settings" method="post" action="{{ settingsUrl }}">
23
+ <form class="flex max-w-2xl flex-col gap-2" method="post" action="{{ settingsUrl }}">
21
24
  <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
22
25
 
23
26
  {% block settingsFields %}{% endblock %}
24
27
 
25
- <p class="admin-settings-buttons">
26
- <button type="submit">Save settings</button>
27
- </p>
28
+ <div class="mt-4">
29
+ {{ button('Save settings', type='submit', color='primary') }}
30
+ </div>
28
31
  </form>
29
32
 
30
- {% block settingsPanels %}{% endblock %}
33
+ <div class="mt-10 flex max-w-2xl flex-col gap-6">
34
+ {% block settingsPanels %}{% endblock %}
35
+ </div>
31
36
  {% endblock %}
@@ -1,6 +1,8 @@
1
- {# The signed-in admin: a bar across the top, the sections down the left, the
2
- screen itself in the middle. Every screen behind the login extends this. #}
1
+ {# The signed-in admin: the bar across the top, the sections down the left,
2
+ the screen itself beside them. Every screen behind the login extends this
3
+ and fills in `content`, starting with its own <h1>. #}
3
4
  {% extends "layouts/base.njk" %}
5
+ {% from "components/menu.njk" import menu %}
4
6
 
5
7
  {% block chrome %}
6
8
  {# View site, because in here you are not looking at it; + New, as on the
@@ -15,34 +17,24 @@
15
17
  {% endblock %}
16
18
 
17
19
  {% block main %}
18
- <div class="admin-shell">
19
- {# WordPress classic: a section is a heading over its screens. The heading
20
- goes to the first of them, the open section shows the rest, and the one
21
- you are on carries aria-current. Nothing here is scripted — the server
22
- already knows which section is open, so expanding one is a page the
23
- browser asks for rather than a class JavaScript toggles. #}
24
- <nav class="admin-nav" aria-label="Sections">
25
- <ul>
26
- {% for item in navigation %}
27
- <li class="admin-nav-section{% if item.open %} admin-nav-open{% endif %}">
28
- <a class="admin-nav-heading" href="{{ item.url }}">{{ item.label }}</a>
29
- {% if item.open %}
30
- <ul class="admin-nav-children">
31
- {% for entry in item.children %}
32
- <li>
33
- <a href="{{ entry.url }}"{% if entry.current %} aria-current="page"{% endif %}>{{ entry.label }}</a>
34
- </li>
35
- {% endfor %}
36
- </ul>
37
- {% endif %}
38
- </li>
39
- {% endfor %}
40
- </ul>
41
- </nav>
42
-
43
- <main id="main" class="admin-main">
44
- {% include "components/flash.njk" %}
45
- {% block content %}{% endblock %}
46
- </main>
20
+ <div class="drawer lg:drawer-open">
21
+ {# `autocomplete="off"`: a browser going back restores a checkbox's state
22
+ otherwise. #}
23
+ <input id="admin-menu" type="checkbox" class="drawer-toggle" autocomplete="off" />
24
+ {# Before the screen, so a keyboard meets the menu first; DaisyUI's
25
+ drawer places both by grid column. #}
26
+ <div class="drawer-side lg:top-(--geekity-admin-bar-height) lg:h-[calc(100dvh-var(--geekity-admin-bar-height))]">
27
+ <label for="admin-menu" class="drawer-overlay" aria-hidden="true"></label>
28
+ <nav aria-label="Sections" class="min-h-full w-64 bg-base-100 pt-(--geekity-admin-bar-height) lg:pt-0">
29
+ {{ menu(navigation) }}
30
+ </nav>
31
+ </div>
32
+ <div class="drawer-content">
33
+ <label for="admin-menu" class="btn btn-sm drawer-button mx-4 mt-4 lg:hidden">Menu</label>
34
+ <main id="main" class="p-4 sm:p-6 lg:p-8">
35
+ {% include "components/flash.njk" %}
36
+ {% block content %}{% endblock %}
37
+ </main>
38
+ </div>
47
39
  </div>
48
40
  {% endblock %}
@@ -6,42 +6,48 @@
6
6
  out which accounts a site has. #}
7
7
  {% extends "layouts/base.njk" %}
8
8
  {% import "components/fields.njk" as field %}
9
+ {% from "components/button.njk" import button %}
10
+ {% from "components/card.njk" import card %}
9
11
 
10
12
  {% block title %}Forgotten password{% endblock %}
11
13
 
12
14
  {% block content %}
13
- <h1>Forgotten password</h1>
14
-
15
- {% if not configured %}
16
- {# No provider and no credential, so there is nothing to send a link with.
17
- Saying so beats a form that quietly does nothing. #}
18
- <p>This site cannot send email, so a password cannot be reset from here.</p>
19
- <p>
20
- Whoever runs the server can add an account from a shell with
21
- <code>geekity user add &lt;username&gt;</code>, and sign in with that to
22
- change anything else. Turning on a mail provider under
23
- <strong>Settings &rarr; Email</strong> makes this page work.
24
- </p>
25
- <p><a href="{{ loginUrl }}">Back to the login form</a></p>
26
- {% elif sent %}
27
- <p>{{ answer }}</p>
28
- <p><a href="{{ loginUrl }}">Back to the login form</a></p>
29
- {% else %}
30
- <p>Type your username or your email address, and a link to set a new password is sent to the address on your account.</p>
31
-
32
- {% if error %}
33
- {{ field.summary(error) }}
15
+ {% call card() %}
16
+ <h1 class="card-title text-2xl">Forgotten password</h1>
17
+
18
+ {% if not configured %}
19
+ {# No provider and no credential, so there is nothing to send a link
20
+ with. Saying so beats a form that quietly does nothing. #}
21
+ <p>This site cannot send email, so a password cannot be reset from here.</p>
22
+ <p>
23
+ Whoever runs the server can add an account from a shell with
24
+ <code>geekity user add &lt;username&gt;</code>, and sign in with that to
25
+ change anything else. Turning on a mail provider under
26
+ <strong>Settings &rarr; Email</strong> makes this page work.
27
+ </p>
28
+ <p><a class="link" href="{{ loginUrl }}">Back to the login form</a></p>
29
+ {% elif sent %}
30
+ <p>{{ answer }}</p>
31
+ <p><a class="link" href="{{ loginUrl }}">Back to the login form</a></p>
32
+ {% else %}
33
+ <p>Type your username or your email address, and a link to set a new password is sent to the address on your account.</p>
34
+
35
+ {% if error %}
36
+ {{ field.summary(error) }}
37
+ {% endif %}
38
+
39
+ <form class="flex flex-col gap-2" method="post" action="{{ forgotUrl }}">
40
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
41
+
42
+ {{ field.text('identifier', fields.identifier, 'Username or email address',
43
+ autocomplete='username', required=true, autofocus=true) }}
44
+
45
+ <div class="mt-4">
46
+ {{ button('Send a reset link', type='submit', color='primary', shape='block') }}
47
+ </div>
48
+ </form>
49
+
50
+ <p class="text-sm"><a class="link" href="{{ loginUrl }}">Back to the login form</a></p>
34
51
  {% endif %}
35
-
36
- <form method="post" action="{{ forgotUrl }}">
37
- <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
38
-
39
- {{ field.text('identifier', fields.identifier, 'Username or email address',
40
- autocomplete='username', required=true, autofocus=true) }}
41
-
42
- <button type="submit">Send a reset link</button>
43
- </form>
44
-
45
- <p class="admin-hint"><a href="{{ loginUrl }}">Back to the login form</a></p>
46
- {% endif %}
52
+ {% endcall %}
47
53
  {% endblock %}