@geekity/cms 0.34.0 → 0.36.1

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 (255) hide show
  1. package/admin/components/job-status.njk +15 -10
  2. package/admin/components/upload.njk +26 -0
  3. package/admin/pages/documents/chooser.njk +30 -0
  4. package/admin/pages/documents/editor.njk +410 -165
  5. package/admin/pages/federation/followers.njk +2 -1
  6. package/admin/pages/media/file.njk +143 -0
  7. package/admin/pages/media/library.njk +107 -198
  8. package/admin/pages/settings/general.njk +52 -2
  9. package/admin/pages/settings/privacy.njk +1 -1
  10. package/admin/pages/tools/export.njk +54 -51
  11. package/admin/pages/tools/maintenance.njk +49 -0
  12. package/admin/pages/users/edit.njk +3 -0
  13. package/admin/src/admin.css +2 -0
  14. package/admin/static/admin.css +843 -106
  15. package/admin/static/copy.js +30 -23
  16. package/admin/static/item-lookup.js +179 -0
  17. package/admin/static/job-status.js +158 -0
  18. package/admin/static/media.js +82 -0
  19. package/admin/static/upload-drop.js +225 -0
  20. package/dist/admin/documents.d.ts.map +1 -1
  21. package/dist/admin/documents.js +141 -24
  22. package/dist/admin/documents.js.map +1 -1
  23. package/dist/admin/editor-actions.d.ts.map +1 -1
  24. package/dist/admin/editor-actions.js +2 -3
  25. package/dist/admin/editor-actions.js.map +1 -1
  26. package/dist/admin/editor-fields.d.ts +20 -8
  27. package/dist/admin/editor-fields.d.ts.map +1 -1
  28. package/dist/admin/editor-fields.js +35 -12
  29. package/dist/admin/editor-fields.js.map +1 -1
  30. package/dist/admin/editor-kinds.d.ts +30 -0
  31. package/dist/admin/editor-kinds.d.ts.map +1 -0
  32. package/dist/admin/editor-kinds.js +136 -0
  33. package/dist/admin/editor-kinds.js.map +1 -0
  34. package/dist/admin/editor-layout.d.ts +10 -1
  35. package/dist/admin/editor-layout.d.ts.map +1 -1
  36. package/dist/admin/editor-layout.js +38 -31
  37. package/dist/admin/editor-layout.js.map +1 -1
  38. package/dist/admin/federation.d.ts +1 -1
  39. package/dist/admin/federation.d.ts.map +1 -1
  40. package/dist/admin/federation.js +2 -1
  41. package/dist/admin/federation.js.map +1 -1
  42. package/dist/admin/formatting.d.ts +2 -0
  43. package/dist/admin/formatting.d.ts.map +1 -1
  44. package/dist/admin/formatting.js +8 -0
  45. package/dist/admin/formatting.js.map +1 -1
  46. package/dist/admin/index.d.ts +1 -1
  47. package/dist/admin/index.d.ts.map +1 -1
  48. package/dist/admin/index.js +1 -1
  49. package/dist/admin/index.js.map +1 -1
  50. package/dist/admin/item-lookup.d.ts +16 -0
  51. package/dist/admin/item-lookup.d.ts.map +1 -0
  52. package/dist/admin/item-lookup.js +192 -0
  53. package/dist/admin/item-lookup.js.map +1 -0
  54. package/dist/admin/job-status.d.ts +1 -0
  55. package/dist/admin/job-status.d.ts.map +1 -1
  56. package/dist/admin/job-status.js +1 -0
  57. package/dist/admin/job-status.js.map +1 -1
  58. package/dist/admin/maintenance.d.ts +2 -0
  59. package/dist/admin/maintenance.d.ts.map +1 -1
  60. package/dist/admin/maintenance.js +14 -0
  61. package/dist/admin/maintenance.js.map +1 -1
  62. package/dist/admin/media.d.ts +47 -36
  63. package/dist/admin/media.d.ts.map +1 -1
  64. package/dist/admin/media.js +293 -68
  65. package/dist/admin/media.js.map +1 -1
  66. package/dist/admin/preview.d.ts.map +1 -1
  67. package/dist/admin/preview.js +9 -12
  68. package/dist/admin/preview.js.map +1 -1
  69. package/dist/admin/routes.d.ts.map +1 -1
  70. package/dist/admin/routes.js +4 -0
  71. package/dist/admin/routes.js.map +1 -1
  72. package/dist/admin/settings-general.d.ts +6 -0
  73. package/dist/admin/settings-general.d.ts.map +1 -1
  74. package/dist/admin/settings-general.js +46 -1
  75. package/dist/admin/settings-general.js.map +1 -1
  76. package/dist/admin/templates.d.ts +2 -0
  77. package/dist/admin/templates.d.ts.map +1 -1
  78. package/dist/admin/templates.js +4 -0
  79. package/dist/admin/templates.js.map +1 -1
  80. package/dist/admin/uploads.d.ts +2 -1
  81. package/dist/admin/uploads.d.ts.map +1 -1
  82. package/dist/admin/uploads.js +5 -2
  83. package/dist/admin/uploads.js.map +1 -1
  84. package/dist/cli.d.ts.map +1 -1
  85. package/dist/cli.js +2 -1
  86. package/dist/cli.js.map +1 -1
  87. package/dist/cms/app.d.ts.map +1 -1
  88. package/dist/cms/app.js +1 -0
  89. package/dist/cms/app.js.map +1 -1
  90. package/dist/cms/services.d.ts +2 -0
  91. package/dist/cms/services.d.ts.map +1 -1
  92. package/dist/cms/services.js +13 -0
  93. package/dist/cms/services.js.map +1 -1
  94. package/dist/content/document-form.d.ts +5 -13
  95. package/dist/content/document-form.d.ts.map +1 -1
  96. package/dist/content/document-form.js +2 -3
  97. package/dist/content/document-form.js.map +1 -1
  98. package/dist/content/excerpt.d.ts +1 -3
  99. package/dist/content/excerpt.d.ts.map +1 -1
  100. package/dist/content/excerpt.js +2 -7
  101. package/dist/content/excerpt.js.map +1 -1
  102. package/dist/content/post-fields.d.ts +1 -1
  103. package/dist/content/post-fields.d.ts.map +1 -1
  104. package/dist/content/post-fields.js +30 -35
  105. package/dist/content/post-fields.js.map +1 -1
  106. package/dist/content/post-type.d.ts +2 -3
  107. package/dist/content/post-type.d.ts.map +1 -1
  108. package/dist/content/post-type.js +8 -9
  109. package/dist/content/post-type.js.map +1 -1
  110. package/dist/content/review-form.d.ts +49 -0
  111. package/dist/content/review-form.d.ts.map +1 -0
  112. package/dist/content/review-form.js +282 -0
  113. package/dist/content/review-form.js.map +1 -0
  114. package/dist/content/review.d.ts +57 -0
  115. package/dist/content/review.d.ts.map +1 -0
  116. package/dist/content/review.js +264 -0
  117. package/dist/content/review.js.map +1 -0
  118. package/dist/content/write.d.ts.map +1 -1
  119. package/dist/content/write.js +6 -9
  120. package/dist/content/write.js.map +1 -1
  121. package/dist/exports.d.ts +1 -1
  122. package/dist/exports.d.ts.map +1 -1
  123. package/dist/federation/article.d.ts.map +1 -1
  124. package/dist/federation/article.js +27 -29
  125. package/dist/federation/article.js.map +1 -1
  126. package/dist/federation/federation.d.ts +3 -0
  127. package/dist/federation/federation.d.ts.map +1 -1
  128. package/dist/federation/federation.js +5 -1
  129. package/dist/federation/federation.js.map +1 -1
  130. package/dist/federation/refusals.d.ts +18 -0
  131. package/dist/federation/refusals.d.ts.map +1 -0
  132. package/dist/federation/refusals.js +84 -0
  133. package/dist/federation/refusals.js.map +1 -0
  134. package/dist/images/variants.d.ts +1 -0
  135. package/dist/images/variants.d.ts.map +1 -1
  136. package/dist/images/variants.js +10 -0
  137. package/dist/images/variants.js.map +1 -1
  138. package/dist/item-lookup/item-id.d.ts +33 -0
  139. package/dist/item-lookup/item-id.d.ts.map +1 -0
  140. package/dist/item-lookup/item-id.js +71 -0
  141. package/dist/item-lookup/item-id.js.map +1 -0
  142. package/dist/item-lookup/lookup.d.ts +58 -0
  143. package/dist/item-lookup/lookup.d.ts.map +1 -0
  144. package/dist/item-lookup/lookup.js +532 -0
  145. package/dist/item-lookup/lookup.js.map +1 -0
  146. package/dist/media/library.d.ts +9 -0
  147. package/dist/media/library.d.ts.map +1 -1
  148. package/dist/media/library.js +25 -1
  149. package/dist/media/library.js.map +1 -1
  150. package/dist/micropub/create.d.ts +1 -1
  151. package/dist/micropub/create.d.ts.map +1 -1
  152. package/dist/micropub/create.js +4 -2
  153. package/dist/micropub/create.js.map +1 -1
  154. package/dist/micropub/endpoint.d.ts.map +1 -1
  155. package/dist/micropub/endpoint.js +3 -10
  156. package/dist/micropub/endpoint.js.map +1 -1
  157. package/dist/micropub/update.d.ts.map +1 -1
  158. package/dist/micropub/update.js +12 -6
  159. package/dist/micropub/update.js.map +1 -1
  160. package/dist/plugin.d.ts +26 -2
  161. package/dist/plugin.d.ts.map +1 -1
  162. package/dist/plugin.js.map +1 -1
  163. package/dist/settings/names.d.ts +1 -1
  164. package/dist/settings/names.d.ts.map +1 -1
  165. package/dist/settings/names.js +8 -1
  166. package/dist/settings/names.js.map +1 -1
  167. package/dist/settings/registry.d.ts.map +1 -1
  168. package/dist/settings/registry.js +2 -1
  169. package/dist/settings/registry.js.map +1 -1
  170. package/dist/settings/tmdb.d.ts +5 -0
  171. package/dist/settings/tmdb.d.ts.map +1 -0
  172. package/dist/settings/tmdb.js +22 -0
  173. package/dist/settings/tmdb.js.map +1 -0
  174. package/dist/store/profiles.d.ts +3 -2
  175. package/dist/store/profiles.d.ts.map +1 -1
  176. package/dist/store/profiles.js +2 -4
  177. package/dist/store/profiles.js.map +1 -1
  178. package/dist/web/context.d.ts +9 -21
  179. package/dist/web/context.d.ts.map +1 -1
  180. package/dist/web/context.js +20 -20
  181. package/dist/web/context.js.map +1 -1
  182. package/dist/web/feed-item.d.ts +6 -6
  183. package/dist/web/feed-item.d.ts.map +1 -1
  184. package/dist/web/feed-item.js +7 -3
  185. package/dist/web/feed-item.js.map +1 -1
  186. package/dist/web/plugin-federation.d.ts +2 -0
  187. package/dist/web/plugin-federation.d.ts.map +1 -1
  188. package/dist/web/plugin-federation.js +3 -0
  189. package/dist/web/plugin-federation.js.map +1 -1
  190. package/dist/webmention/cited-picture.d.ts +1 -0
  191. package/dist/webmention/cited-picture.d.ts.map +1 -1
  192. package/dist/webmention/cited-picture.js +7 -4
  193. package/dist/webmention/cited-picture.js.map +1 -1
  194. package/dist/webmention/fetch-public.d.ts +3 -0
  195. package/dist/webmention/fetch-public.d.ts.map +1 -1
  196. package/dist/webmention/fetch-public.js +1 -1
  197. package/dist/webmention/fetch-public.js.map +1 -1
  198. package/dist/webmention/reply-context/json-ld.d.ts +8 -0
  199. package/dist/webmention/reply-context/json-ld.d.ts.map +1 -1
  200. package/dist/webmention/reply-context/json-ld.js +21 -0
  201. package/dist/webmention/reply-context/json-ld.js.map +1 -1
  202. package/dist/webmention/service.d.ts.map +1 -1
  203. package/dist/webmention/service.js +3 -5
  204. package/dist/webmention/service.js.map +1 -1
  205. package/package.json +6 -1
  206. package/themes/default/layouts/post.njk +6 -5
  207. package/themes/default/partials/post-list.njk +15 -20
  208. package/themes/default/partials/review.njk +1 -0
  209. package/themes/default/src/style.css +24 -4
  210. package/themes/default/static/style.css +33 -5
  211. package/dist/comments/routes.d.ts +0 -65
  212. package/dist/comments/routes.d.ts.map +0 -1
  213. package/dist/comments/routes.js +0 -235
  214. package/dist/comments/routes.js.map +0 -1
  215. package/dist/content/media.d.ts +0 -85
  216. package/dist/content/media.d.ts.map +0 -1
  217. package/dist/content/media.js +0 -237
  218. package/dist/content/media.js.map +0 -1
  219. package/dist/content/read-form.d.ts +0 -31
  220. package/dist/content/read-form.d.ts.map +0 -1
  221. package/dist/content/read-form.js +0 -157
  222. package/dist/content/read-form.js.map +0 -1
  223. package/dist/content/read.d.ts +0 -29
  224. package/dist/content/read.d.ts.map +0 -1
  225. package/dist/content/read.js +0 -82
  226. package/dist/content/read.js.map +0 -1
  227. package/dist/export/export-archives.d.ts +0 -29
  228. package/dist/export/export-archives.d.ts.map +0 -1
  229. package/dist/export/export-archives.js +0 -88
  230. package/dist/export/export-archives.js.map +0 -1
  231. package/dist/federation/mount.d.ts +0 -53
  232. package/dist/federation/mount.d.ts.map +0 -1
  233. package/dist/federation/mount.js +0 -344
  234. package/dist/federation/mount.js.map +0 -1
  235. package/dist/mail/credentials.d.ts +0 -62
  236. package/dist/mail/credentials.d.ts.map +0 -1
  237. package/dist/mail/credentials.js +0 -86
  238. package/dist/mail/credentials.js.map +0 -1
  239. package/dist/site/setting-rows.d.ts +0 -45
  240. package/dist/site/setting-rows.d.ts.map +0 -1
  241. package/dist/site/setting-rows.js +0 -361
  242. package/dist/site/setting-rows.js.map +0 -1
  243. package/dist/site/settings-form.d.ts +0 -30
  244. package/dist/site/settings-form.d.ts.map +0 -1
  245. package/dist/site/settings-form.js +0 -54
  246. package/dist/site/settings-form.js.map +0 -1
  247. package/dist/site/site-json.d.ts +0 -38
  248. package/dist/site/site-json.d.ts.map +0 -1
  249. package/dist/site/site-json.js +0 -69
  250. package/dist/site/site-json.js.map +0 -1
  251. package/dist/webmention/routes.d.ts +0 -37
  252. package/dist/webmention/routes.d.ts.map +0 -1
  253. package/dist/webmention/routes.js +0 -75
  254. package/dist/webmention/routes.js.map +0 -1
  255. package/themes/default/partials/read.njk +0 -5
