@geekity/cms 0.4.0 → 0.5.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 +234 -108
- package/admin/pages/documents/editor.njk +0 -13
- package/admin/pages/{settings/federation.njk → federation/settings.njk} +7 -5
- package/admin/pages/navigation/menus.njk +129 -0
- package/admin/pages/settings/reading.njk +2 -10
- package/admin/pages/tools/content-index.njk +89 -0
- package/admin/pages/users/list.njk +5 -2
- package/admin/static/admin.css +60 -0
- package/dist/access-log.d.ts +44 -0
- package/dist/access-log.d.ts.map +1 -0
- package/dist/access-log.js +71 -0
- package/dist/access-log.js.map +1 -0
- package/dist/admin/documents.d.ts +3 -13
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +2 -34
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/federation.d.ts +10 -0
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +10 -0
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/index.d.ts +3 -3
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +2 -2
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/menu.d.ts.map +1 -1
- package/dist/admin/menu.js +19 -2
- package/dist/admin/menu.js.map +1 -1
- package/dist/admin/navigation.d.ts +155 -0
- package/dist/admin/navigation.d.ts.map +1 -0
- package/dist/admin/navigation.js +292 -0
- package/dist/admin/navigation.js.map +1 -0
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +9 -0
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/session.d.ts +17 -3
- package/dist/admin/session.d.ts.map +1 -1
- package/dist/admin/session.js +19 -5
- package/dist/admin/session.js.map +1 -1
- package/dist/admin/settings-federation.d.ts +10 -1
- package/dist/admin/settings-federation.d.ts.map +1 -1
- package/dist/admin/settings-federation.js +26 -6
- package/dist/admin/settings-federation.js.map +1 -1
- package/dist/admin/settings-page.d.ts +26 -9
- package/dist/admin/settings-page.d.ts.map +1 -1
- package/dist/admin/settings-page.js +18 -12
- package/dist/admin/settings-page.js.map +1 -1
- package/dist/admin/settings-pages.d.ts +14 -3
- package/dist/admin/settings-pages.d.ts.map +1 -1
- package/dist/admin/settings-pages.js +15 -5
- package/dist/admin/settings-pages.js.map +1 -1
- package/dist/admin/settings-reading.d.ts +5 -4
- package/dist/admin/settings-reading.d.ts.map +1 -1
- package/dist/admin/settings-reading.js +6 -5
- package/dist/admin/settings-reading.js.map +1 -1
- package/dist/admin/settings.d.ts +29 -15
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +28 -78
- package/dist/admin/settings.js.map +1 -1
- package/dist/admin/templates.d.ts +3 -1
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +3 -1
- package/dist/admin/templates.js.map +1 -1
- package/dist/admin/tools.d.ts +111 -0
- package/dist/admin/tools.d.ts.map +1 -0
- package/dist/admin/tools.js +182 -0
- package/dist/admin/tools.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -1
- package/dist/cli.js.map +1 -1
- package/dist/comments/form.d.ts +48 -0
- package/dist/comments/form.d.ts.map +1 -1
- package/dist/comments/form.js +15 -0
- package/dist/comments/form.js.map +1 -1
- package/dist/comments/index.d.ts +4 -3
- package/dist/comments/index.d.ts.map +1 -1
- package/dist/comments/index.js +2 -1
- package/dist/comments/index.js.map +1 -1
- package/dist/comments/routes.d.ts +3 -1
- package/dist/comments/routes.d.ts.map +1 -1
- package/dist/comments/routes.js +46 -11
- package/dist/comments/routes.js.map +1 -1
- package/dist/comments/submission.d.ts +37 -3
- package/dist/comments/submission.d.ts.map +1 -1
- package/dist/comments/submission.js +59 -20
- package/dist/comments/submission.js.map +1 -1
- package/dist/comments/viewer.d.ts +21 -0
- package/dist/comments/viewer.d.ts.map +1 -0
- package/dist/comments/viewer.js +45 -0
- package/dist/comments/viewer.js.map +1 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/content/store.d.ts +18 -0
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +18 -0
- package/dist/content/store.js.map +1 -1
- package/dist/env.d.ts +12 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/federation/mount.d.ts +4 -0
- package/dist/federation/mount.d.ts.map +1 -1
- package/dist/federation/mount.js +44 -12
- package/dist/federation/mount.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -4
- package/dist/index.js.map +1 -1
- package/dist/web/index.d.ts +3 -3
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +1 -1
- package/dist/web/index.js.map +1 -1
- package/dist/web/navigation.d.ts +158 -36
- package/dist/web/navigation.d.ts.map +1 -1
- package/dist/web/navigation.js +250 -60
- package/dist/web/navigation.js.map +1 -1
- package/dist/web/negotiate.d.ts +22 -0
- package/dist/web/negotiate.d.ts.map +1 -1
- package/dist/web/negotiate.js +25 -10
- package/dist/web/negotiate.js.map +1 -1
- package/dist/web/render.d.ts +16 -10
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +21 -14
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +11 -2
- package/dist/web/routes.js.map +1 -1
- package/dist/web/themes.d.ts +21 -0
- package/dist/web/themes.d.ts.map +1 -1
- package/dist/web/themes.js +37 -0
- package/dist/web/themes.js.map +1 -1
- package/package.json +1 -1
- package/templates/site/content/_data/site.json +7 -0
- package/templates/site/content/pages/about.md +7 -0
- package/themes/default/README.md +200 -80
- package/themes/default/layouts/author.njk +3 -4
- package/themes/default/layouts/base.njk +38 -49
- package/themes/default/layouts/front-page.njk +1 -2
- package/themes/default/partials/bio.njk +15 -21
- package/themes/default/partials/comment-form.njk +31 -3
- package/themes/default/partials/menu.njk +32 -0
- package/themes/default/partials/search-form.njk +5 -0
- package/themes/default/static/style.css +31 -8
- package/themes/default/theme.json +5 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
{# /admin/navigation: every menu this site holds, and what renders it.
|
|
2
|
+
|
|
3
|
+
Two lists, and a menu is in exactly one of them. First the areas the active
|
|
4
|
+
theme declares, in the theme's own order and under the theme's own labels,
|
|
5
|
+
each a box to type links into — an area the site has never filled in is an
|
|
6
|
+
empty box rather than a missing one. Then every other menu the site stores,
|
|
7
|
+
under a heading that says this theme renders them nowhere, each with a
|
|
8
|
+
Delete, which is the only way a menu is removed. #}
|
|
9
|
+
{% extends "layouts/shell.njk" %}
|
|
10
|
+
{% import "components/fields.njk" as field %}
|
|
11
|
+
|
|
12
|
+
{% block title %}{{ heading }}{% endblock %}
|
|
13
|
+
|
|
14
|
+
{# One menu: its heading, the box its items are typed into, and the button
|
|
15
|
+
that saves it. `extra` is whatever else belongs under the box — the Delete
|
|
16
|
+
form, on a menu nothing renders. #}
|
|
17
|
+
{% macro box(menu, fields, url, csrfToken, extra='') %}
|
|
18
|
+
<li class="admin-menu-box">
|
|
19
|
+
<h3>{{ menu.label }}</h3>
|
|
20
|
+
|
|
21
|
+
<p class="admin-menu-name">
|
|
22
|
+
<code>menus.{{ menu.name }}</code>
|
|
23
|
+
{% if menu.stored %}
|
|
24
|
+
<span class="admin-status">{{ menu.count }} item{% if menu.count != 1 %}s{% endif %}</span>
|
|
25
|
+
{% else %}
|
|
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 <code>| me</code> to mark the link
|
|
40
|
+
<strong>a profile that is you</strong>: it is given
|
|
41
|
+
<code>rel="me"</code>, which is how Mastodon and the rest of the
|
|
42
|
+
IndieWeb verify that the site and the profile it links are the same
|
|
43
|
+
person.
|
|
44
|
+
{% endset %}
|
|
45
|
+
{{ field.textarea('menu-' + menu.name, fields.items, 'Items', menu.items,
|
|
46
|
+
error=menu.problem, hint=itemsHint, rows='5', spellcheck='false') }}
|
|
47
|
+
<p class="admin-menu-buttons">
|
|
48
|
+
<button type="submit">Save menu</button>
|
|
49
|
+
</p>
|
|
50
|
+
</form>
|
|
51
|
+
|
|
52
|
+
{{ extra | safe }}
|
|
53
|
+
</li>
|
|
54
|
+
{% endmacro %}
|
|
55
|
+
|
|
56
|
+
{% block content %}
|
|
57
|
+
<h1>{{ heading }}</h1>
|
|
58
|
+
|
|
59
|
+
{% if hasProblems %}
|
|
60
|
+
<p class="admin-field-error">Nothing was saved. See below.</p>
|
|
61
|
+
{% endif %}
|
|
62
|
+
|
|
63
|
+
<h2>Where {{ themeName }} puts a menu</h2>
|
|
64
|
+
|
|
65
|
+
{% if areas.length %}
|
|
66
|
+
<p class="admin-hint">
|
|
67
|
+
These are the areas the active theme declares in its <code>theme.json</code>, in the order it
|
|
68
|
+
declares them. A theme renders a menu by asking for it by name, and an area with nothing in it
|
|
69
|
+
renders nothing at all.
|
|
70
|
+
</p>
|
|
71
|
+
|
|
72
|
+
<ul class="admin-menu-boxes">
|
|
73
|
+
{% for menu in areas %}
|
|
74
|
+
{{ box(menu, fields, navigationUrl, csrfToken) }}
|
|
75
|
+
{% endfor %}
|
|
76
|
+
</ul>
|
|
77
|
+
{% else %}
|
|
78
|
+
<p class="admin-hint">
|
|
79
|
+
{{ themeName }} declares no areas, so it asks for no menu by name. A theme declares them as an
|
|
80
|
+
<code>areas</code> list in its <code>theme.json</code>, each entry a <code>name</code> —
|
|
81
|
+
the name the template loops over as <code>menus.<name></code> — and a
|
|
82
|
+
<code>label</code> to head the box here. A menu added below is still stored, ready for the
|
|
83
|
+
theme that asks for it.
|
|
84
|
+
</p>
|
|
85
|
+
{% endif %}
|
|
86
|
+
|
|
87
|
+
{% if others.length %}
|
|
88
|
+
<h2>Kept, rendered nowhere</h2>
|
|
89
|
+
|
|
90
|
+
<p class="admin-hint">
|
|
91
|
+
This site stores these menus and {{ themeName }} renders none of them: no template loops over
|
|
92
|
+
those names, so nothing on the public site shows them. They are kept, because that is how the
|
|
93
|
+
menu a theme will use gets written before the site switches to it. Delete is the only way one
|
|
94
|
+
is removed.
|
|
95
|
+
</p>
|
|
96
|
+
|
|
97
|
+
<ul class="admin-menu-boxes">
|
|
98
|
+
{% for menu in others %}
|
|
99
|
+
{% set remove %}
|
|
100
|
+
<form class="admin-menu-delete" method="post" action="{{ deleteUrl }}">
|
|
101
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
102
|
+
<input type="hidden" name="{{ fields.menu }}" value="{{ menu.name }}" />
|
|
103
|
+
<button type="submit">Delete this menu</button>
|
|
104
|
+
</form>
|
|
105
|
+
{% endset %}
|
|
106
|
+
{{ box(menu, fields, navigationUrl, csrfToken, remove) }}
|
|
107
|
+
{% endfor %}
|
|
108
|
+
</ul>
|
|
109
|
+
{% endif %}
|
|
110
|
+
|
|
111
|
+
<h2>Add a menu</h2>
|
|
112
|
+
|
|
113
|
+
<form class="admin-menu-add" method="post" action="{{ addUrl }}">
|
|
114
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
115
|
+
{% set nameHint %}
|
|
116
|
+
The name a theme asks for the menu by: lower-case letters, digits and
|
|
117
|
+
underscores, starting with a letter. It is written after the dot —
|
|
118
|
+
<code>{% raw %}{% for item in menus.footer %}{% endraw %}</code> — which is why a dash
|
|
119
|
+
is not allowed in one. A name this theme declares puts the menu in its place above; any other
|
|
120
|
+
name is a menu kept and rendered nowhere, which is how you fill one in before switching to the
|
|
121
|
+
theme that will use it.
|
|
122
|
+
{% endset %}
|
|
123
|
+
{{ field.text('menu-name', fields.name, 'Name', newName,
|
|
124
|
+
error=nameProblem, hint=nameHint, spellcheck='false', required=true) }}
|
|
125
|
+
<p class="admin-menu-buttons">
|
|
126
|
+
<button type="submit">Add menu</button>
|
|
127
|
+
</p>
|
|
128
|
+
</form>
|
|
129
|
+
{% endblock %}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{# /admin/settings/reading: what a visitor is shown, and who is told when it
|
|
2
|
-
changes.
|
|
2
|
+
changes. The site menu was here until TASK-108; it is on /admin/navigation,
|
|
3
|
+
with every other menu the site holds. #}
|
|
3
4
|
{% extends "layouts/settings-page.njk" %}
|
|
4
5
|
{% import "components/fields.njk" as field %}
|
|
5
6
|
|
|
@@ -53,15 +54,6 @@
|
|
|
53
54
|
error=problems.postsPerPage, inputmode='numeric', required=true,
|
|
54
55
|
hint='How many posts a listing page holds, on the home page and on every archive.') }}
|
|
55
56
|
|
|
56
|
-
{% set navigationHint %}
|
|
57
|
-
One item per line, written <code>Label | URL</code> — for example
|
|
58
|
-
<code>About | /about/</code> or <code>Mastodon | https://example.social/@me</code>.
|
|
59
|
-
They are the menu in the site header, in this order. A page can put itself
|
|
60
|
-
on the end of it by ticking <em>Show in navigation</em> in the editor.
|
|
61
|
-
{% endset %}
|
|
62
|
-
{{ field.textarea('settings-navigation', fields.navigation, 'Menu', form.navigation,
|
|
63
|
-
error=problems.navigation, hint=navigationHint, rows='4', spellcheck='false') }}
|
|
64
|
-
|
|
65
57
|
{% set notifyHint %}
|
|
66
58
|
An rssCloud and WebSub server, told whenever a feed changes so subscribers
|
|
67
59
|
hear at once instead of waiting for their next poll. Every feed advertises
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{# /admin/tools: what the derived index holds, and the button that reads every
|
|
2
|
+
file back into it without stopping the site (TASK-95).
|
|
3
|
+
|
|
4
|
+
The counts are the point of the screen as much as the button is: an index
|
|
5
|
+
that disagrees with the files is not something the site complains about, so
|
|
6
|
+
the only way to notice is to be shown what it holds. #}
|
|
7
|
+
{% extends "layouts/shell.njk" %}
|
|
8
|
+
|
|
9
|
+
{% block title %}{{ heading }}{% endblock %}
|
|
10
|
+
|
|
11
|
+
{% block content %}
|
|
12
|
+
<h1>{{ heading }}</h1>
|
|
13
|
+
|
|
14
|
+
<p>
|
|
15
|
+
Everything in <code>data/geekity.db</code> is a reading of the files under
|
|
16
|
+
<code>{{ contentDir }}</code>, so it can always be read again. Rebuild it after
|
|
17
|
+
editing content outside the admin — over ssh, or with a
|
|
18
|
+
<code>git pull</code> while the site was down — or when what is listed here
|
|
19
|
+
disagrees with what is on disk.
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<table class="admin-list">
|
|
23
|
+
<thead>
|
|
24
|
+
<tr>
|
|
25
|
+
<th scope="col">What the index holds</th>
|
|
26
|
+
<th scope="col">Count</th>
|
|
27
|
+
</tr>
|
|
28
|
+
</thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
<tr>
|
|
31
|
+
<th scope="row">Documents</th>
|
|
32
|
+
<td>{{ indexed.documents }}</td>
|
|
33
|
+
</tr>
|
|
34
|
+
<tr>
|
|
35
|
+
<th scope="row">Followers</th>
|
|
36
|
+
<td>{{ indexed.followers }}</td>
|
|
37
|
+
</tr>
|
|
38
|
+
<tr>
|
|
39
|
+
<th scope="row">Inbox activities</th>
|
|
40
|
+
<td>{{ indexed.activities }}</td>
|
|
41
|
+
</tr>
|
|
42
|
+
<tr>
|
|
43
|
+
<th scope="row">Comments</th>
|
|
44
|
+
<td>{{ indexed.comments }}</td>
|
|
45
|
+
</tr>
|
|
46
|
+
</tbody>
|
|
47
|
+
</table>
|
|
48
|
+
|
|
49
|
+
{% if confirm %}
|
|
50
|
+
{# Offered rather than done, the way a merge is on the taxonomy screens.
|
|
51
|
+
The window in the middle is the whole reason: for as long as the scan
|
|
52
|
+
runs the site answers 404 for documents whose files are perfectly fine,
|
|
53
|
+
and that is not a thing to discover afterwards. #}
|
|
54
|
+
<form class="admin-settings" method="post" action="{{ rebuildUrl }}">
|
|
55
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
56
|
+
<input type="hidden" name="{{ fields.confirm }}" value="1" />
|
|
57
|
+
|
|
58
|
+
<h2>Rebuild the index now?</h2>
|
|
59
|
+
|
|
60
|
+
<p>
|
|
61
|
+
The index is emptied and every file read again. Until the scan finishes, this
|
|
62
|
+
site answers <strong>404</strong> for its posts and pages — under a second
|
|
63
|
+
on a small site, longer on a large one — and it is serving the whole time.
|
|
64
|
+
Nothing is deleted and nothing is sent: you stay signed in, the delivery log and
|
|
65
|
+
the relays are kept, and no follower, feed or linked page is told about a post
|
|
66
|
+
the scan re-indexes.
|
|
67
|
+
</p>
|
|
68
|
+
|
|
69
|
+
<p class="admin-settings-buttons">
|
|
70
|
+
<button type="submit">Rebuild it</button>
|
|
71
|
+
<a href="{{ toolsUrl }}">Cancel</a>
|
|
72
|
+
</p>
|
|
73
|
+
</form>
|
|
74
|
+
{% else %}
|
|
75
|
+
<form class="admin-settings" method="post" action="{{ rebuildUrl }}">
|
|
76
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
77
|
+
<p class="admin-settings-buttons">
|
|
78
|
+
<button type="submit">Rebuild the index</button>
|
|
79
|
+
</p>
|
|
80
|
+
</form>
|
|
81
|
+
{% endif %}
|
|
82
|
+
|
|
83
|
+
<p class="admin-hint">
|
|
84
|
+
This runs on the site as it is: nothing is deleted, no connection is swapped and
|
|
85
|
+
nobody is signed out. <code>geekity rebuild</code> at the command line is the other
|
|
86
|
+
door, and it is for the one case this screen cannot be: a database this version
|
|
87
|
+
refuses to open, where there is no site running to press a button in.
|
|
88
|
+
</p>
|
|
89
|
+
{% endblock %}
|
|
@@ -26,12 +26,14 @@
|
|
|
26
26
|
<tr>
|
|
27
27
|
<td>
|
|
28
28
|
<a href="{{ entry.editUrl }}">{{ entry.username }}</a>{% if entry.you %} <span class="admin-status">(you)</span>{% endif %}
|
|
29
|
-
<br /><a href="{{ entry.archiveUrl }}">{{ entry.archiveUrl }}</a>
|
|
30
29
|
</td>
|
|
31
30
|
<td>{% if entry.email %}{{ entry.email }}{% else %}<span class="admin-status">None</span>{% endif %}</td>
|
|
32
31
|
<td>{{ entry.createdAt | date('html') }}</td>
|
|
32
|
+
{# Edit, then what can be done, then View: the order and the shape
|
|
33
|
+
`pages/documents/list.njk` uses, down to Edit being a plain link
|
|
34
|
+
rather than a button. A row here is a row there. #}
|
|
33
35
|
<td class="admin-actions">
|
|
34
|
-
<a
|
|
36
|
+
<a href="{{ entry.editUrl }}">Edit</a>
|
|
35
37
|
{% if entry.deletable %}
|
|
36
38
|
<form method="post" action="{{ deleteUserUrl }}">
|
|
37
39
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
@@ -39,6 +41,7 @@
|
|
|
39
41
|
<button type="submit">Delete</button>
|
|
40
42
|
</form>
|
|
41
43
|
{% endif %}
|
|
44
|
+
<a href="{{ entry.archiveUrl }}">View</a>
|
|
42
45
|
</td>
|
|
43
46
|
</tr>
|
|
44
47
|
{% endfor %}
|
package/admin/static/admin.css
CHANGED
|
@@ -1219,3 +1219,63 @@ button[type='submit']:hover,
|
|
|
1219
1219
|
color: var(--admin-muted);
|
|
1220
1220
|
overflow-wrap: anywhere;
|
|
1221
1221
|
}
|
|
1222
|
+
|
|
1223
|
+
/* Navigation. One panel per menu, stacked down the page rather than laid out
|
|
1224
|
+
as cards: a menu is a box of text somebody types into, so it wants the width
|
|
1225
|
+
a paragraph wants rather than a column in a grid. The two lists — the areas
|
|
1226
|
+
the theme declares, and the menus it renders nowhere — are the same panel,
|
|
1227
|
+
because they are the same thing in two different situations. */
|
|
1228
|
+
|
|
1229
|
+
.admin-menu-boxes {
|
|
1230
|
+
margin: 1rem 0 2rem;
|
|
1231
|
+
padding: 0;
|
|
1232
|
+
list-style: none;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.admin-menu-box {
|
|
1236
|
+
max-width: 42rem;
|
|
1237
|
+
padding: 0.9rem 1.1rem 1.1rem;
|
|
1238
|
+
background: var(--admin-surface);
|
|
1239
|
+
border: 1px solid var(--admin-border);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
.admin-menu-box + .admin-menu-box {
|
|
1243
|
+
margin-top: 1rem;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
.admin-menu-box h3 {
|
|
1247
|
+
margin: 0;
|
|
1248
|
+
font-size: 1.05rem;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/* The name a theme asks for the menu by, and how much is in it. The name is
|
|
1252
|
+
the useful half: it is what a template loops over and what `site.json`
|
|
1253
|
+
holds, so it is printed rather than left implied by the label above it. */
|
|
1254
|
+
|
|
1255
|
+
.admin-menu-name {
|
|
1256
|
+
margin: 0.15rem 0 0.6rem;
|
|
1257
|
+
color: var(--admin-muted);
|
|
1258
|
+
font-size: 0.9em;
|
|
1259
|
+
overflow-wrap: anywhere;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
.admin-menu-buttons {
|
|
1263
|
+
margin: 0.8rem 0 0;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/* Delete is the only way a menu is removed, and it is offered only on one the
|
|
1267
|
+
theme renders nowhere. It sits apart from the Save above it, so the two
|
|
1268
|
+
buttons of one panel are not a pair to be pressed carelessly. */
|
|
1269
|
+
|
|
1270
|
+
.admin-menu-delete {
|
|
1271
|
+
margin-top: 0.8rem;
|
|
1272
|
+
padding-top: 0.8rem;
|
|
1273
|
+
border-top: 1px solid var(--admin-field);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
.admin-menu-add {
|
|
1277
|
+
max-width: 42rem;
|
|
1278
|
+
padding: 0.9rem 1.1rem 1.1rem;
|
|
1279
|
+
background: var(--admin-surface);
|
|
1280
|
+
border: 1px solid var(--admin-border);
|
|
1281
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { ResolvedConfig } from './config.ts';
|
|
3
|
+
import type { GeekityEnv } from './env.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Where one access-log line goes.
|
|
6
|
+
*
|
|
7
|
+
* The line arrives without a trailing newline; the sink decides the framing.
|
|
8
|
+
* The default sink is stdout — what a container collects, what dockge shows
|
|
9
|
+
* and what journald keeps — and a test hands in one that keeps the lines.
|
|
10
|
+
*/
|
|
11
|
+
export type AccessLogWriter = (line: string) => void;
|
|
12
|
+
/** What {@link createAccessLog} needs to know. */
|
|
13
|
+
export interface AccessLogOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Whether a client address goes on the line, and which one is right. Read
|
|
16
|
+
* once, at boot: the log is registered before the context is, so it cannot
|
|
17
|
+
* read the config off a request.
|
|
18
|
+
*/
|
|
19
|
+
config: Pick<ResolvedConfig, 'accessLogAddress' | 'trustProxy'>;
|
|
20
|
+
/** Where the lines go. Defaults to stdout. */
|
|
21
|
+
write?: AccessLogWriter;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One line per request: what was asked, what was answered, and how long it
|
|
25
|
+
* took.
|
|
26
|
+
*
|
|
27
|
+
* ```
|
|
28
|
+
* GET /.well-known/webfinger?resource=acct:ada@example.com 200 3.1ms
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* The fields are in a fixed order and separated by spaces, so the status is
|
|
32
|
+
* always the third and a line is readable by eye, by `grep` and by `awk`
|
|
33
|
+
* alike. The client address, when the site asks for one, goes last for that
|
|
34
|
+
* reason — turning it on moves nothing else.
|
|
35
|
+
*
|
|
36
|
+
* Only the request line is ever read: no body is touched, so nothing is
|
|
37
|
+
* buffered and no password posted to the login form can reach the log, and no
|
|
38
|
+
* header but the forwarded address a site has said to believe is looked at, so
|
|
39
|
+
* no cookie and no `Authorization` can either. The query string is logged as
|
|
40
|
+
* it arrived, because it is what a peer's WebFinger lookup carries and what a
|
|
41
|
+
* question about who asked for what is answered with.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createAccessLog(options: AccessLogOptions): MiddlewareHandler<GeekityEnv>;
|
|
44
|
+
//# sourceMappingURL=access-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-log.d.ts","sourceRoot":"","sources":["../src/access-log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAG9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAErD,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,kBAAkB,GAAG,YAAY,CAAC,CAAC;IAChE,8CAA8C;IAC9C,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAsBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,iBAAiB,CAAC,UAAU,CAAC,CA4BxF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { clientAddress } from './admin/throttle.js';
|
|
2
|
+
/** The default sink: one write per line, the newline included, like the CLI's. */
|
|
3
|
+
function writeToStdout(line) {
|
|
4
|
+
process.stdout.write(`${line}\n`);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The path and query string of a request, taken out of its URL by hand.
|
|
8
|
+
*
|
|
9
|
+
* `new URL(...)` per request would parse and allocate the whole of an address
|
|
10
|
+
* this only ever wants the tail of, and this runs on every request the site
|
|
11
|
+
* answers. There is nothing to escape: Node's HTTP parser refuses a request
|
|
12
|
+
* target with a control character in it, so whatever survives to here is one
|
|
13
|
+
* line's worth of text.
|
|
14
|
+
*/
|
|
15
|
+
function requestTarget(url) {
|
|
16
|
+
const afterScheme = url.indexOf('://');
|
|
17
|
+
const start = afterScheme < 0 ? -1 : url.indexOf('/', afterScheme + 3);
|
|
18
|
+
return start < 0 ? '/' : url.slice(start);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* One line per request: what was asked, what was answered, and how long it
|
|
22
|
+
* took.
|
|
23
|
+
*
|
|
24
|
+
* ```
|
|
25
|
+
* GET /.well-known/webfinger?resource=acct:ada@example.com 200 3.1ms
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* The fields are in a fixed order and separated by spaces, so the status is
|
|
29
|
+
* always the third and a line is readable by eye, by `grep` and by `awk`
|
|
30
|
+
* alike. The client address, when the site asks for one, goes last for that
|
|
31
|
+
* reason — turning it on moves nothing else.
|
|
32
|
+
*
|
|
33
|
+
* Only the request line is ever read: no body is touched, so nothing is
|
|
34
|
+
* buffered and no password posted to the login form can reach the log, and no
|
|
35
|
+
* header but the forwarded address a site has said to believe is looked at, so
|
|
36
|
+
* no cookie and no `Authorization` can either. The query string is logged as
|
|
37
|
+
* it arrived, because it is what a peer's WebFinger lookup carries and what a
|
|
38
|
+
* question about who asked for what is answered with.
|
|
39
|
+
*/
|
|
40
|
+
export function createAccessLog(options) {
|
|
41
|
+
const write = options.write ?? writeToStdout;
|
|
42
|
+
const { accessLogAddress, trustProxy } = options.config;
|
|
43
|
+
return async function accessLog(c, next) {
|
|
44
|
+
const started = performance.now();
|
|
45
|
+
let threw = false;
|
|
46
|
+
try {
|
|
47
|
+
await next();
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
// A handler that threw has no response of its own: the error is on its
|
|
51
|
+
// way up to Hono's error handler, which answers 500, and `c.res` would
|
|
52
|
+
// hand back a freshly made 200 if asked. A request that failed is the
|
|
53
|
+
// one most worth having a line for, so it gets the status it will be
|
|
54
|
+
// answered with rather than the one it never had.
|
|
55
|
+
threw = true;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
const elapsed = (performance.now() - started).toFixed(1);
|
|
60
|
+
const status = threw ? 500 : c.res.status;
|
|
61
|
+
let line = `${c.req.method} ${requestTarget(c.req.url)} ${String(status)} ${elapsed}ms`;
|
|
62
|
+
if (accessLogAddress) {
|
|
63
|
+
const address = clientAddress(c, { trustProxy });
|
|
64
|
+
if (address !== undefined)
|
|
65
|
+
line += ` ${address}`;
|
|
66
|
+
}
|
|
67
|
+
write(line);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=access-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access-log.js","sourceRoot":"","sources":["../src/access-log.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAyBpD,kFAAkF;AAClF,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,WAAW,GAAG,CAAC,CAAC,CAAC;IACvE,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,eAAe,CAAC,OAAyB;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,aAAa,CAAC;IAC7C,MAAM,EAAE,gBAAgB,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAExD,OAAO,KAAK,UAAU,SAAS,CAAC,CAAC,EAAE,IAAI;QACrC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAClC,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,IAAI,EAAE,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,uEAAuE;YACvE,uEAAuE;YACvE,sEAAsE;YACtE,qEAAqE;YACrE,kDAAkD;YAClD,KAAK,GAAG,IAAI,CAAC;YACb,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;YAC1C,IAAI,IAAI,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,OAAO,IAAI,CAAC;YACxF,IAAI,gBAAgB,EAAE,CAAC;gBACrB,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;gBACjD,IAAI,OAAO,KAAK,SAAS;oBAAE,IAAI,IAAI,IAAI,OAAO,EAAE,CAAC;YACnD,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -34,15 +34,9 @@ export interface DocumentKind {
|
|
|
34
34
|
*/
|
|
35
35
|
excludable: boolean;
|
|
36
36
|
/**
|
|
37
|
-
* Whether the editor offers the
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*/
|
|
41
|
-
navigable: boolean;
|
|
42
|
-
/**
|
|
43
|
-
* Whether the editor offers the contact form (TASK-56). Pages only, for the
|
|
44
|
-
* reason the menu is: a form for writing to the site belongs on a standing
|
|
45
|
-
* page rather than under one post out of a thousand.
|
|
37
|
+
* Whether the editor offers the contact form (TASK-56). Pages only: a form
|
|
38
|
+
* for writing to the site belongs on a standing page rather than under one
|
|
39
|
+
* post out of a thousand.
|
|
46
40
|
*
|
|
47
41
|
* The front matter key itself is honoured wherever it is written, so a theme
|
|
48
42
|
* that includes the partial in its post layout is free to; this only decides
|
|
@@ -168,10 +162,6 @@ export interface EditorForm {
|
|
|
168
162
|
draft: boolean;
|
|
169
163
|
/** Whether `eleventyExcludeFromCollections` is set. Pages only. */
|
|
170
164
|
exclude: boolean;
|
|
171
|
-
/** Whether the page put itself in the site menu. Pages only. */
|
|
172
|
-
navigation: boolean;
|
|
173
|
-
/** Where in the menu it goes, as typed. Empty for "after the ordered ones". */
|
|
174
|
-
navigationOrder: string;
|
|
175
165
|
/** Whether the page offers a contact form. Pages only. */
|
|
176
166
|
contact: boolean;
|
|
177
167
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/admin/documents.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,QAAQ,EAAmB,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAMtF,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AASxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/admin/documents.ts"],"names":[],"mappings":"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"}
|
package/dist/admin/documents.js
CHANGED
|
@@ -10,7 +10,6 @@ import { normalizeBody, serializeDocument } from '../content/writer.js';
|
|
|
10
10
|
import { isPublicDocument } from '../web/documents.js';
|
|
11
11
|
import { COMMENTS_FRONT_MATTER_KEY } from '../comments/policy.js';
|
|
12
12
|
import { CONTACT_FRONT_MATTER_KEY } from '../contact/form.js';
|
|
13
|
-
import { NAVIGATION_KEY, NAVIGATION_ORDER_KEY, navigationOrder } from '../web/navigation.js';
|
|
14
13
|
import { userForAuthor } from '../web/authors.js';
|
|
15
14
|
import { findUserById, listUsers } from './accounts.js';
|
|
16
15
|
import { flash } from './flash.js';
|
|
@@ -62,7 +61,6 @@ export const POST_KIND = {
|
|
|
62
61
|
tagged: true,
|
|
63
62
|
categorised: true,
|
|
64
63
|
excludable: false,
|
|
65
|
-
navigable: false,
|
|
66
64
|
contactable: false,
|
|
67
65
|
};
|
|
68
66
|
/** The pages screens: standing content, no date prefix and no taxonomy. */
|
|
@@ -76,7 +74,6 @@ export const PAGE_KIND = {
|
|
|
76
74
|
tagged: false,
|
|
77
75
|
categorised: false,
|
|
78
76
|
excludable: true,
|
|
79
|
-
navigable: true,
|
|
80
77
|
contactable: true,
|
|
81
78
|
};
|
|
82
79
|
/** The URL of the editor for one document. */
|
|
@@ -202,8 +199,6 @@ async function saveFromForm(c, options) {
|
|
|
202
199
|
author: text(body['author']).trim(),
|
|
203
200
|
draft: body['draft'] !== undefined,
|
|
204
201
|
exclude: body['exclude'] !== undefined,
|
|
205
|
-
navigation: body['navigation'] !== undefined,
|
|
206
|
-
navigationOrder: text(body['navigation_order']).trim(),
|
|
207
202
|
contact: body['contact'] !== undefined,
|
|
208
203
|
comments: commentSetting(text(body['comments'])),
|
|
209
204
|
body: normalizeBody(text(body['body'])),
|
|
@@ -225,9 +220,6 @@ async function saveFromForm(c, options) {
|
|
|
225
220
|
}
|
|
226
221
|
if (form.title === '')
|
|
227
222
|
return refuse(`A ${kind.singular} needs a title.`);
|
|
228
|
-
if (form.navigationOrder !== '' && !Number.isFinite(Number(form.navigationOrder))) {
|
|
229
|
-
return refuse('A menu order is a number, and pulls the lower numbers to the front.');
|
|
230
|
-
}
|
|
231
223
|
const timezone = siteTimezone(c);
|
|
232
224
|
// decision-11: what the file gets is a UTC instant, and an offset-less field
|
|
233
225
|
// is the site's own wall clock rather than the server's.
|
|
@@ -486,29 +478,9 @@ function resolveExtra(kind, document, form) {
|
|
|
486
478
|
else if (EXCLUDE_KEY in extra)
|
|
487
479
|
extra[EXCLUDE_KEY] = false;
|
|
488
480
|
}
|
|
489
|
-
if (kind.navigable) {
|
|
490
|
-
if (form.navigation) {
|
|
491
|
-
extra[NAVIGATION_KEY] = true;
|
|
492
|
-
if (form.navigationOrder === '')
|
|
493
|
-
delete extra[NAVIGATION_ORDER_KEY];
|
|
494
|
-
else
|
|
495
|
-
extra[NAVIGATION_ORDER_KEY] = Number(form.navigationOrder);
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
// Both keys go rather than being written `false`, which is where this
|
|
499
|
-
// parts company with `eleventyExcludeFromCollections` above. That one is
|
|
500
|
-
// Eleventy's key and a site may have written it by hand for a build of
|
|
501
|
-
// its own, so a `false` says something; these two are the CMS's own, and
|
|
502
|
-
// absent and false mean the same thing to everything that reads them. An
|
|
503
|
-
// order on a page that is not in the menu means nothing at all.
|
|
504
|
-
delete extra[NAVIGATION_KEY];
|
|
505
|
-
delete extra[NAVIGATION_ORDER_KEY];
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
481
|
if (kind.contactable) {
|
|
509
|
-
// Written or removed, never `false
|
|
510
|
-
//
|
|
511
|
-
// everything that reads it (TASK-56).
|
|
482
|
+
// Written or removed, never `false`: it is the CMS's own key, and absent
|
|
483
|
+
// and false mean the same thing to everything that reads it (TASK-56).
|
|
512
484
|
if (form.contact)
|
|
513
485
|
extra[CONTACT_FRONT_MATTER_KEY] = true;
|
|
514
486
|
else
|
|
@@ -760,8 +732,6 @@ export function blankForm(kind, timezone = DEFAULT_TIMEZONE, now = new Date()) {
|
|
|
760
732
|
author: '',
|
|
761
733
|
draft: false,
|
|
762
734
|
exclude: false,
|
|
763
|
-
navigation: false,
|
|
764
|
-
navigationOrder: '',
|
|
765
735
|
contact: false,
|
|
766
736
|
comments: COMMENT_SETTINGS.site,
|
|
767
737
|
body: '',
|
|
@@ -788,8 +758,6 @@ export function formFor(document, timezone = DEFAULT_TIMEZONE) {
|
|
|
788
758
|
author: document.author ?? '',
|
|
789
759
|
draft: document.draft,
|
|
790
760
|
exclude: document.extra[EXCLUDE_KEY] === true,
|
|
791
|
-
navigation: document.extra[NAVIGATION_KEY] === true,
|
|
792
|
-
navigationOrder: navigationOrder(document)?.toString() ?? '',
|
|
793
761
|
contact: document.extra[CONTACT_FRONT_MATTER_KEY] === true,
|
|
794
762
|
comments: commentSettingOf(document),
|
|
795
763
|
body: document.body,
|