@geekity/cms 0.23.0 → 0.25.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 (192) hide show
  1. package/README.md +96 -160
  2. package/admin/pages/documents/editor.njk +45 -0
  3. package/admin/pages/federation/settings.njk +1 -59
  4. package/admin/pages/plugins/installed.njk +113 -0
  5. package/admin/pages/plugins/screen.njk +129 -0
  6. package/admin/static/admin.css +136 -4
  7. package/admin/static/editor-actions.js +192 -0
  8. package/admin/static/editor.js +1 -1
  9. package/dist/admin/accounts.d.ts +18 -39
  10. package/dist/admin/accounts.d.ts.map +1 -1
  11. package/dist/admin/accounts.js +19 -48
  12. package/dist/admin/accounts.js.map +1 -1
  13. package/dist/admin/documents.d.ts +5 -0
  14. package/dist/admin/documents.d.ts.map +1 -1
  15. package/dist/admin/documents.js +17 -8
  16. package/dist/admin/documents.js.map +1 -1
  17. package/dist/admin/editor-actions.d.ts +35 -0
  18. package/dist/admin/editor-actions.d.ts.map +1 -0
  19. package/dist/admin/editor-actions.js +168 -0
  20. package/dist/admin/editor-actions.js.map +1 -0
  21. package/dist/admin/federation.d.ts +4 -5
  22. package/dist/admin/federation.d.ts.map +1 -1
  23. package/dist/admin/federation.js +4 -5
  24. package/dist/admin/federation.js.map +1 -1
  25. package/dist/admin/index.d.ts +1 -1
  26. package/dist/admin/index.d.ts.map +1 -1
  27. package/dist/admin/index.js +1 -1
  28. package/dist/admin/index.js.map +1 -1
  29. package/dist/admin/menu.d.ts +1 -1
  30. package/dist/admin/menu.d.ts.map +1 -1
  31. package/dist/admin/menu.js +10 -3
  32. package/dist/admin/menu.js.map +1 -1
  33. package/dist/admin/plugins.d.ts +24 -0
  34. package/dist/admin/plugins.d.ts.map +1 -0
  35. package/dist/admin/plugins.js +312 -0
  36. package/dist/admin/plugins.js.map +1 -0
  37. package/dist/admin/routes.d.ts.map +1 -1
  38. package/dist/admin/routes.js +5 -1
  39. package/dist/admin/routes.js.map +1 -1
  40. package/dist/admin/settings-federation.d.ts +0 -25
  41. package/dist/admin/settings-federation.d.ts.map +1 -1
  42. package/dist/admin/settings-federation.js +1 -54
  43. package/dist/admin/settings-federation.js.map +1 -1
  44. package/dist/admin/settings.d.ts +2 -14
  45. package/dist/admin/settings.d.ts.map +1 -1
  46. package/dist/admin/settings.js +1 -21
  47. package/dist/admin/settings.js.map +1 -1
  48. package/dist/admin/store.d.ts +8 -1
  49. package/dist/admin/store.d.ts.map +1 -1
  50. package/dist/admin/store.js +7 -3
  51. package/dist/admin/store.js.map +1 -1
  52. package/dist/admin/templates.d.ts +2 -0
  53. package/dist/admin/templates.d.ts.map +1 -1
  54. package/dist/admin/templates.js +2 -0
  55. package/dist/admin/templates.js.map +1 -1
  56. package/dist/cli.d.ts +2 -3
  57. package/dist/cli.d.ts.map +1 -1
  58. package/dist/cli.js +278 -212
  59. package/dist/cli.js.map +1 -1
  60. package/dist/config.d.ts +20 -0
  61. package/dist/config.d.ts.map +1 -1
  62. package/dist/config.js +6 -0
  63. package/dist/config.js.map +1 -1
  64. package/dist/content/store.d.ts +6 -0
  65. package/dist/content/store.d.ts.map +1 -1
  66. package/dist/content/store.js +3 -0
  67. package/dist/content/store.js.map +1 -1
  68. package/dist/drain.d.ts +28 -0
  69. package/dist/drain.d.ts.map +1 -0
  70. package/dist/drain.js +106 -0
  71. package/dist/drain.js.map +1 -0
  72. package/dist/env.d.ts +11 -0
  73. package/dist/env.d.ts.map +1 -1
  74. package/dist/federation/federation.d.ts +4 -2
  75. package/dist/federation/federation.d.ts.map +1 -1
  76. package/dist/federation/federation.js +16 -21
  77. package/dist/federation/federation.js.map +1 -1
  78. package/dist/federation/inbox.d.ts +23 -3
  79. package/dist/federation/inbox.d.ts.map +1 -1
  80. package/dist/federation/inbox.js +63 -1
  81. package/dist/federation/inbox.js.map +1 -1
  82. package/dist/federation/index.d.ts +0 -4
  83. package/dist/federation/index.d.ts.map +1 -1
  84. package/dist/federation/index.js +0 -2
  85. package/dist/federation/index.js.map +1 -1
  86. package/dist/federation/mount.d.ts +6 -12
  87. package/dist/federation/mount.d.ts.map +1 -1
  88. package/dist/federation/mount.js +3 -50
  89. package/dist/federation/mount.js.map +1 -1
  90. package/dist/federation/queue.d.ts +22 -0
  91. package/dist/federation/queue.d.ts.map +1 -0
  92. package/dist/federation/queue.js +58 -0
  93. package/dist/federation/queue.js.map +1 -0
  94. package/dist/files/atomic.d.ts +13 -0
  95. package/dist/files/atomic.d.ts.map +1 -1
  96. package/dist/files/atomic.js +45 -0
  97. package/dist/files/atomic.js.map +1 -1
  98. package/dist/index.d.ts +30 -3
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +156 -79
  101. package/dist/index.js.map +1 -1
  102. package/dist/plugin.d.ts +513 -0
  103. package/dist/plugin.d.ts.map +1 -0
  104. package/dist/plugin.js +23 -0
  105. package/dist/plugin.js.map +1 -0
  106. package/dist/plugins/enabled.d.ts +23 -0
  107. package/dist/plugins/enabled.d.ts.map +1 -0
  108. package/dist/plugins/enabled.js +49 -0
  109. package/dist/plugins/enabled.js.map +1 -0
  110. package/dist/plugins/federation.d.ts +26 -0
  111. package/dist/plugins/federation.d.ts.map +1 -0
  112. package/dist/plugins/federation.js +114 -0
  113. package/dist/plugins/federation.js.map +1 -0
  114. package/dist/plugins/fetch.d.ts +15 -0
  115. package/dist/plugins/fetch.d.ts.map +1 -0
  116. package/dist/plugins/fetch.js +33 -0
  117. package/dist/plugins/fetch.js.map +1 -0
  118. package/dist/plugins/folder.d.ts +50 -0
  119. package/dist/plugins/folder.d.ts.map +1 -0
  120. package/dist/plugins/folder.js +171 -0
  121. package/dist/plugins/folder.js.map +1 -0
  122. package/dist/plugins/install.d.ts +45 -0
  123. package/dist/plugins/install.d.ts.map +1 -0
  124. package/dist/plugins/install.js +275 -0
  125. package/dist/plugins/install.js.map +1 -0
  126. package/dist/plugins/mount.d.ts +17 -0
  127. package/dist/plugins/mount.d.ts.map +1 -0
  128. package/dist/plugins/mount.js +44 -0
  129. package/dist/plugins/mount.js.map +1 -0
  130. package/dist/plugins/registry.d.ts +110 -0
  131. package/dist/plugins/registry.d.ts.map +1 -0
  132. package/dist/plugins/registry.js +447 -0
  133. package/dist/plugins/registry.js.map +1 -0
  134. package/dist/plugins/settings.d.ts +64 -0
  135. package/dist/plugins/settings.d.ts.map +1 -0
  136. package/dist/plugins/settings.js +219 -0
  137. package/dist/plugins/settings.js.map +1 -0
  138. package/dist/plugins/site.d.ts +17 -0
  139. package/dist/plugins/site.d.ts.map +1 -0
  140. package/dist/plugins/site.js +67 -0
  141. package/dist/plugins/site.js.map +1 -0
  142. package/dist/supervisor/primary.d.ts +47 -0
  143. package/dist/supervisor/primary.d.ts.map +1 -0
  144. package/dist/supervisor/primary.js +208 -0
  145. package/dist/supervisor/primary.js.map +1 -0
  146. package/dist/supervisor/protocol.d.ts +37 -0
  147. package/dist/supervisor/protocol.d.ts.map +1 -0
  148. package/dist/supervisor/protocol.js +29 -0
  149. package/dist/supervisor/protocol.js.map +1 -0
  150. package/dist/supervisor/supervision.d.ts +25 -0
  151. package/dist/supervisor/supervision.d.ts.map +1 -0
  152. package/dist/supervisor/supervision.js +7 -0
  153. package/dist/supervisor/supervision.js.map +1 -0
  154. package/dist/supervisor/worker.d.ts +29 -0
  155. package/dist/supervisor/worker.d.ts.map +1 -0
  156. package/dist/supervisor/worker.js +97 -0
  157. package/dist/supervisor/worker.js.map +1 -0
  158. package/dist/web/errors.d.ts.map +1 -1
  159. package/dist/web/errors.js +5 -3
  160. package/dist/web/errors.js.map +1 -1
  161. package/dist/web/llms.d.ts +8 -0
  162. package/dist/web/llms.d.ts.map +1 -1
  163. package/dist/web/llms.js +25 -7
  164. package/dist/web/llms.js.map +1 -1
  165. package/dist/web/maintenance.d.ts +2 -2
  166. package/dist/web/maintenance.d.ts.map +1 -1
  167. package/dist/web/maintenance.js +7 -4
  168. package/dist/web/maintenance.js.map +1 -1
  169. package/dist/web/negotiate.d.ts +8 -5
  170. package/dist/web/negotiate.d.ts.map +1 -1
  171. package/dist/web/negotiate.js +21 -10
  172. package/dist/web/negotiate.js.map +1 -1
  173. package/dist/web/routes.d.ts.map +1 -1
  174. package/dist/web/routes.js +84 -36
  175. package/dist/web/routes.js.map +1 -1
  176. package/package.json +11 -5
  177. package/dist/comments/conversation.d.ts +0 -29
  178. package/dist/comments/conversation.d.ts.map +0 -1
  179. package/dist/comments/conversation.js +0 -60
  180. package/dist/comments/conversation.js.map +0 -1
  181. package/dist/federation/import-wordpress.d.ts +0 -128
  182. package/dist/federation/import-wordpress.d.ts.map +0 -1
  183. package/dist/federation/import-wordpress.js +0 -426
  184. package/dist/federation/import-wordpress.js.map +0 -1
  185. package/dist/federation/wordpress.d.ts +0 -131
  186. package/dist/federation/wordpress.d.ts.map +0 -1
  187. package/dist/federation/wordpress.js +0 -342
  188. package/dist/federation/wordpress.js.map +0 -1
  189. package/dist/web/comments.d.ts +0 -60
  190. package/dist/web/comments.d.ts.map +0 -1
  191. package/dist/web/comments.js +0 -207
  192. package/dist/web/comments.js.map +0 -1
