@geekity/cms 0.18.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 +29 -25
- package/admin/components/admin-bar.njk +4 -3
- package/admin/components/public-admin-bar.njk +3 -1
- package/admin/layouts/shell.njk +1 -0
- package/admin/pages/documents/editor.njk +291 -192
- package/admin/static/admin.css +123 -13
- package/admin/static/location.js +38 -0
- package/dist/admin/documents.d.ts +18 -3
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +150 -21
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/editor-layout.d.ts +13 -0
- package/dist/admin/editor-layout.d.ts.map +1 -0
- package/dist/admin/editor-layout.js +62 -0
- package/dist/admin/editor-layout.js.map +1 -0
- package/dist/admin/enclosure-field.d.ts +2 -3
- package/dist/admin/enclosure-field.d.ts.map +1 -1
- package/dist/admin/enclosure-field.js +27 -7
- package/dist/admin/enclosure-field.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/location-field.d.ts +4 -3
- package/dist/admin/location-field.d.ts.map +1 -1
- package/dist/admin/location-field.js +10 -2
- package/dist/admin/location-field.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/admin/photo-field.d.ts +2 -3
- package/dist/admin/photo-field.d.ts.map +1 -1
- package/dist/admin/photo-field.js +4 -2
- package/dist/admin/photo-field.js.map +1 -1
- package/dist/admin/read-field.d.ts +2 -3
- package/dist/admin/read-field.d.ts.map +1 -1
- package/dist/admin/read-field.js +12 -4
- package/dist/admin/read-field.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/kept-properties.d.ts +5 -0
- package/dist/content/kept-properties.d.ts.map +1 -0
- package/dist/content/kept-properties.js +12 -0
- package/dist/content/kept-properties.js.map +1 -0
- package/dist/content/location.d.ts +4 -0
- package/dist/content/location.d.ts.map +1 -1
- package/dist/content/location.js +35 -3
- package/dist/content/location.js.map +1 -1
- package/dist/content/locations.d.ts +2 -5
- package/dist/content/locations.d.ts.map +1 -1
- package/dist/content/locations.js +2 -77
- package/dist/content/locations.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/permalink-file.d.ts +7 -0
- package/dist/content/permalink-file.d.ts.map +1 -0
- package/dist/content/permalink-file.js +77 -0
- package/dist/content/permalink-file.js.map +1 -0
- 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 +8 -7
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +37 -18
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/citations.d.ts +7 -6
- package/dist/federation/citations.d.ts.map +1 -1
- package/dist/federation/citations.js +44 -28
- package/dist/federation/citations.js.map +1 -1
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +23 -16
- package/dist/federation/delivery.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/indieauth/bearer.d.ts +10 -6
- package/dist/indieauth/bearer.d.ts.map +1 -1
- package/dist/indieauth/bearer.js +11 -9
- package/dist/indieauth/bearer.js.map +1 -1
- package/dist/indieauth/token.js +1 -1
- package/dist/indieauth/token.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 +7 -3
- package/dist/micropub/create.d.ts.map +1 -1
- package/dist/micropub/create.js +75 -24
- package/dist/micropub/create.js.map +1 -1
- package/dist/micropub/endpoint.d.ts +4 -5
- package/dist/micropub/endpoint.d.ts.map +1 -1
- package/dist/micropub/endpoint.js +33 -12
- package/dist/micropub/endpoint.js.map +1 -1
- package/dist/micropub/media.d.ts +6 -4
- package/dist/micropub/media.d.ts.map +1 -1
- package/dist/micropub/media.js +7 -5
- package/dist/micropub/media.js.map +1 -1
- package/dist/micropub/update.d.ts +7 -1
- package/dist/micropub/update.d.ts.map +1 -1
- package/dist/micropub/update.js +32 -7
- package/dist/micropub/update.js.map +1 -1
- package/dist/web/admin-bar.d.ts.map +1 -1
- package/dist/web/admin-bar.js +27 -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/documents.d.ts +21 -6
- package/dist/web/documents.d.ts.map +1 -1
- package/dist/web/documents.js +23 -12
- package/dist/web/documents.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 +5 -4
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +34 -12
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +16 -4
- 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 +26 -8
- package/dist/webmention/fetch-public.d.ts.map +1 -1
- package/dist/webmention/fetch-public.js +22 -12
- package/dist/webmention/fetch-public.js.map +1 -1
- package/dist/webmention/reply-context.d.ts +30 -8
- package/dist/webmention/reply-context.d.ts.map +1 -1
- package/dist/webmention/reply-context.js +238 -27
- package/dist/webmention/reply-context.js.map +1 -1
- package/dist/webmention/reply-contexts.d.ts +28 -16
- package/dist/webmention/reply-contexts.d.ts.map +1 -1
- package/dist/webmention/reply-contexts.js +108 -25
- package/dist/webmention/reply-contexts.js.map +1 -1
- package/package.json +10 -9
- package/themes/default/README.md +83 -9
- package/themes/default/layouts/post.njk +7 -7
- package/themes/default/partials/citations.njk +25 -4
- package/themes/default/partials/cited-page.njk +58 -0
- package/themes/default/partials/post-list.njk +8 -5
- package/themes/default/partials/reply-context.njk +9 -11
- package/themes/default/src/style.css +37 -0
- package/themes/default/static/style.css +83 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { discoverPostType } from '../content/post-type.js';
|
|
2
2
|
import { fetchPublic, webUrl } from './fetch-public.js';
|
|
3
|
-
import { elementsIn, parseHtml, textOf } from './html.js';
|
|
3
|
+
import { elementsIn, hasRel, parseHtml, textOf } from './html.js';
|
|
4
4
|
import { citedEntry } from './microformats.js';
|
|
5
5
|
/**
|
|
6
6
|
* Reply context: what a reply shows of the post it answers (TASK-123).
|
|
@@ -13,31 +13,138 @@ import { citedEntry } from './microformats.js';
|
|
|
13
13
|
*/
|
|
14
14
|
/** How long a target is given to answer, body included. */
|
|
15
15
|
export const REPLY_CONTEXT_TIMEOUT_MS = 10_000;
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The most of a target page that is read. Only the head of a bigger page is
|
|
18
|
+
* read, and an oEmbed answer bigger than this is not read at all.
|
|
19
|
+
*/
|
|
17
20
|
export const REPLY_CONTEXT_MAX_BYTES = 1_000_000;
|
|
21
|
+
const IMAGE_TYPE = /^\s*image\//i;
|
|
18
22
|
/** How many words of a target's text a preview keeps. */
|
|
19
23
|
const EXCERPT_WORDS = 40;
|
|
20
24
|
/** And how many characters, for a text with few spaces in it. */
|
|
21
25
|
const EXCERPT_CHARACTERS = 300;
|
|
22
26
|
/**
|
|
23
|
-
* Fetch a
|
|
27
|
+
* Fetch a page a post cites and read what it says about itself. A known
|
|
28
|
+
* provider's oEmbed endpoint is asked first, and the page is read only when
|
|
29
|
+
* that names nothing. Any other page is read, and asked about through the
|
|
30
|
+
* oEmbed endpoint it links when it has no `h-entry`.
|
|
24
31
|
*
|
|
25
32
|
* Only http and https, only public hosts (every redirect hop is checked, and a
|
|
26
33
|
* name is refused when any address it resolves to is private), one timeout
|
|
27
|
-
* over the whole exchange,
|
|
34
|
+
* over the whole exchange, every oEmbed request included, no more of a page
|
|
35
|
+
* than the byte limit, and no oEmbed answer over it. Nothing throws.
|
|
28
36
|
*/
|
|
29
37
|
export async function fetchReplyContext(target, options) {
|
|
30
|
-
const
|
|
38
|
+
const limits = {
|
|
31
39
|
lookup: options.lookup,
|
|
32
|
-
|
|
40
|
+
deadline: Date.now() + (options.timeoutMs ?? REPLY_CONTEXT_TIMEOUT_MS),
|
|
33
41
|
maxBytes: options.maxBytes ?? REPLY_CONTEXT_MAX_BYTES,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
};
|
|
43
|
+
const known = knownEndpoint(target);
|
|
44
|
+
if (known !== undefined) {
|
|
45
|
+
const oembed = withoutSuffix(await fetchOembed(known.endpoint, limits), known.titleSuffix);
|
|
46
|
+
const context = describe(parseHtml(''), undefined, target, oembed);
|
|
47
|
+
if (context !== undefined)
|
|
48
|
+
return described(context, oembed?.picture);
|
|
49
|
+
}
|
|
50
|
+
const timeoutMs = limits.deadline - Date.now();
|
|
51
|
+
const fetched = timeoutMs <= 0
|
|
52
|
+
? { ok: false, reason: 'timed out' }
|
|
53
|
+
: await fetchPublic(target, {
|
|
54
|
+
lookup: limits.lookup,
|
|
55
|
+
timeoutMs,
|
|
56
|
+
maxBytes: limits.maxBytes,
|
|
57
|
+
overflow: 'truncate',
|
|
58
|
+
accept: 'text/html, */*;q=0.8',
|
|
59
|
+
contentType: {
|
|
60
|
+
pattern: /^\s*(text\/html|application\/xhtml\+xml)/i,
|
|
61
|
+
name: 'an HTML page',
|
|
62
|
+
},
|
|
63
|
+
headersOnly: IMAGE_TYPE,
|
|
64
|
+
});
|
|
65
|
+
if (fetched.ok && fetched.read === 'headers') {
|
|
66
|
+
return described({ url: target }, { url: fetched.url, kind: 'photo' });
|
|
67
|
+
}
|
|
68
|
+
const root = parseHtml(fetched.ok ? new TextDecoder().decode(fetched.body) : '');
|
|
69
|
+
const entry = fetched.ok && !fetched.truncated ? citedEntry(root, fetched.url) : undefined;
|
|
70
|
+
const endpoint = fetched.ok && entry === undefined && known === undefined
|
|
71
|
+
? oembedEndpoint(root, fetched.url)
|
|
72
|
+
: undefined;
|
|
73
|
+
const oembed = endpoint === undefined ? undefined : await fetchOembed(endpoint, limits);
|
|
74
|
+
const context = describe(root, entry, target, oembed);
|
|
75
|
+
if (context !== undefined) {
|
|
76
|
+
return described(context, oembed?.picture ?? (fetched.ok ? pagePicture(root, fetched.url) : undefined));
|
|
77
|
+
}
|
|
78
|
+
return fetched.ok ? refuse('nothing to show') : fetched;
|
|
79
|
+
}
|
|
80
|
+
function described(context, picture) {
|
|
81
|
+
return { ok: true, context, ...(picture === undefined ? {} : { picture }) };
|
|
82
|
+
}
|
|
83
|
+
function pagePicture(root, base) {
|
|
84
|
+
const image = metaOf(root, 'og:image') || metaOf(root, 'twitter:image') || metaOf(root, 'twitter:image:src');
|
|
85
|
+
if (image === '')
|
|
86
|
+
return undefined;
|
|
87
|
+
let url;
|
|
88
|
+
try {
|
|
89
|
+
url = webUrl(new URL(image, base).href);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
if (url === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
const video = metaOf(root, 'og:type').toLowerCase().startsWith('video') ||
|
|
97
|
+
metaOf(root, 'twitter:card').toLowerCase() === 'player';
|
|
98
|
+
return { url: url.href, kind: 'thumbnail', ...(video ? { video: true } : {}) };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Providers whose JSON oEmbed endpoint is known, asked before their page:
|
|
102
|
+
* YouTube, TikTok, Reddit and Giphy serve a server a generic or slow page, or
|
|
103
|
+
* refuse it, while their endpoints answer (decision-19).
|
|
104
|
+
*/
|
|
105
|
+
const KNOWN_OEMBED_PROVIDERS = [
|
|
106
|
+
{
|
|
107
|
+
hosts: ['youtube.com', 'www.youtube.com', 'm.youtube.com'],
|
|
108
|
+
path: /^\/(watch\?(.*&)?v=[\w-]+|shorts\/[\w-]+\/?(\?|$))/,
|
|
109
|
+
endpoint: 'https://www.youtube.com/oembed',
|
|
110
|
+
},
|
|
111
|
+
{ hosts: ['youtu.be'], path: /^\/[\w-]+\/?(\?|$)/, endpoint: 'https://www.youtube.com/oembed' },
|
|
112
|
+
{
|
|
113
|
+
hosts: ['tiktok.com', 'www.tiktok.com'],
|
|
114
|
+
path: /^\/@[^/]+\/video\/\d+/,
|
|
115
|
+
endpoint: 'https://www.tiktok.com/oembed',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
hosts: ['reddit.com', 'www.reddit.com'],
|
|
119
|
+
path: /^\/r\/[^/]+\/comments\//,
|
|
120
|
+
endpoint: 'https://www.reddit.com/oembed',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
hosts: ['giphy.com', 'www.giphy.com'],
|
|
124
|
+
path: /^\/gifs\/[^/?]+/,
|
|
125
|
+
endpoint: 'https://giphy.com/services/oembed',
|
|
126
|
+
titleSuffix: ' - Find & Share on GIPHY',
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
function knownEndpoint(target) {
|
|
130
|
+
const url = webUrl(target);
|
|
131
|
+
if (url === undefined)
|
|
132
|
+
return undefined;
|
|
133
|
+
const provider = KNOWN_OEMBED_PROVIDERS.find(({ hosts, path }) => hosts.includes(url.hostname) && path.test(url.pathname + url.search));
|
|
134
|
+
if (provider === undefined)
|
|
135
|
+
return undefined;
|
|
136
|
+
const endpoint = new URL(provider.endpoint);
|
|
137
|
+
endpoint.searchParams.set('format', 'json');
|
|
138
|
+
endpoint.searchParams.set('url', target);
|
|
139
|
+
return { endpoint: endpoint.href, titleSuffix: provider.titleSuffix };
|
|
140
|
+
}
|
|
141
|
+
function withoutSuffix(oembed, suffix) {
|
|
142
|
+
if (oembed?.title === undefined || suffix === undefined || !oembed.title.endsWith(suffix)) {
|
|
143
|
+
return oembed;
|
|
144
|
+
}
|
|
145
|
+
const title = oembed.title.slice(0, -suffix.length).trim();
|
|
146
|
+
const { title: _title, ...rest } = oembed;
|
|
147
|
+
return title === '' ? rest : { ...rest, title };
|
|
41
148
|
}
|
|
42
149
|
/**
|
|
43
150
|
* What a target page says about itself, or `undefined` when it says nothing a
|
|
@@ -45,37 +152,141 @@ export async function fetchReplyContext(target, options) {
|
|
|
45
152
|
*
|
|
46
153
|
* The first `h-entry` when there is one: its name when it has one of its own
|
|
47
154
|
* (the test Post Type Discovery uses), an excerpt of its text, its author and
|
|
48
|
-
* its date. A page with no `h-entry` is described by its
|
|
49
|
-
*
|
|
50
|
-
*
|
|
155
|
+
* its date. A page with no `h-entry` is described by its oEmbed title and
|
|
156
|
+
* author when `oembed` holds them, then its `og:title`, `<title>` and
|
|
157
|
+
* description metadata, where a title that is only a site suffix is none.
|
|
158
|
+
* Everything comes out as plain text; the theme escapes it like any other
|
|
159
|
+
* string.
|
|
51
160
|
*/
|
|
52
|
-
export function readReplyContext(html, target, base = target) {
|
|
161
|
+
export function readReplyContext(html, target, base = target, oembed) {
|
|
53
162
|
const root = parseHtml(html);
|
|
54
|
-
|
|
163
|
+
return describe(root, citedEntry(root, base), target, oembed);
|
|
164
|
+
}
|
|
165
|
+
function describe(root, entry, target, oembed) {
|
|
55
166
|
if (entry !== undefined) {
|
|
56
|
-
const
|
|
57
|
-
const
|
|
167
|
+
const entryName = withoutDirectionControls(entry.name);
|
|
168
|
+
const entryText = withoutDirectionControls(entry.text);
|
|
169
|
+
const named = discoverPostType({ name: entryName, content: entryText }) === 'article';
|
|
170
|
+
const text = excerpt(entryText);
|
|
171
|
+
const authorName = withoutDirectionControls(entry.author?.name);
|
|
58
172
|
const authorUrl = entry.author?.url === null ? undefined : webUrl(entry.author?.url ?? '');
|
|
59
173
|
return {
|
|
60
174
|
url: target,
|
|
61
|
-
...(named ? { name:
|
|
175
|
+
...(named ? { name: entryName } : {}),
|
|
62
176
|
...(text === '' ? {} : { text }),
|
|
63
|
-
...(
|
|
177
|
+
...(authorName === ''
|
|
64
178
|
? {}
|
|
65
179
|
: {
|
|
66
180
|
author: {
|
|
67
|
-
name:
|
|
181
|
+
name: authorName,
|
|
68
182
|
...(authorUrl === undefined ? {} : { url: authorUrl.href }),
|
|
69
183
|
},
|
|
70
184
|
}),
|
|
71
185
|
...(entry.published === null ? {} : { published: entry.published }),
|
|
186
|
+
...(authorName === '' ? siteOf(root) : {}),
|
|
72
187
|
};
|
|
73
188
|
}
|
|
74
|
-
const name =
|
|
75
|
-
|
|
76
|
-
|
|
189
|
+
const name = withoutDirectionControls(oembed?.title) ||
|
|
190
|
+
pageTitle(metaOf(root, 'og:title')) ||
|
|
191
|
+
pageTitle(titleOf(root));
|
|
192
|
+
const text = excerpt(withoutDirectionControls(metaOf(root, 'description')) ||
|
|
193
|
+
withoutDirectionControls(metaOf(root, 'og:description')));
|
|
194
|
+
const authorName = withoutDirectionControls(oembed?.author?.name);
|
|
195
|
+
const author = authorName === '' ? undefined : { ...oembed?.author, name: authorName };
|
|
196
|
+
if (name === '' && text === '' && author === undefined)
|
|
197
|
+
return undefined;
|
|
198
|
+
return {
|
|
199
|
+
url: target,
|
|
200
|
+
...(name === '' ? {} : { name }),
|
|
201
|
+
...(text === '' ? {} : { text }),
|
|
202
|
+
...(author === undefined ? siteOf(root) : { author }),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function siteOf(root) {
|
|
206
|
+
const site = withoutDirectionControls(metaOf(root, 'og:site_name'));
|
|
207
|
+
return site === '' ? {} : { site };
|
|
208
|
+
}
|
|
209
|
+
function withoutDirectionControls(text) {
|
|
210
|
+
return (text ?? '')
|
|
211
|
+
.replace(/\p{Bidi_Control}/gu, '')
|
|
212
|
+
.replace(/\s+/g, ' ')
|
|
213
|
+
.trim();
|
|
214
|
+
}
|
|
215
|
+
function pageTitle(text) {
|
|
216
|
+
const title = withoutDirectionControls(text);
|
|
217
|
+
return /^[-–—|·•:]\s/u.test(title) ? '' : title;
|
|
218
|
+
}
|
|
219
|
+
function oembedEndpoint(root, base) {
|
|
220
|
+
for (const element of elementsIn(root)) {
|
|
221
|
+
if (element.name !== 'link')
|
|
222
|
+
continue;
|
|
223
|
+
// Flickr writes "alternative" and SoundCloud "text/json+oembed".
|
|
224
|
+
if (!hasRel(element, 'alternate') && !hasRel(element, 'alternative'))
|
|
225
|
+
continue;
|
|
226
|
+
const type = element.attributes['type']?.trim().toLowerCase();
|
|
227
|
+
if (type !== 'application/json+oembed' && type !== 'text/json+oembed')
|
|
228
|
+
continue;
|
|
229
|
+
try {
|
|
230
|
+
return new URL(element.attributes['href'] ?? '', base).href;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
async function fetchOembed(endpoint, limits) {
|
|
239
|
+
const timeoutMs = limits.deadline - Date.now();
|
|
240
|
+
if (timeoutMs <= 0)
|
|
241
|
+
return undefined;
|
|
242
|
+
const fetched = await fetchPublic(endpoint, {
|
|
243
|
+
lookup: limits.lookup,
|
|
244
|
+
timeoutMs,
|
|
245
|
+
maxBytes: limits.maxBytes,
|
|
246
|
+
accept: 'application/json+oembed, application/json;q=0.9',
|
|
247
|
+
contentType: {
|
|
248
|
+
pattern: /^\s*(application\/(json|json\+oembed)|text\/(json|javascript))\b/i,
|
|
249
|
+
name: 'oEmbed JSON',
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
if (!fetched.ok)
|
|
253
|
+
return undefined;
|
|
254
|
+
let parsed;
|
|
255
|
+
try {
|
|
256
|
+
parsed = JSON.parse(new TextDecoder().decode(fetched.body));
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
262
|
+
return undefined;
|
|
263
|
+
const fields = parsed;
|
|
264
|
+
const title = plainText(fields['title']);
|
|
265
|
+
const authorName = plainText(fields['author_name']);
|
|
266
|
+
const authorUrl = webUrl(plainText(fields['author_url']) ?? '');
|
|
267
|
+
const type = plainText(fields['type']);
|
|
268
|
+
const photo = type === 'photo' ? webUrl(plainText(fields['url']) ?? '') : undefined;
|
|
269
|
+
const thumbnail = webUrl(plainText(fields['thumbnail_url']) ?? '');
|
|
270
|
+
const picture = photo !== undefined
|
|
271
|
+
? { url: photo.href, kind: 'photo' }
|
|
272
|
+
: thumbnail !== undefined
|
|
273
|
+
? { url: thumbnail.href, kind: 'thumbnail', ...(type === 'video' ? { video: true } : {}) }
|
|
274
|
+
: undefined;
|
|
275
|
+
return {
|
|
276
|
+
...(title === undefined ? {} : { title }),
|
|
277
|
+
...(picture === undefined ? {} : { picture }),
|
|
278
|
+
...(authorName === undefined
|
|
279
|
+
? {}
|
|
280
|
+
: {
|
|
281
|
+
author: { name: authorName, ...(authorUrl === undefined ? {} : { url: authorUrl.href }) },
|
|
282
|
+
}),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function plainText(value) {
|
|
286
|
+
if (typeof value !== 'string')
|
|
77
287
|
return undefined;
|
|
78
|
-
|
|
288
|
+
const text = value.replace(/\s+/g, ' ').trim();
|
|
289
|
+
return text === '' ? undefined : text;
|
|
79
290
|
}
|
|
80
291
|
/** The page's `<title>`, or empty. */
|
|
81
292
|
function titleOf(root) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reply-context.js","sourceRoot":"","sources":["../../src/webmention/reply-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG/C;;;;;;;;GAQG;AAEH,2DAA2D;AAC3D,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAE/C,gFAAgF;AAChF,MAAM,CAAC,MAAM,uBAAuB,GAAG,SAAS,CAAC;AAEjD,yDAAyD;AACzD,MAAM,aAAa,GAAG,EAAE,CAAC;AAEzB,iEAAiE;AACjE,MAAM,kBAAkB,GAAG,GAAG,CAAC;AA+B/B;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAc,EACd,OAAiC;IAEjC,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE;QACxC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,wBAAwB;QACxD,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,uBAAuB;QACrD,MAAM,EAAE,sBAAsB;QAC9B,WAAW,EAAE,EAAE,OAAO,EAAE,2CAA2C,EAAE,IAAI,EAAE,cAAc,EAAE;KAC5F,CAAC,CAAC;IACH,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,OAAO,CAAC;IAEhC,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9F,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AACnF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,MAAc,EACd,IAAI,GAAW,MAAM;IAErB,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAErC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,SAAS,CAAC;QACxF,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;QAC3F,OAAO;YACL,GAAG,EAAE,MAAM;YACX,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;YAChC,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS;gBAC5B,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,MAAM,EAAE;wBACN,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI;wBACvB,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC;qBAC5D;iBACF,CAAC;YACN,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;SACpE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACpF,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAEjD,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC7F,CAAC;AAED,sCAAsC;AACtC,SAAS,OAAO,CAAC,IAAiB;IAChC,KAAK,MAAM,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,uEAAuE;AACvE,SAAS,MAAM,CAAC,IAAiB,EAAE,GAAW;IAC5C,KAAK,MAAM,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;YAAE,SAAS;QACtC,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;QAC3E,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,GAAG,CAAC;YAAE,SAAS;QACjE,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3E,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,8DAA8D;AAC9D,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,GAAG,GAAG,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IAC9F,IAAI,GAAG,CAAC,MAAM,GAAG,kBAAkB;QAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,OAAO,EAAE,CAAC;IACtF,OAAO,GAAG,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC;AACtD,CAAC;AAED,SAAS,MAAM,CAAC,MAAc;IAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC"}
|
|
1
|
+
{"version":3,"file":"reply-context.js","sourceRoot":"","sources":["../../src/webmention/reply-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAElE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAI/C;;;;;;;;GAQG;AAEH,2DAA2D;AAC3D,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAE/C;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,SAAS,CAAC;AAEjD,MAAM,UAAU,GAAG,cAAc,CAAC;AAElC,yDAAyD;AACzD,MAAM,aAAa,GAAG,EAAE,CAAC;AAEzB,iEAAiE;AACjE,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAkC/B;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAc,EACd,OAAiC;IAEjC,MAAM,MAAM,GAAG;QACb,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,wBAAwB,CAAC;QACtE,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,uBAAuB;KACtD,CAAC;IAEF,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;QAC3F,MAAM,OAAO,GAAG,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACnE,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC/C,MAAM,OAAO,GACX,SAAS,IAAI,CAAC;QACZ,CAAC,CAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAY;QAC/C,CAAC,CAAC,MAAM,WAAW,CAAC,MAAM,EAAE;YACxB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,SAAS;YACT,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,UAAU;YACpB,MAAM,EAAE,sBAAsB;YAC9B,WAAW,EAAE;gBACX,OAAO,EAAE,2CAA2C;gBACpD,IAAI,EAAE,cAAc;aACrB;YACD,WAAW,EAAE,UAAU;SACxB,CAAC,CAAC;IAET,IAAI,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7C,OAAO,SAAS,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACjF,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3F,MAAM,QAAQ,GACZ,OAAO,CAAC,EAAE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;QACtD,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;QACnC,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,MAAM,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAExF,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACtD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,SAAS,CACd,OAAO,EACP,MAAM,EAAE,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAC7E,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AAC1D,CAAC;AAED,SAAS,SAAS,CAAC,OAAqB,EAAE,OAAkC;IAC1E,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED,SAAS,WAAW,CAAC,IAAiB,EAAE,IAAY;IAClD,MAAM,KAAK,GACT,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;IACjG,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACnC,IAAI,GAAoB,CAAC;IACzB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,KAAK,GACT,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QACzD,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC;IAC1D,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACjF,CAAC;AAED;;;;GAIG;AACH,MAAM,sBAAsB,GAKtB;IACJ;QACE,KAAK,EAAE,CAAC,aAAa,EAAE,iBAAiB,EAAE,eAAe,CAAC;QAC1D,IAAI,EAAE,oDAAoD;QAC1D,QAAQ,EAAE,gCAAgC;KAC3C;IACD,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,gCAAgC,EAAE;IAC/F;QACE,KAAK,EAAE,CAAC,YAAY,EAAE,gBAAgB,CAAC;QACvC,IAAI,EAAE,uBAAuB;QAC7B,QAAQ,EAAE,+BAA+B;KAC1C;IACD;QACE,KAAK,EAAE,CAAC,YAAY,EAAE,gBAAgB,CAAC;QACvC,IAAI,EAAE,yBAAyB;QAC/B,QAAQ,EAAE,+BAA+B;KAC1C;IACD;QACE,KAAK,EAAE,CAAC,WAAW,EAAE,eAAe,CAAC;QACrC,IAAI,EAAE,iBAAiB;QACvB,QAAQ,EAAE,mCAAmC;QAC7C,WAAW,EAAE,0BAA0B;KACxC;CACF,CAAC;AAEF,SAAS,aAAa,CACpB,MAAc;IAEd,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAC1C,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAC1F,CAAC;IACF,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC5C,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACzC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC;AACxE,CAAC;AAED,SAAS,aAAa,CAAC,MAA0B,EAAE,MAA0B;IAC3E,IAAI,MAAM,EAAE,KAAK,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1F,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3D,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC;IAC1C,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC;AAClD,CAAC;AAQD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,MAAc,EACd,IAAI,GAAW,MAAM,EACrB,MAAe;IAEf,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,QAAQ,CACf,IAAiB,EACjB,KAA6B,EAC7B,MAAc,EACd,MAA0B;IAE1B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,SAAS,GAAG,wBAAwB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,SAAS,GAAG,wBAAwB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,KAAK,SAAS,CAAC;QACtF,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,MAAM,UAAU,GAAG,wBAAwB,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAChE,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;QAC3F,OAAO;YACL,GAAG,EAAE,MAAM;YACX,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrC,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;YAChC,GAAG,CAAC,UAAU,KAAK,EAAE;gBACnB,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,MAAM,EAAE;wBACN,IAAI,EAAE,UAAU;wBAChB,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC;qBAC5D;iBACF,CAAC;YACN,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;YACnE,GAAG,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3C,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GACR,wBAAwB,CAAC,MAAM,EAAE,KAAK,CAAC;QACvC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACnC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,OAAO,CAClB,wBAAwB,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QACnD,wBAAwB,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAC3D,CAAC;IACF,MAAM,UAAU,GAAG,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IACvF,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAEzE,OAAO;QACL,GAAG,EAAE,MAAM;QACX,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QAChC,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QAChC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;KACtD,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,IAAiB;IAC/B,MAAM,IAAI,GAAG,wBAAwB,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC;IACpE,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;AACrC,CAAC;AAED,SAAS,wBAAwB,CAAC,IAAwB;IACxD,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;SAChB,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC;SACjC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAClD,CAAC;AAED,SAAS,cAAc,CAAC,IAAiB,EAAE,IAAY;IACrD,KAAK,MAAM,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;YAAE,SAAS;QACtC,iEAAiE;QACjE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC;YAAE,SAAS;QAC/E,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC9D,IAAI,IAAI,KAAK,yBAAyB,IAAI,IAAI,KAAK,kBAAkB;YAAE,SAAS;QAChF,IAAI,CAAC;YACH,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC;QAC9D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,WAAW,CACxB,QAAgB,EAChB,MAA6F;IAE7F,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC/C,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAErC,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE;QAC1C,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,SAAS;QACT,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAE,iDAAiD;QACzD,WAAW,EAAE;YACX,OAAO,EAAE,mEAAmE;YAC5E,IAAI,EAAE,aAAa;SACpB;KACF,CAAC,CAAC;IACH,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAElC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7F,MAAM,MAAM,GAAG,MAAiC,CAAC;IAEjD,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpF,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACnE,MAAM,OAAO,GACX,KAAK,KAAK,SAAS;QACjB,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE;QACpC,CAAC,CAAC,SAAS,KAAK,SAAS;YACvB,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;YAC1F,CAAC,CAAC,SAAS,CAAC;IAClB,OAAO;QACL,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;QACzC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;QAC7C,GAAG,CAAC,UAAU,KAAK,SAAS;YAC1B,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,MAAM,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE;aAC1F,CAAC;KACP,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;AACxC,CAAC;AAED,sCAAsC;AACtC,SAAS,OAAO,CAAC,IAAiB;IAChC,KAAK,MAAM,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,uEAAuE;AACvE,SAAS,MAAM,CAAC,IAAiB,EAAE,GAAW;IAC5C,KAAK,MAAM,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;YAAE,SAAS;QACtC,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;QAC3E,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,GAAG,CAAC;YAAE,SAAS;QACjE,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3E,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,8DAA8D;AAC9D,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,GAAG,GAAG,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IAC9F,IAAI,GAAG,CAAC,MAAM,GAAG,kBAAkB;QAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,OAAO,EAAE,CAAC;IACtF,OAAO,GAAG,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC;AACtD,CAAC;AAED,SAAS,MAAM,CAAC,MAAc;IAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -1,45 +1,57 @@
|
|
|
1
1
|
import type { ContentStore } from '../content/store.ts';
|
|
2
2
|
import type { DocumentChange } from '../content/sync.ts';
|
|
3
|
+
import type { CitedPictureConfig } from './cited-picture.ts';
|
|
3
4
|
import type { HostLookup } from './public-address.ts';
|
|
4
5
|
import type { ReplyContext } from './reply-context.ts';
|
|
5
6
|
/**
|
|
6
|
-
* Where the fetched
|
|
7
|
-
*
|
|
8
|
-
* as the `replyContexts` global.
|
|
7
|
+
* Where the fetched contexts are kept, relative to the content directory: one
|
|
8
|
+
* JSON object keyed by the URL a post replies to, likes, reposts or bookmarks
|
|
9
|
+
* (decision-19). Eleventy reads it as the `replyContexts` global.
|
|
9
10
|
*/
|
|
10
11
|
export declare const REPLY_CONTEXTS_FILE = "_data/replyContexts.json";
|
|
12
|
+
export declare const CITED_SLUG_TIMEOUT_MS = 3000;
|
|
11
13
|
/** Where the service reports a target it could not read. */
|
|
12
14
|
export interface ReplyContextLogger {
|
|
13
15
|
warn(message: string): void;
|
|
14
16
|
}
|
|
15
17
|
/** What {@link createReplyContextService} needs. */
|
|
16
18
|
export interface CreateReplyContextServiceOptions {
|
|
17
|
-
/** The index, asked which targets are still
|
|
19
|
+
/** The index, asked which targets are still cited. */
|
|
18
20
|
readonly store: ContentStore;
|
|
19
|
-
/** Where the file
|
|
20
|
-
readonly
|
|
21
|
+
/** Where the file and the copied pictures live, and the limit a picture is held to. */
|
|
22
|
+
readonly config: CitedPictureConfig;
|
|
21
23
|
/** How host names are resolved before a target is fetched. */
|
|
22
24
|
readonly lookup: HostLookup;
|
|
23
25
|
/** Defaults to `console`. */
|
|
24
26
|
readonly logger?: ReplyContextLogger | undefined;
|
|
25
27
|
}
|
|
26
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Keeps the stored contexts in step with the posts that cite: a reply's
|
|
30
|
+
* `in-reply-to`, and a like's, repost's or bookmark's target (TASK-244).
|
|
31
|
+
*/
|
|
27
32
|
export interface ReplyContextService {
|
|
28
33
|
/**
|
|
29
|
-
* Consider one index change. A
|
|
30
|
-
* stored,
|
|
31
|
-
*
|
|
32
|
-
*
|
|
34
|
+
* Consider one index change. A target that is new to the post, or has
|
|
35
|
+
* nothing stored, is fetched; a target no post cites any more is forgotten.
|
|
36
|
+
* Returns at once: the work is queued, so a save never waits on a
|
|
37
|
+
* stranger's server.
|
|
33
38
|
*/
|
|
34
39
|
handle(change: DocumentChange): void;
|
|
35
40
|
/**
|
|
36
|
-
* Fetch every target a live post
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
+
* Fetch every target a live post cites that the file holds nothing for. Run
|
|
42
|
+
* when the site starts serving, because a scan of an index that is already
|
|
43
|
+
* up to date reports no change: a site upgraded to reply contexts, or one
|
|
44
|
+
* whose file was removed, would otherwise wait for each reply to be edited.
|
|
45
|
+
* Queued like {@link ReplyContextService.handle}.
|
|
41
46
|
*/
|
|
42
47
|
catchUp(): void;
|
|
48
|
+
/**
|
|
49
|
+
* The context a new post can be named after: the stored one, else one fetch
|
|
50
|
+
* within {@link CITED_SLUG_TIMEOUT_MS} that is stored, so the save's own
|
|
51
|
+
* {@link ReplyContextService.handle} does not fetch it again (TASK-250).
|
|
52
|
+
* `undefined` when the target could not be read in time.
|
|
53
|
+
*/
|
|
54
|
+
describe(target: string): Promise<ReplyContext | undefined>;
|
|
43
55
|
/** The stored context for a target, from the file. Never touches the network. */
|
|
44
56
|
read(target: string): ReplyContext | undefined;
|
|
45
57
|
/** Resolve once everything queued has finished, however it finished. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reply-contexts.d.ts","sourceRoot":"","sources":["../../src/webmention/reply-contexts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reply-contexts.d.ts","sourceRoot":"","sources":["../../src/webmention/reply-contexts.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,OAAO,KAAK,EAAE,kBAAkB,EAAiB,MAAM,oBAAoB,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,6BAA6B,CAAC;AAE9D,eAAO,MAAM,qBAAqB,OAAQ,CAAC;AAE3C,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,oDAAoD;AACpD,MAAM,WAAW,gCAAgC;IAC/C,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,6BAA6B;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CAClD;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACrC;;;;;;OAMG;IACH,OAAO,IAAI,IAAI,CAAC;IAChB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAAC;IAC5D,iFAAiF;IACjF,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC/C,wEAAwE;IACxE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,oDAAoD;AACpD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gCAAgC,GACxC,mBAAmB,CA+JrB"}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { citationsOf } from '../content/citation.js';
|
|
3
4
|
import { replyTarget } from '../content/post-type.js';
|
|
4
5
|
import { updateFileAtomically } from '../files/atomic.js';
|
|
6
|
+
import { copyCitedPicture, parseCitedPicture, sweepCitedPictures } from './cited-picture.js';
|
|
5
7
|
import { fetchReplyContext } from './reply-context.js';
|
|
6
8
|
/**
|
|
7
|
-
* Where the fetched
|
|
8
|
-
*
|
|
9
|
-
* as the `replyContexts` global.
|
|
9
|
+
* Where the fetched contexts are kept, relative to the content directory: one
|
|
10
|
+
* JSON object keyed by the URL a post replies to, likes, reposts or bookmarks
|
|
11
|
+
* (decision-19). Eleventy reads it as the `replyContexts` global.
|
|
10
12
|
*/
|
|
11
13
|
export const REPLY_CONTEXTS_FILE = '_data/replyContexts.json';
|
|
14
|
+
export const CITED_SLUG_TIMEOUT_MS = 3_000;
|
|
12
15
|
/** Build the reply context service for one site. */
|
|
13
16
|
export function createReplyContextService(options) {
|
|
14
|
-
const { store, lookup } = options;
|
|
17
|
+
const { store, lookup, config } = options;
|
|
15
18
|
const logger = options.logger ?? console;
|
|
16
|
-
const file = path.join(
|
|
19
|
+
const file = path.join(config.contentDir, ...REPLY_CONTEXTS_FILE.split('/'));
|
|
17
20
|
let cachedText;
|
|
18
21
|
let cached = {};
|
|
19
22
|
/** The whole file, parsed only when its bytes changed since the last read. */
|
|
@@ -44,22 +47,52 @@ export function createReplyContextService(options) {
|
|
|
44
47
|
return `${JSON.stringify(all, null, 2)}\n`;
|
|
45
48
|
});
|
|
46
49
|
}
|
|
50
|
+
async function withPicture(context, source) {
|
|
51
|
+
const picture = source === undefined ? undefined : await copyCitedPicture(source, { lookup, config });
|
|
52
|
+
if (source !== undefined && picture === undefined) {
|
|
53
|
+
logger.warn(`Could not copy ${source.url}, the picture of ${context.url}`);
|
|
54
|
+
}
|
|
55
|
+
return picture === undefined ? context : { ...context, picture };
|
|
56
|
+
}
|
|
57
|
+
async function sweep() {
|
|
58
|
+
const kept = new Set();
|
|
59
|
+
for (const context of Object.values(readAll())) {
|
|
60
|
+
if (context.picture !== undefined)
|
|
61
|
+
kept.add(context.picture.src);
|
|
62
|
+
}
|
|
63
|
+
await sweepCitedPictures(config, kept);
|
|
64
|
+
}
|
|
47
65
|
async function refresh(target) {
|
|
48
66
|
const fetched = await fetchReplyContext(target, { lookup });
|
|
49
67
|
if (!fetched.ok) {
|
|
50
|
-
logger.warn(`Could not read ${target} for a
|
|
68
|
+
logger.warn(`Could not read ${target} for a citation's context: ${fetched.reason}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const context = await withPicture(fetched.context, fetched.picture);
|
|
72
|
+
if (hasNoPictureOrWords(context))
|
|
73
|
+
return;
|
|
74
|
+
await write(target, context);
|
|
75
|
+
await sweep();
|
|
76
|
+
}
|
|
77
|
+
async function attachPicture(target, source) {
|
|
78
|
+
const held = readAll()[target];
|
|
79
|
+
if (held === undefined || held.picture !== undefined)
|
|
51
80
|
return;
|
|
81
|
+
const context = await withPicture(held, source);
|
|
82
|
+
if (context.picture !== undefined && readAll()[target] !== undefined) {
|
|
83
|
+
await write(target, context);
|
|
52
84
|
}
|
|
53
|
-
await write(target, fetched.context);
|
|
54
85
|
}
|
|
55
|
-
/** Forget a target, unless a post still replies to it. */
|
|
56
86
|
async function forget(target) {
|
|
57
87
|
if (!(target in readAll()))
|
|
58
88
|
return;
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
89
|
+
const stillCited = store.listAll().some((document) => targetsOf(document).includes(target));
|
|
90
|
+
if (stillCited)
|
|
91
|
+
return;
|
|
92
|
+
await write(target, undefined);
|
|
93
|
+
await sweep();
|
|
62
94
|
}
|
|
95
|
+
const describedForSave = new Set();
|
|
63
96
|
let chain = Promise.resolve();
|
|
64
97
|
function enqueue(task) {
|
|
65
98
|
chain = chain.then(task).catch((thrown) => {
|
|
@@ -68,30 +101,63 @@ export function createReplyContextService(options) {
|
|
|
68
101
|
}
|
|
69
102
|
return {
|
|
70
103
|
handle(change) {
|
|
71
|
-
const before =
|
|
72
|
-
const after =
|
|
104
|
+
const before = targetsOf(change.previous);
|
|
105
|
+
const after = targetsOf(change.next);
|
|
106
|
+
const held = readAll();
|
|
73
107
|
// A scan rebuilds the index from files the contexts file sits beside, so
|
|
74
108
|
// it only fetches what that file has never held; an edit fetches a
|
|
75
109
|
// target that changed as well.
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
110
|
+
for (const target of after) {
|
|
111
|
+
const changed = !before.includes(target) && change.origin !== 'scan';
|
|
112
|
+
if (describedForSave.delete(target) && target in held)
|
|
113
|
+
continue;
|
|
114
|
+
if (!(target in held) || changed)
|
|
115
|
+
enqueue(() => refresh(target));
|
|
116
|
+
}
|
|
117
|
+
for (const target of before) {
|
|
118
|
+
if (!after.includes(target))
|
|
119
|
+
enqueue(() => forget(target));
|
|
81
120
|
}
|
|
82
|
-
if (before !== undefined && before !== after)
|
|
83
|
-
enqueue(() => forget(before));
|
|
84
121
|
},
|
|
85
122
|
catchUp() {
|
|
86
123
|
const held = readAll();
|
|
87
124
|
const missing = new Set();
|
|
88
125
|
for (const document of store.listAll()) {
|
|
89
|
-
const target
|
|
90
|
-
|
|
91
|
-
|
|
126
|
+
for (const target of targetsOf(document)) {
|
|
127
|
+
if (!(target in held))
|
|
128
|
+
missing.add(target);
|
|
129
|
+
}
|
|
92
130
|
}
|
|
93
131
|
for (const target of missing)
|
|
94
132
|
enqueue(() => refresh(target));
|
|
133
|
+
enqueue(sweep);
|
|
134
|
+
},
|
|
135
|
+
async describe(target) {
|
|
136
|
+
const held = readAll()[target];
|
|
137
|
+
if (held !== undefined)
|
|
138
|
+
return held;
|
|
139
|
+
const deadline = Date.now() + CITED_SLUG_TIMEOUT_MS;
|
|
140
|
+
const fetched = await fetchReplyContext(target, { lookup, timeoutMs: CITED_SLUG_TIMEOUT_MS });
|
|
141
|
+
if (!fetched.ok)
|
|
142
|
+
return undefined;
|
|
143
|
+
const { picture } = fetched;
|
|
144
|
+
if (hasNoPictureOrWords(fetched.context)) {
|
|
145
|
+
const timeoutMs = deadline - Date.now();
|
|
146
|
+
const copied = picture === undefined || timeoutMs <= 0
|
|
147
|
+
? undefined
|
|
148
|
+
: await copyCitedPicture(picture, { lookup, config, timeoutMs });
|
|
149
|
+
if (copied === undefined)
|
|
150
|
+
return undefined;
|
|
151
|
+
const context = { ...fetched.context, picture: copied };
|
|
152
|
+
await write(target, context);
|
|
153
|
+
describedForSave.add(target);
|
|
154
|
+
return context;
|
|
155
|
+
}
|
|
156
|
+
await write(target, fetched.context);
|
|
157
|
+
describedForSave.add(target);
|
|
158
|
+
if (picture !== undefined)
|
|
159
|
+
enqueue(() => attachPicture(target, picture));
|
|
160
|
+
return fetched.context;
|
|
95
161
|
},
|
|
96
162
|
read(target) {
|
|
97
163
|
return readAll()[target];
|
|
@@ -101,8 +167,22 @@ export function createReplyContextService(options) {
|
|
|
101
167
|
},
|
|
102
168
|
};
|
|
103
169
|
}
|
|
104
|
-
function
|
|
105
|
-
return
|
|
170
|
+
function hasNoPictureOrWords(context) {
|
|
171
|
+
return (context.picture === undefined &&
|
|
172
|
+
context.name === undefined &&
|
|
173
|
+
context.text === undefined &&
|
|
174
|
+
context.author === undefined &&
|
|
175
|
+
context.site === undefined);
|
|
176
|
+
}
|
|
177
|
+
function targetsOf(document) {
|
|
178
|
+
if (document === undefined)
|
|
179
|
+
return [];
|
|
180
|
+
const answered = replyTarget(document);
|
|
181
|
+
const targets = [
|
|
182
|
+
...(answered === undefined ? [] : [answered]),
|
|
183
|
+
...citationsOf(document.extra).map((citation) => citation.url),
|
|
184
|
+
];
|
|
185
|
+
return [...new Set(targets)];
|
|
106
186
|
}
|
|
107
187
|
/**
|
|
108
188
|
* The file's entries, each checked, since a person may have edited it by hand.
|
|
@@ -125,6 +205,7 @@ function parseContexts(text) {
|
|
|
125
205
|
const author = isRecord(value['author']) ? value['author'] : undefined;
|
|
126
206
|
const authorName = stringOf(author?.['name']);
|
|
127
207
|
const authorUrl = webUrlOf(author?.['url']);
|
|
208
|
+
const picture = parseCitedPicture(value['picture']);
|
|
128
209
|
contexts[target] = {
|
|
129
210
|
url: target,
|
|
130
211
|
...optional('name', stringOf(value['name'])),
|
|
@@ -133,6 +214,8 @@ function parseContexts(text) {
|
|
|
133
214
|
? {}
|
|
134
215
|
: { author: { name: authorName, ...optional('url', authorUrl) } }),
|
|
135
216
|
...optional('published', stringOf(value['published'])),
|
|
217
|
+
...optional('site', stringOf(value['site'])),
|
|
218
|
+
...(picture === undefined ? {} : { picture }),
|
|
136
219
|
};
|
|
137
220
|
}
|
|
138
221
|
return contexts;
|