@geekity/cms 0.14.0 → 0.16.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 +270 -52
- package/admin/pages/documents/editor.njk +120 -0
- package/admin/pages/documents/syndication.njk +110 -0
- package/admin/pages/federation/followers.njk +7 -0
- package/admin/pages/media/library.njk +4 -3
- package/admin/pages/settings/general.njk +44 -0
- package/admin/static/admin.css +44 -0
- package/dist/admin/documents.d.ts +104 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +220 -55
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/enclosure-field.d.ts +91 -0
- package/dist/admin/enclosure-field.d.ts.map +1 -0
- package/dist/admin/enclosure-field.js +282 -0
- package/dist/admin/enclosure-field.js.map +1 -0
- package/dist/admin/federation.d.ts +15 -0
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +19 -0
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/media.d.ts +7 -2
- package/dist/admin/media.d.ts.map +1 -1
- package/dist/admin/media.js +4 -2
- package/dist/admin/media.js.map +1 -1
- package/dist/admin/menu.d.ts.map +1 -1
- package/dist/admin/menu.js +3 -0
- package/dist/admin/menu.js.map +1 -1
- package/dist/admin/photo-field.d.ts +39 -0
- package/dist/admin/photo-field.d.ts.map +1 -0
- package/dist/admin/photo-field.js +91 -0
- package/dist/admin/photo-field.js.map +1 -0
- package/dist/admin/preview.d.ts.map +1 -1
- package/dist/admin/preview.js +6 -1
- package/dist/admin/preview.js.map +1 -1
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +4 -0
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/settings-general.d.ts.map +1 -1
- package/dist/admin/settings-general.js +36 -2
- package/dist/admin/settings-general.js.map +1 -1
- package/dist/admin/settings-page.d.ts.map +1 -1
- package/dist/admin/settings-page.js +1 -2
- package/dist/admin/settings-page.js.map +1 -1
- package/dist/admin/settings.d.ts +26 -0
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +88 -2
- package/dist/admin/settings.js.map +1 -1
- package/dist/admin/syndication-targets.d.ts +57 -0
- package/dist/admin/syndication-targets.d.ts.map +1 -0
- package/dist/admin/syndication-targets.js +266 -0
- package/dist/admin/syndication-targets.js.map +1 -0
- 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 +13 -2
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +23 -5
- package/dist/admin/uploads.js.map +1 -1
- package/dist/config.d.ts +17 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -7
- package/dist/config.js.map +1 -1
- package/dist/content/citation.d.ts +36 -0
- package/dist/content/citation.d.ts.map +1 -0
- package/dist/content/citation.js +31 -0
- package/dist/content/citation.js.map +1 -0
- package/dist/content/enclosure.d.ts +69 -0
- package/dist/content/enclosure.d.ts.map +1 -0
- package/dist/content/enclosure.js +165 -0
- package/dist/content/enclosure.js.map +1 -0
- package/dist/content/index.d.ts +5 -1
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +2 -0
- package/dist/content/index.js.map +1 -1
- package/dist/content/media.d.ts +15 -2
- package/dist/content/media.d.ts.map +1 -1
- package/dist/content/media.js +92 -9
- package/dist/content/media.js.map +1 -1
- package/dist/content/photo.d.ts +45 -0
- package/dist/content/photo.d.ts.map +1 -0
- package/dist/content/photo.js +55 -0
- package/dist/content/photo.js.map +1 -0
- package/dist/content/post-type.d.ts +20 -8
- package/dist/content/post-type.d.ts.map +1 -1
- package/dist/content/post-type.js +17 -0
- package/dist/content/post-type.js.map +1 -1
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +93 -10
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/citations.d.ts +38 -0
- package/dist/federation/citations.d.ts.map +1 -0
- package/dist/federation/citations.js +98 -0
- package/dist/federation/citations.js.map +1 -0
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +79 -31
- package/dist/federation/delivery.js.map +1 -1
- package/dist/images/alt-text.d.ts +6 -0
- package/dist/images/alt-text.d.ts.map +1 -1
- package/dist/images/alt-text.js +10 -0
- package/dist/images/alt-text.js.map +1 -1
- package/dist/images/icons.d.ts +5 -0
- package/dist/images/icons.d.ts.map +1 -1
- package/dist/images/icons.js +13 -8
- package/dist/images/icons.js.map +1 -1
- package/dist/images/paths.d.ts +1 -1
- package/dist/images/paths.d.ts.map +1 -1
- package/dist/images/paths.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -4
- package/dist/index.js.map +1 -1
- package/dist/indieauth/bearer.d.ts +6 -1
- package/dist/indieauth/bearer.d.ts.map +1 -1
- package/dist/indieauth/bearer.js +39 -12
- package/dist/indieauth/bearer.js.map +1 -1
- package/dist/indieauth/discovery.d.ts +9 -4
- package/dist/indieauth/discovery.d.ts.map +1 -1
- package/dist/indieauth/discovery.js +18 -7
- package/dist/indieauth/discovery.js.map +1 -1
- package/dist/micropub/create.d.ts +56 -0
- package/dist/micropub/create.d.ts.map +1 -0
- package/dist/micropub/create.js +200 -0
- package/dist/micropub/create.js.map +1 -0
- package/dist/micropub/endpoint.d.ts +21 -0
- package/dist/micropub/endpoint.d.ts.map +1 -0
- package/dist/micropub/endpoint.js +353 -0
- package/dist/micropub/endpoint.js.map +1 -0
- package/dist/micropub/media.d.ts +14 -0
- package/dist/micropub/media.d.ts.map +1 -0
- package/dist/micropub/media.js +93 -0
- package/dist/micropub/media.js.map +1 -0
- package/dist/micropub/update.d.ts +44 -0
- package/dist/micropub/update.d.ts.map +1 -0
- package/dist/micropub/update.js +133 -0
- package/dist/micropub/update.js.map +1 -0
- package/dist/web/assets.d.ts +14 -0
- package/dist/web/assets.d.ts.map +1 -1
- package/dist/web/assets.js +56 -1
- package/dist/web/assets.js.map +1 -1
- package/dist/web/context.d.ts +68 -3
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +56 -1
- package/dist/web/context.js.map +1 -1
- package/dist/web/feed-atom.d.ts.map +1 -1
- package/dist/web/feed-atom.js +20 -0
- package/dist/web/feed-atom.js.map +1 -1
- package/dist/web/feed-item.d.ts +19 -0
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +22 -0
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-json.d.ts +13 -0
- package/dist/web/feed-json.d.ts.map +1 -1
- package/dist/web/feed-json.js +15 -0
- package/dist/web/feed-json.js.map +1 -1
- package/dist/web/feed-rss.d.ts.map +1 -1
- package/dist/web/feed-rss.js +59 -2
- package/dist/web/feed-rss.js.map +1 -1
- package/dist/web/feed-xml.d.ts +7 -1
- package/dist/web/feed-xml.d.ts.map +1 -1
- package/dist/web/feed-xml.js +8 -2
- package/dist/web/feed-xml.js.map +1 -1
- package/dist/web/feeds.d.ts +1 -1
- package/dist/web/feeds.d.ts.map +1 -1
- package/dist/web/feeds.js +6 -0
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/index.d.ts +4 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +2 -0
- package/dist/web/index.js.map +1 -1
- package/dist/web/license.d.ts +94 -0
- package/dist/web/license.d.ts.map +1 -0
- package/dist/web/license.js +116 -0
- package/dist/web/license.js.map +1 -0
- package/dist/web/oembed.d.ts.map +1 -1
- package/dist/web/oembed.js +1 -2
- package/dist/web/oembed.js.map +1 -1
- package/dist/web/render.d.ts +15 -0
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +27 -2
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +10 -6
- package/dist/web/routes.js.map +1 -1
- package/dist/web/share-image.d.ts +7 -7
- package/dist/web/share-image.d.ts.map +1 -1
- package/dist/web/share-image.js +8 -7
- package/dist/web/share-image.js.map +1 -1
- package/dist/webmention/index.d.ts +2 -0
- package/dist/webmention/index.d.ts.map +1 -1
- package/dist/webmention/index.js +1 -0
- package/dist/webmention/index.js.map +1 -1
- package/dist/webmention/service.d.ts.map +1 -1
- package/dist/webmention/service.js +96 -18
- package/dist/webmention/service.js.map +1 -1
- package/dist/webmention/syndication.d.ts +135 -0
- package/dist/webmention/syndication.d.ts.map +1 -0
- package/dist/webmention/syndication.js +337 -0
- package/dist/webmention/syndication.js.map +1 -0
- package/package.json +1 -1
- package/themes/default/README.md +121 -37
- package/themes/default/layouts/base.njk +3 -4
- package/themes/default/layouts/post.njk +23 -6
- package/themes/default/partials/citations.njk +17 -0
- package/themes/default/partials/jsonld.njk +15 -6
- package/themes/default/partials/photos.njk +10 -0
- package/themes/default/partials/post-list.njk +7 -0
- package/themes/default/partials/recording.njk +41 -0
- package/themes/default/src/style.css +25 -0
- package/themes/default/static/style.css +41 -0
package/dist/content/media.js
CHANGED
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
function ascii(value) {
|
|
16
16
|
return [...value].map((character) => character.charCodeAt(0));
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* An ISO base media file — MP4, M4A, M4V — whatever its brand: a box length,
|
|
20
|
+
* then `ftyp`.
|
|
21
|
+
*/
|
|
22
|
+
const ISO_BASE_MEDIA = [{ offset: 4, bytes: ascii('ftyp') }];
|
|
23
|
+
/** An Ogg page, whatever codec is inside it. */
|
|
24
|
+
const OGG = [{ offset: 0, bytes: ascii('OggS') }];
|
|
18
25
|
/**
|
|
19
26
|
* Every extension the upload endpoint can accept, and what it takes to be one.
|
|
20
27
|
*
|
|
@@ -29,7 +36,7 @@ export const UPLOAD_MEDIA_TYPES = new Map([
|
|
|
29
36
|
'.png',
|
|
30
37
|
{
|
|
31
38
|
declared: ['image/png'],
|
|
32
|
-
|
|
39
|
+
kind: 'image',
|
|
33
40
|
signatures: [[{ offset: 0, bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] }]],
|
|
34
41
|
},
|
|
35
42
|
],
|
|
@@ -37,7 +44,7 @@ export const UPLOAD_MEDIA_TYPES = new Map([
|
|
|
37
44
|
'.jpg',
|
|
38
45
|
{
|
|
39
46
|
declared: ['image/jpeg', 'image/jpg'],
|
|
40
|
-
|
|
47
|
+
kind: 'image',
|
|
41
48
|
signatures: [[{ offset: 0, bytes: [0xff, 0xd8, 0xff] }]],
|
|
42
49
|
},
|
|
43
50
|
],
|
|
@@ -45,7 +52,7 @@ export const UPLOAD_MEDIA_TYPES = new Map([
|
|
|
45
52
|
'.jpeg',
|
|
46
53
|
{
|
|
47
54
|
declared: ['image/jpeg', 'image/jpg'],
|
|
48
|
-
|
|
55
|
+
kind: 'image',
|
|
49
56
|
signatures: [[{ offset: 0, bytes: [0xff, 0xd8, 0xff] }]],
|
|
50
57
|
},
|
|
51
58
|
],
|
|
@@ -53,7 +60,7 @@ export const UPLOAD_MEDIA_TYPES = new Map([
|
|
|
53
60
|
'.gif',
|
|
54
61
|
{
|
|
55
62
|
declared: ['image/gif'],
|
|
56
|
-
|
|
63
|
+
kind: 'image',
|
|
57
64
|
signatures: [
|
|
58
65
|
[{ offset: 0, bytes: ascii('GIF87a') }],
|
|
59
66
|
[{ offset: 0, bytes: ascii('GIF89a') }],
|
|
@@ -64,7 +71,7 @@ export const UPLOAD_MEDIA_TYPES = new Map([
|
|
|
64
71
|
'.webp',
|
|
65
72
|
{
|
|
66
73
|
declared: ['image/webp'],
|
|
67
|
-
|
|
74
|
+
kind: 'image',
|
|
68
75
|
// A WebP is a RIFF container whose form type is WEBP, so both halves of
|
|
69
76
|
// the header have to be there.
|
|
70
77
|
signatures: [
|
|
@@ -79,7 +86,7 @@ export const UPLOAD_MEDIA_TYPES = new Map([
|
|
|
79
86
|
'.avif',
|
|
80
87
|
{
|
|
81
88
|
declared: ['image/avif'],
|
|
82
|
-
|
|
89
|
+
kind: 'image',
|
|
83
90
|
// An ISO base media file: a length, then `ftyp`, then the brand.
|
|
84
91
|
signatures: [
|
|
85
92
|
[
|
|
@@ -97,19 +104,87 @@ export const UPLOAD_MEDIA_TYPES = new Map([
|
|
|
97
104
|
'.pdf',
|
|
98
105
|
{
|
|
99
106
|
declared: ['application/pdf'],
|
|
100
|
-
|
|
107
|
+
kind: 'document',
|
|
101
108
|
signatures: [[{ offset: 0, bytes: ascii('%PDF-') }]],
|
|
102
109
|
},
|
|
103
110
|
],
|
|
104
111
|
// The two text formats have no header. Nothing is sniffed, and nothing needs
|
|
105
112
|
// to be: they are served as text, and `application/octet-stream` is allowed
|
|
106
113
|
// because that is what a browser sends for a file type it does not know.
|
|
107
|
-
['.txt', { declared: ['text/plain', 'application/octet-stream'],
|
|
114
|
+
['.txt', { declared: ['text/plain', 'application/octet-stream'], kind: 'document' }],
|
|
108
115
|
[
|
|
109
116
|
'.md',
|
|
110
117
|
{
|
|
111
118
|
declared: ['text/markdown', 'text/x-markdown', 'text/plain', 'application/octet-stream'],
|
|
112
|
-
|
|
119
|
+
kind: 'document',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
// WebVTT opens with its name, after a byte order mark when the editor that
|
|
123
|
+
// wrote it added one. SubRip has no header, so it is taken as text like the
|
|
124
|
+
// two formats above.
|
|
125
|
+
[
|
|
126
|
+
'.vtt',
|
|
127
|
+
{
|
|
128
|
+
declared: ['text/vtt', 'text/plain', 'application/octet-stream'],
|
|
129
|
+
kind: 'document',
|
|
130
|
+
signatures: [
|
|
131
|
+
[{ offset: 0, bytes: ascii('WEBVTT') }],
|
|
132
|
+
[{ offset: 0, bytes: [0xef, 0xbb, 0xbf, ...ascii('WEBVTT')] }],
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
[
|
|
137
|
+
'.srt',
|
|
138
|
+
{
|
|
139
|
+
declared: ['application/x-subrip', 'text/srt', 'text/plain', 'application/octet-stream'],
|
|
140
|
+
kind: 'document',
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
[
|
|
144
|
+
'.mp3',
|
|
145
|
+
{
|
|
146
|
+
declared: ['audio/mpeg', 'audio/mp3'],
|
|
147
|
+
kind: 'audio',
|
|
148
|
+
// An ID3 tag, or straight into an MPEG audio frame: eleven sync bits,
|
|
149
|
+
// then MPEG-1 or MPEG-2 layer III, with or without a CRC.
|
|
150
|
+
signatures: [
|
|
151
|
+
[{ offset: 0, bytes: ascii('ID3') }],
|
|
152
|
+
[{ offset: 0, bytes: [0xff, 0xfb] }],
|
|
153
|
+
[{ offset: 0, bytes: [0xff, 0xfa] }],
|
|
154
|
+
[{ offset: 0, bytes: [0xff, 0xf3] }],
|
|
155
|
+
[{ offset: 0, bytes: [0xff, 0xf2] }],
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
[
|
|
160
|
+
'.m4a',
|
|
161
|
+
{
|
|
162
|
+
declared: ['audio/mp4', 'audio/x-m4a', 'audio/m4a'],
|
|
163
|
+
kind: 'audio',
|
|
164
|
+
signatures: [ISO_BASE_MEDIA],
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
[
|
|
168
|
+
'.aac',
|
|
169
|
+
{
|
|
170
|
+
declared: ['audio/aac', 'audio/x-aac', 'audio/aacp'],
|
|
171
|
+
kind: 'audio',
|
|
172
|
+
// A raw ADTS stream: twelve sync bits, MPEG-4 or MPEG-2, no CRC.
|
|
173
|
+
signatures: [[{ offset: 0, bytes: [0xff, 0xf1] }], [{ offset: 0, bytes: [0xff, 0xf9] }]],
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
['.ogg', { declared: ['audio/ogg', 'application/ogg'], kind: 'audio', signatures: [OGG] }],
|
|
177
|
+
['.oga', { declared: ['audio/ogg', 'application/ogg'], kind: 'audio', signatures: [OGG] }],
|
|
178
|
+
['.opus', { declared: ['audio/ogg', 'audio/opus'], kind: 'audio', signatures: [OGG] }],
|
|
179
|
+
['.mp4', { declared: ['video/mp4'], kind: 'video', signatures: [ISO_BASE_MEDIA] }],
|
|
180
|
+
['.m4v', { declared: ['video/mp4', 'video/x-m4v'], kind: 'video', signatures: [ISO_BASE_MEDIA] }],
|
|
181
|
+
[
|
|
182
|
+
'.webm',
|
|
183
|
+
{
|
|
184
|
+
declared: ['video/webm', 'audio/webm'],
|
|
185
|
+
kind: 'video',
|
|
186
|
+
// The EBML header every Matroska file, WebM included, opens with.
|
|
187
|
+
signatures: [[{ offset: 0, bytes: [0x1a, 0x45, 0xdf, 0xa3] }]],
|
|
113
188
|
},
|
|
114
189
|
],
|
|
115
190
|
]);
|
|
@@ -127,6 +202,14 @@ export function normalizeUploadType(value) {
|
|
|
127
202
|
return '';
|
|
128
203
|
return trimmed.startsWith('.') ? trimmed : `.${trimmed}`;
|
|
129
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* The media type a feed or an attachment announces for this kind of file: the
|
|
207
|
+
* first one {@link UploadMediaType.declared} lists, which is the registered
|
|
208
|
+
* name where a browser may also send an older one.
|
|
209
|
+
*/
|
|
210
|
+
export function canonicalType(media) {
|
|
211
|
+
return media.declared[0];
|
|
212
|
+
}
|
|
130
213
|
/**
|
|
131
214
|
* Whether the first bytes of a file are the ones its extension implies.
|
|
132
215
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../src/content/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../src/content/media.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A post's photos (TASK-166): what Micropub's `photo` property carries, each
|
|
3
|
+
* one an address with optional alt text.
|
|
4
|
+
*
|
|
5
|
+
* ```yaml
|
|
6
|
+
* photo:
|
|
7
|
+
* - url: /uploads/2026/10/beach.jpg
|
|
8
|
+
* alt: Waves breaking at dusk
|
|
9
|
+
* - url: https://cdn.example.com/dog.jpg
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Like `enclosure`, it stays in {@link Document.extra}: {@link photosOf} is the
|
|
13
|
+
* one reading of it, and the theme, the federation and Post Type Discovery
|
|
14
|
+
* read only what it returns.
|
|
15
|
+
*/
|
|
16
|
+
import type { AltTextLibrary } from '../images/alt-text.ts';
|
|
17
|
+
/** The front matter key the photos are under, Micropub's property name. */
|
|
18
|
+
export declare const PHOTO_FRONT_MATTER_KEY = "photo";
|
|
19
|
+
/** One photo, read from the front matter. */
|
|
20
|
+
export interface Photo {
|
|
21
|
+
/** An upload's site-relative URL, or an absolute `http` or `https` one. */
|
|
22
|
+
url: string;
|
|
23
|
+
/** What the post says the photo shows. Absent leaves it to the media library. */
|
|
24
|
+
alt?: string | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The photos a post's front matter lists, in its order.
|
|
28
|
+
*
|
|
29
|
+
* A bare address reads as a photo with no alt text, and a single photo need
|
|
30
|
+
* not be in a list. An entry whose address is neither an upload nor a web
|
|
31
|
+
* address is dropped on its own, so one bad line costs that photo only.
|
|
32
|
+
*/
|
|
33
|
+
export declare function photosOf(extra: Readonly<Record<string, unknown>>): Photo[];
|
|
34
|
+
/** Whether an address is one a photo can have: an upload or a web address. */
|
|
35
|
+
export declare function isPhotoUrl(value: string): boolean;
|
|
36
|
+
/** The photos as the front matter spells them: no alt that nobody gave. */
|
|
37
|
+
export declare function photoFrontMatter(photos: readonly Photo[]): Record<string, string>[];
|
|
38
|
+
/**
|
|
39
|
+
* The alt text a photo is shown with: its own, else what the media library
|
|
40
|
+
* says about the upload (TASK-141), which is empty for a decorative one. So a
|
|
41
|
+
* library picture is described once, in the library, rather than in every
|
|
42
|
+
* post that shows it. `undefined` when nobody has described it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function photoAlt(photo: Photo, library: AltTextLibrary): string | undefined;
|
|
45
|
+
//# sourceMappingURL=photo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"photo.d.ts","sourceRoot":"","sources":["../../src/content/photo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,2EAA2E;AAC3E,eAAO,MAAM,sBAAsB,UAAU,CAAC;AAE9C,6CAA6C;AAC7C,MAAM,WAAW,KAAK;IACpB,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,KAAK,EAAE,CAI1E;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAInF;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAOlF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { UPLOAD_ASSET_PREFIX } from '../web/assets.js';
|
|
2
|
+
import { isUploadUrl, isWebUrl } from './enclosure.js';
|
|
3
|
+
/** The front matter key the photos are under, Micropub's property name. */
|
|
4
|
+
export const PHOTO_FRONT_MATTER_KEY = 'photo';
|
|
5
|
+
/**
|
|
6
|
+
* The photos a post's front matter lists, in its order.
|
|
7
|
+
*
|
|
8
|
+
* A bare address reads as a photo with no alt text, and a single photo need
|
|
9
|
+
* not be in a list. An entry whose address is neither an upload nor a web
|
|
10
|
+
* address is dropped on its own, so one bad line costs that photo only.
|
|
11
|
+
*/
|
|
12
|
+
export function photosOf(extra) {
|
|
13
|
+
const raw = extra[PHOTO_FRONT_MATTER_KEY];
|
|
14
|
+
if (raw === undefined)
|
|
15
|
+
return [];
|
|
16
|
+
return (Array.isArray(raw) ? raw : [raw]).flatMap((entry) => photoOf(entry) ?? []);
|
|
17
|
+
}
|
|
18
|
+
/** Whether an address is one a photo can have: an upload or a web address. */
|
|
19
|
+
export function isPhotoUrl(value) {
|
|
20
|
+
return isUploadUrl(value) || isWebUrl(value);
|
|
21
|
+
}
|
|
22
|
+
/** The photos as the front matter spells them: no alt that nobody gave. */
|
|
23
|
+
export function photoFrontMatter(photos) {
|
|
24
|
+
return photos.map((photo) => photo.alt === undefined ? { url: photo.url } : { url: photo.url, alt: photo.alt });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The alt text a photo is shown with: its own, else what the media library
|
|
28
|
+
* says about the upload (TASK-141), which is empty for a decorative one. So a
|
|
29
|
+
* library picture is described once, in the library, rather than in every
|
|
30
|
+
* post that shows it. `undefined` when nobody has described it.
|
|
31
|
+
*/
|
|
32
|
+
export function photoAlt(photo, library) {
|
|
33
|
+
if (photo.alt !== undefined)
|
|
34
|
+
return photo.alt;
|
|
35
|
+
const described = isUploadUrl(photo.url)
|
|
36
|
+
? library.get(photo.url.slice(UPLOAD_ASSET_PREFIX.length))
|
|
37
|
+
: undefined;
|
|
38
|
+
if (described === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
return described.kind === 'described' ? described.text : '';
|
|
41
|
+
}
|
|
42
|
+
function photoOf(value) {
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
const url = value.trim();
|
|
45
|
+
return isPhotoUrl(url) ? { url } : undefined;
|
|
46
|
+
}
|
|
47
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
48
|
+
return undefined;
|
|
49
|
+
const { url, alt } = value;
|
|
50
|
+
if (typeof url !== 'string' || !isPhotoUrl(url.trim()))
|
|
51
|
+
return undefined;
|
|
52
|
+
const text = typeof alt === 'string' ? alt.trim() : '';
|
|
53
|
+
return text === '' ? { url: url.trim() } : { url: url.trim(), alt: text };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=photo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"photo.js","sourceRoot":"","sources":["../../src/content/photo.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,20 +4,32 @@ import type { Document } from './document.ts';
|
|
|
4
4
|
* ptd.spec.indieweb.org): what kind of post a post is, inferred from its own
|
|
5
5
|
* properties rather than declared by its author.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* order is event, rsvp, repost, like, reply, video,
|
|
9
|
-
* the order matters because the first branch that
|
|
10
|
-
* a photo is a reply. Each new type is a check in
|
|
11
|
-
* that order. granary's `mf2util` diverges from
|
|
12
|
-
* of repost and like and having no video
|
|
7
|
+
* Repost, like, reply, photo and the note/article tail of the algorithm are
|
|
8
|
+
* here. The spec's full order is event, rsvp, repost, like, reply, video,
|
|
9
|
+
* photo, then the tail, and the order matters because the first branch that
|
|
10
|
+
* matches wins: a reply with a photo is a reply. Each new type is a check in
|
|
11
|
+
* {@link discoverPostType} in that order. granary's `mf2util` diverges from
|
|
12
|
+
* the spec, putting reply ahead of repost and like and having no video
|
|
13
|
+
* branch; this follows the spec.
|
|
14
|
+
*
|
|
15
|
+
* Bookmark is an IndieWeb extension the spec lists only as under
|
|
16
|
+
* consideration, so the spec types a bookmark as a note or an article. It
|
|
17
|
+
* sits after photo, just ahead of the tail (TASK-169): every post the spec
|
|
18
|
+
* types as something else keeps that type, and a bookmark claims only what
|
|
19
|
+
* the spec would have called a note or an article.
|
|
13
20
|
*/
|
|
14
|
-
export type PostType = 'reply' | 'note' | 'article';
|
|
21
|
+
export type PostType = 'repost' | 'like' | 'reply' | 'photo' | 'bookmark' | 'note' | 'article';
|
|
15
22
|
/** The mf2 properties the algorithm reads, each as its plain-text value. */
|
|
16
23
|
export interface PostProperties {
|
|
17
24
|
name?: string | undefined;
|
|
18
25
|
content?: string | undefined;
|
|
19
26
|
summary?: string | undefined;
|
|
20
27
|
'in-reply-to'?: string | undefined;
|
|
28
|
+
/** Each photo's address. */
|
|
29
|
+
photo?: readonly string[] | undefined;
|
|
30
|
+
'repost-of'?: string | undefined;
|
|
31
|
+
'like-of'?: string | undefined;
|
|
32
|
+
'bookmark-of'?: string | undefined;
|
|
21
33
|
}
|
|
22
34
|
/** The type of a post with these properties. */
|
|
23
35
|
export declare function discoverPostType(properties: PostProperties): PostType;
|
|
@@ -38,7 +50,7 @@ export declare function isNamed(document: PostDocument): boolean;
|
|
|
38
50
|
*/
|
|
39
51
|
export declare function replyTarget(document: Pick<Document, 'inReplyTo'>): string | undefined;
|
|
40
52
|
/** The parts of a document Post Type Discovery reads. */
|
|
41
|
-
type PostDocument = Pick<Document, 'title' | 'html' | 'description' | 'inReplyTo'>;
|
|
53
|
+
type PostDocument = Pick<Document, 'title' | 'html' | 'description' | 'inReplyTo' | 'extra'>;
|
|
42
54
|
/**
|
|
43
55
|
* The words a link to a document says: its title, or for an untitled post the
|
|
44
56
|
* first words of its text, so a note is never an empty link.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-type.d.ts","sourceRoot":"","sources":["../../src/content/post-type.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"post-type.d.ts","sourceRoot":"","sources":["../../src/content/post-type.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAI9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAE/F,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,4BAA4B;IAC5B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED,gDAAgD;AAChD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,cAAc,GAAG,QAAQ,CAOrE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,GAAG,QAAQ,CAE3D;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAEvD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,MAAM,GAAG,SAAS,CAErF;AAED,yDAAyD;AACzD,KAAK,YAAY,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,OAAO,CAAC,CAAC;AAuB7F;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,aAAa,CAAC,GAAG,MAAM,CAQ5F"}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
import { citationsOf } from './citation.js';
|
|
2
|
+
import { photosOf } from './photo.js';
|
|
1
3
|
import { htmlToText } from './search.js';
|
|
2
4
|
/** The type of a post with these properties. */
|
|
3
5
|
export function discoverPostType(properties) {
|
|
6
|
+
if (validUrl(properties['repost-of']) !== undefined)
|
|
7
|
+
return 'repost';
|
|
8
|
+
if (validUrl(properties['like-of']) !== undefined)
|
|
9
|
+
return 'like';
|
|
4
10
|
if (validUrl(properties['in-reply-to']) !== undefined)
|
|
5
11
|
return 'reply';
|
|
12
|
+
if ((properties.photo ?? []).length > 0)
|
|
13
|
+
return 'photo';
|
|
14
|
+
if (validUrl(properties['bookmark-of']) !== undefined)
|
|
15
|
+
return 'bookmark';
|
|
6
16
|
return isNamedPost(properties) ? 'article' : 'note';
|
|
7
17
|
}
|
|
8
18
|
/**
|
|
@@ -33,6 +43,13 @@ function propertiesOf(document) {
|
|
|
33
43
|
content: htmlToText(document.html),
|
|
34
44
|
summary: document.description,
|
|
35
45
|
'in-reply-to': document.inReplyTo,
|
|
46
|
+
// Only addresses {@link photosOf} accepts, the spec's "valid URL" for a
|
|
47
|
+
// file whose uploads are site-relative.
|
|
48
|
+
photo: photosOf(document.extra).map((photo) => photo.url),
|
|
49
|
+
...Object.fromEntries(citationsOf(document.extra).map(({ property, url }) => [
|
|
50
|
+
property,
|
|
51
|
+
url,
|
|
52
|
+
])),
|
|
36
53
|
};
|
|
37
54
|
}
|
|
38
55
|
/** How many words of an untitled post's text {@link postLabel} keeps. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-type.js","sourceRoot":"","sources":["../../src/content/post-type.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"post-type.js","sourceRoot":"","sources":["../../src/content/post-type.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"article.d.ts","sourceRoot":"","sources":["../../src/federation/article.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACL,GAAG,EACH,OAAO,
|
|
1
|
+
{"version":3,"file":"article.d.ts","sourceRoot":"","sources":["../../src/federation/article.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACL,GAAG,EACH,OAAO,EAEP,MAAM,EACN,MAAM,EAMN,IAAI,EAEJ,MAAM,EAGN,MAAM,EAEP,MAAM,eAAe,CAAC;AAKvB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAIjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAMvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAWxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAG7D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,EACvC,QAAQ,EAAE,QAAQ,GACjB,IAAI,GAAG,SAAS,CAGlB;AAwBD,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAEjD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAEvF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,QAAQ,GAAG,SAAS,CAOtB;AAkDD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,EACvC,QAAQ,EAAE,QAAQ,GACjB,OAAO,GAAG,IAAI,CA4DhB;AAgJD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,EACvC,QAAQ,EAAE,QAAQ,GACjB,MAAM,CAYR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,EACvC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,GAAE,MAA6B,GACtC,MAAM,CAYR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,EACvC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,GACd,MAAM,CAeR;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,EACvC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,MAAM,GACf,GAAG,GAAG,MAAM,CAkBd;AAED,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;AAExC;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAEhG;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC,OAAO,GAAG,SAAS,CAOjF"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { Add, Article, Create, Delete, Hashtag, Image, InteractionPolicy, InteractionRule, LanguageString, Note, PUBLIC_COLLECTION, Remove, Source, Tombstone, Update, } from '@fedify/vocab';
|
|
1
|
+
import { Add, Article, Audio, Create, Delete, Hashtag, Image, InteractionPolicy, InteractionRule, LanguageString, Note, PUBLIC_COLLECTION, Remove, Source, Tombstone, Update, Video, } from '@fedify/vocab';
|
|
2
2
|
import { Temporal as TemporalPolyfill } from '@js-temporal/polyfill';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { listUsers, primaryUser } from '../admin/accounts.js';
|
|
5
5
|
import { readSiteSettings, taxonomyBasesFromSettings } from '../admin/settings.js';
|
|
6
|
-
import {
|
|
6
|
+
import { citationsOf } from '../content/citation.js';
|
|
7
|
+
import { enclosureOf, isUploadUrl, playsAsVideo } from '../content/enclosure.js';
|
|
8
|
+
import { photoAlt, photosOf } from '../content/photo.js';
|
|
9
|
+
import { canonicalType, UPLOAD_MEDIA_TYPES } from '../content/media.js';
|
|
7
10
|
import { readAltTexts } from '../images/alt-text.js';
|
|
8
11
|
import { imagesIn } from '../images/markup.js';
|
|
9
|
-
import { postTypeOf, replyTarget } from '../content/post-type.js';
|
|
12
|
+
import { postLabel, postTypeOf, replyTarget } from '../content/post-type.js';
|
|
10
13
|
import { htmlToText } from '../content/search.js';
|
|
11
14
|
import { userForAuthor } from '../web/authors.js';
|
|
12
15
|
import { isPublicDocument, permalinkOfObjectId, postObjectId } from '../web/documents.js';
|
|
@@ -85,10 +88,17 @@ const OBJECT_TYPES = { Note, Article };
|
|
|
85
88
|
/**
|
|
86
89
|
* The object type each discovered post type federates as: the rows of Post
|
|
87
90
|
* Type Discovery's own AS2 mapping (section 6) that this site has post types
|
|
88
|
-
* for. A reply is a `Note` even with a title of its own (decision-18)
|
|
91
|
+
* for. A reply is a `Note` even with a title of its own (decision-18), and so
|
|
92
|
+
* is a photo post, whose photos are its attachments (TASK-166).
|
|
89
93
|
*/
|
|
90
94
|
const OBJECT_TYPE_OF = {
|
|
95
|
+
// A like or a repost of a fediverse object goes as a `Like` or an
|
|
96
|
+
// `Announce` instead (decision-28); this is the object its permalink serves.
|
|
97
|
+
repost: 'Note',
|
|
98
|
+
like: 'Note',
|
|
99
|
+
bookmark: 'Note',
|
|
91
100
|
reply: 'Note',
|
|
101
|
+
photo: 'Note',
|
|
92
102
|
note: 'Note',
|
|
93
103
|
article: 'Article',
|
|
94
104
|
};
|
|
@@ -165,7 +175,11 @@ export function postObject(context, document) {
|
|
|
165
175
|
// post that federates is addressed to Public, so anybody may quote it, and
|
|
166
176
|
// the inbox approves each QuoteRequest on the same rule (TASK-125).
|
|
167
177
|
interactionPolicy: QUOTABLE_BY_ANYONE,
|
|
168
|
-
attachments:
|
|
178
|
+
attachments: [
|
|
179
|
+
...recordingAttachment(document, baseUrl),
|
|
180
|
+
...photoAttachments(document, context.data.config),
|
|
181
|
+
...imageAttachments(document, context.data.config),
|
|
182
|
+
],
|
|
169
183
|
// Both taxonomies become hashtags: a relay or a search that keys on a
|
|
170
184
|
// hashtag has no reason to care which of the two a term came from, and
|
|
171
185
|
// each one points at the archive the site serves for it.
|
|
@@ -175,14 +189,17 @@ export function postObject(context, document) {
|
|
|
175
189
|
],
|
|
176
190
|
};
|
|
177
191
|
if (postObjectType(document) === 'Note') {
|
|
178
|
-
return new Note({
|
|
192
|
+
return new Note({
|
|
193
|
+
...common,
|
|
194
|
+
contents: inLanguage(citing(document) + noteContent(document), language),
|
|
195
|
+
});
|
|
179
196
|
}
|
|
180
197
|
const summary = feedExcerpt(document);
|
|
181
198
|
return new Article({
|
|
182
199
|
...common,
|
|
183
200
|
name: document.title === '' ? null : document.title,
|
|
184
201
|
summaries: summary === '' ? [] : inLanguage(summary, language),
|
|
185
|
-
contents: inLanguage(document.html, language),
|
|
202
|
+
contents: inLanguage(citing(document) + document.html, language),
|
|
186
203
|
});
|
|
187
204
|
}
|
|
188
205
|
/**
|
|
@@ -193,6 +210,53 @@ export function postObject(context, document) {
|
|
|
193
210
|
function inLanguage(text, language) {
|
|
194
211
|
return [text, new LanguageString(text, language)];
|
|
195
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* The post's recording, as the `Audio` or `Video` Mastodon plays in the
|
|
215
|
+
* timeline (TASK-213), or nothing for a post without one.
|
|
216
|
+
*
|
|
217
|
+
* It goes before the images on purpose. Mastodon shows a status's media by
|
|
218
|
+
* the type of its first attachment, a player for audio or video and a gallery
|
|
219
|
+
* otherwise, and keeps only the first four, so a recording after the pictures
|
|
220
|
+
* could be shown as a broken tile or dropped. Only the main file goes: it is
|
|
221
|
+
* always an upload, while an alternate version may be a link to another host
|
|
222
|
+
* whose type this site cannot check, and a remote server fetches and
|
|
223
|
+
* re-encodes whatever it is given. Its `name` is the post's title, or a
|
|
224
|
+
* note's first words, since a player with no label says nothing about what
|
|
225
|
+
* it plays.
|
|
226
|
+
*/
|
|
227
|
+
function recordingAttachment(document, baseUrl) {
|
|
228
|
+
const enclosure = enclosureOf(document.extra);
|
|
229
|
+
if (enclosure === undefined)
|
|
230
|
+
return [];
|
|
231
|
+
const values = {
|
|
232
|
+
url: new URL(absoluteUrl(enclosure.url, baseUrl)),
|
|
233
|
+
mediaType: enclosure.type,
|
|
234
|
+
name: postLabel(document),
|
|
235
|
+
};
|
|
236
|
+
return [playsAsVideo(enclosure.type) ? new Video(values) : new Audio(values)];
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Each of the post's photos (TASK-166), as an `Image` whose `name` is its alt
|
|
240
|
+
* text, the media library's when the post gives none.
|
|
241
|
+
*
|
|
242
|
+
* They go after the recording and before the body's images, because they are
|
|
243
|
+
* what a photo post is. A photo from another site is attached without a media
|
|
244
|
+
* type, which this site cannot check.
|
|
245
|
+
*/
|
|
246
|
+
function photoAttachments(document, config) {
|
|
247
|
+
const library = readAltTexts(config.contentDir);
|
|
248
|
+
return photosOf(document.extra).map((photo) => {
|
|
249
|
+
const media = isUploadUrl(photo.url)
|
|
250
|
+
? UPLOAD_MEDIA_TYPES.get(path.extname(photo.url).toLowerCase())
|
|
251
|
+
: undefined;
|
|
252
|
+
const alt = photoAlt(photo, library) ?? '';
|
|
253
|
+
return new Image({
|
|
254
|
+
url: new URL(absoluteUrl(photo.url, config.baseUrl)),
|
|
255
|
+
mediaType: media?.kind === 'image' ? (canonicalType(media) ?? null) : null,
|
|
256
|
+
name: alt === '' ? null : alt,
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
196
260
|
/**
|
|
197
261
|
* Each image the post shows from the site's own uploads, as an `Image` whose
|
|
198
262
|
* `name` is its alt text (TASK-141).
|
|
@@ -205,20 +269,22 @@ function inLanguage(text, language) {
|
|
|
205
269
|
*/
|
|
206
270
|
function imageAttachments(document, config) {
|
|
207
271
|
const library = readAltTexts(config.contentDir);
|
|
272
|
+
// A photo shown again in the body is already attached.
|
|
273
|
+
const photos = new Set(photosOf(document.extra).map((photo) => photo.url));
|
|
208
274
|
const attachments = [];
|
|
209
275
|
for (const image of imagesIn(document.html)) {
|
|
210
|
-
if (image.source === undefined)
|
|
276
|
+
if (image.source === undefined || photos.has(image.src))
|
|
211
277
|
continue;
|
|
212
278
|
if (library.get(image.source)?.kind === 'decorative')
|
|
213
279
|
continue;
|
|
214
280
|
const extension = path.extname(image.source).toLowerCase();
|
|
215
281
|
const media = UPLOAD_MEDIA_TYPES.get(extension);
|
|
216
|
-
if (media?.
|
|
282
|
+
if (media?.kind !== 'image')
|
|
217
283
|
continue;
|
|
218
284
|
const alt = image.alt?.trim() ?? '';
|
|
219
285
|
attachments.push(new Image({
|
|
220
286
|
url: new URL(absoluteUrl(image.src, config.baseUrl)),
|
|
221
|
-
mediaType: media
|
|
287
|
+
mediaType: canonicalType(media) ?? null,
|
|
222
288
|
name: alt === '' ? null : alt,
|
|
223
289
|
}));
|
|
224
290
|
}
|
|
@@ -227,6 +293,23 @@ function imageAttachments(document, config) {
|
|
|
227
293
|
const QUOTABLE_BY_ANYONE = new InteractionPolicy({
|
|
228
294
|
canQuote: new InteractionRule({ automaticApproval: PUBLIC_COLLECTION }),
|
|
229
295
|
});
|
|
296
|
+
/**
|
|
297
|
+
* What a like, a repost or a bookmark cites, as a line linking each page
|
|
298
|
+
* (decision-28): the words a peer shows, since a `Note` has no field for it.
|
|
299
|
+
*/
|
|
300
|
+
function citing(document) {
|
|
301
|
+
return citationsOf(document.extra)
|
|
302
|
+
.map(({ property, url }) => {
|
|
303
|
+
const href = escapeHtml(url).replaceAll('"', '"');
|
|
304
|
+
return `<p>${CITING_VERBS[property]} <a href="${href}">${escapeHtml(url)}</a></p>\n`;
|
|
305
|
+
})
|
|
306
|
+
.join('');
|
|
307
|
+
}
|
|
308
|
+
const CITING_VERBS = {
|
|
309
|
+
'repost-of': 'Reposted',
|
|
310
|
+
'like-of': 'Liked',
|
|
311
|
+
'bookmark-of': 'Bookmarked',
|
|
312
|
+
};
|
|
230
313
|
/** A note's HTML: its title first when its text does not already open with it. */
|
|
231
314
|
function noteContent(document) {
|
|
232
315
|
const title = document.title.trim();
|