@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,29 +1,35 @@
1
1
  {% extends "layouts/base.njk" %}
2
2
  {% import "components/fields.njk" as field %}
3
+ {% from "components/button.njk" import button %}
4
+ {% from "components/card.njk" import card %}
3
5
 
4
6
  {% block title %}Log in{% endblock %}
5
7
 
6
8
  {% block content %}
7
- <h1>Log in</h1>
9
+ {% call card() %}
10
+ <h1 class="card-title text-2xl">Log in</h1>
8
11
 
9
- {% if error %}
10
- {{ field.summary(error) }}
11
- {% endif %}
12
-
13
- <form method="post" action="{{ loginUrl }}">
14
- <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
15
- {% if returnTo %}
16
- <input type="hidden" name="return_to" value="{{ returnTo }}" />
12
+ {% if error %}
13
+ {{ field.summary(error) }}
17
14
  {% endif %}
18
15
 
19
- {{ field.text('username', 'username', 'Username', username,
20
- autocomplete='username', required=true, autofocus=true) }}
16
+ <form class="flex flex-col gap-2" method="post" action="{{ loginUrl }}">
17
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
18
+ {% if returnTo %}
19
+ <input type="hidden" name="return_to" value="{{ returnTo }}" />
20
+ {% endif %}
21
+
22
+ {{ field.text('username', 'username', 'Username', username,
23
+ autocomplete='username', required=true, autofocus=true) }}
21
24
 
22
- {{ field.password('password', 'password', 'Password',
23
- autocomplete='current-password', required=true) }}
25
+ {{ field.password('password', 'password', 'Password',
26
+ autocomplete='current-password', required=true) }}
24
27
 
25
- <button type="submit">Log in</button>
26
- </form>
28
+ <div class="mt-4">
29
+ {{ button('Log in', type='submit', color='primary', shape='block') }}
30
+ </div>
31
+ </form>
27
32
 
28
- <p class="admin-hint"><a href="{{ forgotUrl }}">Forgotten your password?</a></p>
33
+ <p class="text-sm"><a class="link" href="{{ forgotUrl }}">Forgotten your password?</a></p>
34
+ {% endcall %}
29
35
  {% endblock %}
@@ -6,35 +6,44 @@
6
6
  passwords. #}
7
7
  {% extends "layouts/base.njk" %}
8
8
  {% import "components/fields.njk" as field %}
9
+ {% from "components/alert.njk" import alert %}
10
+ {% from "components/button.njk" import button %}
11
+ {% from "components/card.njk" import card %}
9
12
 
10
13
  {% block title %}Set a new password{% endblock %}
11
14
 
12
15
  {% block content %}
13
- <h1>Set a new password</h1>
14
-
15
- {% if invalid %}
16
- <p class="error">That link is no longer valid. A reset link lasts an hour and works once, and asking for a new password again invalidates the old one.</p>
17
- <p><a href="{{ forgotUrl }}">Ask for another one</a></p>
18
- {% else %}
19
- {% if problem %}
20
- {% call field.summary('The password was not set.') %}
21
- {{ field.problem('password', problem) }}
16
+ {% call card() %}
17
+ <h1 class="card-title text-2xl">Set a new password</h1>
18
+
19
+ {% if invalid %}
20
+ {% call alert(color='error') %}
21
+ <span>That link is no longer valid. A reset link lasts an hour and works once, and asking for a new password again invalidates the old one.</span>
22
22
  {% endcall %}
23
+ <p><a class="link" href="{{ forgotUrl }}">Ask for another one</a></p>
24
+ {% else %}
25
+ {% if problem %}
26
+ {% call field.summary('The password was not set.') %}
27
+ {{ field.problem('password', problem) }}
28
+ {% endcall %}
29
+ {% endif %}
30
+
31
+ <form class="flex flex-col gap-2" method="post" action="{{ resetUrl }}">
32
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
33
+ <input type="hidden" name="{{ fields.token }}" value="{{ token }}" />
34
+
35
+ {{ field.password('password', fields.password, 'New password', error=problem,
36
+ autocomplete='new-password', required=true, autofocus=true) }}
37
+
38
+ {{ field.password('password_confirmation', fields.passwordConfirmation,
39
+ 'Repeat the password', autocomplete='new-password', required=true) }}
40
+
41
+ <p class="text-sm">Every browser signed in as you is signed out, and this link stops working.</p>
42
+
43
+ <div class="mt-4">
44
+ {{ button('Set the password', type='submit', color='primary', shape='block') }}
45
+ </div>
46
+ </form>
23
47
  {% endif %}