@@ -0,0 +1,129 @@
1
+ {# /admin/plugins/<package name>: a plugin's own screen (decision-33). The
2
+ plugin hands over cards of paragraphs and tables as data and this draws
3
+ them, so a plugin writes no markup and everything it says is escaped.
4
+
5
+ A plugin with settings gets a form here too (TASK-283). A label or hint
6
+ from a plugin is escaped before the field macros, which print theirs as
7
+ markup. A secret is never printed: the form says whether it is set and
8
+ where, and names the environment variable that would set it. #}
9
+ {% extends "layouts/shell.njk" %}
10
+ {% import "components/fields.njk" as field %}
11
+ {% from "components/badge.njk" import status %}
12
+ {% from "components/button.njk" import button %}
13
+ {% from "components/card.njk" import card, cardActions %}
14
+ {% from "components/table.njk" import table %}
15
+
16
+ {% macro text(parts) -%}
17
+ {%- for part in parts -%}
18
+ {%- if part.code is defined -%}<code>{{ part.code }}</code>{%- else -%}{{ part }}{%- endif -%}
19
+ {%- endfor -%}
20
+ {%- endmacro %}
21
+
22
+ {% block title %}{{ title }}{% endblock %}
23
+
24
+ {% block content %}
25
+ <h1 class="mb-6 text-2xl font-bold">{{ title }}</h1>
26
+
27
+ {% if problems.length %}
28
+ {% call field.summary('Nothing was saved. Fix the fields marked below.') %}
29
+ {% for setting in problems %}{{ field.problem(setting.id, setting.error) }}{% endfor %}
30
+ {% endcall %}
31
+ {% endif %}
32
+
33
+ <div class="grid max-w-5xl grid-cols-1 gap-6">
34
+ {% if settings.length or actions.length %}
35
+ {% call card(title='Settings' if settings.length else 'Actions') %}
36
+ {% if settings.length %}
37
+ <form method="post" action="{{ postUrl }}">
38
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
39
+ <input type="hidden" name="{{ actionField }}" value="{{ saveAction }}" />
40
+ {% for setting in settings %}
41
+ {% set label = setting.label | escape %}
42
+ {% set error = setting.error or setting.stored %}
43
+ {% if setting.type == 'secret' %}
44
+ {% set hint %}{% if setting.hint %}{{ setting.hint }} {% endif %}{% if setting.source == 'environment' %}Set by the environment variable <code>{{ setting.variable }}</code>. Change it there.{% elif setting.source == 'file' %}Set in <code>{{ secretsFile }}</code>. Leave the box blank to keep it. The environment variable <code>{{ setting.variable }}</code> would win over it.{% else %}Not set. Or set the environment variable <code>{{ setting.variable }}</code>.{% endif %}{% endset %}
45
+ {% if setting.source == 'environment' %}
46
+ <div class="fieldset">
47
+ <p class="fieldset-legend">{{ label }} {{ status('connected', 'Set') }}</p>
48
+ <p class="label block whitespace-normal">{{ hint | safe }}</p>
49
+ </div>
50
+ {% else %}
51
+ {{ field.password(setting.id, setting.name,
52
+ label ~ ' ' ~ (status('connected', 'Set') if setting.source == 'file' else status('missing', 'Not set')),
53
+ hint=hint, error=setting.error, autocomplete='off', spellcheck='false',
54
+ placeholder=('Leave blank to keep it' if setting.source == 'file' else '')) }}
55
+ {% if setting.source == 'file' %}
56
+ {{ field.checkbox(setting.id ~ '-forget', setting.forgetName, 'Forget the stored ' ~ label) }}
57
+ {% endif %}
58
+ {% endif %}
59
+ {% else %}
60
+ {% set hint %}{{ setting.hint }}{% endset %}
61
+ {% if setting.type == 'checkbox' %}
62
+ {{ field.checkbox(setting.id, setting.name, label, checked=setting.checked,
63
+ hint=hint, error=error) }}
64
+ {% elif setting.type == 'select' %}
65
+ {% call field.select(setting.id, setting.name, label, hint=hint, error=error) %}
66
+ {% for option in setting.options %}
67
+ <option value="{{ option.value }}"{% if option.selected %} selected{% endif %}>{{ option.label }}</option>
68
+ {% endfor %}
69
+ {% endcall %}
70
+ {% else %}
71
+ {{ field.text(setting.id, setting.name, label, setting.value, hint=hint, error=error,
72
+ inputmode=('url' if setting.type == 'url' else ''), spellcheck='false') }}
73
+ {% endif %}
74
+ {% endif %}
75
+ {% endfor %}
76
+ <div class="mt-2">{{ button('Save settings', type='submit', color='primary') }}</div>
77
+ </form>
78
+ {% endif %}
79
+
80
+ {% for action in actions %}
81
+ <form method="post" action="{{ postUrl }}">
82
+ <input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
83
+ <input type="hidden" name="{{ actionField }}" value="{{ action.id }}" />
84
+ {{ button(action.label, type='submit') }}
85
+ </form>
86
+ {% endfor %}
87
+
88
+ {% if settings.length %}
89
+ <p class="text-sm">
90
+ Settings other than secrets are kept under this plugin's key in
91
+ <code>content/_data/site.json</code>. Secrets are kept in
92
+ <code>{{ secretsFile }}</code> at mode <code>0600</code> and are never shown here.
93
+ </p>
94
+ {% endif %}
95
+ {% endcall %}
96
+ {% endif %}
97
+
98
+ {% for entry in cards %}
99
+ {% call card(title=entry.title) %}
100
+ {% for block in entry.blocks %}
101
+ {% if block.table %}
102
+ {% call table(block.table.caption, inCard=true) %}
103
+ <thead>
104
+ <tr>
105
+ {% for column in block.table.columns %}<th scope="col">{{ column }}</th>{% endfor %}
106
+ </tr>
107
+ </thead>
108
+ <tbody>
109
+ {% for row in block.table.rows %}
110
+ <tr>
111
+ {% for cell in row %}
112
+ {% if cell.time is defined %}
113
+ <td class="whitespace-nowrap">{% if cell.time %}{{ cell.time | date('readable') }}{% else %}Never{% endif %}</td>
114
+ {% else %}
115
+ <td>{{ text([cell]) }}</td>
116
+ {% endif %}
117
+ {% endfor %}
118
+ </tr>
119
+ {% endfor %}
120
+ </tbody>
121
+ {% endcall %}
122
+ {% else %}
123
+ <p class="text-sm">{{ text(block.paragraph) }}</p>
124
+ {% endif %}
125
+ {% endfor %}
126
+ {% endcall %}
127
+ {% endfor %}
128
+ </div>
129
+ {% endblock %}
@@ -44,6 +44,7 @@
44
44
  --container-md: 28rem;
45
45
  --container-2xl: 42rem;
46
46
  --container-3xl: 48rem;
47
+ --container-5xl: 64rem;
47
48
  --text-xs: .75rem;
48
49
  --text-xs--line-height: calc(1 / .75);
49
50
  --text-sm: .875rem;
@@ -1572,11 +1573,122 @@
1572
1573
  @layer components;
1573
1574
 
1574
1575
  @layer utilities {
1575
- .\@container {
1576
- container-type: inline-size;
1577
- }
1578
-
1579
1576
  @layer daisyui.l1.l2.l3 {
1577
+ .range {
1578
+ appearance: none;
1579
+ webkit-appearance: none;
1580
+ --range-thumb: var(--color-base-100);
1581
+ --range-thumb-size: calc(var(--size-selector, .25rem) * 6);
1582
+ --range-progress: currentColor;
1583
+ --range-fill: 1;
1584
+ --range-p: .25rem;
1585
+ --range-bg: currentColor;
1586
+ }
1587
+
1588
+ @supports (color: color-mix(in lab, red, red)) {
1589
+ .range {
1590
+ --range-bg: color-mix(in oklab, currentColor 10%, #0000);
1591
+ }
1592
+ }
1593
+
1594
+ .range {
1595
+ --range-fill-x: calc((var(--range-dir, 1) * -100cqw) - (var(--range-dir, 1) * var(--range-thumb-size) / 2));
1596
+ --range-fill-y: 0;
1597
+ --range-fill-spread: calc(100cqw * var(--range-fill));
1598
+ cursor: pointer;
1599
+ vertical-align: middle;
1600
+ --radius-selector-max: calc(var(--radius-selector) + var(--radius-selector) + var(--radius-selector));
1601
+ border-radius: calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)));
1602
+ width: clamp(3rem, 20rem, 100%);
1603
+ height: var(--range-thumb-size);
1604
+ background-color: #0000;
1605
+ border: none;
1606
+ overflow: hidden;
1607
+ container-type: inline-size;
1608
+ }
1609
+
1610
+ [dir="rtl"] .range {
1611
+ --range-dir: -1;
1612
+ }
1613
+
1614
+ .range:focus {
1615
+ outline: none;
1616
+ }
1617
+
1618
+ .range:focus-visible {
1619
+ outline-offset: 2px;
1620
+ outline: 2px solid;
1621
+ }
1622
+
1623
+ .range::-webkit-slider-runnable-track {
1624
+ background-color: var(--range-bg);
1625
+ border-radius: var(--radius-selector);
1626
+ width: 100%;
1627
+ height: calc(var(--range-thumb-size) * .5);
1628
+ }
1629
+
1630
+ @media (forced-colors: active) {
1631
+ .range::-webkit-slider-runnable-track {
1632
+ border: 1px solid;
1633
+ }
1634
+
1635
+ .range::-moz-range-track {
1636
+ border: 1px solid;
1637
+ }
1638
+ }
1639
+
1640
+ .range::-moz-range-track {
1641
+ background-color: var(--range-bg);
1642
+ border-radius: var(--radius-selector);
1643
+ width: 100%;
1644
+ height: calc(var(--range-thumb-size) * .5);
1645
+ }
1646
+
1647
+ .range::-webkit-slider-thumb {
1648
+ box-sizing: border-box;
1649
+ border-radius: calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)));
1650
+ background-color: var(--range-thumb);
1651
+ height: var(--range-thumb-size);
1652
+ width: var(--range-thumb-size);
1653
+ border: var(--range-p) solid;
1654
+ appearance: none;
1655
+ webkit-appearance: none;
1656
+ color: var(--range-progress);
1657
+ box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px currentColor, 0 0 0 2rem var(--range-thumb) inset, var(--range-fill-x) var(--range-fill-y) 0 var(--range-fill-spread);
1658
+ position: relative;
1659
+ inset-block-start: 50%;
1660
+ transform: translateY(-50%);
1661
+ }
1662
+
1663
+ @supports (color: color-mix(in lab, red, red)) {
1664
+ .range::-webkit-slider-thumb {
1665
+ box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, var(--range-fill-x) var(--range-fill-y) 0 var(--range-fill-spread);
1666
+ }
1667
+ }
1668
+
1669
+ .range::-moz-range-thumb {
1670
+ box-sizing: border-box;
1671
+ border-radius: calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)));
1672
+ height: var(--range-thumb-size);
1673
+ width: var(--range-thumb-size);
1674
+ border: var(--range-p) solid;
1675
+ color: var(--range-progress);
1676
+ box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px currentColor, 0 0 0 2rem var(--range-thumb) inset, var(--range-fill-x) var(--range-fill-y) 0 var(--range-fill-spread);
1677
+ background-color: currentColor;
1678
+ position: relative;
1679
+ }
1680
+
1681
+ @supports (color: color-mix(in lab, red, red)) {
1682
+ .range::-moz-range-thumb {
1683
+ box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, var(--range-fill-x) var(--range-fill-y) 0 var(--range-fill-spread);
1684
+ }
1685
+ }
1686
+
1687
+ .range:disabled {
1688
+ cursor: not-allowed;
1689
+ opacity: .3;
1690
+ }
1691
+
1580
1692
  :where(.drawer-side) {
1581
1693
  overflow: hidden;
1582
1694
  }
