@geekity/cms 0.21.0 → 0.22.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.
- package/README.md +23 -0
- package/admin/components/admin-bar.njk +27 -6
- package/admin/components/alert.njk +28 -0
- package/admin/components/badge.njk +58 -0
- package/admin/components/button.njk +56 -0
- package/admin/components/card.njk +40 -0
- package/admin/components/collapse.njk +36 -0
- package/admin/components/copy.njk +20 -0
- package/admin/components/dropdown.njk +32 -0
- package/admin/components/fields.njk +81 -45
- package/admin/components/flash.njk +12 -5
- package/admin/components/menu.njk +51 -0
- package/admin/components/navbar.njk +23 -0
- package/admin/components/pagination.njk +47 -0
- package/admin/components/public-admin-bar.njk +4 -41
- package/admin/components/stat.njk +35 -0
- package/admin/components/table.njk +74 -0
- package/admin/components/tabs.njk +33 -0
- package/admin/components/theme-choice.njk +18 -0
- package/admin/layouts/base.njk +9 -16
- package/admin/layouts/settings-page.njk +15 -10
- package/admin/layouts/shell.njk +23 -31
- package/admin/pages/account/forgot.njk +39 -33
- package/admin/pages/account/login.njk +22 -16
- package/admin/pages/account/reset.njk +34 -25
- package/admin/pages/account/setup.njk +21 -15
- package/admin/pages/appearance/themes.njk +50 -44
- package/admin/pages/comments/all.njk +88 -106
- package/admin/pages/dashboard/home.njk +50 -62
- package/admin/pages/documents/conflict.njk +29 -24
- package/admin/pages/documents/editor.njk +155 -275
- package/admin/pages/documents/list.njk +60 -69
- package/admin/pages/documents/syndication.njk +98 -60
- package/admin/pages/documents/taxonomy.njk +36 -32
- package/admin/pages/error.njk +8 -5
- package/admin/pages/federation/followers.njk +91 -97
- package/admin/pages/federation/settings.njk +10 -11
- package/admin/pages/indieauth/consent.njk +60 -44
- package/admin/pages/indieauth/refused.njk +6 -3
- package/admin/pages/media/library.njk +86 -107
- package/admin/pages/messages/all.njk +71 -77
- package/admin/pages/navigation/menus.njk +90 -85
- package/admin/pages/placeholder.njk +13 -8
- package/admin/pages/settings/discussion.njk +30 -29
- package/admin/pages/settings/email.njk +26 -28
- package/admin/pages/settings/general.njk +10 -4
- package/admin/pages/settings/permalinks.njk +8 -9
- package/admin/pages/settings/privacy.njk +3 -3
- package/admin/pages/settings/reading.njk +3 -3
- package/admin/pages/tools/content-index.njk +79 -73
- package/admin/pages/tools/personal-data.njk +61 -55
- package/admin/pages/users/activity-entry.njk +12 -12
- package/admin/pages/users/activity.njk +13 -19
- package/admin/pages/users/apps.njk +112 -100
- package/admin/pages/users/edit.njk +65 -58
- package/admin/pages/users/list.njk +26 -24
- package/admin/pages/users/new.njk +11 -7
- package/admin/src/admin.css +174 -0
- package/admin/static/admin-bar.css +33 -31
- package/admin/static/admin-bar.js +22 -0
- package/admin/static/admin.css +7388 -1195
- package/admin/static/copy.js +1 -1
- package/admin/static/editor.js +18 -18
- package/dist/admin/accounts.d.ts +20 -0
- package/dist/admin/accounts.d.ts.map +1 -1
- package/dist/admin/accounts.js +25 -0
- package/dist/admin/accounts.js.map +1 -1
- package/dist/admin/admin-theme.d.ts +157 -0
- package/dist/admin/admin-theme.d.ts.map +1 -0
- package/dist/admin/admin-theme.js +52 -0
- package/dist/admin/admin-theme.js.map +1 -0
- package/dist/admin/assets.d.ts +4 -3
- package/dist/admin/assets.d.ts.map +1 -1
- package/dist/admin/assets.js +4 -3
- package/dist/admin/assets.js.map +1 -1
- package/dist/admin/documents.js +1 -1
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/headers.js +1 -1
- package/dist/admin/headers.js.map +1 -1
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +17 -8
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/templates.d.ts +6 -3
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +25 -3
- package/dist/admin/templates.js.map +1 -1
- package/dist/admin/users.d.ts +4 -0
- package/dist/admin/users.d.ts.map +1 -1
- package/dist/admin/users.js +37 -1
- package/dist/admin/users.js.map +1 -1
- package/dist/content/post-type.d.ts.map +1 -1
- package/dist/content/post-type.js +7 -2
- package/dist/content/post-type.js.map +1 -1
- package/dist/web/admin-bar.d.ts +4 -4
- package/dist/web/admin-bar.d.ts.map +1 -1
- package/dist/web/admin-bar.js +11 -34
- package/dist/web/admin-bar.js.map +1 -1
- package/package.json +8 -4
- package/themes/default/README.md +3 -1
|
@@ -2,88 +2,100 @@
|
|
|
2
2
|
|
|
3
3
|
Two lists, and a menu is in exactly one of them. First the areas the active
|
|
4
4
|
theme declares, in the theme's own order and under the theme's own labels,
|
|
5
|
-
each a box to type links into
|
|
6
|
-
empty box rather than a missing one. Then every other menu
|
|
7
|
-
under a heading that says this theme renders them nowhere,
|
|
8
|
-
Delete, which is the only way a menu is removed.
|
|
5
|
+
each a card with a box to type links into: an area the site has never
|
|
6
|
+
filled in is an empty box rather than a missing one. Then every other menu
|
|
7
|
+
the site stores, under a heading that says this theme renders them nowhere,
|
|
8
|
+
each with a Delete, which is the only way a menu is removed. Each save, add
|
|
9
|
+
and delete is a form of its own, so a refused one loses nothing typed into
|
|
10
|
+
another. #}
|
|
9
11
|
{% extends "layouts/shell.njk" %}
|
|
10
12
|
{% import "components/fields.njk" as field %}
|
|
13
|
+
{% from "components/badge.njk" import badge %}
|
|
14
|
+
{% from "components/button.njk" import button %}
|
|
15
|
+
{% from "components/card.njk" import card, cardActions %}
|
|
11
16
|
|
|
12
17
|
{% block title %}{{ heading }}{% endblock %}
|
|
13
18
|
|
|
14
|
-
{# One menu: its
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<span class="admin-status">empty — nothing has been typed here yet</span>
|
|
27
|
-
{% endif %}
|
|
28
|
-
</p>
|
|
29
|
-
|
|
30
|
-
<form method="post" action="{{ url }}">
|
|
31
|
-
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
32
|
-
<input type="hidden" name="{{ fields.menu }}" value="{{ menu.name }}" />
|
|
33
|
-
{% set itemsHint %}
|
|
34
|
-
One item per line, written <code>Label | URL</code> — for example
|
|
35
|
-
<code>About | /about/</code> or
|
|
36
|
-
<code>Mastodon | https://example.social/@me</code>. They are the whole of
|
|
37
|
-
this menu, in this order: nothing else puts a link in it. A page serving
|
|
38
|
-
as the front page goes in as <code>Home | /</code>, where a reader lands.
|
|
39
|
-
End a line with the
|
|
40
|
-
<code>rel</code> values the link carries, a word each —
|
|
41
|
-
<code>Mastodon | https://example.social/@me | me</code>,
|
|
42
|
-
<code>A source | https://example.com/thing | nofollow noopener</code>.
|
|
43
|
-
<code>me</code> marks <strong>a profile that is you</strong>, which is
|
|
44
|
-
how Mastodon and the rest of the IndieWeb verify that the site and the
|
|
45
|
-
profile it links are the same person; the rest are whatever HTML link
|
|
46
|
-
types you want on it.
|
|
47
|
-
{% endset %}
|
|
48
|
-
{{ field.textarea('menu-' + menu.name, fields.items, 'Items', menu.items,
|
|
49
|
-
error=menu.problem, hint=itemsHint, rows='5', spellcheck='false') }}
|
|
50
|
-
<p class="admin-menu-buttons">
|
|
51
|
-
<button type="submit">Save menu</button>
|
|
19
|
+
{# One menu: its card, the box its items are typed into, and the button that
|
|
20
|
+
saves it. `kept` adds the Delete form, on a menu nothing renders. #}
|
|
21
|
+
{% macro box(menu, fields, url, deleteUrl, csrfToken, kept=false) %}
|
|
22
|
+
<li>
|
|
23
|
+
{% call card(title=menu.label) %}
|
|
24
|
+
<p class="flex flex-wrap items-center gap-2">
|
|
25
|
+
<code>menus.{{ menu.name }}</code>
|
|
26
|
+
{% if menu.stored %}
|
|
27
|
+
{{ badge(menu.count + (' item' if menu.count == 1 else ' items'), style='outline') }}
|
|
28
|
+
{% else %}
|
|
29
|
+
{{ badge('empty — nothing has been typed here yet', style='ghost') }}
|
|
30
|
+
{% endif %}
|
|
52
31
|
</p>
|
|
53
|
-
</form>
|
|
54
32
|
|
|
55
|
-
|
|
33
|
+
<form method="post" action="{{ url }}">
|
|
34
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
35
|
+
<input type="hidden" name="{{ fields.menu }}" value="{{ menu.name }}" />
|
|
36
|
+
{% set itemsHint %}
|
|
37
|
+
One item per line, written <code>Label | URL</code> — for example
|
|
38
|
+
<code>About | /about/</code> or
|
|
39
|
+
<code>Mastodon | https://example.social/@me</code>. They are the whole of
|
|
40
|
+
this menu, in this order: nothing else puts a link in it. A page serving
|
|
41
|
+
as the front page goes in as <code>Home | /</code>, where a reader lands.
|
|
42
|
+
End a line with the
|
|
43
|
+
<code>rel</code> values the link carries, a word each —
|
|
44
|
+
<code>Mastodon | https://example.social/@me | me</code>,
|
|
45
|
+
<code>A source | https://example.com/thing | nofollow noopener</code>.
|
|
46
|
+
<code>me</code> marks <strong>a profile that is you</strong>, which is
|
|
47
|
+
how Mastodon and the rest of the IndieWeb verify that the site and the
|
|
48
|
+
profile it links are the same person; the rest are whatever HTML link
|
|
49
|
+
types you want on it.
|
|
50
|
+
{% endset %}
|
|
51
|
+
{{ field.textarea('menu-' + menu.name, fields.items, 'Items', menu.items,
|
|
52
|
+
error=menu.problem, hint=itemsHint, rows='5', spellcheck='false') }}
|
|
53
|
+
<div class="mt-2">{{ button('Save menu', type='submit', color='primary') }}</div>
|
|
54
|
+
</form>
|
|
55
|
+
|
|
56
|
+
{% if kept %}
|
|
57
|
+
<form method="post" action="{{ deleteUrl }}">
|
|
58
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
59
|
+
<input type="hidden" name="{{ fields.menu }}" value="{{ menu.name }}" />
|
|
60
|
+
{% call cardActions() %}
|
|
61
|
+
{{ button('Delete this menu', type='submit', color='error', style='ghost') }}
|
|
62
|
+
{% endcall %}
|
|
63
|
+
</form>
|
|
64
|
+
{% endif %}
|
|
65
|
+
{% endcall %}
|
|
56
66
|
</li>
|
|
57
67
|
{% endmacro %}
|
|
58
68
|
|
|
59
69
|
{% block content %}
|
|
60
|
-
<h1>{{ heading }}</h1>
|
|
70
|
+
<h1 class="mb-6 text-2xl font-bold">{{ heading }}</h1>
|
|
61
71
|
|
|
62
72
|
{% if hasProblems %}
|
|
63
|
-
|
|
64
|
-
{%
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
<div class="mb-6 max-w-2xl">
|
|
74
|
+
{% call field.summary('Nothing was saved. Fix the box marked below and try again.') %}
|
|
75
|
+
{% for menu in areas.concat(others) %}
|
|
76
|
+
{{ field.problem('menu-' + menu.name, menu.problem) }}
|
|
77
|
+
{% endfor %}
|
|
78
|
+
{{ field.problem('menu-name', nameProblem) }}
|
|
79
|
+
{% endcall %}
|
|
80
|
+
</div>
|
|
69
81
|
{% endif %}
|
|
70
82
|
|
|
71
|
-
<h2>Where {{ themeName }} puts a menu</h2>
|
|
83
|
+
<h2 class="mb-3 text-lg font-semibold">Where {{ themeName }} puts a menu</h2>
|
|
72
84
|
|
|
73
85
|
{% if areas.length %}
|
|
74
|
-
<p class="
|
|
86
|
+
<p class="mb-4 max-w-prose text-sm">
|
|
75
87
|
These are the areas the active theme declares in its <code>theme.json</code>, in the order it
|
|
76
88
|
declares them. A theme renders a menu by asking for it by name, and an area with nothing in it
|
|
77
89
|
renders nothing at all.
|
|
78
90
|
</p>
|
|
79
91
|
|
|
80
|
-
<ul class="
|
|
92
|
+
<ul class="mb-10 grid max-w-2xl grid-cols-1 gap-6">
|
|
81
93
|
{% for menu in areas %}
|
|
82
|
-
{{ box(menu, fields, navigationUrl, csrfToken) }}
|
|
94
|
+
{{ box(menu, fields, navigationUrl, deleteUrl, csrfToken) }}
|
|
83
95
|
{% endfor %}
|
|
84
96
|
</ul>
|
|
85
97
|
{% else %}
|
|
86
|
-
<p class="
|
|
98
|
+
<p class="mb-10 max-w-prose text-sm">
|
|
87
99
|
{{ themeName }} declares no areas, so it asks for no menu by name. A theme declares them as an
|
|
88
100
|
<code>areas</code> list in its <code>theme.json</code>, each entry a <code>name</code> —
|
|
89
101
|
the name the template loops over as <code>menus.<name></code> — and a
|
|
@@ -93,45 +105,38 @@
|
|
|
93
105
|
{% endif %}
|
|
94
106
|
|
|
95
107
|
{% if others.length %}
|
|
96
|
-
<h2>Kept, rendered nowhere</h2>
|
|
108
|
+
<h2 class="mb-3 text-lg font-semibold">Kept, rendered nowhere</h2>
|
|
97
109
|
|
|
98
|
-
<p class="
|
|
110
|
+
<p class="mb-4 max-w-prose text-sm">
|
|
99
111
|
This site stores these menus and {{ themeName }} renders none of them: no template loops over
|
|
100
112
|
those names, so nothing on the public site shows them. They are kept, because that is how the
|
|
101
113
|
menu a theme will use gets written before the site switches to it. Delete is the only way one
|
|
102
114
|
is removed.
|
|
103
115
|
</p>
|
|
104
116
|
|
|
105
|
-
<ul class="
|
|
117
|
+
<ul class="mb-10 grid max-w-2xl grid-cols-1 gap-6">
|
|
106
118
|
{% for menu in others %}
|
|
107
|
-
{
|
|
108
|
-
<form class="admin-menu-delete" method="post" action="{{ deleteUrl }}">
|
|
109
|
-
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
110
|
-
<input type="hidden" name="{{ fields.menu }}" value="{{ menu.name }}" />
|
|
111
|
-
<button type="submit">Delete this menu</button>
|
|
112
|
-
</form>
|
|
113
|
-
{% endset %}
|
|
114
|
-
{{ box(menu, fields, navigationUrl, csrfToken, remove) }}
|
|
119
|
+
{{ box(menu, fields, navigationUrl, deleteUrl, csrfToken, kept=true) }}
|
|
115
120
|
{% endfor %}
|
|
116
121
|
</ul>
|
|
117
122
|
{% endif %}
|
|
118
123
|
|
|
119
|
-
<
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
</
|
|
124
|
+
<div class="max-w-2xl">
|
|
125
|
+
{% call card(title='Add a menu') %}
|
|
126
|
+
<form method="post" action="{{ addUrl }}">
|
|
127
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
128
|
+
{% set nameHint %}
|
|
129
|
+
The name a theme asks for the menu by: lower-case letters, digits and
|
|
130
|
+
underscores, starting with a letter. It is written after the dot —
|
|
131
|
+
<code>{% raw %}{% for item in menus.footer %}{% endraw %}</code> — which is why a dash
|
|
132
|
+
is not allowed in one. A name this theme declares puts the menu in its place above; any other
|
|
133
|
+
name is a menu kept and rendered nowhere, which is how you fill one in before switching to the
|
|
134
|
+
theme that will use it.
|
|
135
|
+
{% endset %}
|
|
136
|
+
{{ field.text('menu-name', fields.name, 'Name', newName,
|
|
137
|
+
error=nameProblem, hint=nameHint, spellcheck='false', required=true) }}
|
|
138
|
+
<div class="mt-2">{{ button('Add menu', type='submit', color='primary') }}</div>
|
|
139
|
+
</form>
|
|
140
|
+
{% endcall %}
|
|
141
|
+
</div>
|
|
137
142
|
{% endblock %}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
{# A screen a later task builds. It exists so the navigation goes somewhere
|
|
2
|
+
and the guard covers it from the start.
|
|
3
|
+
|
|
4
|
+
It sits at the top of `pages/` rather than inside one of the section
|
|
5
|
+
folders because it is not any one section's screen: it stands in for
|
|
6
|
+
whichever section has nothing built yet, and the route heads it. #}
|
|
1
7
|
{% extends "layouts/shell.njk" %}
|
|
8
|
+
{% from "components/card.njk" import card %}
|
|
2
9
|
|
|
3
10
|
{% block title %}{{ heading }}{% endblock %}
|
|
4
11
|
|
|
5
12
|
{% block content %}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<h1>{{ heading }}{% if slug %}: {{ slug }}{% endif %}</h1>
|
|
13
|
-
<p class="admin-note">This screen is not built yet.</p>
|
|
13
|
+
<h1 class="mb-6 text-2xl font-bold">{{ heading }}{% if slug %}: {{ slug }}{% endif %}</h1>
|
|
14
|
+
<div class="max-w-2xl">
|
|
15
|
+
{% call card() %}
|
|
16
|
+
<p>This screen is not built yet.</p>
|
|
17
|
+
{% endcall %}
|
|
18
|
+
</div>
|
|
14
19
|
{% endblock %}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{# /admin/settings/discussion: comments, webmentions, and what checks them. #}
|
|
2
2
|
{% extends "layouts/settings-page.njk" %}
|
|
3
3
|
{% import "components/fields.njk" as field %}
|
|
4
|
+
{% from "components/badge.njk" import status %}
|
|
5
|
+
{% from "components/button.njk" import button %}
|
|
6
|
+
{% from "components/card.njk" import card, cardActions %}
|
|
4
7
|
|
|
5
8
|
{# What a retention field says about the period as it stands: kept forever at
|
|
6
9
|
0, removed after so many days otherwise. Nothing for a value the form is
|
|
@@ -21,7 +24,7 @@
|
|
|
21
24
|
{% endblock %}
|
|
22
25
|
|
|
23
26
|
{% block settingsFields %}
|
|
24
|
-
<h2>Comments</h2>
|
|
27
|
+
<h2 class="mt-2 text-lg font-semibold">Comments</h2>
|
|
25
28
|
|
|
26
29
|
{% set commentsHint %}
|
|
27
30
|
Clear this and no post shows a comment form and every submission is
|
|
@@ -41,7 +44,7 @@
|
|
|
41
44
|
form.commentsCloseAfterDays, type='number', min='0', step='1',
|
|
42
45
|
error=problems.commentsCloseAfterDays, hint=closeHint) }}
|
|
43
46
|
|
|
44
|
-
<h2>Webmentions</h2>
|
|
47
|
+
<h2 class="mt-6 text-lg font-semibold">Webmentions</h2>
|
|
45
48
|
|
|
46
49
|
{% set sendHint %}
|
|
47
50
|
When a post is published or edited, every external page it links to is
|
|
@@ -60,9 +63,9 @@
|
|
|
60
63
|
{{ field.checkbox('settings-webmentions-receive', fields.webmentionsReceive,
|
|
61
64
|
'Accept webmentions sent here', checked=form.webmentionsReceive, hint=receiveHint) }}
|
|
62
65
|
|
|
63
|
-
<h2>Keeping personal data</h2>
|
|
66
|
+
<h2 class="mt-6 text-lg font-semibold">Keeping personal data</h2>
|
|
64
67
|
|
|
65
|
-
<p class="
|
|
68
|
+
<p class="max-w-prose text-sm">
|
|
66
69
|
How long the site keeps what commenters and people writing through the
|
|
67
70
|
contact form hand it, in days from when it arrived; <code>0</code> keeps
|
|
68
71
|
it forever. A site that has never set these keeps everything, so upgrading
|
|
@@ -107,21 +110,19 @@
|
|
|
107
110
|
{# Akismet is its own pair of forms for the reason the avatar is: the key is
|
|
108
111
|
a credential in data/akismet.json rather than a setting in site.json, and
|
|
109
112
|
a key Akismet will not take must not lose an edit to the fields above. #}
|
|
110
|
-
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
<p class="admin-akismet-state admin-akismet-{{ akismetState }}">
|
|
113
|
+
{% call card(title='Spam checking') %}
|
|
114
|
+
<p>
|
|
114
115
|
{% if akismetState == 'valid' %}
|
|
115
|
-
|
|
116
|
+
{{ status('connected', 'Connected') }} Key {{ akismetKeyHint }}, checked {{ akismetCheckedAt }}.
|
|
116
117
|
{% elif akismetState == 'invalid' %}
|
|
117
|
-
|
|
118
|
+
{{ status('rejected', 'Not recognised') }} Akismet does not recognise this key: key {{ akismetKeyHint }}, checked {{ akismetCheckedAt }}.
|
|
118
119
|
{% elif akismetState == 'unchecked' %}
|
|
119
|
-
|
|
120
|
+
{{ status('unreachable', 'Not checked') }} Akismet could not be reached to check key {{ akismetKeyHint }}.
|
|
120
121
|
{% else %}
|
|
121
|
-
|
|
122
|
+
{{ status('disconnected', 'Not connected') }} This site has no key.
|
|
122
123
|
{% endif %}
|
|
123
124
|
</p>
|
|
124
|
-
<p class="
|
|
125
|
+
<p class="text-sm">{{ akismetHint }}</p>
|
|
125
126
|
|
|
126
127
|
<form method="post" action="{{ akismetUrl }}">
|
|
127
128
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
@@ -132,28 +133,28 @@
|
|
|
132
133
|
{% endif %}
|
|
133
134
|
{{ field.password('settings-akismet-key', akismetFields.key, 'Akismet API key',
|
|
134
135
|
autocomplete='off', spellcheck='false', placeholder=akismetPlaceholder) }}
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
</
|
|
136
|
+
<div class="mt-2">
|
|
137
|
+
{{ button('Replace key' if akismetPresent else 'Save key', type='submit', color='primary') }}
|
|
138
|
+
</div>
|
|
138
139
|
</form>
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
<
|
|
142
|
-
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
143
|
-
<input type="hidden" name="{{ akismetFields.action }}" value="{{ akismetRemove }}" />
|
|
144
|
-
<p class="admin-settings-buttons">
|
|
145
|
-
<button type="submit">Remove key</button>
|
|
146
|
-
</p>
|
|
147
|
-
</form>
|
|
148
|
-
{% endif %}
|
|
149
|
-
|
|
150
|
-
<p class="admin-hint">
|
|
151
|
-
A key from <a href="https://akismet.com/">akismet.com</a> turns on spam
|
|
141
|
+
<p class="text-sm">
|
|
142
|
+
A key from <a class="link" href="https://akismet.com/">akismet.com</a> turns on spam
|
|
152
143
|
checking for native comments and incoming webmentions. It is checked with
|
|
153
144
|
Akismet when it is saved and kept in <code>data/akismet.json</code>, never
|
|
154
145
|
in <code>site.json</code> and never on the public site. Marking something
|
|
155
146
|
spam or not spam on the Comments screen tells Akismet, which is the only
|
|
156
147
|
way it learns. Fediverse replies are never sent to it.
|
|
157
148
|
</p>
|
|
158
|
-
|
|
149
|
+
|
|
150
|
+
{% if akismetPresent %}
|
|
151
|
+
<form method="post" action="{{ akismetUrl }}">
|
|
152
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
153
|
+
<input type="hidden" name="{{ akismetFields.action }}" value="{{ akismetRemove }}" />
|
|
154
|
+
{% call cardActions() %}
|
|
155
|
+
{{ button('Remove key', type='submit', color='error', style='ghost') }}
|
|
156
|
+
{% endcall %}
|
|
157
|
+
</form>
|
|
158
|
+
{% endif %}
|
|
159
|
+
{% endcall %}
|
|
159
160
|
{% endblock %}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
to it goes. #}
|
|
3
3
|
{% extends "layouts/settings-page.njk" %}
|
|
4
4
|
{% import "components/fields.njk" as field %}
|
|
5
|
+
{% from "components/badge.njk" import status %}
|
|
6
|
+
{% from "components/button.njk" import button %}
|
|
7
|
+
{% from "components/card.njk" import card, cardActions %}
|
|
5
8
|
|
|
6
9
|
{% block settingsProblems %}
|
|
7
10
|
{{ field.problem('settings-mail-provider', problems.mailProvider) }}
|
|
@@ -50,7 +53,7 @@
|
|
|
50
53
|
{{ field.text('settings-contact-email', fields.contactEmail, 'Contact address',
|
|
51
54
|
form.contactEmail, error=problems.contactEmail, hint=contactHint, inputmode='email') }}
|
|
52
55
|
|
|
53
|
-
<h2>Security contact</h2>
|
|
56
|
+
<h2 class="mt-6 text-lg font-semibold">Security contact</h2>
|
|
54
57
|
|
|
55
58
|
{% set securityContactsHint %}
|
|
56
59
|
Where somebody who finds a vulnerability in this site should report it, one per line:
|
|
@@ -76,19 +79,18 @@
|
|
|
76
79
|
key is: an API key and an SMTP password belong in data/mail.json, not in
|
|
77
80
|
the public site.json, and a mistyped one must not lose an edit to the
|
|
78
81
|
From line. #}
|
|
79
|
-
|
|
80
|
-
<h2>Mail credentials</h2>
|
|
82
|
+
{% call card(title='Mail credentials') %}
|
|
81
83
|
|
|
82
|
-
<p
|
|
84
|
+
<p>
|
|
83
85
|
{% if mailConfigured %}
|
|
84
|
-
|
|
86
|
+
{{ status('connected', 'Configured') }} Sending through {{ mailProvider }}.
|
|
85
87
|
{% elif mailProvider == 'none' %}
|
|
86
|
-
|
|
88
|
+
{{ status('disconnected', 'Not sending') }} No provider is chosen.
|
|
87
89
|
{% else %}
|
|
88
|
-
|
|
90
|
+
{{ status('missing', 'Not configured') }} {{ mailProvider }} is chosen but has no credential.
|
|
89
91
|
{% endif %}
|
|
90
92
|
</p>
|
|
91
|
-
<p class="
|
|
93
|
+
<p class="text-sm">{{ mailHint }}</p>
|
|
92
94
|
|
|
93
95
|
{# One provider's boxes, never both. A site sending through Brevo has no
|
|
94
96
|
use for an SMTP port and a site sending through SMTP has none for an API
|
|
@@ -103,14 +105,14 @@
|
|
|
103
105
|
would reasonably call this broken. Doing it the other way round would
|
|
104
106
|
take JavaScript, and no other admin screen needs any. #}
|
|
105
107
|
{% if mailProvider == 'none' %}
|
|
106
|
-
<p class="
|
|
108
|
+
<p class="text-sm">
|
|
107
109
|
No provider is <em>saved</em>, so there is no credential to give and no
|
|
108
110
|
box here to give it in. Choose one under <strong>Provider</strong> above
|
|
109
111
|
and press <strong>Save settings</strong> — picking it in the select
|
|
110
112
|
alone changes nothing — and the boxes it needs appear here.
|
|
111
113
|
</p>
|
|
112
114
|
{% else %}
|
|
113
|
-
<p class="
|
|
115
|
+
<p class="text-sm">
|
|
114
116
|
These are the boxes
|
|
115
117
|
<strong>{% if mailProvider == 'brevo' %}Brevo{% else %}SMTP{% endif %}</strong>
|
|
116
118
|
needs, and they follow the provider this site has <em>saved</em> — not
|
|
@@ -122,9 +124,9 @@
|
|
|
122
124
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
123
125
|
|
|
124
126
|
{% if mailProvider == 'brevo' %}
|
|
125
|
-
<h3>Brevo</h3>
|
|
127
|
+
<h3 class="font-semibold">Brevo</h3>
|
|
126
128
|
{% if mailBrevoPresent %}
|
|
127
|
-
<p class="
|
|
129
|
+
<p class="text-sm">A key is stored, ending {{ mailBrevoKeyHint }}.</p>
|
|
128
130
|
{% set brevoPlaceholder = 'Leave blank to keep the stored key' %}
|
|
129
131
|
{% else %}
|
|
130
132
|
{% set brevoPlaceholder = 'Paste your key' %}
|
|
@@ -132,7 +134,7 @@
|
|
|
132
134
|
{{ field.password('settings-mail-brevo-key', mailFields.brevoApiKey, 'API key',
|
|
133
135
|
autocomplete='off', spellcheck='false', placeholder=brevoPlaceholder) }}
|
|
134
136
|
{% else %}
|
|
135
|
-
<h3>SMTP</h3>
|
|
137
|
+
<h3 class="font-semibold">SMTP</h3>
|
|
136
138
|
{{ field.text('settings-mail-smtp-host', mailFields.smtpHost, 'Host', mailSmtp.host,
|
|
137
139
|
autocomplete='off') }}
|
|
138
140
|
|
|
@@ -159,9 +161,7 @@
|
|
|
159
161
|
autocomplete='off', spellcheck='false', placeholder=smtpPlaceholder) }}
|
|
160
162
|
{% endif %}
|
|
161
163
|
|
|
162
|
-
<
|
|
163
|
-
<button type="submit">Save credentials</button>
|
|
164
|
-
</p>
|
|
164
|
+
<div class="mt-2">{{ button('Save credentials', type='submit', color='primary') }}</div>
|
|
165
165
|
</form>
|
|
166
166
|
{% endif %}
|
|
167
167
|
|
|
@@ -173,22 +173,22 @@
|
|
|
173
173
|
{% set brevoStoredElsewhere = mailBrevoPresent and mailProvider != 'brevo' %}
|
|
174
174
|
{% set smtpStoredElsewhere = mailSmtpPresent and mailProvider != 'smtp' %}
|
|
175
175
|
{% if brevoStoredElsewhere or smtpStoredElsewhere %}
|
|
176
|
-
<h3>Also stored</h3>
|
|
176
|
+
<h3 class="font-semibold">Also stored</h3>
|
|
177
177
|
|
|
178
178
|
{% if brevoStoredElsewhere %}
|
|
179
|
-
<p
|
|
179
|
+
<p>
|
|
180
180
|
A <strong>Brevo</strong> API key, ending {{ mailBrevoKeyHint }}.
|
|
181
181
|
</p>
|
|
182
182
|
{% endif %}
|
|
183
183
|
{% if smtpStoredElsewhere %}
|
|
184
|
-
<p
|
|
184
|
+
<p>
|
|
185
185
|
An <strong>SMTP</strong> server:
|
|
186
186
|
{% if mailSmtp.user %}{{ mailSmtp.user }} at {% endif %}{{ mailSmtp.host }}
|
|
187
187
|
port {{ mailSmtp.port }}{% if mailSmtp.hasPassword %}, with a password{% endif %}.
|
|
188
188
|
</p>
|
|
189
189
|
{% endif %}
|
|
190
190
|
|
|
191
|
-
<p class="
|
|
191
|
+
<p class="text-sm">
|
|
192
192
|
Kept in <code>data/mail.json</code> and used by nothing while
|
|
193
193
|
{% if mailProvider == 'none' %}no provider is chosen{% else %}{% if mailProvider == 'brevo' %}Brevo{% else %}SMTP{% endif %} is the chosen provider{% endif %}.
|
|
194
194
|
Choose that provider above to edit it, or press
|
|
@@ -201,9 +201,9 @@
|
|
|
201
201
|
<form method="post" action="{{ mailUrl }}">
|
|
202
202
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
203
203
|
<input type="hidden" name="{{ mailFields.action }}" value="{{ mailRemove }}" />
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
{% call cardActions() %}
|
|
205
|
+
{{ button('Remove credentials', type='submit', color='error', style='ghost') }}
|
|
206
|
+
{% endcall %}
|
|
207
207
|
</form>
|
|
208
208
|
{% endif %}
|
|
209
209
|
|
|
@@ -216,18 +216,16 @@
|
|
|
216
216
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
217
217
|
{{ field.text('settings-mail-test-to', mailTestFields.to, 'Send a test message to', '',
|
|
218
218
|
inputmode='email', placeholder='you@example.com') }}
|
|
219
|
-
<
|
|
220
|
-
<button type="submit">Send test email</button>
|
|
221
|
-
</p>
|
|
219
|
+
<div class="mt-2">{{ button('Send test email', type='submit') }}</div>
|
|
222
220
|
</form>
|
|
223
221
|
{% endif %}
|
|
224
222
|
|
|
225
|
-
<p class="
|
|
223
|
+
<p class="text-sm">
|
|
226
224
|
Neither the API key nor the SMTP password is ever printed back into this
|
|
227
225
|
page. Both are kept in <code>data/mail.json</code> at mode
|
|
228
226
|
<code>0600</code>, beside the password hashes and the actor's private
|
|
229
227
|
keys, and never in <code>site.json</code>, which is public and in git.
|
|
230
228
|
Leaving a secret blank keeps the one already stored.
|
|
231
229
|
</p>
|
|
232
|
-
|
|
230
|
+
{% endcall %}
|
|
233
231
|
{% endblock %}
|
|
@@ -50,13 +50,19 @@
|
|
|
50
50
|
{{ field.text('settings-icon', fields.icon, 'Site icon', form.icon,
|
|
51
51
|
error=problems.icon, hint=iconHint) }}
|
|
52
52
|
|
|
53
|
-
<div class="
|
|
53
|
+
<div class="flex items-center gap-4">
|
|
54
54
|
{% if iconPreview %}
|
|
55
|
-
<
|
|
55
|
+
<div class="avatar shrink-0">
|
|
56
|
+
<div class="w-24 rounded-box">
|
|
57
|
+
<img class="object-cover" src="{{ iconPreview }}" alt="The current site icon" width="96" height="96" />
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
56
60
|
{% else %}
|
|
57
|
-
<
|
|
61
|
+
<div class="avatar avatar-placeholder shrink-0" aria-hidden="true">
|
|
62
|
+
<div class="w-24 rounded-box bg-base-300"></div>
|
|
63
|
+
</div>
|
|
58
64
|
{% endif %}
|
|
59
|
-
<p class="
|
|
65
|
+
<p class="text-sm">
|
|
60
66
|
{% if iconUnderived and not imageOptimization %}
|
|
61
67
|
Image optimization is off, so no icons are derived and the site links none.
|
|
62
68
|
{% elif iconUnderived %}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
used to. #}
|
|
3
3
|
{% extends "layouts/settings-page.njk" %}
|
|
4
4
|
{% import "components/fields.njk" as field %}
|
|
5
|
+
{% from "components/card.njk" import card %}
|
|
6
|
+
{% from "components/table.njk" import table %}
|
|
5
7
|
|
|
6
8
|
{% block settingsProblems %}
|
|
7
9
|
{{ field.problem('settings-tag-base', problems.tagBase) }}
|
|
@@ -22,12 +24,9 @@
|
|
|
22
24
|
{# The recorded renames are a record of what happened rather than a
|
|
23
25
|
preference: the taxonomy screens write them as a term is renamed or merged,
|
|
24
26
|
and this page is where the URLs they are about are decided. #}
|
|
25
|
-
|
|
26
|
-
<h2>Archive redirects</h2>
|
|
27
|
-
|
|
27
|
+
{% call card(title='Archive redirects') %}
|
|
28
28
|
{% if taxonomyRedirects.length %}
|
|
29
|
-
|
|
30
|
-
<caption class="admin-visually-hidden">Archive redirects</caption>
|
|
29
|
+
{% call table('Archive redirects', inCard=true) %}
|
|
31
30
|
<thead>
|
|
32
31
|
<tr><th scope="col">Taxonomy</th><th scope="col">Was</th><th scope="col">Is now</th></tr>
|
|
33
32
|
</thead>
|
|
@@ -40,17 +39,17 @@
|
|
|
40
39
|
</tr>
|
|
41
40
|
{% endfor %}
|
|
42
41
|
</tbody>
|
|
43
|
-
|
|
44
|
-
<p class="
|
|
42
|
+
{% endcall %}
|
|
43
|
+
<p class="text-sm">
|
|
45
44
|
Every one of these answers with a permanent redirect to the archive the
|
|
46
45
|
term lives at now, so a link somebody published still arrives.
|
|
47
46
|
</p>
|
|
48
47
|
{% else %}
|
|
49
|
-
<p class="
|
|
48
|
+
<p class="text-sm">
|
|
50
49
|
Nothing has moved yet. Renaming or merging a term on the Tags or
|
|
51
50
|
Categories screen records the archive URL it used to have here, and the
|
|
52
51
|
site answers that URL with a permanent redirect from then on.
|
|
53
52
|
</p>
|
|
54
53
|
{% endif %}
|
|
55
|
-
|
|
54
|
+
{% endcall %}
|
|
56
55
|
{% endblock %}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
{% endblock %}
|
|
7
7
|
|
|
8
8
|
{% block settingsFields %}
|
|
9
|
-
<h2>Location</h2>
|
|
9
|
+
<h2 class="mt-2 text-lg font-semibold">Location</h2>
|
|
10
10
|
|
|
11
11
|
{% set locationHint %}
|
|
12
12
|
A Micropub app such as Quill can send where a post was written, and the
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
{% endfor %}
|
|
27
27
|
{% endcall %}
|
|
28
28
|
|
|
29
|
-
<h2>Uploads</h2>
|
|
29
|
+
<h2 class="mt-6 text-lg font-semibold">Uploads</h2>
|
|
30
30
|
|
|
31
|
-
<p class="
|
|
31
|
+
<p class="max-w-prose text-sm">
|
|
32
32
|
Location and camera metadata is always removed from pictures and videos as
|
|
33
33
|
they are uploaded, whatever is chosen above. Files uploaded before this
|
|
34
34
|
version keep theirs until <code>geekity strip-metadata</code> is run over
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
radio and a select that can disagree with each other. #}
|
|
25
25
|
{% set homepageGone %}
|
|
26
26
|
{% if homepageMissing %}
|
|
27
|
-
<p
|
|
27
|
+
<p>
|
|
28
28
|
The homepage was <code>{{ form.homepage }}</code>, which is no longer published,
|
|
29
29
|
so the site is showing its latest posts. Publish that page again to have it back,
|
|
30
30
|
or pick another.
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
{% set postsPageGone %}
|
|
47
47
|
{% if postsPageMissing %}
|
|
48
|
-
<p
|
|
48
|
+
<p>
|
|
49
49
|
The posts page was <code>{{ form.postsPage }}</code>, which is no longer published,
|
|
50
50
|
so the listing has no page of its own.
|
|
51
51
|
</p>
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
|
|
101
101
|
{# TASK-148. What /robots.txt tells crawlers beyond keeping them out of the
|
|
102
102
|
admin, which it does whatever is set here. #}
|
|
103
|
-
<h2>Crawlers</h2>
|
|
103
|
+
<h2 class="mt-6 text-lg font-semibold">Crawlers</h2>
|
|
104
104
|
|
|
105
105
|
{% set aiHint %}
|
|
106
106
|
Training crawlers gather pages to train AI models. Retrieval crawlers fetch a
|