24
-
25
- <form method="post" action="{{ resetUrl }}">
26
- <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
27
- <input type="hidden" name="{{ fields.token }}" value="{{ token }}" />
28
-
29
- {{ field.password('password', fields.password, 'New password', error=problem,
30
- autocomplete='new-password', required=true, autofocus=true) }}
31
-
32
- {{ field.password('password_confirmation', fields.passwordConfirmation,
33
- 'Repeat the password', autocomplete='new-password', required=true) }}
34
-
35
- <p class="admin-hint">Every browser signed in as you is signed out, and this link stops working.</p>
36
-
37
- <button type="submit">Set the password</button>
38
- </form>
39
- {% endif %}
48
+ {% endcall %}
40
49
  {% endblock %}
@@ -1,28 +1,34 @@
1
1
  {% extends "layouts/base.njk" %}
2
2
  {% import "components/fields.njk" as field %}
3
+ {% from "components/button.njk" import button %}
4
+ {% from "components/card.njk" import card %}
3
5
 
4
6
  {% block title %}Set up{% endblock %}
5
7
 
6
8
  {% block content %}
7
- <h1>Create the first admin</h1>
8
- <p>Nobody can sign in to this site yet. Pick a username and a password.</p>
9
+ {% call card() %}
10
+ <h1 class="card-title text-2xl">Create the first admin</h1>
11
+ <p>Nobody can sign in to this site yet. Pick a username and a password.</p>
9
12
 
10
- {% if error %}
11
- {{ field.summary(error) }}
12
- {% endif %}
13
+ {% if error %}
14
+ {{ field.summary(error) }}
15
+ {% endif %}
13
16
 
14
- <form method="post" action="{{ setupUrl }}">
15
- <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
17
+ <form class="flex flex-col gap-2" method="post" action="{{ setupUrl }}">
18
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
16
19
 
17
- {{ field.text('username', 'username', 'Username', username,
18
- autocomplete='username', required=true, autofocus=true) }}
20
+ {{ field.text('username', 'username', 'Username', username,
21
+ autocomplete='username', required=true, autofocus=true) }}
19
22
 
20
- {{ field.password('password', 'password', 'Password',
21
- autocomplete='new-password', required=true) }}
23
+ {{ field.password('password', 'password', 'Password',
24
+ autocomplete='new-password', required=true) }}
22
25
 
23
- {{ field.password('password_confirmation', 'password_confirmation', 'Repeat the password',
24
- autocomplete='new-password', required=true) }}
26
+ {{ field.password('password_confirmation', 'password_confirmation', 'Repeat the password',
27
+ autocomplete='new-password', required=true) }}
25
28
 
26
- <button type="submit">Create admin</button>
27
- </form>
29
+ <div class="mt-4">
30
+ {{ button('Create admin', type='submit', color='primary', shape='block') }}
31
+ </div>
32
+ </form>
33
+ {% endcall %}
28
34
  {% endblock %}
