@geekity/cms 0.17.0 → 0.19.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 (297) hide show
  1. package/README.md +163 -50
  2. package/admin/components/public-admin-bar.njk +3 -1
  3. package/admin/pages/documents/editor.njk +286 -158
  4. package/admin/pages/documents/list.njk +2 -0
  5. package/admin/pages/indieauth/consent.njk +4 -0
  6. package/admin/pages/settings/privacy.njk +37 -0
  7. package/admin/pages/users/apps.njk +109 -3
  8. package/admin/static/admin.css +37 -14
  9. package/dist/admin/documents.d.ts +34 -4
  10. package/dist/admin/documents.d.ts.map +1 -1
  11. package/dist/admin/documents.js +161 -23
  12. package/dist/admin/documents.js.map +1 -1
  13. package/dist/admin/editor-layout.d.ts +13 -0
  14. package/dist/admin/editor-layout.d.ts.map +1 -0
  15. package/dist/admin/editor-layout.js +61 -0
  16. package/dist/admin/editor-layout.js.map +1 -0
  17. package/dist/admin/enclosure-field.d.ts +2 -3
  18. package/dist/admin/enclosure-field.d.ts.map +1 -1
  19. package/dist/admin/enclosure-field.js +27 -7
  20. package/dist/admin/enclosure-field.js.map +1 -1
  21. package/dist/admin/index.d.ts +1 -0
  22. package/dist/admin/index.d.ts.map +1 -1
  23. package/dist/admin/index.js +1 -0
  24. package/dist/admin/index.js.map +1 -1
  25. package/dist/admin/location-field.d.ts +27 -0
  26. package/dist/admin/location-field.d.ts.map +1 -0
  27. package/dist/admin/location-field.js +98 -0
  28. package/dist/admin/location-field.js.map +1 -0
  29. package/dist/admin/menu.d.ts.map +1 -1
  30. package/dist/admin/menu.js +1 -0
  31. package/dist/admin/menu.js.map +1 -1
  32. package/dist/admin/photo-field.d.ts +2 -3
  33. package/dist/admin/photo-field.d.ts.map +1 -1
  34. package/dist/admin/photo-field.js +4 -2
  35. package/dist/admin/photo-field.js.map +1 -1
  36. package/dist/admin/preview.d.ts.map +1 -1
  37. package/dist/admin/preview.js +18 -5
  38. package/dist/admin/preview.js.map +1 -1
  39. package/dist/admin/read-field.d.ts +22 -0
  40. package/dist/admin/read-field.d.ts.map +1 -0
  41. package/dist/admin/read-field.js +61 -0
  42. package/dist/admin/read-field.js.map +1 -0
  43. package/dist/admin/settings-page.d.ts +3 -3
  44. package/dist/admin/settings-page.js +2 -2
  45. package/dist/admin/settings-pages.d.ts +2 -2
  46. package/dist/admin/settings-pages.d.ts.map +1 -1
  47. package/dist/admin/settings-pages.js +4 -2
  48. package/dist/admin/settings-pages.js.map +1 -1
  49. package/dist/admin/settings-privacy.d.ts +3 -0
  50. package/dist/admin/settings-privacy.d.ts.map +1 -0
  51. package/dist/admin/settings-privacy.js +10 -0
  52. package/dist/admin/settings-privacy.js.map +1 -0
  53. package/dist/admin/settings-reading.js +2 -2
  54. package/dist/admin/settings-reading.js.map +1 -1
  55. package/dist/admin/settings.d.ts +18 -2
  56. package/dist/admin/settings.d.ts.map +1 -1
  57. package/dist/admin/settings.js +27 -0
  58. package/dist/admin/settings.js.map +1 -1
  59. package/dist/admin/templates.d.ts +1 -0
  60. package/dist/admin/templates.d.ts.map +1 -1
  61. package/dist/admin/templates.js +1 -0
  62. package/dist/admin/templates.js.map +1 -1
  63. package/dist/admin/uploads.d.ts.map +1 -1
  64. package/dist/admin/uploads.js +14 -1
  65. package/dist/admin/uploads.js.map +1 -1
  66. package/dist/cli.d.ts +1 -1
  67. package/dist/cli.d.ts.map +1 -1
  68. package/dist/cli.js +34 -0
  69. package/dist/cli.js.map +1 -1
  70. package/dist/comments/routes.js +2 -2
  71. package/dist/comments/routes.js.map +1 -1
  72. package/dist/contact/routes.js +2 -2
  73. package/dist/contact/routes.js.map +1 -1
  74. package/dist/content/kept-properties.d.ts +5 -0
  75. package/dist/content/kept-properties.d.ts.map +1 -0
  76. package/dist/content/kept-properties.js +12 -0
  77. package/dist/content/kept-properties.js.map +1 -0
  78. package/dist/content/location.d.ts +62 -0
  79. package/dist/content/location.d.ts.map +1 -0
  80. package/dist/content/location.js +240 -0
  81. package/dist/content/location.js.map +1 -0
  82. package/dist/content/locations.d.ts +6 -0
  83. package/dist/content/locations.d.ts.map +1 -0
  84. package/dist/content/locations.js +7 -0
  85. package/dist/content/locations.js.map +1 -0
  86. package/dist/content/metadata/gif.d.ts +3 -0
  87. package/dist/content/metadata/gif.d.ts.map +1 -0
  88. package/dist/content/metadata/gif.js +65 -0
  89. package/dist/content/metadata/gif.js.map +1 -0
  90. package/dist/content/metadata/index.d.ts +6 -0
  91. package/dist/content/metadata/index.d.ts.map +1 -0
  92. package/dist/content/metadata/index.js +24 -0
  93. package/dist/content/metadata/index.js.map +1 -0
  94. package/dist/content/metadata/isobmff.d.ts +22 -0
  95. package/dist/content/metadata/isobmff.d.ts.map +1 -0
  96. package/dist/content/metadata/isobmff.js +215 -0
  97. package/dist/content/metadata/isobmff.js.map +1 -0
  98. package/dist/content/metadata/jpeg.d.ts +3 -0
  99. package/dist/content/metadata/jpeg.d.ts.map +1 -0
  100. package/dist/content/metadata/jpeg.js +117 -0
  101. package/dist/content/metadata/jpeg.js.map +1 -0
  102. package/dist/content/metadata/png.d.ts +3 -0
  103. package/dist/content/metadata/png.d.ts.map +1 -0
  104. package/dist/content/metadata/png.js +74 -0
  105. package/dist/content/metadata/png.js.map +1 -0
  106. package/dist/content/metadata/shared.d.ts +17 -0
  107. package/dist/content/metadata/shared.d.ts.map +1 -0
  108. package/dist/content/metadata/shared.js +42 -0
  109. package/dist/content/metadata/shared.js.map +1 -0
  110. package/dist/content/metadata/sweep.d.ts +14 -0
  111. package/dist/content/metadata/sweep.d.ts.map +1 -0
  112. package/dist/content/metadata/sweep.js +54 -0
  113. package/dist/content/metadata/sweep.js.map +1 -0
  114. package/dist/content/metadata/tiff.d.ts +7 -0
  115. package/dist/content/metadata/tiff.d.ts.map +1 -0
  116. package/dist/content/metadata/tiff.js +56 -0
  117. package/dist/content/metadata/tiff.js.map +1 -0
  118. package/dist/content/metadata/webp.d.ts +3 -0
  119. package/dist/content/metadata/webp.d.ts.map +1 -0
  120. package/dist/content/metadata/webp.js +67 -0
  121. package/dist/content/metadata/webp.js.map +1 -0
  122. package/dist/content/permalink-file.d.ts +7 -0
  123. package/dist/content/permalink-file.d.ts.map +1 -0
  124. package/dist/content/permalink-file.js +77 -0
  125. package/dist/content/permalink-file.js.map +1 -0
  126. package/dist/content/post-type.d.ts +4 -2
  127. package/dist/content/post-type.d.ts.map +1 -1
  128. package/dist/content/post-type.js +9 -1
  129. package/dist/content/post-type.js.map +1 -1
  130. package/dist/content/read.d.ts +22 -0
  131. package/dist/content/read.d.ts.map +1 -0
  132. package/dist/content/read.js +67 -0
  133. package/dist/content/read.js.map +1 -0
  134. package/dist/content/store.d.ts +4 -4
  135. package/dist/content/store.d.ts.map +1 -1
  136. package/dist/content/store.js +19 -39
  137. package/dist/content/store.js.map +1 -1
  138. package/dist/content/sync.d.ts.map +1 -1
  139. package/dist/content/sync.js +3 -13
  140. package/dist/content/sync.js.map +1 -1
  141. package/dist/content/visibility.d.ts +11 -0
  142. package/dist/content/visibility.d.ts.map +1 -0
  143. package/dist/content/visibility.js +14 -0
  144. package/dist/content/visibility.js.map +1 -0
  145. package/dist/federation/article.d.ts +4 -3
  146. package/dist/federation/article.d.ts.map +1 -1
  147. package/dist/federation/article.js +66 -20
  148. package/dist/federation/article.js.map +1 -1
  149. package/dist/federation/citations.d.ts +7 -6
  150. package/dist/federation/citations.d.ts.map +1 -1
  151. package/dist/federation/citations.js +44 -28
  152. package/dist/federation/citations.js.map +1 -1
  153. package/dist/federation/delivery.d.ts +3 -1
  154. package/dist/federation/delivery.d.ts.map +1 -1
  155. package/dist/federation/delivery.js +29 -21
  156. package/dist/federation/delivery.js.map +1 -1
  157. package/dist/federation/mount.d.ts.map +1 -1
  158. package/dist/federation/mount.js +3 -1
  159. package/dist/federation/mount.js.map +1 -1
  160. package/dist/index.d.ts +6 -5
  161. package/dist/index.d.ts.map +1 -1
  162. package/dist/index.js +5 -1
  163. package/dist/index.js.map +1 -1
  164. package/dist/indexnow.js +2 -2
  165. package/dist/indexnow.js.map +1 -1
  166. package/dist/indieauth/activity-log.d.ts +2 -0
  167. package/dist/indieauth/activity-log.d.ts.map +1 -1
  168. package/dist/indieauth/activity-log.js +12 -2
  169. package/dist/indieauth/activity-log.js.map +1 -1
  170. package/dist/indieauth/app-activity.js +5 -1
  171. package/dist/indieauth/app-activity.js.map +1 -1
  172. package/dist/indieauth/bearer.d.ts +10 -6
  173. package/dist/indieauth/bearer.d.ts.map +1 -1
  174. package/dist/indieauth/bearer.js +28 -25
  175. package/dist/indieauth/bearer.js.map +1 -1
  176. package/dist/indieauth/client-id.d.ts +10 -0
  177. package/dist/indieauth/client-id.d.ts.map +1 -0
  178. package/dist/indieauth/client-id.js +30 -0
  179. package/dist/indieauth/client-id.js.map +1 -0
  180. package/dist/indieauth/connected-apps.d.ts +4 -0
  181. package/dist/indieauth/connected-apps.d.ts.map +1 -1
  182. package/dist/indieauth/connected-apps.js +149 -20
  183. package/dist/indieauth/connected-apps.js.map +1 -1
  184. package/dist/indieauth/consent.d.ts.map +1 -1
  185. package/dist/indieauth/consent.js +5 -1
  186. package/dist/indieauth/consent.js.map +1 -1
  187. package/dist/indieauth/grants.d.ts +2 -7
  188. package/dist/indieauth/grants.d.ts.map +1 -1
  189. package/dist/indieauth/grants.js.map +1 -1
  190. package/dist/indieauth/redeem.d.ts.map +1 -1
  191. package/dist/indieauth/redeem.js +10 -5
  192. package/dist/indieauth/redeem.js.map +1 -1
  193. package/dist/indieauth/request.d.ts +8 -4
  194. package/dist/indieauth/request.d.ts.map +1 -1
  195. package/dist/indieauth/request.js +29 -30
  196. package/dist/indieauth/request.js.map +1 -1
  197. package/dist/indieauth/token.js +2 -2
  198. package/dist/indieauth/token.js.map +1 -1
  199. package/dist/indieauth/tokens.d.ts +28 -7
  200. package/dist/indieauth/tokens.d.ts.map +1 -1
  201. package/dist/indieauth/tokens.js +26 -5
  202. package/dist/indieauth/tokens.js.map +1 -1
  203. package/dist/micropub/create.d.ts +26 -0
  204. package/dist/micropub/create.d.ts.map +1 -1
  205. package/dist/micropub/create.js +158 -13
  206. package/dist/micropub/create.js.map +1 -1
  207. package/dist/micropub/endpoint.d.ts +4 -5
  208. package/dist/micropub/endpoint.d.ts.map +1 -1
  209. package/dist/micropub/endpoint.js +83 -25
  210. package/dist/micropub/endpoint.js.map +1 -1
  211. package/dist/micropub/media.d.ts +6 -2
  212. package/dist/micropub/media.d.ts.map +1 -1
  213. package/dist/micropub/media.js +43 -37
  214. package/dist/micropub/media.js.map +1 -1
  215. package/dist/micropub/update.d.ts +13 -3
  216. package/dist/micropub/update.d.ts.map +1 -1
  217. package/dist/micropub/update.js +60 -7
  218. package/dist/micropub/update.js.map +1 -1
  219. package/dist/notify.d.ts.map +1 -1
  220. package/dist/notify.js +2 -5
  221. package/dist/notify.js.map +1 -1
  222. package/dist/web/absolute-urls.d.ts +2 -0
  223. package/dist/web/absolute-urls.d.ts.map +1 -0
  224. package/dist/web/absolute-urls.js +22 -0
  225. package/dist/web/absolute-urls.js.map +1 -0
  226. package/dist/web/admin-bar.d.ts.map +1 -1
  227. package/dist/web/admin-bar.js +25 -0
  228. package/dist/web/admin-bar.js.map +1 -1
  229. package/dist/web/context.d.ts +29 -1
  230. package/dist/web/context.d.ts.map +1 -1
  231. package/dist/web/context.js +33 -0
  232. package/dist/web/context.js.map +1 -1
  233. package/dist/web/conversation.d.ts.map +1 -1
  234. package/dist/web/conversation.js +5 -4
  235. package/dist/web/conversation.js.map +1 -1
  236. package/dist/web/documents.d.ts +34 -10
  237. package/dist/web/documents.d.ts.map +1 -1
  238. package/dist/web/documents.js +41 -15
  239. package/dist/web/documents.js.map +1 -1
  240. package/dist/web/feed-item.d.ts +25 -6
  241. package/dist/web/feed-item.d.ts.map +1 -1
  242. package/dist/web/feed-item.js +31 -7
  243. package/dist/web/feed-item.js.map +1 -1
  244. package/dist/web/feed-json.d.ts +3 -1
  245. package/dist/web/feed-json.d.ts.map +1 -1
  246. package/dist/web/feed-json.js +2 -0
  247. package/dist/web/feed-json.js.map +1 -1
  248. package/dist/web/feed-source.d.ts +6 -0
  249. package/dist/web/feed-source.d.ts.map +1 -1
  250. package/dist/web/feed-source.js.map +1 -1
  251. package/dist/web/feeds.d.ts.map +1 -1
  252. package/dist/web/feeds.js +2 -0
  253. package/dist/web/feeds.js.map +1 -1
  254. package/dist/web/html-attributes.d.ts +3 -0
  255. package/dist/web/html-attributes.d.ts.map +1 -0
  256. package/dist/web/html-attributes.js +15 -0
  257. package/dist/web/html-attributes.js.map +1 -0
  258. package/dist/web/index.d.ts +1 -1
  259. package/dist/web/index.d.ts.map +1 -1
  260. package/dist/web/index.js +1 -1
  261. package/dist/web/index.js.map +1 -1
  262. package/dist/web/negotiate.d.ts +2 -0
  263. package/dist/web/negotiate.d.ts.map +1 -1
  264. package/dist/web/negotiate.js +2 -1
  265. package/dist/web/negotiate.js.map +1 -1
  266. package/dist/web/render.d.ts +13 -4
  267. package/dist/web/render.d.ts.map +1 -1
  268. package/dist/web/render.js +25 -12
  269. package/dist/web/render.js.map +1 -1
  270. package/dist/web/routes.d.ts.map +1 -1
  271. package/dist/web/routes.js +26 -11
  272. package/dist/web/routes.js.map +1 -1
  273. package/dist/webmention/fetch-public.d.ts +3 -1
  274. package/dist/webmention/fetch-public.d.ts.map +1 -1
  275. package/dist/webmention/fetch-public.js +17 -11
  276. package/dist/webmention/fetch-public.js.map +1 -1
  277. package/dist/webmention/reply-context.d.ts +18 -5
  278. package/dist/webmention/reply-context.d.ts.map +1 -1
  279. package/dist/webmention/reply-context.js +118 -19
  280. package/dist/webmention/reply-context.js.map +1 -1
  281. package/dist/webmention/reply-contexts.d.ts +17 -14
  282. package/dist/webmention/reply-contexts.d.ts.map +1 -1
  283. package/dist/webmention/reply-contexts.js +32 -21
  284. package/dist/webmention/reply-contexts.js.map +1 -1
  285. package/dist/webmention/service.d.ts.map +1 -1
  286. package/dist/webmention/service.js +7 -2
  287. package/dist/webmention/service.js.map +1 -1
  288. package/package.json +9 -9
  289. package/themes/default/README.md +45 -7
  290. package/themes/default/layouts/base.njk +2 -0
  291. package/themes/default/layouts/post.njk +12 -7
  292. package/themes/default/partials/citations.njk +12 -3
  293. package/themes/default/partials/cited-page.njk +41 -0
  294. package/themes/default/partials/location.njk +12 -0
  295. package/themes/default/partials/post-list.njk +10 -5
  296. package/themes/default/partials/read.njk +5 -0
  297. package/themes/default/partials/reply-context.njk +2 -9
