@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.
Files changed (209) hide show
  1. package/README.md +270 -52
  2. package/admin/pages/documents/editor.njk +120 -0
  3. package/admin/pages/documents/syndication.njk +110 -0
  4. package/admin/pages/federation/followers.njk +7 -0
  5. package/admin/pages/media/library.njk +4 -3
  6. package/admin/pages/settings/general.njk +44 -0
  7. package/admin/static/admin.css +44 -0
  8. package/dist/admin/documents.d.ts +104 -0
  9. package/dist/admin/documents.d.ts.map +1 -1
  10. package/dist/admin/documents.js +220 -55
  11. package/dist/admin/documents.js.map +1 -1
  12. package/dist/admin/enclosure-field.d.ts +91 -0
  13. package/dist/admin/enclosure-field.d.ts.map +1 -0
  14. package/dist/admin/enclosure-field.js +282 -0
  15. package/dist/admin/enclosure-field.js.map +1 -0
  16. package/dist/admin/federation.d.ts +15 -0
  17. package/dist/admin/federation.d.ts.map +1 -1
  18. package/dist/admin/federation.js +19 -0
  19. package/dist/admin/federation.js.map +1 -1
  20. package/dist/admin/media.d.ts +7 -2
  21. package/dist/admin/media.d.ts.map +1 -1
  22. package/dist/admin/media.js +4 -2
  23. package/dist/admin/media.js.map +1 -1
  24. package/dist/admin/menu.d.ts.map +1 -1
  25. package/dist/admin/menu.js +3 -0
  26. package/dist/admin/menu.js.map +1 -1
  27. package/dist/admin/photo-field.d.ts +39 -0
  28. package/dist/admin/photo-field.d.ts.map +1 -0
  29. package/dist/admin/photo-field.js +91 -0
  30. package/dist/admin/photo-field.js.map +1 -0
  31. package/dist/admin/preview.d.ts.map +1 -1
  32. package/dist/admin/preview.js +6 -1
  33. package/dist/admin/preview.js.map +1 -1
  34. package/dist/admin/routes.d.ts.map +1 -1
  35. package/dist/admin/routes.js +4 -0
  36. package/dist/admin/routes.js.map +1 -1
  37. package/dist/admin/settings-general.d.ts.map +1 -1
  38. package/dist/admin/settings-general.js +36 -2
  39. package/dist/admin/settings-general.js.map +1 -1
  40. package/dist/admin/settings-page.d.ts.map +1 -1
  41. package/dist/admin/settings-page.js +1 -2
  42. package/dist/admin/settings-page.js.map +1 -1
  43. package/dist/admin/settings.d.ts +26 -0
  44. package/dist/admin/settings.d.ts.map +1 -1
  45. package/dist/admin/settings.js +88 -2
  46. package/dist/admin/settings.js.map +1 -1
  47. package/dist/admin/syndication-targets.d.ts +57 -0
  48. package/dist/admin/syndication-targets.d.ts.map +1 -0
  49. package/dist/admin/syndication-targets.js +266 -0
  50. package/dist/admin/syndication-targets.js.map +1 -0
  51. package/dist/admin/templates.d.ts +1 -0
  52. package/dist/admin/templates.d.ts.map +1 -1
  53. package/dist/admin/templates.js +1 -0
  54. package/dist/admin/templates.js.map +1 -1
  55. package/dist/admin/uploads.d.ts +13 -2
  56. package/dist/admin/uploads.d.ts.map +1 -1
  57. package/dist/admin/uploads.js +23 -5
  58. package/dist/admin/uploads.js.map +1 -1
  59. package/dist/config.d.ts +17 -3
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/config.js +11 -7
  62. package/dist/config.js.map +1 -1
  63. package/dist/content/citation.d.ts +36 -0
  64. package/dist/content/citation.d.ts.map +1 -0
  65. package/dist/content/citation.js +31 -0
  66. package/dist/content/citation.js.map +1 -0
  67. package/dist/content/enclosure.d.ts +69 -0
  68. package/dist/content/enclosure.d.ts.map +1 -0
  69. package/dist/content/enclosure.js +165 -0
  70. package/dist/content/enclosure.js.map +1 -0
  71. package/dist/content/index.d.ts +5 -1
  72. package/dist/content/index.d.ts.map +1 -1
  73. package/dist/content/index.js +2 -0
  74. package/dist/content/index.js.map +1 -1
  75. package/dist/content/media.d.ts +15 -2
  76. package/dist/content/media.d.ts.map +1 -1
  77. package/dist/content/media.js +92 -9
  78. package/dist/content/media.js.map +1 -1
  79. package/dist/content/photo.d.ts +45 -0
  80. package/dist/content/photo.d.ts.map +1 -0
  81. package/dist/content/photo.js +55 -0
  82. package/dist/content/photo.js.map +1 -0
  83. package/dist/content/post-type.d.ts +20 -8
  84. package/dist/content/post-type.d.ts.map +1 -1
  85. package/dist/content/post-type.js +17 -0
  86. package/dist/content/post-type.js.map +1 -1
  87. package/dist/federation/article.d.ts.map +1 -1
  88. package/dist/federation/article.js +93 -10
  89. package/dist/federation/article.js.map +1 -1
  90. package/dist/federation/citations.d.ts +38 -0
  91. package/dist/federation/citations.d.ts.map +1 -0
  92. package/dist/federation/citations.js +98 -0
  93. package/dist/federation/citations.js.map +1 -0
  94. package/dist/federation/delivery.d.ts.map +1 -1
  95. package/dist/federation/delivery.js +79 -31
  96. package/dist/federation/delivery.js.map +1 -1
  97. package/dist/images/alt-text.d.ts +6 -0
  98. package/dist/images/alt-text.d.ts.map +1 -1
  99. package/dist/images/alt-text.js +10 -0
  100. package/dist/images/alt-text.js.map +1 -1
  101. package/dist/images/icons.d.ts +5 -0
  102. package/dist/images/icons.d.ts.map +1 -1
  103. package/dist/images/icons.js +13 -8
  104. package/dist/images/icons.js.map +1 -1
  105. package/dist/images/paths.d.ts +1 -1
  106. package/dist/images/paths.d.ts.map +1 -1
  107. package/dist/images/paths.js.map +1 -1
  108. package/dist/index.d.ts +7 -7
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +25 -4
  111. package/dist/index.js.map +1 -1
  112. package/dist/indieauth/bearer.d.ts +6 -1
  113. package/dist/indieauth/bearer.d.ts.map +1 -1
  114. package/dist/indieauth/bearer.js +39 -12
  115. package/dist/indieauth/bearer.js.map +1 -1
  116. package/dist/indieauth/discovery.d.ts +9 -4
  117. package/dist/indieauth/discovery.d.ts.map +1 -1
  118. package/dist/indieauth/discovery.js +18 -7
  119. package/dist/indieauth/discovery.js.map +1 -1
  120. package/dist/micropub/create.d.ts +56 -0
  121. package/dist/micropub/create.d.ts.map +1 -0
  122. package/dist/micropub/create.js +200 -0
  123. package/dist/micropub/create.js.map +1 -0
  124. package/dist/micropub/endpoint.d.ts +21 -0
  125. package/dist/micropub/endpoint.d.ts.map +1 -0
  126. package/dist/micropub/endpoint.js +353 -0
  127. package/dist/micropub/endpoint.js.map +1 -0
  128. package/dist/micropub/media.d.ts +14 -0
  129. package/dist/micropub/media.d.ts.map +1 -0
  130. package/dist/micropub/media.js +93 -0
  131. package/dist/micropub/media.js.map +1 -0
  132. package/dist/micropub/update.d.ts +44 -0
  133. package/dist/micropub/update.d.ts.map +1 -0
  134. package/dist/micropub/update.js +133 -0
  135. package/dist/micropub/update.js.map +1 -0
  136. package/dist/web/assets.d.ts +14 -0
  137. package/dist/web/assets.d.ts.map +1 -1
  138. package/dist/web/assets.js +56 -1
  139. package/dist/web/assets.js.map +1 -1
  140. package/dist/web/context.d.ts +68 -3
  141. package/dist/web/context.d.ts.map +1 -1
  142. package/dist/web/context.js +56 -1
  143. package/dist/web/context.js.map +1 -1
  144. package/dist/web/feed-atom.d.ts.map +1 -1
  145. package/dist/web/feed-atom.js +20 -0
  146. package/dist/web/feed-atom.js.map +1 -1
  147. package/dist/web/feed-item.d.ts +19 -0
  148. package/dist/web/feed-item.d.ts.map +1 -1
  149. package/dist/web/feed-item.js +22 -0
  150. package/dist/web/feed-item.js.map +1 -1
  151. package/dist/web/feed-json.d.ts +13 -0
  152. package/dist/web/feed-json.d.ts.map +1 -1
  153. package/dist/web/feed-json.js +15 -0
  154. package/dist/web/feed-json.js.map +1 -1
  155. package/dist/web/feed-rss.d.ts.map +1 -1
  156. package/dist/web/feed-rss.js +59 -2
  157. package/dist/web/feed-rss.js.map +1 -1
  158. package/dist/web/feed-xml.d.ts +7 -1
  159. package/dist/web/feed-xml.d.ts.map +1 -1
  160. package/dist/web/feed-xml.js +8 -2
  161. package/dist/web/feed-xml.js.map +1 -1
  162. package/dist/web/feeds.d.ts +1 -1
  163. package/dist/web/feeds.d.ts.map +1 -1
  164. package/dist/web/feeds.js +6 -0
  165. package/dist/web/feeds.js.map +1 -1
  166. package/dist/web/index.d.ts +4 -1
  167. package/dist/web/index.d.ts.map +1 -1
  168. package/dist/web/index.js +2 -0
  169. package/dist/web/index.js.map +1 -1
  170. package/dist/web/license.d.ts +94 -0
  171. package/dist/web/license.d.ts.map +1 -0
  172. package/dist/web/license.js +116 -0
  173. package/dist/web/license.js.map +1 -0
  174. package/dist/web/oembed.d.ts.map +1 -1
  175. package/dist/web/oembed.js +1 -2
  176. package/dist/web/oembed.js.map +1 -1
  177. package/dist/web/render.d.ts +15 -0
  178. package/dist/web/render.d.ts.map +1 -1
  179. package/dist/web/render.js +27 -2
  180. package/dist/web/render.js.map +1 -1
  181. package/dist/web/routes.d.ts.map +1 -1
  182. package/dist/web/routes.js +10 -6
  183. package/dist/web/routes.js.map +1 -1
  184. package/dist/web/share-image.d.ts +7 -7
  185. package/dist/web/share-image.d.ts.map +1 -1
  186. package/dist/web/share-image.js +8 -7
  187. package/dist/web/share-image.js.map +1 -1
  188. package/dist/webmention/index.d.ts +2 -0
  189. package/dist/webmention/index.d.ts.map +1 -1
  190. package/dist/webmention/index.js +1 -0
  191. package/dist/webmention/index.js.map +1 -1
  192. package/dist/webmention/service.d.ts.map +1 -1
  193. package/dist/webmention/service.js +96 -18
  194. package/dist/webmention/service.js.map +1 -1
  195. package/dist/webmention/syndication.d.ts +135 -0
  196. package/dist/webmention/syndication.d.ts.map +1 -0
  197. package/dist/webmention/syndication.js +337 -0
  198. package/dist/webmention/syndication.js.map +1 -0
  199. package/package.json +1 -1
  200. package/themes/default/README.md +121 -37
  201. package/themes/default/layouts/base.njk +3 -4
  202. package/themes/default/layouts/post.njk +23 -6
  203. package/themes/default/partials/citations.njk +17 -0
  204. package/themes/default/partials/jsonld.njk +15 -6
  205. package/themes/default/partials/photos.njk +10 -0
  206. package/themes/default/partials/post-list.njk +7 -0
  207. package/themes/default/partials/recording.njk +41 -0
  208. package/themes/default/src/style.css +25 -0
  209. package/themes/default/static/style.css +41 -0