@@ -1,67 +1,73 @@
1
1
  {# /admin/appearance/themes: the theme the site is wearing, everything else on
2
2
  disk, and the one button that changes it (decision-15).
3
3
 
4
- The packaged theme comes first because it is the floor the others are laid
5
- over rather than a peer in the list: a site theme ships what it changes and
6
- inherits the rest. The themes directory is read on every request, so a theme
7
- copied in over ssh or pulled down by git is here without a restart. #}
4
+ One card per theme. The packaged theme comes first because it is the floor
5
+ the others are laid over rather than a peer in the list: a site theme ships
6
+ what it changes and inherits the rest. The themes directory is read on
7
+ every request, so a theme copied in over ssh or pulled down by git is here
8
+ without a restart. #}
8
9
  {% extends "layouts/shell.njk" %}
10
+ {% from "components/badge.njk" import badge, status %}
11
+ {% from "components/button.njk" import button %}
12
+ {% from "components/card.njk" import card, cardActions %}
9
13
 
10
14
  {% block title %}{{ heading }}{% endblock %}
11
15
 
12
16
  {% block content %}
13
- <h1>{{ heading }}</h1>
17
+ <h1 class="mb-6 text-2xl font-bold">{{ heading }}</h1>
14
18
 
15
- <ul class="admin-themes">
19
+ <ul class="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 2xl:grid-cols-3">
16
20
  {% for theme in themes %}
17
- <li class="admin-theme{% if theme.active %} admin-theme-current{% endif %}">
18
- <h2>{{ theme.name }}</h2>
21
+ <li>
22
+ {% call card(title=theme.name, style=('border' if theme.active else none)) %}
23
+ <p class="flex flex-wrap items-center gap-2">
24
+ <code>{{ theme.id }}</code>
25
+ {% if theme.active %}
26
+ {{ status('active', 'Active') }}
27
+ {% endif %}
28
+ {% if theme.packaged %}
29
+ {{ badge('the theme this package ships', style='outline') }}
30
+ {% endif %}
31
+ </p>
19
32
 
20
- {% if theme.active %}
21
- <p class="admin-status admin-status-active">Active</p>
22
- {% endif %}
23
-
24
- {% if theme.description %}
25
- <p class="admin-theme-description">{{ theme.description }}</p>
26
- {% endif %}
27
-
28
- <p class="admin-theme-id">
29
- <code>{{ theme.id }}</code>
30
- {% if theme.packaged %}
31
- <span class="admin-status">the theme this package ships</span>
33
+ {% if theme.description %}
34
+ <p>{{ theme.description }}</p>
32
35
  {% endif %}
33
- </p>
34
36
 
35
- {% if not theme.active %}
36
- <form method="post" action="{{ themesUrl }}">
37
- <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
38
- <input type="hidden" name="{{ fields.theme }}" value="{{ theme.value }}" />
39
- <button type="submit">Activate</button>
40
- </form>
41
- {% endif %}
37
+ {% if not theme.active %}
38
+ <form method="post" action="{{ themesUrl }}">
39
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
40
+ <input type="hidden" name="{{ fields.theme }}" value="{{ theme.value }}" />
41
+ {% call cardActions() %}
42
+ {{ button('Activate', type='submit', color='primary') }}
43
+ {% endcall %}
44
+ </form>
45
+ {% endif %}
46
+ {% endcall %}
42
47
  </li>
43
48
  {% endfor %}
44
49
  </ul>
45
50
 
46
51
  {% if unreadable.length %}
47
- <h2>Not themes</h2>
48
-
49
- <p class="admin-hint">
50
- These are directories in <code>{{ themesDir }}</code> that were meant to be themes and are
51
- not. They are listed rather than skipped, so a typo in a <code>theme.json</code> is something
52
- you can see rather than a theme that quietly stopped appearing. None of them can be
53
- activated.
54
- </p>
55
-
56
- <ul class="admin-broken-themes">
57
- {% for entry in unreadable %}
58
- <li class="admin-broken-theme"><code>{{ entry.id }}</code> — {{ entry.reason }}</li>
59
- {% endfor %}
60
- </ul>
52
+ <div class="mb-8 max-w-2xl">
53
+ {% call card(title='Not themes') %}
54
+ <p class="text-sm">
55
+ These are directories in <code class="break-all">{{ themesDir }}</code> that were meant to be themes and
56
+ are not. They are listed rather than skipped, so a typo in a <code>theme.json</code> is
57
+ something you can see rather than a theme that quietly stopped appearing. None of them
58
+ can be activated.
59
+ </p>
60
+ <ul class="list-disc ps-5">
61
+ {% for entry in unreadable %}
62
+ <li class="break-all"><code>{{ entry.id }}</code> — {{ entry.reason }}</li>
63
+ {% endfor %}
64
+ </ul>
65
+ {% endcall %}
66
+ </div>
61
67
  {% endif %}
62
68
 
63
- <p class="admin-hint">
64
- A theme is a directory in <code>{{ themesDir }}</code> with a <code>theme.json</code> in it,
69
+ <p class="max-w-prose text-sm">
70
+ A theme is a directory in <code class="break-all">{{ themesDir }}</code> with a <code>theme.json</code> in it,
65
71
  named by that directory. It is laid over the theme this package ships, one file at a time, so
66
72
  it need only carry what it changes. Activating one writes the directory name into
67
73
  <code>content/_data/site.json</code>; the next request is already wearing it.
@@ -1,133 +1,115 @@
1
- {# /admin/comments: the moderation queue. There is no email in this milestone,
2
- so this screen is the notification — which is why the dashboard carries the
3
- pending count and why this list opens on Pending.
1
+ {# /admin/comments: the moderation queue, opening on Pending.
4
2
 
5
- Every button is a form of its own, so the whole screen works with
6
- JavaScript switched off. #}
3
+ One card per comment: who wrote it, where and when, what it says, and a row
4
+ of buttons, each a form of its own so the screen works with JavaScript
5
+ switched off. Reply is a collapse holding its own form, folded until it is
6
+ opened. #}
7
7
  {% extends "layouts/shell.njk" %}
8
+ {% import "components/fields.njk" as field %}
9
+ {% from "components/badge.njk" import badge, status as statusBadge %}
10
+ {% from "components/button.njk" import button %}
11
+ {% from "components/card.njk" import card %}
12
+ {% from "components/collapse.njk" import collapse %}
13
+ {% from "components/pagination.njk" import pagination %}
14
+ {% from "components/tabs.njk" import tabs as tabsNav %}
8
15
 
9
16
  {% block title %}{{ heading }}{% endblock %}
10
17
 
18
+ {% macro rowFields(row, fields, status, csrfToken) %}
19
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
20
+ <input type="hidden" name="{{ fields.id }}" value="{{ row.id }}" />
21
+ <input type="hidden" name="{{ fields.status }}" value="{{ status }}" />
22
+ {% endmacro %}
23
+
11
24
  {% block content %}
12
- <h1>{{ heading }}</h1>
25
+ <h1 class="mb-6 text-2xl font-bold">{{ heading }}</h1>
13
26
 
14
- <nav class="admin-filters" aria-label="Comment status">
15
- <ul>
16
- {% for tab in tabs %}
17
- <li>
18
- <a href="{{ tab.url }}"{% if tab.current %} aria-current="page"{% endif %}>
19
- {{ tab.label }} ({{ tab.count }})
20
- </a>
21
- </li>
22
- {% endfor %}
23
- </ul>
24
- </nav>
27
+ <div class="mb-6">{{ tabsNav(tabs, 'Comment status') }}</div>
25
28
 
26
29
  {% if rows.length == 0 %}
27
- <p class="admin-empty">Nothing here.</p>
30
+ <p class="mb-6">Nothing here.</p>
28
31
  {% else %}
29
- <ul class="admin-comments">
32
+ <ul class="mb-6 flex max-w-3xl flex-col gap-4">
30
33
  {% for row in rows %}
31
- <li class="admin-comment">
32
- <div class="admin-comment-meta">
33
- <span class="admin-comment-author">{{ row.author }}</span>
34
- {% if row.url %}
35
- <a class="admin-comment-site" href="{{ row.url }}" rel="nofollow ugc noopener noreferrer">{{ row.url }}</a>
36
- {% endif %}
37
- {% if row.email %}
38
- <span class="admin-comment-email">{{ row.email }}</span>
39
- {% endif %}
40
- <span class="admin-status admin-status-{{ row.status }}">{{ row.status }}</span>
41
- {% if row.source != 'comment' %}
42
- <span class="admin-status">{{ row.source }}</span>
43
- {% endif %}
44
- </div>
34
+ <li>
35
+ {% call card() %}
36
+ <p class="flex flex-wrap items-center gap-x-3 gap-y-1">
37
+ <span class="font-semibold">{{ row.author }}</span>
38
+ {% if row.url %}
39
+ <a class="link break-all" href="{{ row.url }}" rel="nofollow ugc noopener noreferrer">{{ row.url }}</a>
40
+ {% endif %}
41
+ {% if row.email %}
42
+ <span class="break-all">{{ row.email }}</span>
43
+ {% endif %}
44
+ {{ statusBadge(row.status, row.status | capitalize) }}
45
+ {% if row.source != 'comment' %}
46
+ {{ badge(row.source | capitalize, style='outline') }}
47
+ {% endif %}
48
+ </p>
45
49
 
46
- <p class="admin-comment-where">
47
- <time>{{ row.submitted }}</time>
48
- on
49
- {% if row.editUrl %}
50
- <a href="{{ row.editUrl }}">{{ row.post }}</a>
51
- {% else %}
52
- {{ row.post }}
53
- {% endif %}
54
- {% if row.postUrl %}
55
- &middot; <a href="{{ row.postUrl }}#comment-{{ row.id }}">View</a>
56
- {% endif %}
57
- {% if row.inReplyTo %}
58
- &middot; a reply to another comment
59
- {% endif %}
60
- {% if row.address %}
61
- &middot; <code title="A salted hash of the address it came from">{{ row.address }}</code>
62
- {% endif %}
63
- {% if row.removed %}
64
- &middot; {{ row.removed }}
65
- {% endif %}
66
- </p>
50
+ <p class="text-sm">
51
+ <time>{{ row.submitted }}</time>
52
+ on
53
+ {% if row.editUrl %}
54
+ <a class="link" href="{{ row.editUrl }}">{{ row.post }}</a>
55
+ {% else %}
56
+ {{ row.post }}
57
+ {% endif %}
58
+ {% if row.postUrl %}
59
+ &middot; <a class="link" href="{{ row.postUrl }}#comment-{{ row.id }}">View</a>
60
+ {% endif %}
61
+ {% if row.inReplyTo %}
62
+ &middot; a reply to another comment
63
+ {% endif %}
64
+ {% if row.address %}
65
+ &middot; <code title="A salted hash of the address it came from">{{ row.address }}</code>
66
+ {% endif %}
67
+ {% if row.removed %}
68
+ &middot; {{ row.removed }}
69
+ {% endif %}
70
+ </p>
67
71
 
68
- <div class="admin-comment-body">{{ row.html | safe }}</div>
72
+ <div class="space-y-2 break-words">{{ row.html | safe }}</div>
69
73
 
70
- <div class="admin-actions">
71
- {% if row.status != 'approved' %}
74
+ <div class="flex flex-wrap gap-1">
75
+ {% if row.status != 'approved' %}
76
+ <form method="post" action="{{ moderateUrl }}">
77
+ {{ rowFields(row, fields, status, csrfToken) }}
78
+ {{ button('Approve', type='submit', style='ghost', size='xs', name=fields.action, value='approve') }}
79
+ </form>
80
+ {% endif %}
81
+ {% if row.status != 'spam' %}
82
+ <form method="post" action="{{ moderateUrl }}">
83
+ {{ rowFields(row, fields, status, csrfToken) }}
84
+ {{ button('Spam', type='submit', style='ghost', size='xs', name=fields.action, value='spam') }}
85
+ </form>
86
+ {% endif %}
72
87
  <form method="post" action="{{ moderateUrl }}">
73
- <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
74
- <input type="hidden" name="{{ fields.id }}" value="{{ row.id }}" />
75
- <input type="hidden" name="{{ fields.status }}" value="{{ status }}" />
76
- <button type="submit" name="{{ fields.action }}" value="approve">Approve</button>
88
+ {{ rowFields(row, fields, status, csrfToken) }}
89
+ {{ button('Delete', type='submit', color='error', style='ghost', size='xs', name=fields.action, value='delete') }}
77
90
  </form>
78
- {% endif %}
91
+ </div>
79
92
 
80
- {% if row.status != 'spam' %}
81
- <form method="post" action="{{ moderateUrl }}">
82
- <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
83
- <input type="hidden" name="{{ fields.id }}" value="{{ row.id }}" />
84
- <input type="hidden" name="{{ fields.status }}" value="{{ status }}" />
85
- <button type="submit" name="{{ fields.action }}" value="spam">Spam</button>
93
+ {% call collapse('Reply') %}
94
+ <form method="post" action="{{ replyUrl }}">
95
+ {{ rowFields(row, fields, status, csrfToken) }}
96
+ {% set replyLabel %}Reply to {{ row.author }}{% endset %}
97
+ {{ field.textarea('reply-' + loop.index, fields.body, replyLabel, rows='4', required=true,
98
+ hint='Markdown, rendered the same way a visitor’s comment is. It is posted approved, under this one.') }}
99
+ <div class="mt-2">{{ button('Post reply', type='submit', color='primary') }}</div>
86
100
  </form>
87
- {% endif %}
88
-
89
- <form method="post" action="{{ moderateUrl }}">
90
- <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
91
- <input type="hidden" name="{{ fields.id }}" value="{{ row.id }}" />
92
- <input type="hidden" name="{{ fields.status }}" value="{{ status }}" />
93
- <button type="submit" name="{{ fields.action }}" value="delete">Delete</button>
94
- </form>
95
- </div>
96
-
97
- <details class="admin-comment-reply">
98
- <summary>Reply</summary>
99
- <form method="post" action="{{ replyUrl }}">
100
- <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
101
- <input type="hidden" name="{{ fields.id }}" value="{{ row.id }}" />
102
- <input type="hidden" name="{{ fields.status }}" value="{{ status }}" />
103
- <label class="admin-visually-hidden" for="reply-{{ loop.index }}">
104
- Reply to {{ row.author }}
105
- </label>
106
- <textarea id="reply-{{ loop.index }}" name="{{ fields.body }}" rows="4" required></textarea>
107
- <p class="admin-hint">Markdown, rendered the same way a visitor’s comment is. It is posted approved, under this one.</p>
108
- <p class="admin-settings-buttons">
109
- <button type="submit">Post reply</button>
110
- </p>
111
- </form>
112
- </details>
101
+ {% endcall %}
102
+ {% endcall %}
113
103
  </li>
114
104
  {% endfor %}
115
105
  </ul>
116
106
 
117
- {% if previousUrl or nextUrl %}
118
- <nav class="admin-pagination" aria-label="Pages">
119
- {% if previousUrl %}
120
- <a rel="prev" href="{{ previousUrl }}">Newer</a>
121
- {% endif %}
122
- <span>Page {{ page }} of {{ pages }}</span>
123
- {% if nextUrl %}
124
- <a rel="next" href="{{ nextUrl }}">Older</a>
125
- {% endif %}
126
- </nav>
127
- {% endif %}
107
+ <div class="mb-6">
108
+ {{ pagination(page, pages=pages, previousUrl=previousUrl, nextUrl=nextUrl) }}
109
+ </div>
128
110
  {% endif %}
129
111
 
130
- <p class="admin-hint">
112
+ <p class="max-w-prose text-sm">
131
113
  Comments live as one JSON file per post under <code>content/_data/comments/</code>, so they
132
114
  are in git beside the posts and an Eleventy build of the same directory shows them. Everything
133
115
  here rewrites that file; the database is only an index of it. Spam is kept rather than deleted,
@@ -1,72 +1,60 @@
1
1
  {% extends "layouts/shell.njk" %}
2
+ {% from "components/stat.njk" import stats, stat %}
3
+ {% from "components/card.njk" import card %}
4
+ {% from "components/table.njk" import table %}
5
+ {% from "components/badge.njk" import status %}
2
6
 
3
7
  {% block title %}Dashboard{% endblock %}
4
8
 
5
9
  {% block content %}
6
- <h1>Dashboard</h1>
10
+ <h1 class="mb-6 text-2xl font-bold">Dashboard</h1>
7
11
 
8
- <div class="admin-panels">
9
- <section class="admin-panel">
10
- <h2>At a glance</h2>
11
- <dl class="admin-counts">
12
- <div>
13
- <dt>Published posts</dt>
14
- <dd>{{ counts.posts }}</dd>
15
- </div>
16
- <div>
17
- <dt>Drafts</dt>
18
- <dd>{{ counts.drafts }}</dd>
19
- </div>
20
- <div>
21
- <dt>Pages</dt>
22
- <dd>{{ counts.pages }}</dd>
23
- </div>
24
- <div>
25
- <dt>Followers</dt>
26
- <dd><a href="{{ federationUrl }}">{{ followers }}</a></dd>
27
- </div>
28
- <div>
29
- <dt>Comments waiting</dt>
30
- <dd><a href="{{ commentsUrl }}?status=pending">{{ pendingComments }}</a></dd>
31
- </div>
32
- <div>
33
- <dt>Messages unread</dt>
34
- <dd><a href="{{ messagesUrl }}">{{ unreadMessages }}</a></dd>
35
- </div>
36
- </dl>
37
- </section>
12
+ {% set glance = [
13
+ { label: 'Published posts', value: counts.posts },
14
+ { label: 'Drafts', value: counts.drafts },
15
+ { label: 'Pages', value: counts.pages },
16
+ { label: 'Followers', value: followers, href: federationUrl },
17
+ { label: 'Comments waiting', value: pendingComments, href: commentsUrl + '?status=pending' },
18
+ { label: 'Messages unread', value: unreadMessages, href: messagesUrl }
19
+ ] %}
38
20
 
39
- <section class="admin-panel">
40
- <h2>Recent posts</h2>
41
- {% if recent.length %}
42
- <table class="admin-list">
43
- <caption class="admin-visually-hidden">Recent posts</caption>
44
- <thead>
21
+ <section class="@container mb-8">
22
+ <h2 class="mb-3 text-lg font-semibold">At a glance</h2>
23
+ <div class="grid grid-cols-2 gap-4 @2xl:grid-cols-3 @5xl:grid-cols-6">
24
+ {% for count in glance %}
25
+ {% call stats() %}{{ stat(count.label, count.value, href=count.href) }}{% endcall %}
26
+ {% endfor %}
27
+ </div>
28
+ </section>
29
+
30
+ {% call card(title='Recent posts') %}
31
+ {% if recent.length %}
32
+ {% call table('Recent posts', inCard=true) %}
33
+ <thead>
34
+ <tr>
35
+ <th scope="col">Title</th>
36
+ <th scope="col">Date</th>
37
+ <th scope="col">Status</th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ {% for post in recent %}
45
42
  <tr>
46
- <th scope="col">Title</th>
47
- <th scope="col">Date</th>
48
- <th scope="col">Status</th>
43
+ <td><a class="link link-hover" href="{{ post.editUrl }}">{{ post.title }}</a></td>
44
+ <td class="whitespace-nowrap">{{ post.date | date('html') }}</td>
45
+ <td>
46
+ {% if post.draft %}
47
+ {{ status('draft', 'Draft') }}
48
+ {% else %}
49
+ {{ status('published', 'Published') }}
50
+ {% endif %}
51
+ </td>
49
52
  </tr>
50
- </thead>
51
- <tbody>
52
- {% for post in recent %}
53
- <tr>
54
- <td><a href="{{ post.editUrl }}">{{ post.title }}</a></td>
55
- <td>{{ post.date | date('html') }}</td>
56
- <td>
57
- {% if post.draft %}
58
- <span class="admin-status admin-status-draft">Draft</span>
59
- {% else %}
60
- <span class="admin-status">Published</span>
61
- {% endif %}
62
- </td>
63
- </tr>
64
- {% endfor %}
65
- </tbody>
66
- </table>
67
- {% else %}
68
- <p class="admin-empty">Nothing written yet.</p>
69
- {% endif %}
70
- </section>
71
- </div>
53
+ {% endfor %}
54
+ </tbody>
55
+ {% endcall %}
56
+ {% else %}
57
+ <p>Nothing written yet.</p>
58
+ {% endif %}
59
+ {% endcall %}
72
60
  {% endblock %}