@@ -4257,6 +4369,10 @@
4257
4369
  }
4258
4370
  }
4259
4371
 
4372
+ .\@container {
4373
+ container-type: inline-size;
4374
+ }
4375
+
4260
4376
  @layer daisyui.l1.l2 {
4261
4377
  .drawer-open > .drawer-side {
4262
4378
  overflow-y: auto;
@@ -6432,6 +6548,10 @@
6432
6548
  margin-top: calc(var(--spacing) * -1);
6433
6549
  }
6434
6550
 
6551
+ .mt-0\.5 {
6552
+ margin-top: calc(var(--spacing) * .5);
6553
+ }
6554
+
6435
6555
  .mt-1 {
6436
6556
  margin-top: var(--spacing);
6437
6557
  }
@@ -6583,6 +6703,10 @@
6583
6703
  max-width: var(--container-3xl);
6584
6704
  }
6585
6705
 
6706
+ .max-w-5xl {
6707
+ max-width: var(--container-5xl);
6708
+ }
6709
+
6586
6710
  .max-w-full {
6587
6711
  max-width: 100%;
6588
6712
  }
@@ -6884,6 +7008,10 @@
6884
7008
  white-space: pre-line;
6885
7009
  }
6886
7010
 
7011
+ .whitespace-pre-wrap {
7012
+ white-space: pre-wrap;
7013
+ }
7014
+
6887
7015
  .text-base-content, .text-base-content\/70 {
6888
7016
  color: var(--color-base-content);
6889
7017
  }
