@geekity/cms 0.19.0 → 0.20.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 +3 -0
- package/admin/components/admin-bar.njk +4 -3
- package/admin/layouts/shell.njk +1 -0
- package/admin/pages/documents/editor.njk +34 -0
- package/admin/static/admin.css +87 -0
- package/admin/static/location.js +38 -0
- package/dist/admin/documents.d.ts +13 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +94 -21
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/editor-layout.d.ts.map +1 -1
- package/dist/admin/editor-layout.js +1 -0
- package/dist/admin/editor-layout.js.map +1 -1
- package/dist/admin/headers.d.ts +1 -0
- package/dist/admin/headers.d.ts.map +1 -1
- package/dist/admin/headers.js +3 -0
- package/dist/admin/headers.js.map +1 -1
- package/dist/admin/media.d.ts.map +1 -1
- package/dist/admin/media.js +3 -0
- package/dist/admin/media.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/content/citation.d.ts +5 -0
- package/dist/content/citation.d.ts.map +1 -1
- package/dist/content/citation.js +17 -0
- package/dist/content/citation.js.map +1 -1
- package/dist/content/markdown.d.ts +9 -0
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +37 -1
- package/dist/content/markdown.js.map +1 -1
- package/dist/content/post-type.d.ts +1 -6
- package/dist/content/post-type.d.ts.map +1 -1
- package/dist/content/post-type.js +13 -14
- package/dist/content/post-type.js.map +1 -1
- package/dist/content/slug.d.ts +2 -1
- package/dist/content/slug.d.ts.map +1 -1
- package/dist/content/slug.js +3 -1
- package/dist/content/slug.js.map +1 -1
- package/dist/env.d.ts +6 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/federation/article.d.ts +4 -4
- package/dist/federation/article.js +5 -5
- package/dist/federation/article.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/micropub/content.d.ts +15 -0
- package/dist/micropub/content.d.ts.map +1 -0
- package/dist/micropub/content.js +254 -0
- package/dist/micropub/content.js.map +1 -0
- package/dist/micropub/create.d.ts.map +1 -1
- package/dist/micropub/create.js +13 -8
- package/dist/micropub/create.js.map +1 -1
- package/dist/micropub/endpoint.d.ts.map +1 -1
- package/dist/micropub/endpoint.js +18 -4
- package/dist/micropub/endpoint.js.map +1 -1
- package/dist/web/admin-bar.js +2 -1
- package/dist/web/admin-bar.js.map +1 -1
- package/dist/web/context.d.ts +10 -3
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +12 -2
- package/dist/web/context.js.map +1 -1
- package/dist/web/feed-item.d.ts +26 -3
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +63 -6
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-rss.js +3 -3
- package/dist/web/feed-rss.js.map +1 -1
- package/dist/web/feed-source.d.ts +7 -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 +7 -1
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/negotiate.d.ts +4 -0
- package/dist/web/negotiate.d.ts.map +1 -1
- package/dist/web/negotiate.js +5 -1
- package/dist/web/negotiate.js.map +1 -1
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +19 -6
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +1 -0
- package/dist/web/routes.js.map +1 -1
- package/dist/web/sanitize.d.ts +10 -0
- package/dist/web/sanitize.d.ts.map +1 -1
- package/dist/web/sanitize.js +180 -0
- package/dist/web/sanitize.js.map +1 -1
- package/dist/web/templates.d.ts.map +1 -1
- package/dist/web/templates.js +2 -0
- package/dist/web/templates.js.map +1 -1
- package/dist/webmention/cited-picture.d.ts +37 -0
- package/dist/webmention/cited-picture.d.ts.map +1 -0
- package/dist/webmention/cited-picture.js +121 -0
- package/dist/webmention/cited-picture.js.map +1 -0
- package/dist/webmention/fetch-public.d.ts +23 -7
- package/dist/webmention/fetch-public.d.ts.map +1 -1
- package/dist/webmention/fetch-public.js +6 -2
- package/dist/webmention/fetch-public.js.map +1 -1
- package/dist/webmention/reply-context.d.ts +17 -8
- package/dist/webmention/reply-context.d.ts.map +1 -1
- package/dist/webmention/reply-context.js +140 -28
- package/dist/webmention/reply-context.js.map +1 -1
- package/dist/webmention/reply-contexts.d.ts +11 -2
- package/dist/webmention/reply-contexts.d.ts.map +1 -1
- package/dist/webmention/reply-contexts.js +78 -6
- package/dist/webmention/reply-contexts.js.map +1 -1
- package/package.json +3 -2
- package/themes/default/README.md +56 -5
- package/themes/default/layouts/post.njk +2 -1
- package/themes/default/partials/citations.njk +14 -2
- package/themes/default/partials/cited-page.njk +21 -4
- package/themes/default/partials/reply-context.njk +7 -2
- package/themes/default/src/style.css +37 -0
- package/themes/default/static/style.css +83 -0
package/README.md
CHANGED
|
@@ -2424,6 +2424,9 @@ what is in it. Each header does something else:
|
|
|
2424
2424
|
leaves alone what an embedded video player asks for: autoplay, fullscreen,
|
|
2425
2425
|
`encrypted-media`, picture-in-picture, and the motion sensors behind a
|
|
2426
2426
|
360-degree video.
|
|
2427
|
+
The post editor is the one response that allows more: it sends the site's
|
|
2428
|
+
policy with `geolocation=()` changed to `geolocation=(self)`, so its Use my
|
|
2429
|
+
location button can ask the browser where it is.
|
|
2427
2430
|
- `Cross-Origin-Opener-Policy: same-origin` keeps a window the site opens, or a
|
|
2428
2431
|
window that opens the site, from reaching into it through `window.opener`.
|
|
2429
2432
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{# The admin bar, in the admin and across the top of the public site alike
|
|
2
|
-
(TASK-126, TASK-183). The site title leads to
|
|
3
|
-
the shortcuts beside it,
|
|
2
|
+
(TASK-126, TASK-183). The site title leads to `siteHref` and `barLinks` are
|
|
3
|
+
the shortcuts beside it, both chosen by each place for itself: the admin's
|
|
4
|
+
title leads to the dashboard, the public site's home (TASK-257). The account
|
|
4
5
|
menu ends it.
|
|
5
6
|
|
|
6
7
|
WordPress's "Howdy," menu, greeting the way this site's owner does. A
|
|
@@ -8,7 +9,7 @@
|
|
|
8
9
|
Escape or a click elsewhere, and tells a screen reader whether it is
|
|
9
10
|
expanded, all under the admin CSP's script-src 'self'. #}
|
|
10
11
|
<nav class="admin-bar" aria-label="Admin bar">
|
|
11
|
-
<a class="admin-bar-site" href="{{
|
|
12
|
+
<a class="admin-bar-site" href="{{ siteHref }}">{{ site.title }}</a>
|
|
12
13
|
{% for link in barLinks %}
|
|
13
14
|
<a href="{{ link.url }}">{{ link.label }}</a>
|
|
14
15
|
{% endfor %}
|
package/admin/layouts/shell.njk
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
{# View site, because in here you are not looking at it; + New, as on the
|
|
7
7
|
public site; and, on the editor of something readers can see, the way to
|
|
8
8
|
go and look at it. #}
|
|
9
|
+
{% set siteHref = adminUrl %}
|
|
9
10
|
{% set barLinks = [{ label: "View site", url: siteUrl }, { label: "+ New", url: newPostUrl }] %}
|
|
10
11
|
{% if viewUrl and kind %}
|
|
11
12
|
{% set barLinks = barLinks.concat([{ label: "View " + (kind.singular | capitalize), url: viewUrl }]) %}
|
|
@@ -28,6 +28,25 @@
|
|
|
28
28
|
{% if errorField == id %}<p class="admin-field-error" id="{{ id }}-error">{{ error }}</p>{% endif %}
|
|
29
29
|
{% endmacro %}
|
|
30
30
|
|
|
31
|
+
{% macro citedCard(property) %}
|
|
32
|
+
{% set card = citedPreviews[property] %}
|
|
33
|
+
{% if card %}
|
|
34
|
+
<div class="admin-cited-card">
|
|
35
|
+
<img src="{{ card.picture.src }}" alt="" width="{{ card.picture.width }}" height="{{ card.picture.height }}" loading="lazy">
|
|
36
|
+
<p class="admin-cited-title">{{ card.name }}</p>
|
|
37
|
+
<input type="checkbox" id="editor-preview-{{ property }}" name="preview" value="hide"{% if form.previewHidden %} checked{% endif %}>
|
|
38
|
+
<label class="admin-cited-remove" for="editor-preview-{{ property }}"><span aria-hidden="true">×</span><span class="admin-visually-hidden">Remove the preview of {{ card.name }}</span></label>
|
|
39
|
+
<p class="admin-cited-removed">Removed. The post shows the plain citation until you select the X again.</p>
|
|
40
|
+
{%- if card.described %}
|
|
41
|
+
<label for="editor-cited-alt">Alt text of the reposted image</label>
|
|
42
|
+
<input id="editor-cited-alt" name="cited-alt" type="text" value="{{ form.citedAlt or form.title }}"{{ invalid('editor-cited-alt', 'editor-cited-alt-hint') }} />
|
|
43
|
+
{{ problem('editor-cited-alt') }}
|
|
44
|
+
<p class="admin-hint" id="editor-cited-alt-hint">What the image shows, for readers who cannot see it. It starts as the post’s title.</p>
|
|
45
|
+
{%- endif %}
|
|
46
|
+
</div>
|
|
47
|
+
{% endif %}
|
|
48
|
+
{% endmacro %}
|
|
49
|
+
|
|
31
50
|
{% block content %}
|
|
32
51
|
<h1>{{ heading }}</h1>
|
|
33
52
|
|
|
@@ -118,6 +137,8 @@
|
|
|
118
137
|
Kept in <code>data/locations.json</code>, not in the post’s file. What readers see of it
|
|
119
138
|
is chosen on Settings > Privacy. Empty every box to remove it.
|
|
120
139
|
</p>
|
|
140
|
+
<button type="button" id="editor-location-here" class="admin-location-here" hidden>Use my location</button>
|
|
141
|
+
<p id="editor-location-status" class="admin-hint" role="status"></p>
|
|
121
142
|
<label for="editor-location-geo">Coordinates</label>
|
|
122
143
|
<input id="editor-location-geo" name="{{ locationFields.geo }}" type="text" value="{{ form.location.geo }}" placeholder="48.85837, 2.29448" autocomplete="off" spellcheck="false"{{ invalid('editor-location-geo', 'editor-location-geo-hint') }} />
|
|
123
144
|
{{ problem('editor-location-geo') }}
|
|
@@ -313,6 +334,7 @@
|
|
|
313
334
|
<label for="editor-in-reply-to">In reply to</label>
|
|
314
335
|
<input id="editor-in-reply-to" name="in-reply-to" type="url" value="{{ form.inReplyTo }}"{{ invalid('editor-in-reply-to') }} />
|
|
315
336
|
{{ problem('editor-in-reply-to') }}
|
|
337
|
+
{{ citedCard('in-reply-to') }}
|
|
316
338
|
<p class="admin-hint">The address of the post this one answers. Leave it empty for a post that answers nobody.</p>
|
|
317
339
|
|
|
318
340
|
{#- The mf2 `like-of`, `repost-of` and `bookmark-of` (TASK-169): each
|
|
@@ -320,13 +342,22 @@
|
|
|
320
342
|
<label for="editor-like-of">Like of</label>
|
|
321
343
|
<input id="editor-like-of" name="like-of" type="url" value="{{ form.likeOf }}"{{ invalid('editor-like-of') }} />
|
|
322
344
|
{{ problem('editor-like-of') }}
|
|
345
|
+
{{ citedCard('like-of') }}
|
|
323
346
|
<label for="editor-repost-of">Repost of</label>
|
|
324
347
|
<input id="editor-repost-of" name="repost-of" type="url" value="{{ form.repostOf }}"{{ invalid('editor-repost-of') }} />
|
|
325
348
|
{{ problem('editor-repost-of') }}
|
|
349
|
+
{{ citedCard('repost-of') }}
|
|
326
350
|
<label for="editor-bookmark-of">Bookmark of</label>
|
|
327
351
|
<input id="editor-bookmark-of" name="bookmark-of" type="url" value="{{ form.bookmarkOf }}"{{ invalid('editor-bookmark-of') }} />
|
|
328
352
|
{{ problem('editor-bookmark-of') }}
|
|
353
|
+
{{ citedCard('bookmark-of') }}
|
|
329
354
|
<p class="admin-hint">The address of a post this one likes, reposts or bookmarks. Leave them empty for a post that cites nothing.</p>
|
|
355
|
+
{% if form.previewHidden and not (citedPreviews | length) %}
|
|
356
|
+
<input type="hidden" name="preview" value="hide">
|
|
357
|
+
{% endif %}
|
|
358
|
+
{% if form.citedAlt and not describesCitedImage %}
|
|
359
|
+
<input type="hidden" name="cited-alt" value="{{ form.citedAlt }}">
|
|
360
|
+
{% endif %}
|
|
330
361
|
{% endcall %}
|
|
331
362
|
|
|
332
363
|
{% call group('Read', open.read) %}
|
|
@@ -460,4 +491,7 @@
|
|
|
460
491
|
{# The slug and permalink auto-fill, in a file of its own so the policy never
|
|
461
492
|
has to allow an inline script. #}
|
|
462
493
|
<script defer src="{{ assetPrefix }}slug.js"></script>
|
|
494
|
+
{% if kind.type == 'post' %}
|
|
495
|
+
<script defer src="{{ assetPrefix }}location.js"></script>
|
|
496
|
+
{% endif %}
|
|
463
497
|
{% endblock %}
|
package/admin/static/admin.css
CHANGED
|
@@ -544,6 +544,93 @@ button[type='submit']:hover,
|
|
|
544
544
|
grid-column: 1 / -1;
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
+
.admin-cited-card {
|
|
548
|
+
position: relative;
|
|
549
|
+
margin-top: 0.5rem;
|
|
550
|
+
padding: 0.5rem;
|
|
551
|
+
border: 1px solid var(--admin-border);
|
|
552
|
+
background: var(--admin-surface);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.admin-cited-card img {
|
|
556
|
+
display: block;
|
|
557
|
+
width: 100%;
|
|
558
|
+
height: auto;
|
|
559
|
+
max-height: 8rem;
|
|
560
|
+
object-fit: cover;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.admin-cited-title {
|
|
564
|
+
margin: 0.375rem 0 0;
|
|
565
|
+
overflow-wrap: break-word;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.admin-cited-card input[type='checkbox'] {
|
|
569
|
+
position: absolute;
|
|
570
|
+
width: 1px;
|
|
571
|
+
height: 1px;
|
|
572
|
+
margin: 0;
|
|
573
|
+
opacity: 0;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.admin-editor-group .admin-cited-remove {
|
|
577
|
+
position: absolute;
|
|
578
|
+
top: 0.75rem;
|
|
579
|
+
inset-inline-end: 0.75rem;
|
|
580
|
+
display: inline-grid;
|
|
581
|
+
place-items: center;
|
|
582
|
+
width: 1.75rem;
|
|
583
|
+
height: 1.75rem;
|
|
584
|
+
margin: 0;
|
|
585
|
+
border: 1px solid var(--admin-border);
|
|
586
|
+
border-radius: 50%;
|
|
587
|
+
background: var(--admin-field);
|
|
588
|
+
font-size: 1.1rem;
|
|
589
|
+
line-height: 1;
|
|
590
|
+
cursor: pointer;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.admin-cited-card input:focus-visible + .admin-cited-remove {
|
|
594
|
+
outline: 2px solid var(--admin-focus);
|
|
595
|
+
outline-offset: 2px;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.admin-cited-card input:checked + .admin-cited-remove {
|
|
599
|
+
background: var(--admin-text);
|
|
600
|
+
color: var(--admin-surface);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.admin-cited-removed {
|
|
604
|
+
display: none;
|
|
605
|
+
margin: 0.25rem 0 0;
|
|
606
|
+
color: var(--admin-muted);
|
|
607
|
+
font-size: 0.9em;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.admin-cited-card:has(input:checked) img,
|
|
611
|
+
.admin-cited-card:has(input:checked) .admin-cited-title {
|
|
612
|
+
opacity: 0.4;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.admin-cited-card:has(input:checked) .admin-cited-removed {
|
|
616
|
+
display: block;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.admin-location-here {
|
|
620
|
+
margin-top: 0.75rem;
|
|
621
|
+
padding: 0.3rem 0.8rem;
|
|
622
|
+
border: 1px solid var(--admin-link);
|
|
623
|
+
border-radius: 3px;
|
|
624
|
+
background: var(--admin-surface);
|
|
625
|
+
color: var(--admin-link);
|
|
626
|
+
font: inherit;
|
|
627
|
+
cursor: pointer;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.admin-location-here:hover {
|
|
631
|
+
background: var(--admin-field);
|
|
632
|
+
}
|
|
633
|
+
|
|
547
634
|
.admin-hint {
|
|
548
635
|
margin: 0.25rem 0 0;
|
|
549
636
|
color: var(--admin-muted);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
if (!navigator.geolocation) return;
|
|
3
|
+
|
|
4
|
+
var button = document.getElementById('editor-location-here');
|
|
5
|
+
var status = document.getElementById('editor-location-status');
|
|
6
|
+
var geo = document.getElementById('editor-location-geo');
|
|
7
|
+
var accuracy = document.getElementById('editor-location-accuracy');
|
|
8
|
+
if (!button || !status || !geo || !accuracy) return;
|
|
9
|
+
|
|
10
|
+
var problems = {
|
|
11
|
+
1: 'The browser was not allowed to share its location. Type the coordinates instead, or allow location for this site and try again.',
|
|
12
|
+
2: 'The browser could not find its location. Type the coordinates instead.',
|
|
13
|
+
3: 'The browser took too long to find its location. Try again, or type the coordinates instead.',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
button.hidden = false;
|
|
17
|
+
button.addEventListener('click', function () {
|
|
18
|
+
button.disabled = true;
|
|
19
|
+
status.textContent = 'Finding your location…';
|
|
20
|
+
navigator.geolocation.getCurrentPosition(
|
|
21
|
+
function (position) {
|
|
22
|
+
var coords = position.coords;
|
|
23
|
+
geo.value = coords.latitude.toFixed(5) + ', ' + coords.longitude.toFixed(5);
|
|
24
|
+
accuracy.value = String(Math.round(coords.accuracy));
|
|
25
|
+
var block = button.closest('details');
|
|
26
|
+
if (block) block.open = true;
|
|
27
|
+
status.textContent =
|
|
28
|
+
'Filled in from the browser, to within about ' + accuracy.value + ' metres.';
|
|
29
|
+
button.disabled = false;
|
|
30
|
+
},
|
|
31
|
+
function (error) {
|
|
32
|
+
status.textContent = problems[error.code] || problems[2];
|
|
33
|
+
button.disabled = false;
|
|
34
|
+
},
|
|
35
|
+
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 60000 },
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
})();
|
|
@@ -15,6 +15,7 @@ import type { Refusal } from './editor-layout.ts';
|
|
|
15
15
|
import type { LocationForm } from './location-field.ts';
|
|
16
16
|
import type { PhotoRow } from './photo-field.ts';
|
|
17
17
|
import type { ReadOfForm } from './read-field.ts';
|
|
18
|
+
import type { ReplyContext } from '../webmention/reply-context.ts';
|
|
18
19
|
/**
|
|
19
20
|
* Everything that differs between the posts screens and the pages screens.
|
|
20
21
|
*
|
|
@@ -126,6 +127,8 @@ export interface DocumentSite {
|
|
|
126
127
|
readonly announce: (change: DocumentChange) => Promise<void>;
|
|
127
128
|
/** The username of whoever is writing: the author a new document starts on. */
|
|
128
129
|
readonly writer: string | undefined;
|
|
130
|
+
readonly citedContext: (target: string) => Promise<ReplyContext | undefined>;
|
|
131
|
+
readonly storedContext: (target: string) => ReplyContext | undefined;
|
|
129
132
|
}
|
|
130
133
|
/** What {@link writeDocument} is asked to write. */
|
|
131
134
|
export interface DocumentWrite {
|
|
@@ -285,6 +288,16 @@ export interface EditorForm {
|
|
|
285
288
|
contact: boolean;
|
|
286
289
|
/** Whether the post is pinned to its author's profile (TASK-207). Posts only. */
|
|
287
290
|
pinned: boolean;
|
|
291
|
+
/**
|
|
292
|
+
* Whether the post hides the previews of the pages it cites, `preview:
|
|
293
|
+
* false` (TASK-252). Posts only.
|
|
294
|
+
*/
|
|
295
|
+
previewHidden: boolean;
|
|
296
|
+
/**
|
|
297
|
+
* The alt text of an image the post cites, `cited-alt` (TASK-255). Posts
|
|
298
|
+
* only; empty for none, when the post's title describes it.
|
|
299
|
+
*/
|
|
300
|
+
citedAlt: string;
|
|
288
301
|
/**
|
|
289
302
|
* What the document says about comments: one of {@link COMMENT_SETTINGS}.
|
|
290
303
|
*
|
|
@@ -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;
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, rm } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { CITATION_PROPERTIES, citationText } from '../content/citation.js';
|
|
3
|
+
import { CITATION_PROPERTIES, citationText, CITED_ALT_FRONT_MATTER_KEY, citedHost, PREVIEW_FRONT_MATTER_KEY, previewShown, } from '../content/citation.js';
|
|
4
4
|
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';
|
|
@@ -35,10 +35,12 @@ import { ADMIN_TEMPLATES } from './templates.js';
|
|
|
35
35
|
import { UPLOADS_PATH } from './uploads.js';
|
|
36
36
|
import { BLANK_ALTERNATE_ROW, BLANK_ENCLOSURE_FORM, ENCLOSURE_FIELDS, enclosureChoices, enclosureForm, enclosureFrontMatter, readEnclosureForm, resolveEnclosure, } from './enclosure-field.js';
|
|
37
37
|
import { openGroups } from './editor-layout.js';
|
|
38
|
+
import { allowGeolocation } from './headers.js';
|
|
38
39
|
import { BLANK_LOCATION_FORM, LOCATION_FIELDS, locationForm, readLocationForm, resolveLocation, } from './location-field.js';
|
|
39
40
|
import { PHOTO_FIELDS, photoChoices, photoRows, photoRowViews, readPhotoForm, resolvePhotos, } from './photo-field.js';
|
|
40
41
|
import { BLANK_READ_OF_FORM, READ_FIELDS, readOfForm, resolveRead, submittedReadOfForm, } from './read-field.js';
|
|
41
42
|
import { SYNDICATE_TO_FRONT_MATTER_KEY, syndicateToOf, syndicationTargetsReader, } from '../webmention/syndication.js';
|
|
43
|
+
import { citesAnImage, shownInFull } from '../webmention/cited-picture.js';
|
|
42
44
|
/**
|
|
43
45
|
* The Eleventy key that keeps a document out of every collection.
|
|
44
46
|
*
|
|
@@ -234,6 +236,8 @@ async function saveFromForm(c, options) {
|
|
|
234
236
|
exclude: body['exclude'] !== undefined,
|
|
235
237
|
contact: body['contact'] !== undefined,
|
|
236
238
|
pinned: kind.type === 'post' && body['pinned'] !== undefined,
|
|
239
|
+
previewHidden: kind.type === 'post' && body[PREVIEW_FRONT_MATTER_KEY] !== undefined,
|
|
240
|
+
citedAlt: kind.type === 'post' ? text(body[CITED_ALT_FRONT_MATTER_KEY]).trim() : '',
|
|
237
241
|
comments: commentSetting(text(body['comments'])),
|
|
238
242
|
enclosure: kind.type === 'post' ? readEnclosureForm(body) : BLANK_ENCLOSURE_FORM,
|
|
239
243
|
photos: kind.type === 'post' ? readPhotoForm(body) : [],
|
|
@@ -265,6 +269,8 @@ async function saveFromForm(c, options) {
|
|
|
265
269
|
config: c.var.config,
|
|
266
270
|
announce: c.var.announce,
|
|
267
271
|
writer: currentUsername(c),
|
|
272
|
+
citedContext: (target) => c.var.replyContexts.describe(target),
|
|
273
|
+
storedContext: (target) => c.var.replyContexts.read(target),
|
|
268
274
|
}, { kind, document, form, draft });
|
|
269
275
|
if (written.outcome === 'refused')
|
|
270
276
|
return refuse(written);
|
|
@@ -368,6 +374,12 @@ export async function writeDocument(site, write) {
|
|
|
368
374
|
if (undescribed.length > 0 && config.requireAltText) {
|
|
369
375
|
return refused(`This site publishes no image without alt text. ${missingAltText(undescribed)}`);
|
|
370
376
|
}
|
|
377
|
+
if (!draft &&
|
|
378
|
+
config.requireAltText &&
|
|
379
|
+
kind.type === 'post' &&
|
|
380
|
+
(await citesUndescribedImage(site, document, form))) {
|
|
381
|
+
return refused('This site publishes no image without alt text. Describe the image this post reposts in its alt text field.', 'editor-cited-alt');
|
|
382
|
+
}
|
|
371
383
|
const timezone = readSiteSettings(contentDir).timezone;
|
|
372
384
|
// decision-11: what the file gets is a UTC instant, and an offset-less field
|
|
373
385
|
// is the site's own wall clock rather than the server's.
|
|
@@ -384,7 +396,7 @@ export async function writeDocument(site, write) {
|
|
|
384
396
|
(document?.slug ?? '') ||
|
|
385
397
|
noteSlug(form.body) ||
|
|
386
398
|
slugify(form.readOf.name) ||
|
|
387
|
-
typeSlug(kind, form, media.photos) ||
|
|
399
|
+
(await typeSlug(kind, form, media.photos, site.citedContext)) ||
|
|
388
400
|
'untitled';
|
|
389
401
|
const trashed = document !== undefined && isTrashedPath(document.path);
|
|
390
402
|
// The calendar day the document is filed under: the site zone's day at its
|
|
@@ -487,6 +499,19 @@ export async function writeDocument(site, write) {
|
|
|
487
499
|
});
|
|
488
500
|
return { outcome: 'saved', saved, undescribed };
|
|
489
501
|
}
|
|
502
|
+
async function citesUndescribedImage(site, document, form) {
|
|
503
|
+
if (form.previewHidden || form.citedAlt !== '' || form.title !== '')
|
|
504
|
+
return false;
|
|
505
|
+
for (const property of CITATION_PROPERTIES) {
|
|
506
|
+
const url = form[CITATION_FIELDS[property]];
|
|
507
|
+
if (url === '' || !shownInFull(property, { kind: 'photo' }))
|
|
508
|
+
continue;
|
|
509
|
+
const context = document === undefined ? await site.citedContext(url) : site.storedContext(url);
|
|
510
|
+
if (context !== undefined && citesAnImage(context))
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
490
515
|
/** What a save says about the images it found with no alt text, naming each. */
|
|
491
516
|
function missingAltText(images) {
|
|
492
517
|
const names = images.map((image) => image.name).join(', ');
|
|
@@ -502,7 +527,13 @@ function noteSlug(body) {
|
|
|
502
527
|
return slugify(words.join(' '));
|
|
503
528
|
}
|
|
504
529
|
const TARGET_SLUG_WORDS = 4;
|
|
505
|
-
|
|
530
|
+
const CITED_SLUGS = {
|
|
531
|
+
repost: { prefix: 'reposted', field: 'repostOf' },
|
|
532
|
+
like: { prefix: 'liked', field: 'likeOf' },
|
|
533
|
+
reply: { prefix: 'reply-to', field: 'inReplyTo' },
|
|
534
|
+
bookmark: { prefix: 'bookmarked', field: 'bookmarkOf' },
|
|
535
|
+
};
|
|
536
|
+
async function typeSlug(kind, form, photos, citedContext) {
|
|
506
537
|
if (kind.type !== 'post')
|
|
507
538
|
return '';
|
|
508
539
|
const type = discoverPostType({
|
|
@@ -512,28 +543,22 @@ function typeSlug(kind, form, photos) {
|
|
|
512
543
|
'bookmark-of': form.bookmarkOf,
|
|
513
544
|
photo: photos.map((photo) => photo.url),
|
|
514
545
|
});
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
return `bookmarked-${targetWords(form.bookmarkOf)}`;
|
|
524
|
-
case 'photo':
|
|
525
|
-
return 'photo';
|
|
526
|
-
default:
|
|
527
|
-
return '';
|
|
528
|
-
}
|
|
546
|
+
if (type === 'photo')
|
|
547
|
+
return 'photo';
|
|
548
|
+
const cited = CITED_SLUGS[type];
|
|
549
|
+
if (cited === undefined)
|
|
550
|
+
return '';
|
|
551
|
+
const target = form[cited.field];
|
|
552
|
+
const title = slugWords((await citedContext(target))?.name ?? '', NOTE_SLUG_WORDS);
|
|
553
|
+
return `${cited.prefix}-${title || targetWords(target)}`;
|
|
529
554
|
}
|
|
530
555
|
function targetWords(address) {
|
|
531
556
|
const url = new URL(address);
|
|
532
557
|
const segments = url.pathname.split('/').map(decodedSegment).filter(holdsALetter);
|
|
533
|
-
return
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
558
|
+
return slugWords([url.hostname.replace(/^www\./, ''), ...segments].join(' '), TARGET_SLUG_WORDS);
|
|
559
|
+
}
|
|
560
|
+
function slugWords(text, cap) {
|
|
561
|
+
return slugify(text).split('-').slice(0, cap).join('-');
|
|
537
562
|
}
|
|
538
563
|
function holdsALetter(segment) {
|
|
539
564
|
return /\p{L}/u.test(segment);
|
|
@@ -729,6 +754,14 @@ function resolveExtra(kind, document, form, now, media, declared) {
|
|
|
729
754
|
else
|
|
730
755
|
extra[property] = cited;
|
|
731
756
|
}
|
|
757
|
+
if (form.previewHidden)
|
|
758
|
+
extra[PREVIEW_FRONT_MATTER_KEY] = false;
|
|
759
|
+
else
|
|
760
|
+
delete extra[PREVIEW_FRONT_MATTER_KEY];
|
|
761
|
+
if (form.citedAlt === '')
|
|
762
|
+
delete extra[CITED_ALT_FRONT_MATTER_KEY];
|
|
763
|
+
else
|
|
764
|
+
extra[CITED_ALT_FRONT_MATTER_KEY] = form.citedAlt;
|
|
732
765
|
const resolved = resolveRead(form.readStatus, form.readOf);
|
|
733
766
|
if ('read' in resolved) {
|
|
734
767
|
const { read } = resolved;
|
|
@@ -1106,6 +1139,8 @@ export function blankForm(kind, timezone = DEFAULT_TIMEZONE, now = new Date()) {
|
|
|
1106
1139
|
exclude: false,
|
|
1107
1140
|
contact: false,
|
|
1108
1141
|
pinned: false,
|
|
1142
|
+
previewHidden: false,
|
|
1143
|
+
citedAlt: '',
|
|
1109
1144
|
comments: COMMENT_SETTINGS.site,
|
|
1110
1145
|
enclosure: BLANK_ENCLOSURE_FORM,
|
|
1111
1146
|
photos: [],
|
|
@@ -1152,6 +1187,10 @@ export function formFor(document, timezone, location) {
|
|
|
1152
1187
|
exclude: document.extra[EXCLUDE_KEY] === true,
|
|
1153
1188
|
contact: document.extra[CONTACT_FRONT_MATTER_KEY] === true,
|
|
1154
1189
|
pinned: pinnedAt(document) !== undefined,
|
|
1190
|
+
previewHidden: document.type === 'post' && !previewShown(document.extra),
|
|
1191
|
+
citedAlt: document.type === 'post' && typeof document.extra[CITED_ALT_FRONT_MATTER_KEY] === 'string'
|
|
1192
|
+
? document.extra[CITED_ALT_FRONT_MATTER_KEY]
|
|
1193
|
+
: '',
|
|
1155
1194
|
comments: commentSettingOf(document),
|
|
1156
1195
|
enclosure: document.type === 'post' ? enclosureForm(document) : BLANK_ENCLOSURE_FORM,
|
|
1157
1196
|
photos: document.type === 'post' ? photoRows(document) : [],
|
|
@@ -1191,6 +1230,10 @@ async function renderEditor(c, options) {
|
|
|
1191
1230
|
actions.push({ value: 'trash', label: 'Move to trash' });
|
|
1192
1231
|
if (options.status !== undefined)
|
|
1193
1232
|
c.status(options.status);
|
|
1233
|
+
const permissions = c.var.config.securityHeaders['permissions-policy'];
|
|
1234
|
+
if (kind.type === 'post' && permissions !== undefined) {
|
|
1235
|
+
c.header('Permissions-Policy', allowGeolocation(permissions));
|
|
1236
|
+
}
|
|
1194
1237
|
return options.render(c, ADMIN_TEMPLATES.documentEditor, {
|
|
1195
1238
|
section: kind.section,
|
|
1196
1239
|
// The blank form is Add new; editing one that exists is still All posts,
|
|
@@ -1211,6 +1254,7 @@ async function renderEditor(c, options) {
|
|
|
1211
1254
|
photoRows: photoRowViews(form.photos, readAltTexts(c.var.config.contentDir)),
|
|
1212
1255
|
photoChoices: await photoChoices(c.var.config.contentDir),
|
|
1213
1256
|
locationFields: LOCATION_FIELDS,
|
|
1257
|
+
...citedPreviews(c, form),
|
|
1214
1258
|
readFields: READ_FIELDS,
|
|
1215
1259
|
readStatuses: READ_STATUSES.map((value) => ({ value, label: READ_STATUS_LABELS[value] })),
|
|
1216
1260
|
...(form.readStatus === '' || isReadStatus(form.readStatus)
|
|
@@ -1246,6 +1290,35 @@ async function renderEditor(c, options) {
|
|
|
1246
1290
|
open: openGroups(form, options.refusal?.field),
|
|
1247
1291
|
});
|
|
1248
1292
|
}
|
|
1293
|
+
function citedPreviews(c, form) {
|
|
1294
|
+
const cited = [
|
|
1295
|
+
['in-reply-to', form.inReplyTo],
|
|
1296
|
+
...CITATION_PROPERTIES.map((property) => [
|
|
1297
|
+
property,
|
|
1298
|
+
form[CITATION_FIELDS[property]],
|
|
1299
|
+
]),
|
|
1300
|
+
];
|
|
1301
|
+
const cards = {};
|
|
1302
|
+
for (const [property, url] of cited) {
|
|
1303
|
+
if (url === '')
|
|
1304
|
+
continue;
|
|
1305
|
+
const context = c.var.replyContexts.read(url);
|
|
1306
|
+
if (context?.picture === undefined)
|
|
1307
|
+
continue;
|
|
1308
|
+
const image = citesAnImage(context);
|
|
1309
|
+
cards[property] = {
|
|
1310
|
+
name: context.name ??
|
|
1311
|
+
context.author?.name ??
|
|
1312
|
+
`${image ? 'An image from' : 'A page on'} ${citedHost(url)}`,
|
|
1313
|
+
picture: context.picture,
|
|
1314
|
+
described: image && shownInFull(property, context.picture),
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
return {
|
|
1318
|
+
citedPreviews: cards,
|
|
1319
|
+
describesCitedImage: Object.values(cards).some((card) => card.described),
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1249
1322
|
/**
|
|
1250
1323
|
* The document of this kind with this slug.
|
|
1251
1324
|
*
|