@geekity/cms 0.17.0 → 0.18.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 +162 -50
- package/admin/pages/documents/editor.njk +63 -0
- package/admin/pages/documents/list.njk +2 -0
- package/admin/pages/indieauth/consent.njk +4 -0
- package/admin/pages/settings/privacy.njk +37 -0
- package/admin/pages/users/apps.njk +109 -3
- package/dist/admin/documents.d.ts +29 -1
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +88 -6
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/index.d.ts +1 -0
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +1 -0
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/location-field.d.ts +26 -0
- package/dist/admin/location-field.d.ts.map +1 -0
- package/dist/admin/location-field.js +90 -0
- package/dist/admin/location-field.js.map +1 -0
- package/dist/admin/menu.d.ts.map +1 -1
- package/dist/admin/menu.js +1 -0
- package/dist/admin/menu.js.map +1 -1
- package/dist/admin/preview.d.ts.map +1 -1
- package/dist/admin/preview.js +18 -5
- package/dist/admin/preview.js.map +1 -1
- package/dist/admin/read-field.d.ts +23 -0
- package/dist/admin/read-field.d.ts.map +1 -0
- package/dist/admin/read-field.js +53 -0
- package/dist/admin/read-field.js.map +1 -0
- package/dist/admin/settings-page.d.ts +3 -3
- package/dist/admin/settings-page.js +2 -2
- package/dist/admin/settings-pages.d.ts +2 -2
- package/dist/admin/settings-pages.d.ts.map +1 -1
- package/dist/admin/settings-pages.js +4 -2
- package/dist/admin/settings-pages.js.map +1 -1
- package/dist/admin/settings-privacy.d.ts +3 -0
- package/dist/admin/settings-privacy.d.ts.map +1 -0
- package/dist/admin/settings-privacy.js +10 -0
- package/dist/admin/settings-privacy.js.map +1 -0
- package/dist/admin/settings-reading.js +2 -2
- package/dist/admin/settings-reading.js.map +1 -1
- package/dist/admin/settings.d.ts +18 -2
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +27 -0
- package/dist/admin/settings.js.map +1 -1
- package/dist/admin/templates.d.ts +1 -0
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +1 -0
- package/dist/admin/templates.js.map +1 -1
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +14 -1
- package/dist/admin/uploads.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +34 -0
- package/dist/cli.js.map +1 -1
- package/dist/comments/routes.js +2 -2
- package/dist/comments/routes.js.map +1 -1
- package/dist/contact/routes.js +2 -2
- package/dist/contact/routes.js.map +1 -1
- package/dist/content/location.d.ts +58 -0
- package/dist/content/location.d.ts.map +1 -0
- package/dist/content/location.js +208 -0
- package/dist/content/location.js.map +1 -0
- package/dist/content/locations.d.ts +9 -0
- package/dist/content/locations.d.ts.map +1 -0
- package/dist/content/locations.js +82 -0
- package/dist/content/locations.js.map +1 -0
- package/dist/content/metadata/gif.d.ts +3 -0
- package/dist/content/metadata/gif.d.ts.map +1 -0
- package/dist/content/metadata/gif.js +65 -0
- package/dist/content/metadata/gif.js.map +1 -0
- package/dist/content/metadata/index.d.ts +6 -0
- package/dist/content/metadata/index.d.ts.map +1 -0
- package/dist/content/metadata/index.js +24 -0
- package/dist/content/metadata/index.js.map +1 -0
- package/dist/content/metadata/isobmff.d.ts +22 -0
- package/dist/content/metadata/isobmff.d.ts.map +1 -0
- package/dist/content/metadata/isobmff.js +215 -0
- package/dist/content/metadata/isobmff.js.map +1 -0
- package/dist/content/metadata/jpeg.d.ts +3 -0
- package/dist/content/metadata/jpeg.d.ts.map +1 -0
- package/dist/content/metadata/jpeg.js +117 -0
- package/dist/content/metadata/jpeg.js.map +1 -0
- package/dist/content/metadata/png.d.ts +3 -0
- package/dist/content/metadata/png.d.ts.map +1 -0
- package/dist/content/metadata/png.js +74 -0
- package/dist/content/metadata/png.js.map +1 -0
- package/dist/content/metadata/shared.d.ts +17 -0
- package/dist/content/metadata/shared.d.ts.map +1 -0
- package/dist/content/metadata/shared.js +42 -0
- package/dist/content/metadata/shared.js.map +1 -0
- package/dist/content/metadata/sweep.d.ts +14 -0
- package/dist/content/metadata/sweep.d.ts.map +1 -0
- package/dist/content/metadata/sweep.js +54 -0
- package/dist/content/metadata/sweep.js.map +1 -0
- package/dist/content/metadata/tiff.d.ts +7 -0
- package/dist/content/metadata/tiff.d.ts.map +1 -0
- package/dist/content/metadata/tiff.js +56 -0
- package/dist/content/metadata/tiff.js.map +1 -0
- package/dist/content/metadata/webp.d.ts +3 -0
- package/dist/content/metadata/webp.d.ts.map +1 -0
- package/dist/content/metadata/webp.js +67 -0
- package/dist/content/metadata/webp.js.map +1 -0
- package/dist/content/post-type.d.ts +4 -2
- package/dist/content/post-type.d.ts.map +1 -1
- package/dist/content/post-type.js +9 -1
- package/dist/content/post-type.js.map +1 -1
- package/dist/content/read.d.ts +22 -0
- package/dist/content/read.d.ts.map +1 -0
- package/dist/content/read.js +67 -0
- package/dist/content/read.js.map +1 -0
- package/dist/content/store.d.ts +4 -4
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +19 -39
- package/dist/content/store.js.map +1 -1
- package/dist/content/sync.d.ts.map +1 -1
- package/dist/content/sync.js +3 -13
- package/dist/content/sync.js.map +1 -1
- package/dist/content/visibility.d.ts +11 -0
- package/dist/content/visibility.d.ts.map +1 -0
- package/dist/content/visibility.js +14 -0
- package/dist/content/visibility.js.map +1 -0
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +41 -14
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/delivery.d.ts +3 -1
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +6 -5
- package/dist/federation/delivery.js.map +1 -1
- package/dist/federation/mount.d.ts.map +1 -1
- package/dist/federation/mount.js +3 -1
- package/dist/federation/mount.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/indexnow.js +2 -2
- package/dist/indexnow.js.map +1 -1
- package/dist/indieauth/activity-log.d.ts +2 -0
- package/dist/indieauth/activity-log.d.ts.map +1 -1
- package/dist/indieauth/activity-log.js +12 -2
- package/dist/indieauth/activity-log.js.map +1 -1
- package/dist/indieauth/app-activity.js +5 -1
- package/dist/indieauth/app-activity.js.map +1 -1
- package/dist/indieauth/bearer.d.ts.map +1 -1
- package/dist/indieauth/bearer.js +17 -16
- package/dist/indieauth/bearer.js.map +1 -1
- package/dist/indieauth/client-id.d.ts +10 -0
- package/dist/indieauth/client-id.d.ts.map +1 -0
- package/dist/indieauth/client-id.js +30 -0
- package/dist/indieauth/client-id.js.map +1 -0
- package/dist/indieauth/connected-apps.d.ts +4 -0
- package/dist/indieauth/connected-apps.d.ts.map +1 -1
- package/dist/indieauth/connected-apps.js +149 -20
- package/dist/indieauth/connected-apps.js.map +1 -1
- package/dist/indieauth/consent.d.ts.map +1 -1
- package/dist/indieauth/consent.js +5 -1
- package/dist/indieauth/consent.js.map +1 -1
- package/dist/indieauth/grants.d.ts +2 -7
- package/dist/indieauth/grants.d.ts.map +1 -1
- package/dist/indieauth/grants.js.map +1 -1
- package/dist/indieauth/redeem.d.ts.map +1 -1
- package/dist/indieauth/redeem.js +10 -5
- package/dist/indieauth/redeem.js.map +1 -1
- package/dist/indieauth/request.d.ts +8 -4
- package/dist/indieauth/request.d.ts.map +1 -1
- package/dist/indieauth/request.js +29 -30
- package/dist/indieauth/request.js.map +1 -1
- package/dist/indieauth/token.js +1 -1
- package/dist/indieauth/token.js.map +1 -1
- package/dist/indieauth/tokens.d.ts +28 -7
- package/dist/indieauth/tokens.d.ts.map +1 -1
- package/dist/indieauth/tokens.js +26 -5
- package/dist/indieauth/tokens.js.map +1 -1
- package/dist/micropub/create.d.ts +22 -0
- package/dist/micropub/create.d.ts.map +1 -1
- package/dist/micropub/create.js +107 -8
- package/dist/micropub/create.js.map +1 -1
- package/dist/micropub/endpoint.d.ts.map +1 -1
- package/dist/micropub/endpoint.js +71 -20
- package/dist/micropub/endpoint.js.map +1 -1
- package/dist/micropub/media.d.ts +4 -2
- package/dist/micropub/media.d.ts.map +1 -1
- package/dist/micropub/media.js +41 -37
- package/dist/micropub/media.js.map +1 -1
- package/dist/micropub/update.d.ts +6 -2
- package/dist/micropub/update.d.ts.map +1 -1
- package/dist/micropub/update.js +31 -3
- package/dist/micropub/update.js.map +1 -1
- package/dist/notify.d.ts.map +1 -1
- package/dist/notify.js +2 -5
- package/dist/notify.js.map +1 -1
- package/dist/web/absolute-urls.d.ts +2 -0
- package/dist/web/absolute-urls.d.ts.map +1 -0
- package/dist/web/absolute-urls.js +22 -0
- package/dist/web/absolute-urls.js.map +1 -0
- package/dist/web/context.d.ts +29 -1
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +33 -0
- package/dist/web/context.js.map +1 -1
- package/dist/web/conversation.d.ts.map +1 -1
- package/dist/web/conversation.js +5 -4
- package/dist/web/conversation.js.map +1 -1
- package/dist/web/documents.d.ts +19 -10
- package/dist/web/documents.d.ts.map +1 -1
- package/dist/web/documents.js +28 -13
- package/dist/web/documents.js.map +1 -1
- package/dist/web/feed-item.d.ts +25 -6
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +31 -7
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-json.d.ts +3 -1
- package/dist/web/feed-json.d.ts.map +1 -1
- package/dist/web/feed-json.js +2 -0
- package/dist/web/feed-json.js.map +1 -1
- package/dist/web/feed-source.d.ts +6 -0
- package/dist/web/feed-source.d.ts.map +1 -1
- package/dist/web/feed-source.js.map +1 -1
- package/dist/web/feeds.d.ts.map +1 -1
- package/dist/web/feeds.js +2 -0
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/html-attributes.d.ts +3 -0
- package/dist/web/html-attributes.d.ts.map +1 -0
- package/dist/web/html-attributes.js +15 -0
- package/dist/web/html-attributes.js.map +1 -0
- package/dist/web/index.d.ts +1 -1
- 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/negotiate.d.ts +2 -0
- package/dist/web/negotiate.d.ts.map +1 -1
- package/dist/web/negotiate.js +2 -1
- package/dist/web/negotiate.js.map +1 -1
- package/dist/web/render.d.ts +8 -0
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +5 -1
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +14 -10
- package/dist/web/routes.js.map +1 -1
- package/dist/webmention/service.d.ts.map +1 -1
- package/dist/webmention/service.js +7 -2
- package/dist/webmention/service.js.map +1 -1
- package/package.json +1 -1
- package/themes/default/README.md +15 -0
- package/themes/default/layouts/base.njk +2 -0
- package/themes/default/layouts/post.njk +7 -1
- package/themes/default/partials/location.njk +12 -0
- package/themes/default/partials/post-list.njk +2 -0
- package/themes/default/partials/read.njk +5 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Only your own connections, each with a Revoke button that names the app,
|
|
4
4
|
so a screen reader moving between buttons hears which one it is on. #}
|
|
5
5
|
{% extends "layouts/shell.njk" %}
|
|
6
|
+
{% import "components/fields.njk" as field %}
|
|
6
7
|
|
|
7
8
|
{% block title %}Connected apps{% endblock %}
|
|
8
9
|
|
|
@@ -13,8 +14,9 @@
|
|
|
13
14
|
<p class="admin-hint">
|
|
14
15
|
These apps signed in as you and can act as you, within the permissions
|
|
15
16
|
listed. An app that keeps using its connection stays connected; one left
|
|
16
|
-
unused is disconnected on the date shown.
|
|
17
|
-
|
|
17
|
+
unused is disconnected on the date shown. A token you created works
|
|
18
|
+
until the date shown. Revoking disconnects it now: its next request is
|
|
19
|
+
refused, and it has to ask you again.
|
|
18
20
|
</p>
|
|
19
21
|
|
|
20
22
|
<table class="admin-list">
|
|
@@ -32,7 +34,13 @@
|
|
|
32
34
|
<tbody>
|
|
33
35
|
{% for connection in connections %}
|
|
34
36
|
<tr>
|
|
35
|
-
<td
|
|
37
|
+
<td>
|
|
38
|
+
{% if connection.url %}
|
|
39
|
+
<a href="{{ connection.url }}">{{ connection.name }}</a>
|
|
40
|
+
{% else %}
|
|
41
|
+
{{ connection.name }} <span class="admin-status">Token you created</span>
|
|
42
|
+
{% endif %}
|
|
43
|
+
</td>
|
|
36
44
|
<td>
|
|
37
45
|
<ul>
|
|
38
46
|
{% for scope in connection.scopes %}
|
|
@@ -71,4 +79,102 @@
|
|
|
71
79
|
is listed here, with a button to disconnect it.
|
|
72
80
|
</p>
|
|
73
81
|
{% endif %}
|
|
82
|
+
|
|
83
|
+
<h2 id="create-token">Create a token</h2>
|
|
84
|
+
|
|
85
|
+
{% if createToken.created %}
|
|
86
|
+
<div class="admin-flash" role="status">
|
|
87
|
+
{{ field.text('created-token', 'created-token', 'Token for ' ~ createToken.created.name, createToken.created.token,
|
|
88
|
+
readonly=true, spellcheck='false',
|
|
89
|
+
hint='Copy it now. It will not be shown again: the site keeps only a fingerprint of it.') }}
|
|
90
|
+
</div>
|
|
91
|
+
{% endif %}
|
|
92
|
+
|
|
93
|
+
{% if createToken.problems.name or createToken.problems.scopes or createToken.problems.expires %}
|
|
94
|
+
{% call field.summary('Nothing was saved. Fix the fields marked below and try again.') %}
|
|
95
|
+
{{ field.problem('create-token-name', createToken.problems.name) }}
|
|
96
|
+
{{ field.problem('create-token-scope-profile', createToken.problems.scopes) }}
|
|
97
|
+
{{ field.problem('create-token-expires', createToken.problems.expires) }}
|
|
98
|
+
{% endcall %}
|
|
99
|
+
{% endif %}
|
|
100
|
+
|
|
101
|
+
<p class="admin-hint">
|
|
102
|
+
Some tools and scripts cannot sign in, but accept a token you paste in,
|
|
103
|
+
such as the Manual tab of micropub.rocks. A token acts as you on this
|
|
104
|
+
site's Micropub endpoint, with only the permissions you tick, until it
|
|
105
|
+
expires or you revoke it above.
|
|
106
|
+
</p>
|
|
107
|
+
|
|
108
|
+
<form method="post" action="{{ createToken.url }}">
|
|
109
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
110
|
+
{{ field.text('create-token-name', 'name', 'Name', createToken.name,
|
|
111
|
+
error=createToken.problems.name, required=true,
|
|
112
|
+
hint='What will use it, such as Publishing script.') }}
|
|
113
|
+
<fieldset>
|
|
114
|
+
<legend>It may</legend>
|
|
115
|
+
{% for scope in createToken.scopes %}
|
|
116
|
+
{{ field.checkbox('create-token-scope-' ~ scope.name, 'scope', scope.label, checked=scope.checked, value=scope.name,
|
|
117
|
+
error=(createToken.problems.scopes if loop.first else '')) }}
|
|
118
|
+
{% endfor %}
|
|
119
|
+
</fieldset>
|
|
120
|
+
{% call field.select('create-token-expires', 'expires', 'Expires after', error=createToken.problems.expires) %}
|
|
121
|
+
{% for expiry in createToken.expiries %}
|
|
122
|
+
<option value="{{ expiry.days }}"{% if expiry.selected %} selected{% endif %}>{% if expiry.days == 365 %}1 year{% else %}{{ expiry.days }} days{% endif %}</option>
|
|
123
|
+
{% endfor %}
|
|
124
|
+
{% endcall %}
|
|
125
|
+
<button type="submit">Create token</button>
|
|
126
|
+
</form>
|
|
127
|
+
|
|
128
|
+
<h2 id="without-pkce">Apps allowed without PKCE</h2>
|
|
129
|
+
|
|
130
|
+
{% if withoutPkce.problem %}
|
|
131
|
+
{% call field.summary('Nothing was saved. Fix the field marked below and try again.') %}
|
|
132
|
+
{{ field.problem('without-pkce-client', withoutPkce.problem) }}
|
|
133
|
+
{% endcall %}
|
|
134
|
+
{% endif %}
|
|
135
|
+
|
|
136
|
+
<p class="admin-hint">
|
|
137
|
+
PKCE protects a sign-in when somebody intercepts the one-time code an app
|
|
138
|
+
is handed: without the secret the app kept, the code is useless. Every app
|
|
139
|
+
must use it, unless you list it here. List only an app you trust that
|
|
140
|
+
cannot sign in otherwise, such as iA Writer. A listed app is let in without
|
|
141
|
+
PKCE only when it sends you back to an https address on its own host, and
|
|
142
|
+
the consent screen tells you when it is.
|
|
143
|
+
</p>
|
|
144
|
+
|
|
145
|
+
{% if withoutPkce.clients | length %}
|
|
146
|
+
<table class="admin-list">
|
|
147
|
+
<caption class="admin-visually-hidden">Apps allowed without PKCE</caption>
|
|
148
|
+
<thead>
|
|
149
|
+
<tr>
|
|
150
|
+
<th scope="col">client_id</th>
|
|
151
|
+
<th scope="col">Actions</th>
|
|
152
|
+
</tr>
|
|
153
|
+
</thead>
|
|
154
|
+
<tbody>
|
|
155
|
+
{% for client in withoutPkce.clients %}
|
|
156
|
+
<tr>
|
|
157
|
+
<td><code>{{ client }}</code></td>
|
|
158
|
+
<td class="admin-actions">
|
|
159
|
+
<form method="post" action="{{ withoutPkce.removeUrl }}">
|
|
160
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
161
|
+
<input type="hidden" name="{{ withoutPkce.field }}" value="{{ client }}" />
|
|
162
|
+
<button type="submit">Remove {{ client }}</button>
|
|
163
|
+
</form>
|
|
164
|
+
</td>
|
|
165
|
+
</tr>
|
|
166
|
+
{% endfor %}
|
|
167
|
+
</tbody>
|
|
168
|
+
</table>
|
|
169
|
+
{% else %}
|
|
170
|
+
<p>No apps are listed. Every app must use PKCE to sign in.</p>
|
|
171
|
+
{% endif %}
|
|
172
|
+
|
|
173
|
+
<form method="post" action="{{ withoutPkce.addUrl }}">
|
|
174
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
175
|
+
{{ field.text('without-pkce-client', withoutPkce.field, 'App’s client_id', withoutPkce.value,
|
|
176
|
+
error=withoutPkce.problem, inputmode='url', spellcheck='false', required=true,
|
|
177
|
+
hint='The URL the app names itself by when it signs in, as App activity shows it, such as <code>https://ia.net/writer</code>.') }}
|
|
178
|
+
<button type="submit">Allow without PKCE</button>
|
|
179
|
+
</form>
|
|
74
180
|
{% endblock %}
|
|
@@ -2,13 +2,17 @@ import type { Context, Hono } from 'hono';
|
|
|
2
2
|
import type { Document, DocumentType } from '../content/document.ts';
|
|
3
3
|
import type { Enclosure } from '../content/enclosure.ts';
|
|
4
4
|
import type { Photo } from '../content/photo.ts';
|
|
5
|
+
import type { PostLocation } from '../content/location.ts';
|
|
5
6
|
import type { ContentStore, ListAllOptions } from '../content/store.ts';
|
|
7
|
+
import type { StoredVisibility } from '../content/visibility.ts';
|
|
6
8
|
import type { ResolvedConfig } from '../config.ts';
|
|
7
9
|
import type { DocumentChange } from '../content/sync.ts';
|
|
8
10
|
import type { GeekityEnv } from '../env.ts';
|
|
9
11
|
import type { UndescribedImage } from '../images/alt-text.ts';
|
|
10
12
|
import type { EnclosureForm } from './enclosure-field.ts';
|
|
13
|
+
import type { LocationForm } from './location-field.ts';
|
|
11
14
|
import type { PhotoRow } from './photo-field.ts';
|
|
15
|
+
import type { ReadOfForm } from './read-field.ts';
|
|
12
16
|
/**
|
|
13
17
|
* Everything that differs between the posts screens and the pages screens.
|
|
14
18
|
*
|
|
@@ -258,9 +262,21 @@ export interface EditorForm {
|
|
|
258
262
|
likeOf: string;
|
|
259
263
|
/** The page this one bookmarks, the mf2 `bookmark-of` (TASK-169). Posts only. */
|
|
260
264
|
bookmarkOf: string;
|
|
265
|
+
/**
|
|
266
|
+
* How far the post's author got with what they read, the mf2 `read-status`
|
|
267
|
+
* (TASK-229), as the file or the form spells it. Posts only.
|
|
268
|
+
*/
|
|
269
|
+
readStatus: string;
|
|
270
|
+
/** What was read, the mf2 `read-of` (TASK-229). Posts only. */
|
|
271
|
+
readOf: ReadOfForm;
|
|
261
272
|
/** The language it is written in, the `lang` key; empty for the site's. */
|
|
262
273
|
lang: string;
|
|
263
274
|
draft: boolean;
|
|
275
|
+
/**
|
|
276
|
+
* Whether the document is listed or only served at its URL (TASK-227), or a
|
|
277
|
+
* value the site does not recognize, which hides it until one is chosen.
|
|
278
|
+
*/
|
|
279
|
+
visibility: StoredVisibility;
|
|
264
280
|
/** Whether `eleventyExcludeFromCollections` is set. Pages only. */
|
|
265
281
|
exclude: boolean;
|
|
266
282
|
/** Whether the page offers a contact form. Pages only. */
|
|
@@ -280,6 +296,12 @@ export interface EditorForm {
|
|
|
280
296
|
enclosure: EnclosureForm;
|
|
281
297
|
/** The post's photos (TASK-166), without the blank row the editor adds. Posts only. */
|
|
282
298
|
photos: PhotoRow[];
|
|
299
|
+
/**
|
|
300
|
+
* Where the post was written (TASK-223). Posts only; blank on a page. It is
|
|
301
|
+
* the one field that is not written into the file: the save puts it in
|
|
302
|
+
* `data/locations.json` under the post's permalink (decision-29).
|
|
303
|
+
*/
|
|
304
|
+
location: LocationForm;
|
|
283
305
|
/** The ids of the declared syndication targets it selects (TASK-155). Posts only. */
|
|
284
306
|
syndicateTo: string[];
|
|
285
307
|
body: string;
|
|
@@ -301,8 +323,12 @@ export declare function blankForm(kind: DocumentKind, timezone?: string, now?: D
|
|
|
301
323
|
* {@link wallClockIn} keeps whatever precision the instant has, so a form
|
|
302
324
|
* submitted with the field untouched writes back the very instant it was
|
|
303
325
|
* filled in from.
|
|
326
|
+
*
|
|
327
|
+
* `location` is what `data/locations.json` holds for the post (decision-29),
|
|
328
|
+
* which a document does not carry. It is required because a form loaded
|
|
329
|
+
* without it would remove the stored location on save.
|
|
304
330
|
*/
|
|
305
|
-
export declare function formFor(document: Document, timezone
|
|
331
|
+
export declare function formFor(document: Document, timezone: string, location: PostLocation | undefined): EditorForm;
|
|
306
332
|
/**
|
|
307
333
|
* The document of this kind with this slug.
|
|
308
334
|
*
|
|
@@ -327,6 +353,8 @@ export interface DocumentRow {
|
|
|
327
353
|
trashed: boolean;
|
|
328
354
|
/** Whether its date has not arrived, so the public site is holding it back. */
|
|
329
355
|
scheduled: boolean;
|
|
356
|
+
/** Whether its `visibility` is one the site does not recognize, so it is not served. */
|
|
357
|
+
hidden: boolean;
|
|
330
358
|
/** Where the editor for it lives. */
|
|
331
359
|
editUrl: string;
|
|
332
360
|
/** Its public URL, or `undefined` when the public site would not serve it. */
|
|
@@ -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;AAI1C,OAAO,KAAK,EAAE,QAAQ,EAAmB,YAAY,EAAE,MAAM,wBAAwB,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
|
@@ -5,17 +5,20 @@ import { renderMarkdown } from '../content/markdown.js';
|
|
|
5
5
|
import { parseDocument } from '../content/parser.js';
|
|
6
6
|
import { PINNED_FRONT_MATTER_KEY, PINNED_POST_LIMIT, pinnedAt } from '../content/pinned.js';
|
|
7
7
|
import { postLabel, replyTarget } from '../content/post-type.js';
|
|
8
|
+
import { isReadStatus, READ_OF_FRONT_MATTER_KEY, READ_STATUS_FRONT_MATTER_KEY, READ_STATUS_LABELS, READ_STATUSES, readWork, readWorkFrontMatter, } from '../content/read.js';
|
|
8
9
|
import { ENCLOSURE_FRONT_MATTER_KEY, enclosureOf, isWebUrl, TRANSCRIPT_TYPES, } from '../content/enclosure.js';
|
|
9
10
|
import { PHOTO_FRONT_MATTER_KEY, photoFrontMatter } from '../content/photo.js';
|
|
11
|
+
import { postLocations } from '../content/locations.js';
|
|
10
12
|
import { contentFilePath, freeSlug, saveDocument } from '../content/save.js';
|
|
11
13
|
import { scheduledFor } from '../content/schedule.js';
|
|
12
14
|
import { htmlToText } from '../content/search.js';
|
|
13
15
|
import { defaultPermalink, slugify } from '../content/slug.js';
|
|
14
16
|
import { DuplicatePermalinkError, isTrashedPath, TRASH_DIRECTORY } from '../content/store.js';
|
|
15
17
|
import { calendarDayIn, DEFAULT_TIMEZONE, toUtcInstant, wallClockIn, zoneLabel, } from '../content/time.js';
|
|
18
|
+
import { isVisibility, VISIBILITY_FRONT_MATTER_KEY, visibilityOf } from '../content/visibility.js';
|
|
16
19
|
import { normalizeBody, serializeDocument } from '../content/writer.js';
|
|
17
20
|
import { readAltTexts, undescribedImages, undescribedPhotos } from '../images/alt-text.js';
|
|
18
|
-
import {
|
|
21
|
+
import { isServed } from '../web/documents.js';
|
|
19
22
|
import { LANG_FRONT_MATTER_KEY } from '../web/locale.js';
|
|
20
23
|
import { absoluteUrl } from '../web/negotiate.js';
|
|
21
24
|
import { COMMENTS_FRONT_MATTER_KEY } from '../comments/policy.js';
|
|
@@ -30,7 +33,9 @@ import { ADMIN_PREFIX } from './session.js';
|
|
|
30
33
|
import { ADMIN_TEMPLATES } from './templates.js';
|
|
31
34
|
import { UPLOADS_PATH } from './uploads.js';
|
|
32
35
|
import { BLANK_ALTERNATE_ROW, BLANK_ENCLOSURE_FORM, ENCLOSURE_FIELDS, enclosureChoices, enclosureForm, enclosureFrontMatter, readEnclosureForm, resolveEnclosure, } from './enclosure-field.js';
|
|
36
|
+
import { BLANK_LOCATION_FORM, LOCATION_FIELDS, locationForm, readLocationForm, resolveLocation, } from './location-field.js';
|
|
33
37
|
import { PHOTO_FIELDS, photoChoices, photoRows, photoRowViews, readPhotoForm, resolvePhotos, } from './photo-field.js';
|
|
38
|
+
import { BLANK_READ_OF_FORM, READ_FIELDS, readOfForm, resolveRead, submittedReadOfForm, } from './read-field.js';
|
|
34
39
|
import { SYNDICATE_TO_FRONT_MATTER_KEY, syndicateToOf, syndicationTargetsReader, } from '../webmention/syndication.js';
|
|
35
40
|
/**
|
|
36
41
|
* The Eleventy key that keeps a document out of every collection.
|
|
@@ -54,6 +59,11 @@ function commentSetting(value) {
|
|
|
54
59
|
? value
|
|
55
60
|
: COMMENT_SETTINGS.site;
|
|
56
61
|
}
|
|
62
|
+
function formVisibility(value) {
|
|
63
|
+
if (value === '')
|
|
64
|
+
return 'public';
|
|
65
|
+
return isVisibility(value) ? value : { unrecognized: value };
|
|
66
|
+
}
|
|
57
67
|
/** What a document's front matter already says about comments. */
|
|
58
68
|
function commentSettingOf(document) {
|
|
59
69
|
const own = document.extra[COMMENTS_FRONT_MATTER_KEY];
|
|
@@ -174,7 +184,13 @@ export function mountDocumentScreens(app, options) {
|
|
|
174
184
|
const document = findBySlug(c.var.store, kind, c.req.param('slug'));
|
|
175
185
|
if (document === undefined)
|
|
176
186
|
return c.notFound();
|
|
177
|
-
|
|
187
|
+
const location = postLocations(c.var.config.dataDir).read(document.permalink);
|
|
188
|
+
return renderEditor(c, {
|
|
189
|
+
kind,
|
|
190
|
+
render,
|
|
191
|
+
document,
|
|
192
|
+
form: formFor(document, siteTimezone(c), location),
|
|
193
|
+
});
|
|
178
194
|
});
|
|
179
195
|
app.post(`${kind.basePath}/:slug`, async (c) => {
|
|
180
196
|
const document = findBySlug(c.var.store, kind, c.req.param('slug'));
|
|
@@ -208,14 +224,18 @@ async function saveFromForm(c, options) {
|
|
|
208
224
|
author: text(body['author']).trim(),
|
|
209
225
|
inReplyTo: text(body['in-reply-to']).trim(),
|
|
210
226
|
...citationFields((property) => (kind.type === 'post' ? text(body[property]).trim() : '')),
|
|
227
|
+
readStatus: kind.type === 'post' ? text(body[READ_FIELDS.status]).trim() : '',
|
|
228
|
+
readOf: kind.type === 'post' ? submittedReadOfForm(body) : BLANK_READ_OF_FORM,
|
|
211
229
|
lang: text(body['lang']).trim(),
|
|
212
230
|
draft: body['draft'] !== undefined,
|
|
231
|
+
visibility: formVisibility(text(body['visibility'])),
|
|
213
232
|
exclude: body['exclude'] !== undefined,
|
|
214
233
|
contact: body['contact'] !== undefined,
|
|
215
234
|
pinned: kind.type === 'post' && body['pinned'] !== undefined,
|
|
216
235
|
comments: commentSetting(text(body['comments'])),
|
|
217
236
|
enclosure: kind.type === 'post' ? readEnclosureForm(body) : BLANK_ENCLOSURE_FORM,
|
|
218
237
|
photos: kind.type === 'post' ? readPhotoForm(body) : [],
|
|
238
|
+
location: kind.type === 'post' ? readLocationForm(body) : BLANK_LOCATION_FORM,
|
|
219
239
|
syndicateTo: kind.type === 'post'
|
|
220
240
|
? syndicationTargetsReader(contentDir)()
|
|
221
241
|
.filter((target) => body[syndicateToField(target)] !== undefined)
|
|
@@ -287,6 +307,7 @@ export async function writeDocument(site, write) {
|
|
|
287
307
|
if (kind.type === 'post' && form.inReplyTo !== '' && replyTarget(form) === undefined) {
|
|
288
308
|
return refused('In reply to has to be a web address, like https://example.com/a-post/.');
|
|
289
309
|
}
|
|
310
|
+
let read;
|
|
290
311
|
if (kind.type === 'post') {
|
|
291
312
|
for (const property of CITATION_PROPERTIES) {
|
|
292
313
|
const cited = form[CITATION_FIELDS[property]];
|
|
@@ -294,6 +315,13 @@ export async function writeDocument(site, write) {
|
|
|
294
315
|
return refused(`${CITATION_LABELS[property]} has to be a web address, like https://example.com/a-post/.`);
|
|
295
316
|
}
|
|
296
317
|
}
|
|
318
|
+
const resolved = resolveRead(form.readStatus, form.readOf);
|
|
319
|
+
if ('error' in resolved)
|
|
320
|
+
return refused(resolved.error);
|
|
321
|
+
read = resolved.read;
|
|
322
|
+
}
|
|
323
|
+
if (read !== undefined && form.description !== '') {
|
|
324
|
+
return refused('A read is described by what it says, so it keeps no Description. Empty Description to save it.');
|
|
297
325
|
}
|
|
298
326
|
if (form.lang !== '' && !LANGUAGE_TAG_PATTERN.test(form.lang)) {
|
|
299
327
|
return refused('That is not a language tag, such as en, fr or pt-BR.');
|
|
@@ -308,6 +336,13 @@ export async function writeDocument(site, write) {
|
|
|
308
336
|
return refused(photos.error);
|
|
309
337
|
media = { recording: resolved.enclosure, photos: photos.photos };
|
|
310
338
|
}
|
|
339
|
+
let location;
|
|
340
|
+
if (kind.type === 'post') {
|
|
341
|
+
const resolved = resolveLocation(form.location);
|
|
342
|
+
if ('error' in resolved)
|
|
343
|
+
return refused(resolved.error);
|
|
344
|
+
location = resolved.location;
|
|
345
|
+
}
|
|
311
346
|
// TASK-207: a pin is new when the file does not carry one yet, and only a
|
|
312
347
|
// new one can take an author past Mastodon's limit.
|
|
313
348
|
if (form.pinned && (document === undefined || pinnedAt(document) === undefined)) {
|
|
@@ -346,6 +381,7 @@ export async function writeDocument(site, write) {
|
|
|
346
381
|
slugify(form.title) ||
|
|
347
382
|
(document?.slug ?? '') ||
|
|
348
383
|
noteSlug(form.body) ||
|
|
384
|
+
slugify(form.readOf.name) ||
|
|
349
385
|
'untitled';
|
|
350
386
|
const trashed = document !== undefined && isTrashedPath(document.path);
|
|
351
387
|
// The calendar day the document is filed under: the site zone's day at its
|
|
@@ -421,6 +457,13 @@ export async function writeDocument(site, write) {
|
|
|
421
457
|
if (renamedFrom !== undefined) {
|
|
422
458
|
await rm(path.join(contentDir, ...renamedFrom.path.split('/')), { force: true });
|
|
423
459
|
}
|
|
460
|
+
if (kind.type === 'post') {
|
|
461
|
+
const locations = postLocations(config.dataDir);
|
|
462
|
+
if (document !== undefined && document.permalink !== saved.permalink) {
|
|
463
|
+
await locations.move(document.permalink, saved.permalink);
|
|
464
|
+
}
|
|
465
|
+
await locations.set(saved.permalink, location);
|
|
466
|
+
}
|
|
424
467
|
// Announced rather than left to the watcher: the index already holds what
|
|
425
468
|
// was written, so the watcher's re-read is a no-op and nobody would ever
|
|
426
469
|
// hear that this post was published. Awaited, so a subscriber that writes
|
|
@@ -482,7 +525,7 @@ function promisedDocument(document, now) {
|
|
|
482
525
|
if (document === undefined)
|
|
483
526
|
return undefined;
|
|
484
527
|
const announced = document.activitypub?.published !== undefined;
|
|
485
|
-
return
|
|
528
|
+
return isServed(document, now) || announced ? document : undefined;
|
|
486
529
|
}
|
|
487
530
|
/**
|
|
488
531
|
* The URLs a document is saved as having lived at (TASK-127).
|
|
@@ -633,6 +676,18 @@ function resolveExtra(kind, document, form, now, media, declared) {
|
|
|
633
676
|
else
|
|
634
677
|
extra[property] = cited;
|
|
635
678
|
}
|
|
679
|
+
const resolved = resolveRead(form.readStatus, form.readOf);
|
|
680
|
+
if ('read' in resolved) {
|
|
681
|
+
const { read } = resolved;
|
|
682
|
+
if (read === undefined) {
|
|
683
|
+
delete extra[READ_OF_FRONT_MATTER_KEY];
|
|
684
|
+
delete extra[READ_STATUS_FRONT_MATTER_KEY];
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
extra[READ_OF_FRONT_MATTER_KEY] = readWorkFrontMatter(read.of);
|
|
688
|
+
extra[READ_STATUS_FRONT_MATTER_KEY] = read.status;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
636
691
|
}
|
|
637
692
|
// The checkboxes speak for the targets the site declares (TASK-155). An id
|
|
638
693
|
// the file lists that names no declared target has no checkbox, so it is
|
|
@@ -657,6 +712,12 @@ function resolveExtra(kind, document, form, now, media, declared) {
|
|
|
657
712
|
else if (pinned === undefined)
|
|
658
713
|
extra[PINNED_FRONT_MATTER_KEY] = toUtcInstant(now, 'UTC');
|
|
659
714
|
}
|
|
715
|
+
if (form.visibility === 'public')
|
|
716
|
+
delete extra[VISIBILITY_FRONT_MATTER_KEY];
|
|
717
|
+
else if (typeof form.visibility === 'string')
|
|
718
|
+
extra[VISIBILITY_FRONT_MATTER_KEY] = form.visibility;
|
|
719
|
+
else
|
|
720
|
+
extra[VISIBILITY_FRONT_MATTER_KEY] = form.visibility.unrecognized;
|
|
660
721
|
// Empty is the site's language, which is the key's absence (TASK-154).
|
|
661
722
|
if (form.lang === '')
|
|
662
723
|
delete extra[LANG_FRONT_MATTER_KEY];
|
|
@@ -984,14 +1045,18 @@ export function blankForm(kind, timezone = DEFAULT_TIMEZONE, now = new Date()) {
|
|
|
984
1045
|
author: '',
|
|
985
1046
|
inReplyTo: '',
|
|
986
1047
|
...citationFields(() => ''),
|
|
1048
|
+
readStatus: '',
|
|
1049
|
+
readOf: BLANK_READ_OF_FORM,
|
|
987
1050
|
lang: '',
|
|
988
1051
|
draft: false,
|
|
1052
|
+
visibility: 'public',
|
|
989
1053
|
exclude: false,
|
|
990
1054
|
contact: false,
|
|
991
1055
|
pinned: false,
|
|
992
1056
|
comments: COMMENT_SETTINGS.site,
|
|
993
1057
|
enclosure: BLANK_ENCLOSURE_FORM,
|
|
994
1058
|
photos: [],
|
|
1059
|
+
location: BLANK_LOCATION_FORM,
|
|
995
1060
|
syndicateTo: [],
|
|
996
1061
|
body: '',
|
|
997
1062
|
hash: '',
|
|
@@ -1004,8 +1069,12 @@ export function blankForm(kind, timezone = DEFAULT_TIMEZONE, now = new Date()) {
|
|
|
1004
1069
|
* {@link wallClockIn} keeps whatever precision the instant has, so a form
|
|
1005
1070
|
* submitted with the field untouched writes back the very instant it was
|
|
1006
1071
|
* filled in from.
|
|
1072
|
+
*
|
|
1073
|
+
* `location` is what `data/locations.json` holds for the post (decision-29),
|
|
1074
|
+
* which a document does not carry. It is required because a form loaded
|
|
1075
|
+
* without it would remove the stored location on save.
|
|
1007
1076
|
*/
|
|
1008
|
-
export function formFor(document, timezone
|
|
1077
|
+
export function formFor(document, timezone, location) {
|
|
1009
1078
|
return {
|
|
1010
1079
|
title: document.title,
|
|
1011
1080
|
slug: document.slug,
|
|
@@ -1018,16 +1087,22 @@ export function formFor(document, timezone = DEFAULT_TIMEZONE) {
|
|
|
1018
1087
|
inReplyTo: document.inReplyTo ?? '',
|
|
1019
1088
|
// As the file spells it, so a save writes back what it read.
|
|
1020
1089
|
...citationFields((property) => document.type === 'post' ? citationText(document.extra[property]) : ''),
|
|
1090
|
+
readStatus: document.type === 'post' && typeof document.extra[READ_STATUS_FRONT_MATTER_KEY] === 'string'
|
|
1091
|
+
? document.extra[READ_STATUS_FRONT_MATTER_KEY]
|
|
1092
|
+
: '',
|
|
1093
|
+
readOf: readOfForm(document.type === 'post' ? readWork(document.extra[READ_OF_FRONT_MATTER_KEY]) : undefined),
|
|
1021
1094
|
lang: typeof document.extra[LANG_FRONT_MATTER_KEY] === 'string'
|
|
1022
1095
|
? document.extra[LANG_FRONT_MATTER_KEY]
|
|
1023
1096
|
: '',
|
|
1024
1097
|
draft: document.draft,
|
|
1098
|
+
visibility: visibilityOf(document),
|
|
1025
1099
|
exclude: document.extra[EXCLUDE_KEY] === true,
|
|
1026
1100
|
contact: document.extra[CONTACT_FRONT_MATTER_KEY] === true,
|
|
1027
1101
|
pinned: pinnedAt(document) !== undefined,
|
|
1028
1102
|
comments: commentSettingOf(document),
|
|
1029
1103
|
enclosure: document.type === 'post' ? enclosureForm(document) : BLANK_ENCLOSURE_FORM,
|
|
1030
1104
|
photos: document.type === 'post' ? photoRows(document) : [],
|
|
1105
|
+
location: document.type === 'post' ? locationForm(location) : BLANK_LOCATION_FORM,
|
|
1031
1106
|
syndicateTo: document.type === 'post' ? syndicateToOf(document.extra) : [],
|
|
1032
1107
|
body: document.body,
|
|
1033
1108
|
hash: document.hash,
|
|
@@ -1082,6 +1157,12 @@ async function renderEditor(c, options) {
|
|
|
1082
1157
|
photoFields: PHOTO_FIELDS,
|
|
1083
1158
|
photoRows: photoRowViews(form.photos, readAltTexts(c.var.config.contentDir)),
|
|
1084
1159
|
photoChoices: await photoChoices(c.var.config.contentDir),
|
|
1160
|
+
locationFields: LOCATION_FIELDS,
|
|
1161
|
+
readFields: READ_FIELDS,
|
|
1162
|
+
readStatuses: READ_STATUSES.map((value) => ({ value, label: READ_STATUS_LABELS[value] })),
|
|
1163
|
+
...(form.readStatus === '' || isReadStatus(form.readStatus)
|
|
1164
|
+
? {}
|
|
1165
|
+
: { unrecognizedReadStatus: form.readStatus }),
|
|
1085
1166
|
// TASK-155: one checkbox per target the site declares.
|
|
1086
1167
|
syndicationTargets: syndicationTargetsReader(c.var.config.contentDir)().map((target) => ({
|
|
1087
1168
|
...target,
|
|
@@ -1101,7 +1182,7 @@ async function renderEditor(c, options) {
|
|
|
1101
1182
|
listUrl: kind.basePath,
|
|
1102
1183
|
previewUrl: PREVIEW_PATH,
|
|
1103
1184
|
uploadUrl: UPLOADS_PATH,
|
|
1104
|
-
viewUrl: document !== undefined &&
|
|
1185
|
+
viewUrl: document !== undefined && isServed(document, now) ? document.permalink : undefined,
|
|
1105
1186
|
// Named beside the date field, because a wall clock with no zone on it is
|
|
1106
1187
|
// exactly the ambiguity decision-11 exists to remove.
|
|
1107
1188
|
...(kind.dated ? { dateZone: zoneLabel(form.date === '' ? now : form.date, timezone) } : {}),
|
|
@@ -1150,7 +1231,7 @@ function pageRole(kind, document, c) {
|
|
|
1150
1231
|
return undefined;
|
|
1151
1232
|
}
|
|
1152
1233
|
function listRow(kind, document, now, role) {
|
|
1153
|
-
const isPublic =
|
|
1234
|
+
const isPublic = isServed(document, now);
|
|
1154
1235
|
return {
|
|
1155
1236
|
title: postLabel(document),
|
|
1156
1237
|
slug: document.slug,
|
|
@@ -1165,6 +1246,7 @@ function listRow(kind, document, now, role) {
|
|
|
1165
1246
|
draft: document.draft,
|
|
1166
1247
|
trashed: isTrashedPath(document.path),
|
|
1167
1248
|
scheduled: scheduledFor(document, now) !== undefined,
|
|
1249
|
+
hidden: typeof visibilityOf(document) !== 'string',
|
|
1168
1250
|
editUrl: editorPath(kind, document.slug),
|
|
1169
1251
|
viewUrl: isPublic ? document.permalink : undefined,
|
|
1170
1252
|
role,
|