@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
|
@@ -6,62 +6,64 @@
|
|
|
6
6
|
readonly fields, which select and copy like any other text; copy.js adds a
|
|
7
7
|
button beside each of them and nothing else. #}
|
|
8
8
|
{% extends "layouts/shell.njk" %}
|
|
9
|
+
{% from "components/badge.njk" import status %}
|
|
10
|
+
{% from "components/button.njk" import button, buttonLink %}
|
|
11
|
+
{% from "components/card.njk" import card, cardActions %}
|
|
12
|
+
{% from "components/copy.njk" import copyField %}
|
|
13
|
+
{% from "components/pagination.njk" import pagination %}
|
|
14
|
+
{% from "components/table.njk" import table %}
|
|
9
15
|
|
|
10
16
|
{% block title %}{{ heading }}{% endblock %}
|
|
11
17
|
|
|
12
18
|
{% block content %}
|
|
13
|
-
<h1>{{ heading }}</h1>
|
|
19
|
+
<h1 class="mb-6 text-2xl font-bold">{{ heading }}</h1>
|
|
14
20
|
|
|
15
21
|
{% if confirm %}
|
|
16
|
-
<form class="
|
|
22
|
+
<form class="mb-6 max-w-2xl" method="post" action="{{ deleteUrl }}">
|
|
17
23
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
18
24
|
<input type="hidden" name="{{ fields.path }}" value="{{ confirm.path }}" />
|
|
19
25
|
<input type="hidden" name="{{ fields.confirm }}" value="1" />
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<button type="submit">Delete it anyway</button>
|
|
41
|
-
<a href="{{ listUrl }}">Cancel</a>
|
|
42
|
-
</p>
|
|
26
|
+
{% call card(title='Delete ' + confirm.url + '?', style='border') %}
|
|
27
|
+
<p>
|
|
28
|
+
{{ confirm.references.length }}
|
|
29
|
+
{% if confirm.references.length == 1 %}document still points{% else %}documents still point{% endif %}
|
|
30
|
+
at it. Deleting the file does not change them, so they will point at nothing.
|
|
31
|
+
</p>
|
|
32
|
+
<ul class="list-disc pl-6">
|
|
33
|
+
{% for reference in confirm.references %}
|
|
34
|
+
<li>
|
|
35
|
+
<a class="link" href="{{ reference.editUrl }}">{{ reference.title }}</a>
|
|
36
|
+
{% if reference.trashed %}{{ status('trashed', 'Trash') }}{% endif %}
|
|
37
|
+
<code>{{ reference.path }}</code>
|
|
38
|
+
</li>
|
|
39
|
+
{% endfor %}
|
|
40
|
+
</ul>
|
|
41
|
+
{% call cardActions() %}
|
|
42
|
+
{{ buttonLink('Cancel', listUrl, style='ghost') }}
|
|
43
|
+
{{ button('Delete it anyway', type='submit', color='error') }}
|
|
44
|
+
{% endcall %}
|
|
45
|
+
{% endcall %}
|
|
43
46
|
</form>
|
|
44
47
|
{% endif %}
|
|
45
48
|
|
|
46
|
-
<form class="
|
|
49
|
+
<form class="mb-8 max-w-2xl" method="post" action="{{ uploadUrl }}" enctype="multipart/form-data">
|
|
47
50
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
</
|
|
51
|
+
<div class="fieldset">
|
|
52
|
+
<label class="fieldset-legend" for="media-file">Add a file</label>
|
|
53
|
+
<input id="media-file" name="{{ fields.file }}" type="file" accept="{{ accepts }}" required class="file-input file-input-sm w-full" />
|
|
54
|
+
<p class="label block whitespace-normal">
|
|
55
|
+
This site accepts {{ accepts | replace(',', ', ') }}, up to {{ maxBytes }} bytes, or
|
|
56
|
+
{{ mediaMaxBytes }} bytes for audio and video. The same
|
|
57
|
+
rules the editor’s upload control enforces.
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="mt-2">{{ button('Upload', type='submit', color='primary') }}</div>
|
|
58
61
|
</form>
|
|
59
62
|
|
|
60
63
|
{% if total == 0 %}
|
|
61
|
-
<p
|
|
64
|
+
<p>Nothing has been uploaded yet.</p>
|
|
62
65
|
{% else %}
|
|
63
|
-
|
|
64
|
-
<caption class="admin-visually-hidden">{{ heading }}</caption>
|
|
66
|
+
{% call table(heading) %}
|
|
65
67
|
<thead>
|
|
66
68
|
<tr>
|
|
67
69
|
<th scope="col">File</th>
|
|
@@ -76,117 +78,94 @@
|
|
|
76
78
|
<tbody>
|
|
77
79
|
{% for file in files %}
|
|
78
80
|
<tr>
|
|
79
|
-
<td
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
<td>
|
|
82
|
+
<div class="flex min-w-56 items-center gap-3">
|
|
83
|
+
{% if file.kind == 'image' %}
|
|
84
|
+
<a class="shrink-0" href="{{ file.url }}">
|
|
85
|
+
<img class="size-15 rounded-box bg-base-200 object-cover" src="{{ file.url }}" alt="" width="60" height="60" />
|
|
86
|
+
</a>
|
|
87
|
+
{% else %}
|
|
88
|
+
<span class="flex size-15 shrink-0 items-center justify-center rounded-box bg-base-200 text-xs font-bold" aria-hidden="true">{{ file.extension | replace('.', '') | upper }}</span>
|
|
89
|
+
{% endif %}
|
|
90
|
+
<a class="link link-hover break-all" href="{{ file.url }}">{{ file.path }}</a>
|
|
91
|
+
</div>
|
|
88
92
|
</td>
|
|
89
|
-
<td
|
|
93
|
+
<td>
|
|
90
94
|
{% if file.kind == 'image' %}
|
|
91
|
-
<form method="post" action="{{ altUrl }}">
|
|
95
|
+
<form class="flex min-w-72 flex-wrap items-center gap-2" method="post" action="{{ altUrl }}">
|
|
92
96
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
93
97
|
<input type="hidden" name="{{ fields.path }}" value="{{ file.path }}" />
|
|
94
98
|
<input type="hidden" name="{{ fields.page }}" value="{{ page }}" />
|
|
95
|
-
<label class="
|
|
96
|
-
Alt text for {{ file.name }}
|
|
97
|
-
</label>
|
|
99
|
+
<label class="sr-only" for="media-alt-{{ loop.index }}">Alt text for {{ file.name }}</label>
|
|
98
100
|
<input
|
|
99
101
|
id="media-alt-{{ loop.index }}"
|
|
100
102
|
name="{{ fields.alt }}"
|
|
101
103
|
type="text"
|
|
102
104
|
value="{% if file.alt and file.alt.kind == 'described' %}{{ file.alt.text }}{% endif %}"
|
|
103
105
|
aria-describedby="media-alt-state-{{ loop.index }}"
|
|
106
|
+
class="input input-sm min-w-40 flex-1"
|
|
104
107
|
/>
|
|
105
|
-
<
|
|
108
|
+
<label class="label text-sm text-base-content" for="media-decorative-{{ loop.index }}">
|
|
106
109
|
<input
|
|
107
110
|
id="media-decorative-{{ loop.index }}"
|
|
108
111
|
name="{{ fields.decorative }}"
|
|
109
112
|
type="checkbox"
|
|
110
113
|
value="1"
|
|
111
114
|
{% if file.alt and file.alt.kind == 'decorative' %}checked{% endif %}
|
|
115
|
+
class="checkbox checkbox-sm"
|
|
112
116
|
/>
|
|
113
|
-
|
|
114
|
-
</
|
|
115
|
-
|
|
116
|
-
<
|
|
117
|
-
{%- if not file.alt %}Missing
|
|
118
|
-
{%- elif file.alt.kind == 'decorative' %}Decorative
|
|
119
|
-
{%- else %}Offered when it is inserted{% endif -%}
|
|
120
|
-
</
|
|
117
|
+
Decorative
|
|
118
|
+
</label>
|
|
119
|
+
{{ button('Save', type='submit', size='sm') }}
|
|
120
|
+
<p id="media-alt-state-{{ loop.index }}" class="w-full text-sm">
|
|
121
|
+
{%- if not file.alt %}{{ status('missing', 'Missing') }} Describe it, or mark it decorative.
|
|
122
|
+
{%- elif file.alt.kind == 'decorative' %}{{ status('decorative', 'Decorative') }} Inserted with an empty alt.
|
|
123
|
+
{%- else %}{{ status('described', 'Described') }} Offered when it is inserted.{% endif -%}
|
|
124
|
+
</p>
|
|
121
125
|
</form>
|
|
122
126
|
{% else %}
|
|
123
|
-
|
|
127
|
+
Not an image
|
|
124
128
|
{% endif %}
|
|
125
129
|
</td>
|
|
126
|
-
<td>{{ file.modified | date('html') }}</td>
|
|
127
|
-
<td>{{ file.bytes }}</td>
|
|
128
|
-
<td
|
|
129
|
-
<
|
|
130
|
-
URL of
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
<input id="media-url-{{ loop.index }}" type="text" value="{{ file.url }}" readonly />
|
|
134
|
-
<button type="button" class="admin-copy-button" data-copy="media-url-{{ loop.index }}" hidden>
|
|
135
|
-
Copy
|
|
136
|
-
</button>
|
|
137
|
-
</span>
|
|
138
|
-
|
|
139
|
-
<label class="admin-visually-hidden" for="media-markdown-{{ loop.index }}">
|
|
140
|
-
Markdown for {{ file.name }}
|
|
141
|
-
</label>
|
|
142
|
-
<span class="admin-copy">
|
|
143
|
-
<input id="media-markdown-{{ loop.index }}" type="text" value="{{ file.markdown }}" readonly />
|
|
144
|
-
<button type="button" class="admin-copy-button" data-copy="media-markdown-{{ loop.index }}" hidden>
|
|
145
|
-
Copy
|
|
146
|
-
</button>
|
|
147
|
-
</span>
|
|
130
|
+
<td class="whitespace-nowrap">{{ file.modified | date('html') }}</td>
|
|
131
|
+
<td class="whitespace-nowrap">{{ file.bytes }}</td>
|
|
132
|
+
<td>
|
|
133
|
+
<div class="flex min-w-60 flex-col gap-2">
|
|
134
|
+
{{ copyField('media-url-' + loop.index, 'URL of ' + file.name, file.url) }}
|
|
135
|
+
{{ copyField('media-markdown-' + loop.index, 'Markdown for ' + file.name, file.markdown) }}
|
|
136
|
+
</div>
|
|
148
137
|
</td>
|
|
149
138
|
<td>
|
|
150
139
|
{% if file.references.length %}
|
|
151
|
-
<ul
|
|
140
|
+
<ul>
|
|
152
141
|
{% for reference in file.references %}
|
|
153
|
-
<li>
|
|
154
|
-
<a href="{{ reference.editUrl }}">{{ reference.title }}</a>
|
|
155
|
-
{% if reference.trashed %}
|
|
156
|
-
<span class="admin-status admin-status-trashed">Trash</span>
|
|
157
|
-
{% endif %}
|
|
142
|
+
<li class="whitespace-nowrap">
|
|
143
|
+
<a class="link link-hover" href="{{ reference.editUrl }}">{{ reference.title }}</a>
|
|
144
|
+
{% if reference.trashed %}{{ status('trashed', 'Trash') }}{% endif %}
|
|
158
145
|
</li>
|
|
159
146
|
{% endfor %}
|
|
160
147
|
</ul>
|
|
161
148
|
{% else %}
|
|
162
|
-
|
|
149
|
+
Nothing
|
|
163
150
|
{% endif %}
|
|
164
151
|
</td>
|
|
165
|
-
<td
|
|
152
|
+
<td>
|
|
166
153
|
<form method="post" action="{{ deleteUrl }}">
|
|
167
154
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
168
155
|
<input type="hidden" name="{{ fields.path }}" value="{{ file.path }}" />
|
|
169
|
-
|
|
156
|
+
{{ button('Delete', type='submit', style='ghost', size='xs') }}
|
|
170
157
|
</form>
|
|
171
158
|
</td>
|
|
172
159
|
</tr>
|
|
173
160
|
{% endfor %}
|
|
174
161
|
</tbody>
|
|
175
|
-
|
|
162
|
+
{% endcall %}
|
|
176
163
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
<a rel="prev" href="{{ previousUrl }}">Newer</a>
|
|
181
|
-
{% endif %}
|
|
182
|
-
<span>Page {{ page }} of {{ pages }}</span>
|
|
183
|
-
{% if nextUrl %}
|
|
184
|
-
<a rel="next" href="{{ nextUrl }}">Older</a>
|
|
185
|
-
{% endif %}
|
|
186
|
-
</nav>
|
|
187
|
-
{% endif %}
|
|
164
|
+
<div class="mt-4">
|
|
165
|
+
{{ pagination(page, pages=pages, previousUrl=previousUrl, nextUrl=nextUrl) }}
|
|
166
|
+
</div>
|
|
188
167
|
|
|
189
|
-
<p class="
|
|
168
|
+
<p class="mt-4 max-w-prose text-sm">
|
|
190
169
|
The list is the <code>content/uploads</code> directory itself, read afresh on every request,
|
|
191
170
|
so a file copied in by hand is here without a restart. Deleting one removes the file and
|
|
192
171
|
whatever was derived from it; it does not touch the documents that link to it, which is why
|
|
@@ -1,104 +1,98 @@
|
|
|
1
1
|
{# /admin/messages: what the contact form left behind (TASK-56).
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
One card per message, as on Comments. An unread one says so in a badge and
|
|
4
|
+
in a heavier name, so it is not told by colour alone. A message is a file
|
|
5
|
+
under data/contact/ and nothing else, so the two buttons are a rewrite and
|
|
6
|
+
an unlink, each a form of its own. #}
|
|
6
7
|
{% extends "layouts/shell.njk" %}
|
|
8
|
+
{% from "components/badge.njk" import status as statusBadge %}
|
|
9
|
+
{% from "components/button.njk" import button, buttonLink %}
|
|
10
|
+
{% from "components/card.njk" import card %}
|
|
11
|
+
{% from "components/pagination.njk" import pagination %}
|
|
12
|
+
{% from "components/tabs.njk" import tabs as tabsNav %}
|
|
7
13
|
|
|
8
14
|
{% block title %}{{ heading }}{% endblock %}
|
|
9
15
|
|
|
10
16
|
{% block content %}
|
|
11
|
-
<h1>{{ heading }}</h1>
|
|
17
|
+
<h1 class="mb-6 text-2xl font-bold">{{ heading }}</h1>
|
|
12
18
|
|
|
13
|
-
<
|
|
14
|
-
<ul>
|
|
15
|
-
{% for tab in tabs %}
|
|
16
|
-
<li>
|
|
17
|
-
<a href="{{ tab.url }}"{% if tab.current %} aria-current="page"{% endif %}>
|
|
18
|
-
{{ tab.label }} ({{ tab.count }})
|
|
19
|
-
</a>
|
|
20
|
-
</li>
|
|
21
|
-
{% endfor %}
|
|
22
|
-
</ul>
|
|
23
|
-
</nav>
|
|
19
|
+
<div class="mb-6">{{ tabsNav(tabs, 'Message status') }}</div>
|
|
24
20
|
|
|
25
21
|
{% if rows.length == 0 %}
|
|
26
|
-
<p class="
|
|
22
|
+
<p class="mb-6">Nothing here.</p>
|
|
27
23
|
{% else %}
|
|
28
|
-
<ul class="
|
|
24
|
+
<ul class="mb-6 flex max-w-3xl flex-col gap-4">
|
|
29
25
|
{% for row in rows %}
|
|
30
|
-
<li
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
<li>
|
|
27
|
+
{% call card() %}
|
|
28
|
+
<p class="flex flex-wrap items-center gap-x-3 gap-y-1">
|
|
29
|
+
{% if row.read %}
|
|
30
|
+
<span class="font-medium">{{ row.author }}</span>
|
|
31
|
+
{% else %}
|
|
32
|
+
<span class="font-bold">{{ row.author }}</span>
|
|
33
|
+
{% endif %}
|
|
34
|
+
<a class="link break-all" href="mailto:{{ row.email }}">{{ row.email }}</a>
|
|
35
|
+
{% if not row.read %}
|
|
36
|
+
{{ statusBadge('unread', 'Unread') }}
|
|
37
|
+
{% endif %}
|
|
38
|
+
{% if row.status == 'spam' %}
|
|
39
|
+
{{ statusBadge('spam', 'Spam') }}
|
|
40
|
+
{% endif %}
|
|
41
|
+
</p>
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
<p class="text-sm">
|
|
44
|
+
<time>{{ row.received }}</time>
|
|
45
|
+
from
|
|
46
|
+
{% if row.pageUrl %}
|
|
47
|
+
<a class="link" href="{{ row.pageUrl }}">{{ row.page }}</a>
|
|
48
|
+
{% else %}
|
|
49
|
+
{{ row.page }}
|
|
50
|
+
{% endif %}
|
|
51
|
+
{% if row.address %}
|
|
52
|
+
· <code title="A salted hash of the address it came from">{{ row.address }}</code>
|
|
53
|
+
{% endif %}
|
|
54
|
+
</p>
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
{% if row.subject %}
|
|
57
|
+
<p class="font-semibold">{{ row.subject }}</p>
|
|
58
|
+
{% endif %}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
{# Printed, never rendered: a contact message is plain text, and
|
|
61
|
+
nothing a stranger typed is turned into markup here. #}
|
|
62
|
+
<div class="whitespace-pre-line break-words">{{ row.message }}</div>
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
<div class="flex flex-wrap gap-1">
|
|
65
|
+
{{ buttonLink('Reply', row.replyUrl, style='ghost', size='xs') }}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
<form method="post" action="{{ readUrl }}">
|
|
68
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
69
|
+
<input type="hidden" name="{{ fields.id }}" value="{{ row.id }}" />
|
|
70
|
+
<input type="hidden" name="{{ fields.status }}" value="{{ status }}" />
|
|
71
|
+
{% if row.read %}
|
|
72
|
+
{{ button('Mark unread', type='submit', style='ghost', size='xs', name=fields.read) }}
|
|
73
|
+
{% else %}
|
|
74
|
+
{{ button('Mark read', type='submit', style='ghost', size='xs', name=fields.read, value='1') }}
|
|
75
|
+
{% endif %}
|
|
76
|
+
</form>
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
<form method="post" action="{{ deleteUrl }}">
|
|
79
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
80
|
+
<input type="hidden" name="{{ fields.id }}" value="{{ row.id }}" />
|
|
81
|
+
<input type="hidden" name="{{ fields.status }}" value="{{ status }}" />
|
|
82
|
+
{{ button('Delete', type='submit', color='error', style='ghost', size='xs') }}
|
|
83
|
+
</form>
|
|
84
|
+
</div>
|
|
85
|
+
{% endcall %}
|
|
84
86
|
</li>
|
|
85
87
|
{% endfor %}
|
|
86
88
|
</ul>
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<a rel="prev" href="{{ previousUrl }}">Newer</a>
|
|
92
|
-
{% endif %}
|
|
93
|
-
<span>Page {{ page }} of {{ pages }}</span>
|
|
94
|
-
{% if nextUrl %}
|
|
95
|
-
<a rel="next" href="{{ nextUrl }}">Older</a>
|
|
96
|
-
{% endif %}
|
|
97
|
-
</nav>
|
|
98
|
-
{% endif %}
|
|
90
|
+
<div class="mb-6">
|
|
91
|
+
{{ pagination(page, pages=pages, previousUrl=previousUrl, nextUrl=nextUrl) }}
|
|
92
|
+
</div>
|
|
99
93
|
{% endif %}
|
|
100
94
|
|
|
101
|
-
<p class="
|
|
95
|
+
<p class="max-w-prose text-sm">
|
|
102
96
|
A page carrying <code>contact: true</code> in its front matter offers a contact form, and every
|
|
103
97
|
message it takes is one JSON file under <code>data/contact/</code> — written before
|
|
104
98
|
anything is emailed, so a message survives a mail outage and a site with no mail configured
|