@@ -7261,6 +7389,10 @@
7261
7389
  }
7262
7390
 
7263
7391
  @media (min-width: 80rem) {
7392
+ .xl\:grid-cols-2 {
7393
+ grid-template-columns: repeat(2, minmax(0, 1fr));
7394
+ }
7395
+
7264
7396
  .xl\:grid-cols-\[minmax\(0\,1fr\)_20rem\] {
7265
7397
  grid-template-columns: minmax(0, 1fr) 20rem;
7266
7398
  }
@@ -0,0 +1,192 @@
1
+ /* The buttons running plugins put beside the editor's title, description and
2
+ tags (decision-33). Each posts the form as it stands to its plugin's
3
+ endpoint, which answers { ok, value }, { ok, choices } or { ok: false,
4
+ message }, and shows the suggestion with Accept and Dismiss. Choices come
5
+ only beside the tags field, each drawn from the page's template row with a
6
+ box to tick, and Accept adds the ticked ones. Accept fills the field and saves
7
+ nothing; a save is still only the form's own buttons.
8
+
9
+ A file rather than an inline script, loaded from the admin's own origin
10
+ like slug.js, so the admin's policy stays `script-src 'self'` and
11
+ `connect-src 'self'`. Plain ES5-shaped browser JavaScript, served as it is
12
+ written. */
13
+ (function () {
14
+ function plainForm(form) {
15
+ var fields = new URLSearchParams();
16
+ new FormData(form).forEach(function (value, name) {
17
+ if (typeof value === 'string') fields.append(name, value);
18
+ });
19
+ return fields;
20
+ }
21
+
22
+ function tagsOf(value) {
23
+ return value
24
+ .split(',')
25
+ .map(function (tag) {
26
+ return tag.trim();
27
+ })
28
+ .filter(function (tag) {
29
+ return tag !== '';
30
+ });
31
+ }
32
+
33
+ /* A tags suggestion adds what the field does not already hold; a title or
34
+ description suggestion replaces the field. */
35
+ function accepted(field, value) {
36
+ if (field.id !== 'editor-tags') return value;
37
+ var held = tagsOf(field.value);
38
+ var known = held.map(function (tag) {
39
+ return tag.toLowerCase();
40
+ });
41
+ tagsOf(value).forEach(function (tag) {
42
+ if (known.indexOf(tag.toLowerCase()) === -1) {
43
+ held.push(tag);
44
+ known.push(tag.toLowerCase());
45
+ }
46
+ });
47
+ return held.join(', ');
48
+ }
49
+
50
+ function enhance(block, field) {
51
+ var url = block.getAttribute('data-editor-action');
52
+ var press = block.querySelector('button');
53
+ var status = block.querySelector('[role="status"]');
54
+ var suggestion = block.querySelector('[role="group"]');
55
+ var text = suggestion && suggestion.querySelector('p');
56
+ var list = suggestion && suggestion.querySelector('[data-editor-choices]');
57
+ var row = block.querySelector('template[data-editor-choice]');
58
+ if (!url || !press || !status || !text || !list || !row) return;
59
+ var buttons = suggestion.querySelectorAll('button');
60
+ var accept = buttons[0];
61
+ var dismiss = buttons[1];
62
+ var pending = null;
63
+ var offered = '';
64
+
65
+ function say(message, failed) {
66
+ status.textContent = message;
67
+ status.classList.toggle('text-error', failed === true);
68
+ }
69
+
70
+ function close() {
71
+ suggestion.hidden = true;
72
+ offered = '';
73
+ list.replaceChildren();
74
+ }
75
+
76
+ function drawChoices(choices) {
77
+ choices.forEach(function (choice) {
78
+ var item = row.content.firstElementChild.cloneNode(true);
79
+ var badge = item.querySelector('.badge');
80
+ item.querySelector('input').value = String(choice.value);
81
+ item.querySelector('[data-choice-value]').textContent = String(choice.value);
82
+ badge.textContent = choice.badge ? String(choice.badge) : '';
83
+ badge.hidden = !choice.badge;
84
+ item.querySelector('[data-choice-note]').textContent = choice.note
85
+ ? String(choice.note)
86
+ : '';
87
+ list.appendChild(item);
88
+ });
89
+ }
90
+
91
+ function ticked() {
92
+ var values = [];
93
+ list.querySelectorAll('input:checked').forEach(function (box) {
94
+ values.push(box.value);
95
+ });
96
+ return values.join(', ');
97
+ }
98
+
99
+ press.hidden = false;
100
+ press.addEventListener('click', function () {
101
+ if (pending) pending.abort();
102
+ var controller = new AbortController();
103
+ pending = controller;
104
+ close();
105
+ press.disabled = true;
106
+ say('Asking for a suggestion…');
107
+
108
+ fetch(url, {
109
+ method: 'POST',
110
+ body: plainForm(field.form),
111
+ credentials: 'same-origin',
112
+ headers: { accept: 'application/json' },
113
+ signal: controller.signal,
114
+ })
115
+ .then(function (response) {
116
+ var type = response.headers.get('content-type') || '';
117
+ if (type.indexOf('application/json') === -1) {
118
+ throw new Error(
119
+ response.status === 403
120
+ ? 'The site refused the request because this page is out of date. Copy your changes somewhere safe, then reload the page.'
121
+ : response.redirected
122
+ ? 'You are signed out. Sign in again in another tab, then try again.'
123
+ : 'The site answered ' + response.status + ' instead of a suggestion.',
124
+ );
125
+ }
126
+ return response.json();
127
+ })
128
+ .then(function (answer) {
129
+ if (answer.ok && Array.isArray(answer.choices)) {
130
+ if (answer.choices.length === 0) {
131
+ say('There was nothing to suggest.');
132
+ return;
133
+ }
134
+ text.hidden = true;
135
+ drawChoices(answer.choices);
136
+ list.hidden = false;
137
+ suggestion.hidden = false;
138
+ say('');
139
+ list.querySelector('input').focus();
140
+ return;
141
+ }
142
+ if (answer.ok) {
143
+ offered = String(answer.value);
144
+ text.textContent = offered;
145
+ text.hidden = false;
146
+ list.hidden = true;
147
+ suggestion.hidden = false;
148
+ say('');
149
+ accept.focus();
150
+ return;
151
+ }
152
+ say(String(answer.message), true);
153
+ if (answer.withdrawn) press.hidden = true;
154
+ })
155
+ .catch(function (error) {
156
+ if (controller.signal.aborted) return;
157
+ say(
158
+ error instanceof TypeError
159
+ ? 'The site could not be reached. Check the connection and try again.'
160
+ : error.message,
161
+ true,
162
+ );
163
+ })
164
+ .then(function () {
165
+ if (pending === controller) pending = null;
166
+ press.disabled = false;
167
+ });
168
+ });
169
+
170
+ accept.addEventListener('click', function () {
171
+ field.value = accepted(field, list.hidden ? offered : ticked());
172
+ field.dispatchEvent(new Event('input', { bubbles: true }));
173
+ close();
174
+ say('');
175
+ field.focus();
176
+ });
177
+
178
+ dismiss.addEventListener('click', function () {
179
+ close();
180
+ say('');
181
+ press.focus();
182
+ });
183
+ }
184
+
185
+ var groups = document.querySelectorAll('[data-editor-actions]');
186
+ for (var i = 0; i < groups.length; i++) {
187
+ var field = document.getElementById(groups[i].getAttribute('data-editor-actions'));
188
+ if (!field || !field.form) continue;
189
+ var blocks = groups[i].querySelectorAll('[data-editor-action]');
190
+ for (var j = 0; j < blocks.length; j++) enhance(blocks[j], field);
191
+ }
192
+ })();