@@ -1,4 +1,4 @@
1
- /* The media screen's copy buttons, and nothing else.
1
+ /* The copy buttons beside readonly fields, and nothing else.
2
2
 
3
3
  Every URL and every ready-made Markdown line is already a readonly text
4
4
  input, which selects and copies like any other text, so the screen is
@@ -17,30 +17,37 @@
17
17
  button appears. */
18
18
  if (!navigator.clipboard) return;
19
19
 
20
- var buttons = document.querySelectorAll('button[data-copy]');
21
- if (!buttons.length) return;
22
-
23
- Array.prototype.forEach.call(buttons, function (button) {
24
- var field = document.getElementById(button.getAttribute('data-copy'));
25
- if (!field) return;
26
-
27
- button.hidden = false;
28
- button.addEventListener('click', function () {
29
- navigator.clipboard.writeText(field.value).then(
30
- function () {
31
- said(button, 'Copied');
32
- },
33
- function () {
34
- /* Refused, usually because the document was not focused. Selecting
35
- the text is then the useful thing to do: the keyboard shortcut
36
- everybody already knows takes it from there. */
37
- field.select();
38
- said(button, 'Press ⌘C');
39
- },
40
- );
41
- });
20
+ enhance(document);
21
+
22
+ /* media.js shows a details screen in a dialog after this has run, and
23
+ announces the content it inserted. */
24
+ document.addEventListener('geekity:inserted', function (event) {
25
+ enhance(event.target);
42
26
  });
43
27
 
28
+ function enhance(root) {
29
+ Array.prototype.forEach.call(root.querySelectorAll('button[data-copy]'), function (button) {
30
+ var field = document.getElementById(button.getAttribute('data-copy'));
31
+ if (!field) return;
32
+
33
+ button.hidden = false;
34
+ button.addEventListener('click', function () {
35
+ navigator.clipboard.writeText(field.value).then(
36
+ function () {
37
+ said(button, 'Copied');
38
+ },
39
+ function () {
40
+ /* Refused, usually because the document was not focused. Selecting
41
+ the text is then the useful thing to do: the keyboard shortcut
42
+ everybody already knows takes it from there. */
43
+ field.select();
44
+ said(button, 'Press ⌘C');
45
+ },
46
+ );
47
+ });
48
+ });
49
+ }
50
+
44
51
  /* Say something on the button for a moment, then put its label back. */
45
52
  function said(button, message) {
46
53
  if (button.dataset.label === undefined) button.dataset.label = button.textContent;
@@ -0,0 +1,179 @@
1
+ (function () {
2
+ var press = document.querySelector('button[data-item-lookup]');
3
+ var status = document.getElementById('editor-item-lookup-status');
4
+ if (!press || !status || !press.form) return;
5
+ var form = press.form;
6
+ var url = press.getAttribute('data-item-lookup');
7
+
8
+ function plainForm() {
9
+ var fields = new URLSearchParams();
10
+ new FormData(form).forEach(function (value, name) {
11
+ if (typeof value === 'string') fields.append(name, value);
12
+ });
13
+ return fields;
14
+ }
15
+
16
+ function tell(line, message, failed) {
17
+ line.textContent = message;
18
+ line.classList.toggle('text-error', failed === true);
19
+ line.classList.toggle('text-base-content', failed !== true);
20
+ }
21
+
22
+ function say(message, failed) {
23
+ tell(status, message, failed);
24
+ }
25
+
26
+ function answerOf(response) {
27
+ var type = response.headers.get('content-type') || '';
28
+ if (type.indexOf('application/json') === -1) {
29
+ throw new Error(
30
+ response.status === 403
31
+ ? 'The site refused the request because this page is out of date. Copy your changes somewhere safe, then reload the page.'
32
+ : response.redirected
33
+ ? 'You are signed out. Sign in again in another tab, then try again.'
34
+ : 'The site answered ' + response.status + ' instead of JSON.',
35
+ );
36
+ }
37
+ return response.json();
38
+ }
39
+
40
+ function lookUp(pick) {
41
+ var fields = plainForm();
42
+ if (pick) fields.set('item-pick', pick);
43
+ press.disabled = true;
44
+ say('Looking it up…');
45
+
46
+ return fetch(url, {
47
+ method: 'POST',
48
+ body: fields,
49
+ credentials: 'same-origin',
50
+ headers: { accept: 'application/json' },
51
+ })
52
+ .then(answerOf)
53
+ .then(function (answer) {
54
+ var filled = answer.fields || {};
55
+ Object.keys(filled).forEach(function (name) {
56
+ var box = form.elements.namedItem(name);
57
+ if (box && box.value === '') box.value = String(filled[name]);
58
+ });
59
+ var preview = document.getElementById('editor-item-photo-preview');
60
+ if (preview && answer.thumbnail && filled['item-photo']) {
61
+ preview.src = answer.thumbnail;
62
+ preview.hidden = false;
63
+ }
64
+ say(String(answer.message || ''), !answer.ok);
65
+ })
66
+ .catch(function (error) {
67
+ say(error.message || 'The lookup failed.', true);
68
+ })
69
+ .then(function () {
70
+ press.disabled = false;
71
+ });
72
+ }
73
+
74
+ press.addEventListener('click', function (event) {
75
+ event.preventDefault();
76
+ lookUp('');
77
+ });
78
+
79
+ var find = document.getElementById('editor-item-find');
80
+ var query = document.getElementById('editor-item-find-query');
81
+ var search = document.getElementById('editor-item-find-button');
82
+ var said = document.getElementById('editor-item-find-status');
83
+ var list = document.getElementById('editor-item-find-matches');
84
+ var credit = document.getElementById('editor-item-find-credit');
85
+ var shape = document.getElementById('editor-item-find-match');
86
+ if (!find || !query || !search || !said || !list || !credit || !shape) return;
87
+ find.hidden = false;
88
+ var searching = false;
89
+
90
+ function chosenKind() {
91
+ var ticked = form.querySelector('input[name="item-kind"]:checked');
92
+ return ticked ? ticked.value : '';
93
+ }
94
+
95
+ function clear() {
96
+ Array.prototype.slice.call(list.querySelectorAll('li[data-match]')).forEach(function (row) {
97
+ row.remove();
98
+ });
99
+ credit.hidden = true;
100
+ list.hidden = true;
101
+ }
102
+
103
+ function show(matches) {
104
+ clear();
105
+ matches.forEach(function (match) {
106
+ var row = shape.content.firstElementChild.cloneNode(true);
107
+ row.setAttribute('data-match', '');
108
+ var picture = row.querySelector('img');
109
+ if (match.thumbnail) {
110
+ picture.src = match.thumbnail;
111
+ picture.hidden = false;
112
+ row.querySelector('[data-no-picture]').hidden = true;
113
+ }
114
+ row.querySelector('[data-name]').textContent = match.name;
115
+ row.querySelector('[data-detail]').textContent = match.detail;
116
+ row.querySelector('[data-source]').textContent = match.source;
117
+ row.querySelector('button').addEventListener('click', function () {
118
+ clear();
119
+ tell(said, '');
120
+ lookUp(match.id);
121
+ });
122
+ list.insertBefore(row, credit);
123
+ });
124
+ }
125
+
126
+ function run() {
127
+ if (searching) return;
128
+ searching = true;
129
+ search.disabled = true;
130
+ tell(said, 'Searching…');
131
+ var address =
132
+ find.getAttribute('data-item-search') +
133
+ '?' +
134
+ new URLSearchParams({ kind: chosenKind(), q: query.value }).toString();
135
+
136
+ fetch(address, { credentials: 'same-origin', headers: { accept: 'application/json' } })
137
+ .then(answerOf)
138
+ .then(function (answer) {
139
+ var matches = answer.matches || [];
140
+ show(matches);
141
+ if (answer.credit) {
142
+ credit.textContent = answer.credit;
143
+ credit.hidden = false;
144
+ }
145
+ list.hidden = !answer.ok;
146
+ tell(
147
+ said,
148
+ answer.ok
149
+ ? matches.length === 0
150
+ ? 'Nothing matches “' + query.value.trim() + '”.'
151
+ : matches.length === 1
152
+ ? 'One match. Pick it to fill in the item.'
153
+ : matches.length + ' matches. Pick one to fill in the item.'
154
+ : String(answer.message || ''),
155
+ !answer.ok,
156
+ );
157
+ })
158
+ .catch(function (error) {
159
+ clear();
160
+ tell(said, error.message || 'The search failed.', true);
161
+ })
162
+ .then(function () {
163
+ searching = false;
164
+ search.disabled = false;
165
+ });
166
+ }
167
+
168
+ search.addEventListener('click', run);
169
+ query.addEventListener('keydown', function (event) {
170
+ if (event.key !== 'Enter') return;
171
+ event.preventDefault();
172
+ run();
173
+ });
174
+ form.addEventListener('change', function (event) {
175
+ if (event.target.name !== 'item-kind') return;
176
+ clear();
177
+ tell(said, '');
178
+ });
179
+ })();
@@ -0,0 +1,158 @@
1
+ /* Redraws a queued or running job's status box until the job ends.
2
+
3
+ A box carries the job's name in data-job-status and, while the job is
4
+ active, the page to fetch in data-job-refresh. A box that also names
5
+ data-job-redraw is redrawn from that page only when the job's state
6
+ changes on the data-job-refresh page.
7
+
8
+ Plain ES5-shaped browser JavaScript, like copy.js: it is served as it is
9
+ written. */
10
+ (function () {
11
+ var EVERY = 3000;
12
+ var AFTER_FAILURE = 15000;
13
+ var ANNOUNCE_PROGRESS_EVERY = 15000;
14
+
15
+ var boxes = document.querySelectorAll('[data-job-status][data-job-refresh]');
16
+ if (!boxes.length) return;
17
+
18
+ /* Made before any update, so screen readers are watching it when the
19
+ first one arrives. */
20
+ var live = document.createElement('div');
21
+ live.className = 'sr-only';
22
+ live.setAttribute('role', 'status');
23
+ document.body.appendChild(live);
24
+ var said = { text: '', at: 0 };
25
+
26
+ Array.prototype.forEach.call(boxes, watch);
27
+
28
+ function watch(box) {
29
+ var selector = '[data-job-status="' + CSS.escape(box.getAttribute('data-job-status')) + '"]';
30
+ var region = regionOf(box);
31
+ var drawn = region.outerHTML;
32
+ var waiting = false;
33
+ checkAgain(region, false);
34
+ later(EVERY);
35
+
36
+ document.addEventListener('visibilitychange', function () {
37
+ if (document.hidden || !waiting) return;
38
+ waiting = false;
39
+ poll();
40
+ });
41
+
42
+ function later(delay) {
43
+ setTimeout(function () {
44
+ if (document.hidden) waiting = true;
45
+ else poll();
46
+ }, delay);
47
+ }
48
+
49
+ function poll() {
50
+ var redraw = box.getAttribute('data-job-redraw');
51
+ var polled;
52
+ find(box.getAttribute('data-job-refresh'))
53
+ .then(function (found) {
54
+ polled = found;
55
+ if (!redraw) return polled;
56
+ if (
57
+ polled &&
58
+ polled.getAttribute('data-job-state') === box.getAttribute('data-job-state')
59
+ ) {
60
+ return box;
61
+ }
62
+ return find(redraw);
63
+ })
64
+ .then(function (next) {
65
+ if (!next) {
66
+ drop(region);
67
+ if (polled) announce(polled, true);
68
+ return;
69
+ }
70
+ var nextRegion = regionOf(next);
71
+ if (next !== box && nextRegion.outerHTML !== drawn) {
72
+ drawn = nextRegion.outerHTML;
73
+ var changed =
74
+ next.getAttribute('data-job-state') !== box.getAttribute('data-job-state');
75
+ var replacement = document.importNode(nextRegion, true);
76
+ swapKeepingFocus(region, replacement);
77
+ region = replacement;
78
+ box = region.matches(selector) ? region : region.querySelector(selector);
79
+ announce(box, changed);
80
+ }
81
+ if (!box.hasAttribute('data-job-refresh')) return;
82
+ checkAgain(region, false);
83
+ later(EVERY);
84
+ })
85
+ .catch(function () {
86
+ checkAgain(region, true);
87
+ later(AFTER_FAILURE);
88
+ });
89
+ }
90
+
91
+ function find(url) {
92
+ return fetch(url, { credentials: 'same-origin' })
93
+ .then(function (response) {
94
+ if (!response.ok || response.redirected) throw new Error(String(response.status));
95
+ return response.text();
96
+ })
97
+ .then(function (html) {
98
+ /* A template's content is inert: the other page's nonced <style>
99
+ elements are neither applied nor checked against this page's CSP. */
100
+ var parsed = document.createElement('template');
101
+ parsed.innerHTML = html;
102
+ return parsed.content.querySelector(selector);
103
+ });
104
+ }
105
+ }
106
+
107
+ function regionOf(box) {
108
+ return box.closest('[data-job-region]') || box;
109
+ }
110
+
111
+ function checkAgain(region, shown) {
112
+ Array.prototype.forEach.call(
113
+ region.querySelectorAll('[data-job-check-again]'),
114
+ function (link) {
115
+ link.hidden = !shown;
116
+ },
117
+ );
118
+ }
119
+
120
+ function swapKeepingFocus(old, replacement) {
121
+ var focused = document.activeElement;
122
+ var inside = old.contains(focused);
123
+ old.replaceWith(replacement);
124
+ if (!inside) return;
125
+ var same = Array.prototype.filter.call(
126
+ replacement.querySelectorAll(focused.tagName),
127
+ function (candidate) {
128
+ return candidate.textContent.trim() === focused.textContent.trim();
129
+ },
130
+ )[0];
131
+ if (!same) {
132
+ replacement.tabIndex = -1;
133
+ same = replacement;
134
+ }
135
+ same.focus({ preventScroll: true });
136
+ }
137
+
138
+ function drop(region) {
139
+ var inside = region.contains(document.activeElement);
140
+ region.remove();
141
+ if (!inside) return;
142
+ var main = document.getElementById('main');
143
+ main.tabIndex = -1;
144
+ main.focus({ preventScroll: true });
145
+ }
146
+
147
+ function announce(box, changed) {
148
+ var message = box.querySelector('[data-job-message]');
149
+ var text =
150
+ box.getAttribute('data-job-label') +
151
+ '.' +
152
+ (message ? ' ' + message.textContent.replace(/\s+/g, ' ').trim() : '');
153
+ var now = Date.now();
154
+ if (text === said.text || (!changed && now - said.at < ANNOUNCE_PROGRESS_EVERY)) return;
155
+ said = { text: text, at: now };
156
+ live.textContent = text;
157
+ }
158
+ })();
@@ -0,0 +1,82 @@
1
+ /* The media library's details dialog (TASK-429).
2
+
3
+ Every tile is already a link to its file's details screen, so the library
4
+ is complete before this file loads. This opens that screen in a dialog
5
+ instead: it fetches the page the link names, takes its
6
+ [data-media-details], and shows it over the grid. Previous and Next inside
7
+ the dialog, and the left and right arrow keys, fetch the neighbouring file
8
+ the same way. Anything that goes wrong falls back to following the link.
9
+
10
+ Forms inside the dialog post as they would on the details screen, which is
11
+ where their answers land.
12
+
13
+ Plain ES5-shaped browser JavaScript, like copy.js: it is served as it is
14
+ written. */
15
+ (function () {
16
+ var tiles = document.querySelectorAll('a[data-media-tile]');
17
+ if (!tiles.length || !window.fetch) return;
18
+
19
+ var dialog = document.createElement('dialog');
20
+ dialog.className = 'modal';
21
+ dialog.setAttribute('aria-label', 'File details');
22
+ dialog.innerHTML =
23
+ '<div class="modal-box max-w-6xl">' +
24
+ '<form method="dialog" class="mb-2 text-right"><button class="btn btn-sm btn-ghost">Close</button></form>' +
25
+ '<div data-media-dialog-body></div>' +
26
+ '</div>' +
27
+ '<form method="dialog" class="modal-backdrop"><button>Close</button></form>';
28
+ document.body.appendChild(dialog);
29
+ dialog.tabIndex = -1;
30
+ var body = dialog.querySelector('[data-media-dialog-body]');
31
+
32
+ Array.prototype.forEach.call(tiles, function (tile) {
33
+ tile.addEventListener('click', function (event) {
34
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
35
+ return;
36
+ }
37
+ event.preventDefault();
38
+ show(tile.href);
39
+ });
40
+ });
41
+
42
+ body.addEventListener('click', function (event) {
43
+ var link = event.target.closest('a[data-media-step], a[data-media-library]');
44
+ if (!link) return;
45
+ event.preventDefault();
46
+ if (link.hasAttribute('data-media-library')) dialog.close();
47
+ else show(link.href);
48
+ });
49
+
50
+ dialog.addEventListener('keydown', function (event) {
51
+ if (event.target.closest('input, textarea, select')) return;
52
+ var rel = event.key === 'ArrowLeft' ? 'prev' : event.key === 'ArrowRight' ? 'next' : '';
53
+ if (!rel) return;
54
+ var link = body.querySelector('a[rel="' + rel + '"]');
55
+ if (!link) return;
56
+ event.preventDefault();
57
+ show(link.href);
58
+ });
59
+
60
+ function show(url) {
61
+ fetch(url, { credentials: 'same-origin' })
62
+ .then(function (response) {
63
+ if (!response.ok) throw new Error(String(response.status));
64
+ return response.text();
65
+ })
66
+ .then(function (html) {
67
+ /* A template's content is inert: the other page's nonced <style>
68
+ elements are neither applied nor checked against this page's CSP. */
69
+ var parsed = document.createElement('template');
70
+ parsed.innerHTML = html;
71
+ var details = parsed.content.querySelector('[data-media-details]');
72
+ if (!details) throw new Error('no details');
73
+ body.replaceChildren(document.importNode(details, true));
74
+ body.dispatchEvent(new CustomEvent('geekity:inserted', { bubbles: true }));
75
+ if (!dialog.open) dialog.showModal();
76
+ dialog.focus();
77
+ })
78
+ .catch(function () {
79
+ window.location.href = url;
80
+ });
81
+ }
82
+ })();