@@ -6,11 +6,37 @@
6
6
 
7
7
  {% block title %}{{ heading }}{% endblock %}
8
8
 
9
+ {% import "components/fields.njk" as field %}
10
+
11
+ {# The legend repeats the summary so every box in it is still announced with
12
+ the name of its group. #}
13
+ {% macro group(name, open, class='admin-editor-group') %}
14
+ <details class="{{ class }}"{% if open %} open{% endif %}>
15
+ <summary>{{ name }}</summary>
16
+ <fieldset>
17
+ <legend class="admin-visually-hidden">{{ name }}</legend>
18
+ {{ caller() }}
19
+ </fieldset>
20
+ </details>
21
+ {% endmacro %}
22
+
23
+ {% macro invalid(id, hint='') -%}
24
+ {% if errorField == id %} aria-invalid="true" aria-describedby="{{ id }}-error{% if hint %} {{ hint }}{% endif %}"{% elif hint %} aria-describedby="{{ hint }}"{% endif %}
25
+ {%- endmacro %}
26
+
27
+ {% macro problem(id) %}
28
+ {% if errorField == id %}<p class="admin-field-error" id="{{ id }}-error">{{ error }}</p>{% endif %}
29
+ {% endmacro %}
30
+
9
31
  {% block content %}
10
32
  <h1>{{ heading }}</h1>
11
33
 
12
- {% if error %}
13
- <p class="admin-error">{{ error }}</p>
34
+ {% if error and errorField %}
35
+ {% call field.summary('Nothing was saved. Fix the field marked below and try again.') %}
36
+ {{ field.problem(errorField, error) }}
37
+ {% endcall %}
38
+ {% elif error %}
39
+ {{ field.summary(error) }}
14
40
  {% endif %}
15
41
 
16
42
  {% if trashed %}
@@ -33,7 +59,8 @@
33
59
  <div class="admin-editor-main">
34
60
  <label for="editor-title">Title</label>
35
61
  {#- A post may go without one, and is then a note; a page is always named. -#}
36
- <input id="editor-title" name="title" type="text" value="{{ form.title }}"{% if kind.type == "page" %} required{% endif %} autofocus />
62
+ <input id="editor-title" name="title" type="text" value="{{ form.title }}"{% if kind.type == "page" %} required{% endif %} autofocus{{ invalid('editor-title') }} />
63
+ {{ problem('editor-title') }}
37
64
  {% if kind.type == "post" %}
38
65
  <p class="admin-hint">Leave it empty to write a note: the post is then its words, and takes its slug from the first of them.</p>
39
66
  {% endif %}
@@ -64,189 +91,288 @@
64
91
  </div>
65
92
 
66
93
  {% if kind.type == "post" %}
67
- <fieldset class="admin-editor-enclosure admin-editor-photos">
68
- <legend>Photos</legend>
94
+ {% call group('Photos', open.photos) %}
69
95
  <p class="admin-hint">
70
96
  Each is an image in the media library or an address starting https://. Leave the alt text
71
97
  empty to use the media library’s. Empty an address to remove that photo.
72
98
  </p>
73
99
  {% for row in photoRows %}
74
100
  {% set n = loop.index0 %}
75
- <fieldset class="admin-editor-alternate">
76
- <legend>{% if row.url %}Photo {{ loop.index }}{% else %}Add a photo{% endif %}</legend>
101
+ {% call group('Photo ' + loop.index if row.url else 'Add a photo', row.url, 'admin-editor-group admin-editor-row') %}
77
102
  <label for="editor-photo-url-{{ n }}">Address</label>
78
- <input id="editor-photo-url-{{ n }}" name="{{ photoFields.url }}{{ n }}" type="text" value="{{ row.url }}" list="editor-photo-uploads" autocomplete="off" />
103
+ <input id="editor-photo-url-{{ n }}" name="{{ photoFields.url }}{{ n }}" type="text" value="{{ row.url }}" list="editor-photo-uploads" autocomplete="off"{{ invalid('editor-photo-url-' + n) }} />
104
+ {{ problem('editor-photo-url-' + n) }}
79
105
  <label for="editor-photo-alt-{{ n }}">Alt text</label>
80
106
  <input id="editor-photo-alt-{{ n }}" name="{{ photoFields.alt }}{{ n }}" type="text" value="{{ row.alt }}"{% if row.libraryAlt %} placeholder="{{ row.libraryAlt }}"{% endif %} />
81
- </fieldset>
107
+ {% endcall %}
82
108
  {% endfor %}
83
109
  <datalist id="editor-photo-uploads">
84
110
  {% for url in photoChoices %}
85
111
  <option value="{{ url }}"></option>
86
112
  {% endfor %}
87
113
  </datalist>
88
- </fieldset>
89
- <fieldset class="admin-editor-enclosure">
90
- <legend>Recording</legend>
114
+ {% endcall %}
91
115
 
116
+ {% call group('Location', open.location) %}
117
+ <p class="admin-hint">
118
+ Kept in <code>data/locations.json</code>, not in the post’s file. What readers see of it
119
+ is chosen on Settings &gt; Privacy. Empty every box to remove it.
120
+ </p>
121
+ <label for="editor-location-geo">Coordinates</label>
122
+ <input id="editor-location-geo" name="{{ locationFields.geo }}" type="text" value="{{ form.location.geo }}" placeholder="48.85837, 2.29448" autocomplete="off" spellcheck="false"{{ invalid('editor-location-geo', 'editor-location-geo-hint') }} />
123
+ {{ problem('editor-location-geo') }}
124
+ <p class="admin-hint" id="editor-location-geo-hint">Latitude, longitude, as decimal degrees. A <code>geo:</code> URI works too.</p>
125
+ <label for="editor-location-accuracy">Accuracy</label>
126
+ <input id="editor-location-accuracy" name="{{ locationFields.accuracy }}" type="text" inputmode="numeric" value="{{ form.location.accuracy }}" autocomplete="off"{{ invalid('editor-location-accuracy', 'editor-location-accuracy-hint') }} />
127
+ {{ problem('editor-location-accuracy') }}
128
+ <p class="admin-hint" id="editor-location-accuracy-hint">Metres. Leave it empty if you do not know.</p>
129
+ <label for="editor-location-name">Place</label>
130
+ <input id="editor-location-name" name="{{ locationFields.name }}" type="text" value="{{ form.location.name }}" />
131
+ <label for="editor-location-locality">Locality</label>
132
+ <input id="editor-location-locality" name="{{ locationFields.locality }}" type="text" value="{{ form.location.locality }}" />
133
+ <label for="editor-location-region">Region</label>
134
+ <input id="editor-location-region" name="{{ locationFields.region }}" type="text" value="{{ form.location.region }}" />
135
+ <label for="editor-location-country">Country</label>
136
+ <input id="editor-location-country" name="{{ locationFields.country }}" type="text" value="{{ form.location.country }}" />
137
+ <p class="admin-check">
138
+ <input id="editor-location-checkin" name="{{ locationFields.checkin }}" type="checkbox" value="1"{% if form.location.checkin %} checked{% endif %} aria-describedby="editor-location-checkin-hint" />
139
+ <label for="editor-location-checkin">A check-in</label>
140
+ </p>
141
+ <p class="admin-hint" id="editor-location-checkin-hint">A Micropub app sent it as a checkin, and reads it back as one. Readers see it as any other location.</p>
142
+ {% endcall %}
143
+
144
+ {% call group('Recording', open.recording) %}
92
145
  <label for="editor-enclosure-url">Audio or video file</label>
93
- <select id="editor-enclosure-url" name="{{ enclosureFields.url }}" aria-describedby="editor-enclosure-url-hint">
146
+ <select id="editor-enclosure-url" name="{{ enclosureFields.url }}"{{ invalid('editor-enclosure-url', 'editor-enclosure-url-hint') }}>
94
147
  <option value=""{% if form.enclosure.url == "" %} selected{% endif %}>None</option>
95
148
  {% for choice in enclosureChoices.media %}
96
149
  <option value="{{ choice.url }}"{% if choice.url == form.enclosure.url %} selected{% endif %}>{{ choice.label }}</option>
97
150
  {% endfor %}
98
151
  </select>
152
+ {{ problem('editor-enclosure-url') }}
99
153
  <p class="admin-hint" id="editor-enclosure-url-hint">
100
154
  Upload the file to the media library first. Feeds carry it as the post’s enclosure and the
101
155
  post page plays it. None removes the recording, its transcript and its other versions.
102
156
  </p>
103
157
 
104
158
  <label for="editor-enclosure-duration">Duration</label>
105
- <input id="editor-enclosure-duration" name="{{ enclosureFields.duration }}" type="text" inputmode="numeric" value="{{ form.enclosure.duration }}" autocomplete="off" aria-describedby="editor-enclosure-duration-hint" />
159
+ <input id="editor-enclosure-duration" name="{{ enclosureFields.duration }}" type="text" inputmode="numeric" value="{{ form.enclosure.duration }}" autocomplete="off"{{ invalid('editor-enclosure-duration', 'editor-enclosure-duration-hint') }} />
160
+ {{ problem('editor-enclosure-duration') }}
106
161
  <p class="admin-hint" id="editor-enclosure-duration-hint">Seconds, or 30:34, or 1:02:03. Leave it empty if you do not know.</p>
107
162
 
108
163
  <label for="editor-enclosure-transcript">Transcript or captions</label>
109
- <input id="editor-enclosure-transcript" name="{{ enclosureFields.transcriptUrl }}" type="text" value="{{ form.enclosure.transcriptUrl }}" list="editor-transcript-uploads" autocomplete="off" aria-describedby="editor-enclosure-transcript-hint" />
164
+ <input id="editor-enclosure-transcript" name="{{ enclosureFields.transcriptUrl }}" type="text" value="{{ form.enclosure.transcriptUrl }}" list="editor-transcript-uploads" autocomplete="off"{{ invalid('editor-enclosure-transcript', 'editor-enclosure-transcript-hint') }} />
165
+ {{ problem('editor-enclosure-transcript') }}
110
166
  <datalist id="editor-transcript-uploads">
111
167
  {% for url in enclosureChoices.transcripts %}
112
168
  <option value="{{ url }}"></option>
113
169
  {% endfor %}
114
170
  </datalist>
115
171
  <label for="editor-enclosure-transcript-type">Transcript type</label>
116
- <select id="editor-enclosure-transcript-type" name="{{ enclosureFields.transcriptType }}" aria-describedby="editor-enclosure-transcript-hint">
172
+ <select id="editor-enclosure-transcript-type" name="{{ enclosureFields.transcriptType }}"{{ invalid('editor-enclosure-transcript-type', 'editor-enclosure-transcript-hint') }}>
117
173
  <option value=""{% if form.enclosure.transcriptType == "" %} selected{% endif %}>From the file</option>
118
174
  {% for type in transcriptTypes %}
119
175
  <option value="{{ type }}"{% if type == form.enclosure.transcriptType %} selected{% endif %}>{{ type }}</option>
120
176
  {% endfor %}
121
177
  </select>
178
+ {{ problem('editor-enclosure-transcript-type') }}
122
179
  <p class="admin-hint" id="editor-enclosure-transcript-hint">
123
180
  A .vtt, .srt or .txt file in the media library, or an address starting https://. A linked
124
181
  transcript needs its type.
125
182
  </p>
126
183
 
127
- <h2 class="admin-editor-enclosure-heading">Other versions</h2>
128
- <p class="admin-hint">
129
- A video of an audio episode, a smaller file, another language. Each is a file in the media
130
- library or an address starting https://, which needs its type. Empty an address to remove
131
- that version.
132
- </p>
133
- {% for row in alternateRows %}
134
- {% set n = loop.index0 %}
135
- <fieldset class="admin-editor-alternate">
136
- <legend>{% if row.url %}Version {{ loop.index }}{% else %}Add a version{% endif %}</legend>
137
- <label for="editor-alternate-url-{{ n }}">Address</label>
138
- <input id="editor-alternate-url-{{ n }}" name="{{ enclosureFields.alternate.url }}{{ n }}" type="text" value="{{ row.url }}" list="editor-media-uploads" autocomplete="off" />
139
- <label for="editor-alternate-type-{{ n }}">Type</label>
140
- <input id="editor-alternate-type-{{ n }}" name="{{ enclosureFields.alternate.type }}{{ n }}" type="text" value="{{ row.type }}" placeholder="video/mp4" autocomplete="off" spellcheck="false" />
141
- <label for="editor-alternate-title-{{ n }}">Title</label>
142
- <input id="editor-alternate-title-{{ n }}" name="{{ enclosureFields.alternate.title }}{{ n }}" type="text" value="{{ row.title }}" maxlength="32" />
143
- <label for="editor-alternate-height-{{ n }}">Height</label>
144
- <input id="editor-alternate-height-{{ n }}" name="{{ enclosureFields.alternate.height }}{{ n }}" type="text" inputmode="numeric" value="{{ row.height }}" autocomplete="off" />
145
- <label for="editor-alternate-lang-{{ n }}">Language</label>
146
- <input id="editor-alternate-lang-{{ n }}" name="{{ enclosureFields.alternate.lang }}{{ n }}" type="text" value="{{ row.lang }}" autocomplete="off" spellcheck="false" />
147
- </fieldset>
148
- {% endfor %}
149
- <datalist id="editor-media-uploads">
150
- {% for choice in enclosureChoices.media %}
151
- <option value="{{ choice.url }}"></option>
184
+ {% call group('Other versions', open.versions) %}
185
+ <p class="admin-hint">
186
+ A video of an audio episode, a smaller file, another language. Each is a file in the media
187
+ library or an address starting https://, which needs its type. Empty an address to remove
188
+ that version.
189
+ </p>
190
+ {% for row in alternateRows %}
191
+ {% set n = loop.index0 %}
192
+ {% call group('Version ' + loop.index if row.url else 'Add a version', row.url, 'admin-editor-group admin-editor-row') %}
193
+ <label for="editor-alternate-url-{{ n }}">Address</label>
194
+ <input id="editor-alternate-url-{{ n }}" name="{{ enclosureFields.alternate.url }}{{ n }}" type="text" value="{{ row.url }}" list="editor-media-uploads" autocomplete="off"{{ invalid('editor-alternate-url-' + n) }} />
195
+ {{ problem('editor-alternate-url-' + n) }}
196
+ <label for="editor-alternate-type-{{ n }}">Type</label>
197
+ <input id="editor-alternate-type-{{ n }}" name="{{ enclosureFields.alternate.type }}{{ n }}" type="text" value="{{ row.type }}" placeholder="video/mp4" autocomplete="off" spellcheck="false"{{ invalid('editor-alternate-type-' + n) }} />
198
+ {{ problem('editor-alternate-type-' + n) }}
199
+ <label for="editor-alternate-title-{{ n }}">Title</label>
200
+ <input id="editor-alternate-title-{{ n }}" name="{{ enclosureFields.alternate.title }}{{ n }}" type="text" value="{{ row.title }}" maxlength="32"{{ invalid('editor-alternate-title-' + n) }} />
201
+ {{ problem('editor-alternate-title-' + n) }}
202
+ <label for="editor-alternate-height-{{ n }}">Height</label>
203
+ <input id="editor-alternate-height-{{ n }}" name="{{ enclosureFields.alternate.height }}{{ n }}" type="text" inputmode="numeric" value="{{ row.height }}" autocomplete="off"{{ invalid('editor-alternate-height-' + n) }} />
204
+ {{ problem('editor-alternate-height-' + n) }}
205
+ <label for="editor-alternate-lang-{{ n }}">Language</label>
206
+ <input id="editor-alternate-lang-{{ n }}" name="{{ enclosureFields.alternate.lang }}{{ n }}" type="text" value="{{ row.lang }}" autocomplete="off" spellcheck="false"{{ invalid('editor-alternate-lang-' + n) }} />
207
+ {{ problem('editor-alternate-lang-' + n) }}
208
+ {% endcall %}
152
209
  {% endfor %}
153
- </datalist>
154
- </fieldset>
210
+ <datalist id="editor-media-uploads">
211
+ {% for choice in enclosureChoices.media %}
212
+ <option value="{{ choice.url }}"></option>
213
+ {% endfor %}
214
+ </datalist>
215
+ {% endcall %}
216
+ {% endcall %}
155
217
  {% endif %}
156
218
  </div>
157
219
 
158
220
  <div class="admin-editor-side">
159
- <label for="editor-slug">Slug</label>
160
- <input id="editor-slug" name="slug" type="text" value="{{ form.slug }}" />
161
-
162
- <label for="editor-permalink">Permalink</label>
163
- <input id="editor-permalink" name="permalink" type="text" value="{{ form.permalink }}" />
164
- <p class="admin-hint">Leave it empty to let the {{ kind.singular }} take the URL its slug implies.</p>
165
-
166
- {% if kind.dated %}
167
- <label for="editor-date">Date</label>
168
- <input id="editor-date" name="date" type="text" value="{{ form.date }}" />
169
- {# The file holds a UTC instant; this field is that instant as the
170
- site's own clock reads it, so the zone has to be on the screen. #}
171
- <p class="admin-hint">Wall-clock time in {{ dateZone }}. Stored as UTC.</p>
172
- {% endif %}
221
+ {% call group('Publishing', open.publishing) %}
222
+ <p class="admin-check">
223
+ <input id="editor-draft" name="draft" type="checkbox" value="1"{% if form.draft %} checked{% endif %} />
224
+ <label for="editor-draft">Draft</label>
225
+ </p>
173
226
 
174
- {% if kind.tagged %}
175
- <label for="editor-tags">Tags</label>
176
- <input id="editor-tags" name="tags" type="text" value="{{ form.tags }}" />
177
- <p class="admin-hint">Separated by commas.</p>
178
- {% endif %}
227
+ {% if kind.dated %}
228
+ <label for="editor-date">Date</label>
229
+ <input id="editor-date" name="date" type="text" value="{{ form.date }}"{{ invalid('editor-date') }} />
230
+ {{ problem('editor-date') }}
231
+ {# The file holds a UTC instant; this field is that instant as the
232
+ site's own clock reads it, so the zone has to be on the screen. #}
233
+ <p class="admin-hint">Wall-clock time in {{ dateZone }}. Stored as UTC.</p>
234
+ {% endif %}
179
235
 
180
- {% if kind.categorised %}
181
- <label for="editor-categories">Categories</label>
182
- <input id="editor-categories" name="categories" type="text" value="{{ form.categories }}" />
183
- <p class="admin-hint">Separated by commas.</p>
184
- {% endif %}
236
+ <label for="editor-visibility">Visibility</label>
237
+ <select id="editor-visibility" name="visibility" aria-describedby="editor-visibility-hint">
238
+ {% if form.visibility.unrecognized is defined %}
239
+ <option value="{{ form.visibility.unrecognized }}" selected>{{ form.visibility.unrecognized }} (not recognized)</option>
240
+ {% endif %}
241
+ <option value="public"{% if form.visibility == "public" %} selected{% endif %}>Public</option>
242
+ <option value="unlisted"{% if form.visibility == "unlisted" %} selected{% endif %}>Unlisted</option>
243
+ </select>
244
+ <p class="admin-hint" id="editor-visibility-hint">
245
+ {% if form.visibility.unrecognized is defined %}
246
+ The front matter says <code>visibility: {{ form.visibility.unrecognized }}</code>, which this
247
+ site does not recognize. The {{ kind.singular }} is hidden until you choose Public or Unlisted:
248
+ its page answers 404 and it is not listed or sent to your followers.
249
+ {% endif %}
250
+ Unlisted writes <code>visibility: unlisted</code> into the front matter. The {{ kind.singular }}
251
+ keeps its page and still goes to your followers, but is left off the home page, archives,
252
+ feeds, the sitemap, search and llms.txt, and asks search engines not to index it.
253
+ </p>
185
254
 
186
- {% if kind.type == "post" %}
187
- {#- The mf2 `in-reply-to`: a URL here makes the post a reply, threads
188
- it in the fediverse and sends that page a webmention. -#}
189
- <label for="editor-in-reply-to">In reply to</label>
190
- <input id="editor-in-reply-to" name="in-reply-to" type="url" value="{{ form.inReplyTo }}" />
191
- <p class="admin-hint">The address of the post this one answers. Leave it empty for a post that answers nobody.</p>
192
-
193
- {#- The mf2 `like-of`, `repost-of` and `bookmark-of` (TASK-169): each
194
- cites a page the way a reply does, and sends it a webmention. -#}
195
- <label for="editor-like-of">Like of</label>
196
- <input id="editor-like-of" name="like-of" type="url" value="{{ form.likeOf }}" />
197
- <label for="editor-repost-of">Repost of</label>
198
- <input id="editor-repost-of" name="repost-of" type="url" value="{{ form.repostOf }}" />
199
- <label for="editor-bookmark-of">Bookmark of</label>
200
- <input id="editor-bookmark-of" name="bookmark-of" type="url" value="{{ form.bookmarkOf }}" />
201
- <p class="admin-hint">The address of a post this one likes, reposts or bookmarks. Leave them empty for a post that cites nothing.</p>
255
+ {% if kind.type == "post" %}
256
+ {# TASK-207: shown at the top of the author's fediverse profile, from
257
+ the featured collection their actor links. #}
258
+ <p class="admin-check">
259
+ <input id="editor-pinned" name="pinned" type="checkbox" value="1"{% if form.pinned %} checked{% endif %}{{ invalid('editor-pinned') }} />
260
+ <label for="editor-pinned">Pinned</label>
261
+ </p>
262
+ {{ problem('editor-pinned') }}
263
+ <p class="admin-hint">
264
+ Writes <code>pinned:</code> and the moment you pinned it into the front matter. Pinned posts
265
+ show first on your fediverse profile, newest pin first, up to five.
266
+ </p>
267
+ {% endif %}
268
+
269
+ {# Who the front matter says wrote this (TASK-67). A select of the site's
270
+ users rather than a free box, because after decision-14 the value is a
271
+ person rather than a string: it decides whose archive the post is on
272
+ and, once the actors land, whose followers hear about it. A file that
273
+ still names somebody who has no account keeps an option of its own, so
274
+ opening the editor cannot silently reattribute it. #}
275
+ <label for="editor-author">Author</label>
276
+ <select id="editor-author" name="author">
277
+ {% for choice in authors %}
278
+ <option value="{{ choice.value }}"{% if choice.chosen %} selected{% endif %}>{{ choice.label }}</option>
279
+ {% endfor %}
280
+ </select>
281
+ {% endcall %}
282
+
283
+ {% if kind.tagged or kind.categorised %}
284
+ {% call group('Tags and categories', open.taxonomy) %}
285
+ {% if kind.tagged %}
286
+ <label for="editor-tags">Tags</label>
287
+ <input id="editor-tags" name="tags" type="text" value="{{ form.tags }}" />
288
+ <p class="admin-hint">Separated by commas.</p>
289
+ {% endif %}
290
+
291
+ {% if kind.categorised %}
292
+ <label for="editor-categories">Categories</label>
293
+ <input id="editor-categories" name="categories" type="text" value="{{ form.categories }}" />
294
+ <p class="admin-hint">Separated by commas.</p>
295
+ {% endif %}
296
+ {% endcall %}
202
297
  {% endif %}
203
298
 
204
- <label for="editor-description">Description</label>
205
- <input id="editor-description" name="description" type="text" value="{{ form.description }}" />
206
-
207
- {#- The `lang` front matter (TASK-154): a post or page written in another
208
- language than the site's says so, for screen readers, translation
209
- and fediverse language filters. -#}
210
- <label for="editor-lang">Language</label>
211
- <input id="editor-lang" name="lang" type="text" value="{{ form.lang }}" autocomplete="off" spellcheck="false" aria-describedby="editor-lang-hint" />
212
- <p class="admin-hint" id="editor-lang-hint">A language tag, such as fr or pt-BR. Leave it empty for the site’s language, {{ siteLanguage }}.</p>
213
-
214
- {# Who the front matter says wrote this (TASK-67). A select of the site's
215
- users rather than a free box, because after decision-14 the value is a
216
- person rather than a string: it decides whose archive the post is on
217
- and, once the actors land, whose followers hear about it. A file that
218
- still names somebody who has no account keeps an option of its own, so
219
- opening the editor cannot silently reattribute it. #}
220
- <label for="editor-author">Author</label>
221
- <select id="editor-author" name="author">
222
- {% for choice in authors %}
223
- <option value="{{ choice.value }}"{% if choice.chosen %} selected{% endif %}>{{ choice.label }}</option>
224
- {% endfor %}
225
- </select>
299
+ {% call group('Address', open.address) %}
300
+ <label for="editor-slug">Slug</label>
301
+ <input id="editor-slug" name="slug" type="text" value="{{ form.slug }}"{{ invalid('editor-slug') }} />
302
+ {{ problem('editor-slug') }}
226
303
 
227
- <p class="admin-check">
228
- <input id="editor-draft" name="draft" type="checkbox" value="1"{% if form.draft %} checked{% endif %} />
229
- <label for="editor-draft">Draft</label>
230
- </p>
304
+ <label for="editor-permalink">Permalink</label>
305
+ <input id="editor-permalink" name="permalink" type="text" value="{{ form.permalink }}" />
306
+ <p class="admin-hint">Leave it empty to let the {{ kind.singular }} take the URL its slug implies.</p>
307
+ {% endcall %}
231
308
 
232
309
  {% if kind.type == "post" %}
233
- {# TASK-207: shown at the top of the author's fediverse profile, from
234
- the featured collection their actor links. #}
235
- <p class="admin-check">
236
- <input id="editor-pinned" name="pinned" type="checkbox" value="1"{% if form.pinned %} checked{% endif %} />
237
- <label for="editor-pinned">Pinned</label>
238
- </p>
239
- <p class="admin-hint">
240
- Writes <code>pinned:</code> and the moment you pinned it into the front matter. Pinned posts
241
- show first on your fediverse profile, newest pin first, up to five.
242
- </p>
310
+ {% call group('Responding to', open.responding) %}
311
+ {#- The mf2 `in-reply-to`: a URL here makes the post a reply, threads
312
+ it in the fediverse and sends that page a webmention. -#}
313
+ <label for="editor-in-reply-to">In reply to</label>
314
+ <input id="editor-in-reply-to" name="in-reply-to" type="url" value="{{ form.inReplyTo }}"{{ invalid('editor-in-reply-to') }} />
315
+ {{ problem('editor-in-reply-to') }}
316
+ <p class="admin-hint">The address of the post this one answers. Leave it empty for a post that answers nobody.</p>
317
+
318
+ {#- The mf2 `like-of`, `repost-of` and `bookmark-of` (TASK-169): each
319
+ cites a page the way a reply does, and sends it a webmention. -#}
320
+ <label for="editor-like-of">Like of</label>
321
+ <input id="editor-like-of" name="like-of" type="url" value="{{ form.likeOf }}"{{ invalid('editor-like-of') }} />
322
+ {{ problem('editor-like-of') }}
323
+ <label for="editor-repost-of">Repost of</label>
324
+ <input id="editor-repost-of" name="repost-of" type="url" value="{{ form.repostOf }}"{{ invalid('editor-repost-of') }} />
325
+ {{ problem('editor-repost-of') }}
326
+ <label for="editor-bookmark-of">Bookmark of</label>
327
+ <input id="editor-bookmark-of" name="bookmark-of" type="url" value="{{ form.bookmarkOf }}"{{ invalid('editor-bookmark-of') }} />
328
+ {{ problem('editor-bookmark-of') }}
329
+ <p class="admin-hint">The address of a post this one likes, reposts or bookmarks. Leave them empty for a post that cites nothing.</p>
330
+ {% endcall %}
331
+
332
+ {% call group('Read', open.read) %}
333
+ <label for="editor-read-status">Read status</label>
334
+ <select id="editor-read-status" name="{{ readFields.status }}"{{ invalid('editor-read-status') }}>
335
+ <option value=""{% if form.readStatus == "" %} selected{% endif %}>Not a read post</option>
336
+ {% for status in readStatuses %}
337
+ <option value="{{ status.value }}"{% if form.readStatus == status.value %} selected{% endif %}>{{ status.label }}</option>
338
+ {% endfor %}
339
+ {% if unrecognizedReadStatus %}
340
+ <option value="{{ unrecognizedReadStatus }}" selected>{{ unrecognizedReadStatus }} (not recognized)</option>
341
+ {% endif %}
342
+ </select>
343
+ {{ problem('editor-read-status') }}
344
+ <label for="editor-read-of-name">Title</label>
345
+ <input id="editor-read-of-name" name="{{ readFields.name }}" type="text" value="{{ form.readOf.name }}"{{ invalid('editor-read-of-name') }} />
346
+ {{ problem('editor-read-of-name') }}
347
+ <label for="editor-read-of-author">Author</label>
348
+ <input id="editor-read-of-author" name="{{ readFields.author }}" type="text" value="{{ form.readOf.author }}" />
349
+ <label for="editor-read-of-uid">Identifier</label>
350
+ <input id="editor-read-of-uid" name="{{ readFields.uid }}" type="text" value="{{ form.readOf.uid }}" placeholder="isbn:9780441478125" autocomplete="off" spellcheck="false" />
351
+ <label for="editor-read-of-url">Address</label>
352
+ <input id="editor-read-of-url" name="{{ readFields.url }}" type="url" value="{{ form.readOf.url }}"{{ invalid('editor-read-of-url') }} />
353
+ {{ problem('editor-read-of-url') }}
354
+ <p class="admin-hint">A book or paper this post says its author read. An identifier is an ISBN or a DOI, written isbn:… or doi:…. Choose Not a read post and empty every box to remove it. A read is described by what it says, so leave its Description empty.</p>
355
+ {% endcall %}
243
356
  {% endif %}
244
357
 
358
+ {% call group('Summary and language', open.summary) %}
359
+ <label for="editor-description">Description</label>
360
+ <input id="editor-description" name="description" type="text" value="{{ form.description }}"{{ invalid('editor-description') }} />
361
+ {{ problem('editor-description') }}
362
+
363
+ {#- The `lang` front matter (TASK-154): a post or page written in another
364
+ language than the site's says so, for screen readers, translation
365
+ and fediverse language filters. -#}
366
+ <label for="editor-lang">Language</label>
367
+ <input id="editor-lang" name="lang" type="text" value="{{ form.lang }}" autocomplete="off" spellcheck="false"{{ invalid('editor-lang', 'editor-lang-hint') }} />
368
+ {{ problem('editor-lang') }}
369
+ <p class="admin-hint" id="editor-lang-hint">A language tag, such as fr or pt-BR. Leave it empty for the site’s language, {{ siteLanguage }}.</p>
370
+ {% endcall %}
371
+
245
372
  {% if kind.type == "post" and syndicationTargets.length %}
246
373
  {# TASK-155: the targets content/_data/syndicationTargets.json declares.
247
374
  A target whose tag the post carries is selected without a tick. #}
248
- <fieldset class="admin-editor-syndication">
249
- <legend>Syndicate to</legend>
375
+ {% call group('Syndicate to', open.syndication) %}
250
376
  {% for target in syndicationTargets %}
251
377
  <p class="admin-check">
252
378
  <input id="editor-{{ target.field }}" name="{{ target.field }}" type="checkbox" value="1"{% if target.checked %} checked{% endif %} />
@@ -258,47 +384,49 @@
258
384
  sends it a webmention when it is published or updated. A post tagged with a target’s tag is
259
385
  sent there without a tick.
260
386
  </p>
261
- </fieldset>
262
- {% endif %}
263
-
264
- {# What this document says about comments, if anything. Three answers
265
- rather than two: the site's own rules, always open, always closed. #}
266
- <label for="editor-comments">Comments</label>
267
- <select id="editor-comments" name="comments">
268
- <option value="{{ commentSettings.site }}"{% if form.comments == commentSettings.site %} selected{% endif %}>Follow the site settings</option>
269
- <option value="{{ commentSettings.open }}"{% if form.comments == commentSettings.open %} selected{% endif %}>Open</option>
270
- <option value="{{ commentSettings.closed }}"{% if form.comments == commentSettings.closed %} selected{% endif %}>Closed</option>
271
- </select>
272
- <p class="admin-hint">
273
- Writes <code>comments: true</code> or <code>comments: false</code> into the front matter,
274
- which beats the site's switch and its closing window in both directions. Fediverse replies
275
- are shown either way.
276
- </p>
277
-
278
- {% if kind.excludable %}
279
- {# doc-2's eleventyExcludeFromCollections: still published and still at
280
- its URL, but out of every collection a template lists from. #}
281
- <p class="admin-check">
282
- <input id="editor-exclude" name="exclude" type="checkbox" value="1"{% if form.exclude %} checked{% endif %} />
283
- <label for="editor-exclude">Hide from collections</label>
284
- </p>
285
- <p class="admin-hint">Keeps the {{ kind.singular }} out of listings that gather content, without unpublishing it.</p>
387
+ {% endcall %}
286
388
  {% endif %}
287
389
 
288
- {% if kind.contactable %}
289
- {# The page offers a contact form under its content (TASK-56). Where a
290
- message goes is the contact address on the Settings screen; it is
291
- never on this page or in its HTML. #}
292
- <p class="admin-check">
293
- <input id="editor-contact" name="contact" type="checkbox" value="1"{% if form.contact %} checked{% endif %} />
294
- <label for="editor-contact">Contact form</label>
295
- </p>
390
+ {% call group('Display and discussion', open.display) %}
391
+ {# What this document says about comments, if anything. Three answers
392
+ rather than two: the site's own rules, always open, always closed. #}
393
+ <label for="editor-comments">Comments</label>
394
+ <select id="editor-comments" name="comments">
395
+ <option value="{{ commentSettings.site }}"{% if form.comments == commentSettings.site %} selected{% endif %}>Follow the site settings</option>
396
+ <option value="{{ commentSettings.open }}"{% if form.comments == commentSettings.open %} selected{% endif %}>Open</option>
397
+ <option value="{{ commentSettings.closed }}"{% if form.comments == commentSettings.closed %} selected{% endif %}>Closed</option>
398
+ </select>
296
399
  <p class="admin-hint">
297
- Writes <code>contact: true</code> into the front matter and puts a name, email, subject
298
- and message form under this {{ kind.singular }}. Messages arrive on the Messages screen
299
- and are emailed to the contact address on Settings.
400
+ Writes <code>comments: true</code> or <code>comments: false</code> into the front matter,
401
+ which beats the site's switch and its closing window in both directions. Fediverse replies
402
+ are shown either way.
300
403
  </p>
301
- {% endif %}
404
+
405
+ {% if kind.excludable %}
406
+ {# doc-2's eleventyExcludeFromCollections: still published and still at
407
+ its URL, but out of every collection a template lists from. #}
408
+ <p class="admin-check">
409
+ <input id="editor-exclude" name="exclude" type="checkbox" value="1"{% if form.exclude %} checked{% endif %} />
410
+ <label for="editor-exclude">Hide from collections</label>
411
+ </p>
412
+ <p class="admin-hint">Keeps the {{ kind.singular }} out of listings that gather content, without unpublishing it.</p>
413
+ {% endif %}
414
+
415
+ {% if kind.contactable %}
416
+ {# The page offers a contact form under its content (TASK-56). Where a
417
+ message goes is the contact address on the Settings screen; it is
418
+ never on this page or in its HTML. #}
419
+ <p class="admin-check">
420
+ <input id="editor-contact" name="contact" type="checkbox" value="1"{% if form.contact %} checked{% endif %} />
421
+ <label for="editor-contact">Contact form</label>
422
+ </p>
423
+ <p class="admin-hint">
424
+ Writes <code>contact: true</code> into the front matter and puts a name, email, subject
425
+ and message form under this {{ kind.singular }}. Messages arrive on the Messages screen
426
+ and are emailed to the contact address on Settings.
427
+ </p>
428
+ {% endif %}
429
+ {% endcall %}
302
430
 
303
431
  <p class="admin-editor-buttons">
304
432
  {% for button in actions %}
@@ -69,6 +69,8 @@
69
69
  <span class="admin-status admin-status-draft">Draft</span>
70
70
  {% elif document.scheduled %}
71
71
  <span class="admin-status admin-status-scheduled">Scheduled</span>
72
+ {% elif document.hidden %}
73
+ <span class="admin-status admin-status-draft">Hidden</span>
72
74
  {% else %}
73
75
  <span class="admin-status">Published</span>
74
76
  {% endif %}
@@ -26,6 +26,10 @@
26
26
  {% endif %}
27
27
  </dl>
28
28
 
29
+ {% if withoutPkce %}
30
+ <p class="admin-hint">This app does not use PKCE, which protects a sign-in if the code it is handed is intercepted. It is let in only because it is on your list of apps allowed without PKCE, under Users &gt; Connected apps.</p>
31
+ {% endif %}
32
+
29
33
  {% if loopback %}
30
34
  <p class="admin-hint">This app runs on your own computer or network. Approve only if you started this sign-in yourself.</p>
31
35
  {% endif %}
@@ -0,0 +1,37 @@
1
+ {% extends "layouts/settings-page.njk" %}
2
+ {% import "components/fields.njk" as field %}
3
+
4
+ {% block settingsProblems %}
5
+ {{ field.problem('settings-location-sharing', problems.locationSharing) }}
6
+ {% endblock %}
7
+
8
+ {% block settingsFields %}
9
+ <h2>Location</h2>
10
+
11
+ {% set locationHint %}
12
+ A Micropub app such as Quill can send where a post was written, and the
13
+ editor has fields for it. The site keeps it in <code>data/locations.json</code>,
14
+ never in the post's file, so it stays out of <code>content/</code> and its git
15
+ history whatever is chosen here. Exact coordinates can say where you live or
16
+ are right now. Changing this changes every post at once, and nothing is
17
+ rewritten.
18
+ {% endset %}
19
+ {% call field.select('settings-location-sharing', fields.locationSharing, 'Location on posts',
20
+ error=problems.locationSharing, hint=locationHint, autofocus=true) %}
21
+ {% for option in [
22
+ ['none', 'Keep it, publish nothing'],
23
+ ['place', 'Publish the place only: its name, locality, region and country'],
24
+ ['exact', 'Publish the exact coordinates too']] %}
25
+ <option value="{{ option[0] }}"{% if option[0] == form.locationSharing %} selected{% endif %}>{{ option[1] }}</option>
26
+ {% endfor %}
27
+ {% endcall %}
28
+
29
+ <h2>Uploads</h2>
30
+
31
+ <p class="admin-hint">
32
+ Location and camera metadata is always removed from pictures and videos as
33
+ they are uploaded, whatever is chosen above. Files uploaded before this
34
+ version keep theirs until <code>geekity strip-metadata</code> is run over
35
+ <code>content/uploads/</code>; the package README describes the command.
36
+ </p>
37
+ {% endblock %}