@@ -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
- image: true,
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
- image: true,
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
- image: true,
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
- image: true,
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
- image: true,
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
- image: true,
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
- image: false,
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'], image: false }],
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
- image: false,
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":"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"}
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
- * Reply and the note/article tail of the algorithm are here. The spec's full
8
- * order is event, rsvp, repost, like, reply, video, photo, then the tail, and
9
- * the order matters because the first branch that matches wins: a reply with
10
- * a photo is a reply. Each new type is a check in {@link discoverPostType} in
11
- * that order. granary's `mf2util` diverges from the spec, putting reply ahead
12
- * of repost and like and having no video branch; this follows the spec.
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":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAEpD,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;CACpC;AAED,gDAAgD;AAChD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,cAAc,GAAG,QAAQ,CAGrE;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,CAAC,CAAC;AAcnF;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,aAAa,CAAC,GAAG,MAAM,CAQ5F"}
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":"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"}
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,EACP,MAAM,EACN,MAAM,EAMN,IAAI,EAEJ,MAAM,EAGN,MAAM,EACP,MAAM,eAAe,CAAC;AAKvB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAEjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAIvD,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;AA2CD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,EACvC,QAAQ,EAAE,QAAQ,GACjB,OAAO,GAAG,IAAI,CAqDhB;AAwED;;;;;;;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
+ {"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 { UPLOAD_MEDIA_TYPES } from '../content/media.js';
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: imageAttachments(document, context.data.config),
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({ ...common, contents: inLanguage(noteContent(document), language) });
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?.image !== true)
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.declared[0] ?? null,
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('"', '&quot;');
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();