@geekity/cms 0.13.0 → 0.15.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 (184) hide show
  1. package/README.md +181 -57
  2. package/admin/pages/documents/editor.njk +82 -0
  3. package/admin/pages/media/library.njk +4 -3
  4. package/admin/pages/settings/general.njk +51 -6
  5. package/admin/static/admin.css +33 -0
  6. package/dist/admin/documents.d.ts +5 -0
  7. package/dist/admin/documents.d.ts.map +1 -1
  8. package/dist/admin/documents.js +63 -6
  9. package/dist/admin/documents.js.map +1 -1
  10. package/dist/admin/enclosure-field.d.ts +91 -0
  11. package/dist/admin/enclosure-field.d.ts.map +1 -0
  12. package/dist/admin/enclosure-field.js +282 -0
  13. package/dist/admin/enclosure-field.js.map +1 -0
  14. package/dist/admin/federation.js +2 -2
  15. package/dist/admin/federation.js.map +1 -1
  16. package/dist/admin/headers.d.ts +4 -1
  17. package/dist/admin/headers.d.ts.map +1 -1
  18. package/dist/admin/headers.js +9 -4
  19. package/dist/admin/headers.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/settings-general.d.ts.map +1 -1
  25. package/dist/admin/settings-general.js +63 -4
  26. package/dist/admin/settings-general.js.map +1 -1
  27. package/dist/admin/settings-page.d.ts.map +1 -1
  28. package/dist/admin/settings-page.js +1 -2
  29. package/dist/admin/settings-page.js.map +1 -1
  30. package/dist/admin/settings.d.ts +37 -9
  31. package/dist/admin/settings.d.ts.map +1 -1
  32. package/dist/admin/settings.js +93 -11
  33. package/dist/admin/settings.js.map +1 -1
  34. package/dist/admin/uploads.d.ts +7 -2
  35. package/dist/admin/uploads.d.ts.map +1 -1
  36. package/dist/admin/uploads.js +23 -5
  37. package/dist/admin/uploads.js.map +1 -1
  38. package/dist/config.d.ts +21 -3
  39. package/dist/config.d.ts.map +1 -1
  40. package/dist/config.js +11 -7
  41. package/dist/config.js.map +1 -1
  42. package/dist/content/enclosure.d.ts +69 -0
  43. package/dist/content/enclosure.d.ts.map +1 -0
  44. package/dist/content/enclosure.js +165 -0
  45. package/dist/content/enclosure.js.map +1 -0
  46. package/dist/content/index.d.ts +3 -1
  47. package/dist/content/index.d.ts.map +1 -1
  48. package/dist/content/index.js +1 -0
  49. package/dist/content/index.js.map +1 -1
  50. package/dist/content/media.d.ts +15 -2
  51. package/dist/content/media.d.ts.map +1 -1
  52. package/dist/content/media.js +92 -9
  53. package/dist/content/media.js.map +1 -1
  54. package/dist/content/pinned.d.ts +26 -0
  55. package/dist/content/pinned.d.ts.map +1 -0
  56. package/dist/content/pinned.js +42 -0
  57. package/dist/content/pinned.js.map +1 -0
  58. package/dist/content/store.d.ts +7 -0
  59. package/dist/content/store.d.ts.map +1 -1
  60. package/dist/content/store.js +20 -0
  61. package/dist/content/store.js.map +1 -1
  62. package/dist/env.d.ts +7 -0
  63. package/dist/env.d.ts.map +1 -1
  64. package/dist/federation/actor.d.ts +15 -0
  65. package/dist/federation/actor.d.ts.map +1 -1
  66. package/dist/federation/actor.js +94 -3
  67. package/dist/federation/actor.js.map +1 -1
  68. package/dist/federation/article.d.ts +13 -1
  69. package/dist/federation/article.d.ts.map +1 -1
  70. package/dist/federation/article.js +62 -7
  71. package/dist/federation/article.js.map +1 -1
  72. package/dist/federation/delivery.d.ts.map +1 -1
  73. package/dist/federation/delivery.js +36 -4
  74. package/dist/federation/delivery.js.map +1 -1
  75. package/dist/federation/federation.d.ts.map +1 -1
  76. package/dist/federation/federation.js +24 -2
  77. package/dist/federation/federation.js.map +1 -1
  78. package/dist/federation/mount.js +2 -2
  79. package/dist/federation/mount.js.map +1 -1
  80. package/dist/federation/paths.d.ts +30 -0
  81. package/dist/federation/paths.d.ts.map +1 -1
  82. package/dist/federation/paths.js +36 -0
  83. package/dist/federation/paths.js.map +1 -1
  84. package/dist/images/icons.d.ts +5 -0
  85. package/dist/images/icons.d.ts.map +1 -1
  86. package/dist/images/icons.js +13 -8
  87. package/dist/images/icons.js.map +1 -1
  88. package/dist/images/paths.d.ts +1 -1
  89. package/dist/images/paths.d.ts.map +1 -1
  90. package/dist/images/paths.js.map +1 -1
  91. package/dist/images/variants.d.ts +11 -0
  92. package/dist/images/variants.d.ts.map +1 -1
  93. package/dist/images/variants.js +21 -0
  94. package/dist/images/variants.js.map +1 -1
  95. package/dist/index.d.ts +5 -5
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +7 -3
  98. package/dist/index.js.map +1 -1
  99. package/dist/indieauth/identity.d.ts +3 -3
  100. package/dist/indieauth/identity.d.ts.map +1 -1
  101. package/dist/indieauth/identity.js +6 -7
  102. package/dist/indieauth/identity.js.map +1 -1
  103. package/dist/web/admin-bar.d.ts +3 -1
  104. package/dist/web/admin-bar.d.ts.map +1 -1
  105. package/dist/web/admin-bar.js +5 -1
  106. package/dist/web/admin-bar.js.map +1 -1
  107. package/dist/web/assets.d.ts +14 -0
  108. package/dist/web/assets.d.ts.map +1 -1
  109. package/dist/web/assets.js +56 -1
  110. package/dist/web/assets.js.map +1 -1
  111. package/dist/web/authors.d.ts +19 -0
  112. package/dist/web/authors.d.ts.map +1 -1
  113. package/dist/web/authors.js +25 -0
  114. package/dist/web/authors.js.map +1 -1
  115. package/dist/web/context.d.ts +26 -0
  116. package/dist/web/context.d.ts.map +1 -1
  117. package/dist/web/context.js +15 -0
  118. package/dist/web/context.js.map +1 -1
  119. package/dist/web/feed-atom.d.ts.map +1 -1
  120. package/dist/web/feed-atom.js +22 -1
  121. package/dist/web/feed-atom.js.map +1 -1
  122. package/dist/web/feed-item.d.ts +34 -6
  123. package/dist/web/feed-item.d.ts.map +1 -1
  124. package/dist/web/feed-item.js +31 -7
  125. package/dist/web/feed-item.js.map +1 -1
  126. package/dist/web/feed-json.d.ts +15 -2
  127. package/dist/web/feed-json.d.ts.map +1 -1
  128. package/dist/web/feed-json.js +17 -1
  129. package/dist/web/feed-json.js.map +1 -1
  130. package/dist/web/feed-rss.d.ts.map +1 -1
  131. package/dist/web/feed-rss.js +60 -3
  132. package/dist/web/feed-rss.js.map +1 -1
  133. package/dist/web/feed-source.d.ts +3 -0
  134. package/dist/web/feed-source.d.ts.map +1 -1
  135. package/dist/web/feed-source.js.map +1 -1
  136. package/dist/web/feed-xml.d.ts +7 -1
  137. package/dist/web/feed-xml.d.ts.map +1 -1
  138. package/dist/web/feed-xml.js +8 -2
  139. package/dist/web/feed-xml.js.map +1 -1
  140. package/dist/web/feeds.d.ts +1 -1
  141. package/dist/web/feeds.d.ts.map +1 -1
  142. package/dist/web/feeds.js +13 -2
  143. package/dist/web/feeds.js.map +1 -1
  144. package/dist/web/index.d.ts +5 -2
  145. package/dist/web/index.d.ts.map +1 -1
  146. package/dist/web/index.js +3 -1
  147. package/dist/web/index.js.map +1 -1
  148. package/dist/web/license.d.ts +94 -0
  149. package/dist/web/license.d.ts.map +1 -0
  150. package/dist/web/license.js +116 -0
  151. package/dist/web/license.js.map +1 -0
  152. package/dist/web/oembed.d.ts +104 -0
  153. package/dist/web/oembed.d.ts.map +1 -0
  154. package/dist/web/oembed.js +289 -0
  155. package/dist/web/oembed.js.map +1 -0
  156. package/dist/web/opensearch.d.ts +35 -0
  157. package/dist/web/opensearch.d.ts.map +1 -0
  158. package/dist/web/opensearch.js +65 -0
  159. package/dist/web/opensearch.js.map +1 -0
  160. package/dist/web/render.d.ts.map +1 -1
  161. package/dist/web/render.js +16 -8
  162. package/dist/web/render.js.map +1 -1
  163. package/dist/web/routes.d.ts.map +1 -1
  164. package/dist/web/routes.js +82 -6
  165. package/dist/web/routes.js.map +1 -1
  166. package/dist/web/share-image.d.ts +7 -7
  167. package/dist/web/share-image.d.ts.map +1 -1
  168. package/dist/web/share-image.js +8 -7
  169. package/dist/web/share-image.js.map +1 -1
  170. package/dist/web/templates.d.ts.map +1 -1
  171. package/dist/web/templates.js +5 -0
  172. package/dist/web/templates.js.map +1 -1
  173. package/package.json +2 -1
  174. package/templates/site/content/_data/site.json +0 -1
  175. package/themes/default/README.md +142 -48
  176. package/themes/default/layouts/base.njk +25 -6
  177. package/themes/default/layouts/front-page.njk +1 -0
  178. package/themes/default/layouts/home.njk +1 -0
  179. package/themes/default/layouts/post.njk +4 -0
  180. package/themes/default/partials/bio.njk +7 -0
  181. package/themes/default/partials/jsonld.njk +30 -7
  182. package/themes/default/partials/recording.njk +41 -0
  183. package/themes/default/src/style.css +16 -0
  184. package/themes/default/static/style.css +29 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../../src/web/license.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;iBAElC,IAAI,EAAE,WAAW;iBACjB,KAAK,EAAE,aAAa;iBACpB,GAAG,EAAE,8CAA8C;;;iBAGnD,IAAI,EAAE,cAAc;iBACpB,KAAK,EAAE,wBAAwB;iBAC/B,GAAG,EAAE,iDAAiD;;;iBAGtD,IAAI,EAAE,cAAc;iBACpB,KAAK,EAAE,2BAA2B;iBAClC,GAAG,EAAE,iDAAiD;;;iBAGtD,IAAI,EAAE,iBAAiB;iBACvB,KAAK,EAAE,sCAAsC;iBAC7C,GAAG,EAAE,oDAAoD;;;iBAGzD,IAAI,EAAE,cAAc;iBACpB,KAAK,EAAE,2BAA2B;iBAClC,GAAG,EAAE,iDAAiD;;;iBAGtD,IAAI,EAAE,iBAAiB;iBACvB,KAAK,EAAE,yCAAyC;iBAChD,GAAG,EAAE,oDAAoD;;;iBAGzD,IAAI,EAAE,SAAS;iBACf,KAAK,EAAE,0BAA0B;iBACjC,GAAG,EAAE,oDAAoD;;CAEmB,CAAC;AAEjF,yDAAyD;AACzD,MAAM,MAAM,kBAAkB,GAAG,MAAM,OAAO,yBAAyB,CAAC;AAExE,+DAA+D;AAC/D,eAAO,MAAM,UAAU,SAAS,CAAC;AAEjC,sEAAsE;AACtE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,2DAA2D;AAC3D,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,kBAAkB,CAE/E;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,aAAa,EACnB,IAAI,GAAE,aAAkB,GACvB,cAAc,GAAG,SAAS,CAK5B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,GACb;IAAE,GAAG,EAAE,kBAAkB,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,UAAU,GAAG,SAAS,CAO/E"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * What readers may do with a site's posts: the license a page is
3
+ * under, read once from `site.json` and the page's front matter, and printed
4
+ * by the footer, the JSON-LD and the feeds from that one answer.
5
+ *
6
+ * A license value, in `site.json` or in front matter, is one of three things:
7
+ *
8
+ * - a Creative Commons key from {@link CREATIVE_COMMONS_LICENSES}, such as
9
+ * `cc-by-sa`, in any case;
10
+ * - an `http` or `https` URL, named by the `licenseName` beside it, else by
11
+ * its deed when it is a Creative Commons URL, else by the URL itself;
12
+ * - `none`, or nothing, which is all rights reserved and prints nothing.
13
+ *
14
+ * Anything else is no value at all: a post's typo falls back to the site's
15
+ * license rather than dropping it.
16
+ */
17
+ /**
18
+ * The Creative Commons licenses, at their current versions: 4.0 for the six
19
+ * licenses and 1.0 for the CC0 public domain dedication. The keys are what
20
+ * `site.json` and front matter write; `title` is the long name the settings
21
+ * screen offers beside the short one.
22
+ */
23
+ export const CREATIVE_COMMONS_LICENSES = {
24
+ 'cc-by': {
25
+ name: 'CC BY 4.0',
26
+ title: 'Attribution',
27
+ url: 'https://creativecommons.org/licenses/by/4.0/',
28
+ },
29
+ 'cc-by-sa': {
30
+ name: 'CC BY-SA 4.0',
31
+ title: 'Attribution-ShareAlike',
32
+ url: 'https://creativecommons.org/licenses/by-sa/4.0/',
33
+ },
34
+ 'cc-by-nc': {
35
+ name: 'CC BY-NC 4.0',
36
+ title: 'Attribution-NonCommercial',
37
+ url: 'https://creativecommons.org/licenses/by-nc/4.0/',
38
+ },
39
+ 'cc-by-nc-sa': {
40
+ name: 'CC BY-NC-SA 4.0',
41
+ title: 'Attribution-NonCommercial-ShareAlike',
42
+ url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/',
43
+ },
44
+ 'cc-by-nd': {
45
+ name: 'CC BY-ND 4.0',
46
+ title: 'Attribution-NoDerivatives',
47
+ url: 'https://creativecommons.org/licenses/by-nd/4.0/',
48
+ },
49
+ 'cc-by-nc-nd': {
50
+ name: 'CC BY-NC-ND 4.0',
51
+ title: 'Attribution-NonCommercial-NoDerivatives',
52
+ url: 'https://creativecommons.org/licenses/by-nc-nd/4.0/',
53
+ },
54
+ cc0: {
55
+ name: 'CC0 1.0',
56
+ title: 'Public domain dedication',
57
+ url: 'https://creativecommons.org/publicdomain/zero/1.0/',
58
+ },
59
+ };
60
+ /** The value that says, outright, that there is no license. */
61
+ export const NO_LICENSE = 'none';
62
+ /** Whether a value is one of the Creative Commons keys. */
63
+ export function isCreativeCommonsKey(value) {
64
+ return Object.hasOwn(CREATIVE_COMMONS_LICENSES, value);
65
+ }
66
+ /** Whether a value is an absolute `http` or `https` URL. */
67
+ export function isLicenseUrl(value) {
68
+ try {
69
+ const url = new URL(value);
70
+ return url.protocol === 'https:' || url.protocol === 'http:';
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ /**
77
+ * The license a page is under, or `undefined` for none: the page's own when
78
+ * its front matter says one (or says `none`), else the site's.
79
+ */
80
+ export function resolveLicense(site, page = {}) {
81
+ const own = parseLicense(page);
82
+ if (own !== 'unsaid')
83
+ return own;
84
+ const sites = parseLicense(site);
85
+ return sites === 'unsaid' ? undefined : sites;
86
+ }
87
+ /**
88
+ * What one license value says: a Creative Commons key, a URL, `none`, or
89
+ * `undefined` when it says nothing usable.
90
+ */
91
+ export function classifyLicense(value) {
92
+ if (typeof value !== 'string')
93
+ return undefined;
94
+ const trimmed = value.trim();
95
+ const key = trimmed.toLowerCase();
96
+ if (key === NO_LICENSE)
97
+ return NO_LICENSE;
98
+ if (isCreativeCommonsKey(key))
99
+ return { key };
100
+ return isLicenseUrl(trimmed) ? { url: trimmed } : undefined;
101
+ }
102
+ function parseLicense(source) {
103
+ const said = classifyLicense(source.license);
104
+ if (said === undefined)
105
+ return 'unsaid';
106
+ if (said === NO_LICENSE)
107
+ return undefined;
108
+ if ('key' in said) {
109
+ const { name, url } = CREATIVE_COMMONS_LICENSES[said.key];
110
+ return { name, url };
111
+ }
112
+ const name = typeof source.licenseName === 'string' ? source.licenseName.trim() : '';
113
+ const deed = Object.values(CREATIVE_COMMONS_LICENSES).find((known) => known.url === said.url);
114
+ return { name: name || deed?.name || said.url, url: said.url };
115
+ }
116
+ //# sourceMappingURL=license.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"license.js","sourceRoot":"","sources":["../../src/web/license.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ import type { Document } from '../content/document.ts';
2
+ import type { ImageConfig } from '../images/paths.ts';
3
+ import type { AuthorContext } from './authors.ts';
4
+ import type { SiteData } from './context.ts';
5
+ import type { ConditionalHeaders } from './negotiate.ts';
6
+ /**
7
+ * The oEmbed provider (TASK-205): what WordPress, Discourse and the rest ask
8
+ * to turn a pasted post URL into a card.
9
+ *
10
+ * Under `/_geekity/`, the CMS's own prefix, so no permalink can take it. Each
11
+ * post and page links it from its head, with its own URL in the query.
12
+ */
13
+ /** The endpoint's URL. */
14
+ export declare const OEMBED_PATH = "/_geekity/oembed";
15
+ /**
16
+ * The embed view's URL (TASK-208): the card alone as a page, with the
17
+ * document's absolute URL in `?url=`, and the one response on the site another
18
+ * origin may frame.
19
+ *
20
+ * WordPress keeps a rich embed from a provider it does not know only when its
21
+ * html holds an `<iframe>`, so the oEmbed html frames this page after the
22
+ * blockquote, the way WordPress's own provider does.
23
+ */
24
+ export declare const EMBED_PATH = "/_geekity/embed";
25
+ /** The formats the spec names. Anything else is a 501. */
26
+ export type OEmbedFormat = 'json' | 'xml';
27
+ /** One request, from its query. */
28
+ export interface OEmbedRequest {
29
+ /** The URL to embed, as given. */
30
+ url: string;
31
+ /** `undefined` for a format this provider does not serve. */
32
+ format: OEmbedFormat | undefined;
33
+ maxwidth?: number | undefined;
34
+ maxheight?: number | undefined;
35
+ }
36
+ /** A `type: rich` response. The thumbnail's three fields come together or not at all. */
37
+ export interface OEmbedRich {
38
+ version: '1.0';
39
+ type: 'rich';
40
+ title: string;
41
+ author_name?: string;
42
+ author_url?: string;
43
+ provider_name: string;
44
+ provider_url: string;
45
+ html: string;
46
+ width: number;
47
+ height: number;
48
+ thumbnail_url?: string;
49
+ thumbnail_width?: number;
50
+ thumbnail_height?: number;
51
+ }
52
+ /** A query as a request. A missing `format` is JSON, which every consumer reads. */
53
+ export declare function oEmbedRequest(query: (name: string) => string | undefined): OEmbedRequest;
54
+ /** One published document, served at `href`, and what its card names beside it. */
55
+ export interface EmbedSubject {
56
+ document: Document;
57
+ href: string;
58
+ author: AuthorContext | undefined;
59
+ config: {
60
+ baseUrl: string;
61
+ };
62
+ site: SiteData;
63
+ }
64
+ /**
65
+ * The embed of one published document, served at `href`.
66
+ *
67
+ * The title is the document's, else a note's first words. The html is the
68
+ * card, then a hidden sandboxed frame of {@link EMBED_PATH}: a consumer that
69
+ * keeps the html as given shows the blockquote, one that strips iframes still
70
+ * has it, and WordPress, which keeps only the frame, shows that once the page
71
+ * inside has told it how tall it is. The thumbnail is the
72
+ * document's own `image`, never the site's avatar, and only when its size is
73
+ * recorded: the spec requires the size with the URL, and a render never opens
74
+ * an image (decision-10). One wider or taller than the consumer allows is left
75
+ * out, because the spec says it must respect `maxwidth` and `maxheight` too.
76
+ */
77
+ export declare function oEmbedFor(input: EmbedSubject & {
78
+ config: ImageConfig & {
79
+ baseUrl: string;
80
+ };
81
+ request: OEmbedRequest;
82
+ }): OEmbedRich;
83
+ /** The embed as a response in `format`, or a 304 to a client that already holds it. */
84
+ export declare function oEmbedResponse(embed: OEmbedRich, format: OEmbedFormat, conditional: ConditionalHeaders | undefined): Response;
85
+ /**
86
+ * The embed view's page: the card, a style to read it by, and the script that
87
+ * speaks WordPress's side of the embed.
88
+ *
89
+ * Nothing in it depends on who asked. There is no theme, no admin bar and no
90
+ * session, so a reader of a framing site sees the same card a signed-in admin
91
+ * would, and nothing about either. `noindex` because the post is the page to
92
+ * find, not its card.
93
+ */
94
+ export declare function embedPage(subject: EmbedSubject): string;
95
+ /**
96
+ * The embed page as a response, or a 304 to a client that already holds it.
97
+ *
98
+ * Its policy is its own and complete: the one inline style and the one inline
99
+ * script by hash, nothing else loaded, and any origin may frame it. The
100
+ * caller marks the context frameable as well, so the baseline leaves off
101
+ * `X-Frame-Options`, which has no way to say "anyone".
102
+ */
103
+ export declare function embedResponse(page: string, conditional: ConditionalHeaders | undefined): Response;
104
+ //# sourceMappingURL=oembed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oembed.d.ts","sourceRoot":"","sources":["../../src/web/oembed.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAIzD;;;;;;GAMG;AAEH,0BAA0B;AAC1B,eAAO,MAAM,WAAW,qBAAqB,CAAC;AAE9C;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,oBAAoB,CAAC;AAE5C,0DAA0D;AAC1D,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,CAAC;AAe1C,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,MAAM,EAAE,YAAY,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,yFAAyF;AACzF,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,aAAa,CAQxF;AAED,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC;IAClC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5B,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,YAAY,GAAG;IAAE,MAAM,EAAE,WAAW,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,GAC1F,UAAU,CA8CZ;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,YAAY,EACpB,WAAW,EAAE,kBAAkB,GAAG,SAAS,GAC1C,QAAQ,CAWV;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAkBvD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,kBAAkB,GAAG,SAAS,GAAG,QAAQ,CAcjG"}
@@ -0,0 +1,289 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { postLabel } from '../content/post-type.js';
3
+ import { siteLocale, siteTimezone } from './context.js';
4
+ import { feedExcerpt } from './feed-item.js';
5
+ import { escapeXml } from './feed-xml.js';
6
+ import { absoluteUrl, contentEtag, isNotModified } from './negotiate.js';
7
+ import { shareImage } from './share-image.js';
8
+ import { formatDate } from './templates.js';
9
+ /**
10
+ * The oEmbed provider (TASK-205): what WordPress, Discourse and the rest ask
11
+ * to turn a pasted post URL into a card.
12
+ *
13
+ * Under `/_geekity/`, the CMS's own prefix, so no permalink can take it. Each
14
+ * post and page links it from its head, with its own URL in the query.
15
+ */
16
+ /** The endpoint's URL. */
17
+ export const OEMBED_PATH = '/_geekity/oembed';
18
+ /**
19
+ * The embed view's URL (TASK-208): the card alone as a page, with the
20
+ * document's absolute URL in `?url=`, and the one response on the site another
21
+ * origin may frame.
22
+ *
23
+ * WordPress keeps a rich embed from a provider it does not know only when its
24
+ * html holds an `<iframe>`, so the oEmbed html frames this page after the
25
+ * blockquote, the way WordPress's own provider does.
26
+ */
27
+ export const EMBED_PATH = '/_geekity/embed';
28
+ const CONTENT_TYPES = {
29
+ json: 'application/json; charset=utf-8',
30
+ xml: 'text/xml; charset=utf-8',
31
+ };
32
+ /**
33
+ * The size the card asks for. A rich embed must give both, and a blockquote
34
+ * of words reflows to whatever it is given, so these are what WordPress asks
35
+ * for its own cards rather than a measurement.
36
+ */
37
+ const DEFAULT_WIDTH = 600;
38
+ const DEFAULT_HEIGHT = 338;
39
+ /** A query as a request. A missing `format` is JSON, which every consumer reads. */
40
+ export function oEmbedRequest(query) {
41
+ const format = query('format') ?? 'json';
42
+ return {
43
+ url: query('url') ?? '',
44
+ format: format === 'json' || format === 'xml' ? format : undefined,
45
+ maxwidth: positiveInteger(query('maxwidth')),
46
+ maxheight: positiveInteger(query('maxheight')),
47
+ };
48
+ }
49
+ /**
50
+ * The embed of one published document, served at `href`.
51
+ *
52
+ * The title is the document's, else a note's first words. The html is the
53
+ * card, then a hidden sandboxed frame of {@link EMBED_PATH}: a consumer that
54
+ * keeps the html as given shows the blockquote, one that strips iframes still
55
+ * has it, and WordPress, which keeps only the frame, shows that once the page
56
+ * inside has told it how tall it is. The thumbnail is the
57
+ * document's own `image`, never the site's avatar, and only when its size is
58
+ * recorded: the spec requires the size with the URL, and a render never opens
59
+ * an image (decision-10). One wider or taller than the consumer allows is left
60
+ * out, because the spec says it must respect `maxwidth` and `maxheight` too.
61
+ */
62
+ export function oEmbedFor(input) {
63
+ const { document, author, config, site, request } = input;
64
+ const url = (pathname) => absoluteUrl(pathname, config.baseUrl);
65
+ const title = postLabel(document);
66
+ const authorUrl = author?.url === undefined ? undefined : url(author.url);
67
+ const width = Math.min(DEFAULT_WIDTH, request.maxwidth ?? DEFAULT_WIDTH);
68
+ const height = Math.min(DEFAULT_HEIGHT, request.maxheight ?? DEFAULT_HEIGHT);
69
+ const image = shareImage({
70
+ config,
71
+ image: document.extra['image'],
72
+ imageAlt: document.extra['imageAlt'],
73
+ title,
74
+ fallbacks: [],
75
+ });
76
+ const thumbnail = image?.size !== undefined &&
77
+ image.size.width <= (request.maxwidth ?? Infinity) &&
78
+ image.size.height <= (request.maxheight ?? Infinity)
79
+ ? { url: url(image.url), ...image.size }
80
+ : undefined;
81
+ return {
82
+ version: '1.0',
83
+ type: 'rich',
84
+ title,
85
+ ...(author === undefined ? {} : { author_name: author.name }),
86
+ ...(authorUrl === undefined ? {} : { author_url: authorUrl }),
87
+ provider_name: site.title,
88
+ provider_url: url('/'),
89
+ html: `${embedCard(input)}${embedFrame({
90
+ src: url(`${EMBED_PATH}?${new URLSearchParams({ url: url(input.href) }).toString()}`),
91
+ title,
92
+ width,
93
+ height,
94
+ })}`,
95
+ width,
96
+ height,
97
+ ...(thumbnail === undefined
98
+ ? {}
99
+ : {
100
+ thumbnail_url: thumbnail.url,
101
+ thumbnail_width: thumbnail.width,
102
+ thumbnail_height: thumbnail.height,
103
+ }),
104
+ };
105
+ }
106
+ /** The embed as a response in `format`, or a 304 to a client that already holds it. */
107
+ export function oEmbedResponse(embed, format, conditional) {
108
+ const body = format === 'json' ? JSON.stringify(embed) : oEmbedXml(embed);
109
+ const etag = contentEtag(`oembed-${format}`, body);
110
+ const headers = new Headers({ etag, 'cache-control': 'no-cache' });
111
+ if (isNotModified(conditional, etag, undefined)) {
112
+ return new Response(null, { status: 304, headers });
113
+ }
114
+ headers.set('content-type', CONTENT_TYPES[format]);
115
+ return new Response(body, { headers });
116
+ }
117
+ /**
118
+ * The embed view's page: the card, a style to read it by, and the script that
119
+ * speaks WordPress's side of the embed.
120
+ *
121
+ * Nothing in it depends on who asked. There is no theme, no admin bar and no
122
+ * session, so a reader of a framing site sees the same card a signed-in admin
123
+ * would, and nothing about either. `noindex` because the post is the page to
124
+ * find, not its card.
125
+ */
126
+ export function embedPage(subject) {
127
+ return [
128
+ '<!doctype html>',
129
+ `<html lang="${escapeXml(siteLocale(subject.site))}">`,
130
+ '<head>',
131
+ '<meta charset="utf-8">',
132
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
133
+ '<meta name="robots" content="noindex">',
134
+ `<title>${escapeXml(postLabel(subject.document))}</title>`,
135
+ `<style>${EMBED_STYLE}</style>`,
136
+ '</head>',
137
+ '<body>',
138
+ embedCard(subject),
139
+ `<script>${EMBED_SCRIPT}</script>`,
140
+ '</body>',
141
+ '</html>',
142
+ '',
143
+ ].join('\n');
144
+ }
145
+ /**
146
+ * The embed page as a response, or a 304 to a client that already holds it.
147
+ *
148
+ * Its policy is its own and complete: the one inline style and the one inline
149
+ * script by hash, nothing else loaded, and any origin may frame it. The
150
+ * caller marks the context frameable as well, so the baseline leaves off
151
+ * `X-Frame-Options`, which has no way to say "anyone".
152
+ */
153
+ export function embedResponse(page, conditional) {
154
+ const etag = contentEtag('embed', page);
155
+ const headers = new Headers({
156
+ etag,
157
+ 'cache-control': 'no-cache',
158
+ 'content-security-policy': EMBED_CONTENT_SECURITY_POLICY,
159
+ });
160
+ if (isNotModified(conditional, etag, undefined)) {
161
+ return new Response(null, { status: 304, headers });
162
+ }
163
+ headers.set('content-type', 'text/html; charset=utf-8');
164
+ return new Response(page, { headers });
165
+ }
166
+ /**
167
+ * The card's look inside the frame. Inline, like the script, because the page
168
+ * loads nothing: one request per embed is all a framing page should cost.
169
+ */
170
+ const EMBED_STYLE = [
171
+ 'html{color-scheme:light}',
172
+ 'body{margin:0;background:#fff;color:#1f1f1f;font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}',
173
+ '.geekity-embed{margin:0;padding:16px 20px;border:1px solid #d6d6d6;border-radius:8px;overflow-wrap:anywhere}',
174
+ '.geekity-embed p{margin:0 0 8px}',
175
+ '.geekity-embed p:last-child{margin:0;font-size:14px;color:#595959}',
176
+ '.geekity-embed a{color:#0b57d0}',
177
+ ].join('');
178
+ /**
179
+ * WordPress's embed protocol, the framed side (wp-includes/js/wp-embed.js is
180
+ * the other).
181
+ *
182
+ * WordPress puts a secret in the frame's URL as `#?secret=` and keeps the
183
+ * frame hidden until a message with that secret says how tall the page is.
184
+ * The page says so when it loads, when it is resized, and whenever the host
185
+ * says it is ready. Its frame is sandboxed with scripts only, so a link cannot
186
+ * navigate by itself: a click is sent to the host as a `link` message, which
187
+ * WordPress follows when the link is on this site's host. Opened directly
188
+ * there is no secret, and the links are ordinary links.
189
+ */
190
+ const EMBED_SCRIPT = [
191
+ '(function(){',
192
+ 'var found=/[?&]secret=([A-Za-z0-9]+)/.exec(location.hash);',
193
+ 'if(!found||window.parent===window)return;',
194
+ 'var secret=found[1];',
195
+ "function send(message,value){window.parent.postMessage({message:message,value:value,secret:secret},'*');}",
196
+ "function height(){send('height',Math.ceil(document.documentElement.getBoundingClientRect().height));}",
197
+ "addEventListener('load',height);",
198
+ "addEventListener('resize',height);",
199
+ "addEventListener('message',function(event){var data=event.data;if(data&&data.message==='ready'&&data.secret===secret)height();});",
200
+ "document.addEventListener('click',function(event){var link=event.target instanceof Element?event.target.closest('a[href]'):null;if(!link)return;event.preventDefault();send('link',link.href);});",
201
+ '})();',
202
+ ].join('');
203
+ /** A CSP source naming one inline element's exact contents. */
204
+ function inlineHash(contents) {
205
+ return `'sha256-${createHash('sha256').update(contents, 'utf8').digest('base64')}'`;
206
+ }
207
+ const EMBED_CONTENT_SECURITY_POLICY = [
208
+ "default-src 'none'",
209
+ `style-src ${inlineHash(EMBED_STYLE)}`,
210
+ `script-src ${inlineHash(EMBED_SCRIPT)}`,
211
+ "base-uri 'none'",
212
+ "form-action 'none'",
213
+ 'frame-ancestors *',
214
+ ].join('; ');
215
+ /**
216
+ * The frame of the embed view, in the shape WordPress's own provider sends:
217
+ * sandboxed, borderless, and hidden until the page inside reports its height.
218
+ */
219
+ function embedFrame(input) {
220
+ return [
221
+ '<iframe sandbox="allow-scripts" security="restricted"',
222
+ ` src="${escapeXml(input.src)}"`,
223
+ ` width="${String(input.width)}" height="${String(input.height)}"`,
224
+ ` title="${escapeXml(input.title)}"`,
225
+ ' frameborder="0" marginwidth="0" marginheight="0" scrolling="no"',
226
+ ' style="position: absolute; visibility: hidden;"></iframe>',
227
+ ].join('');
228
+ }
229
+ /** The XML spelling: one element per field, under `<oembed>`. */
230
+ function oEmbedXml(embed) {
231
+ const fields = Object.entries(embed).map(([name, value]) => ` <${name}>${escapeXml(String(value))}</${name}>`);
232
+ return [
233
+ '<?xml version="1.0" encoding="utf-8" standalone="yes"?>',
234
+ '<oembed>',
235
+ ...fields,
236
+ '</oembed>',
237
+ '',
238
+ ].join('\n');
239
+ }
240
+ /**
241
+ * The card of one document, as both the oEmbed html and the embed page carry
242
+ * it. A blockquote of escaped text and links, so it needs nothing from this
243
+ * site to draw and can carry nothing a consumer would run.
244
+ */
245
+ function embedCard(subject) {
246
+ const { document, author, site, config } = subject;
247
+ const url = (pathname) => absoluteUrl(pathname, config.baseUrl);
248
+ return card({
249
+ href: url(subject.href),
250
+ title: postLabel(document),
251
+ excerpt: document.title === '' ? '' : feedExcerpt(document),
252
+ author: author === undefined
253
+ ? undefined
254
+ : { name: author.name, url: author.url === undefined ? undefined : url(author.url) },
255
+ date: {
256
+ iso: formatDate(document.date, 'iso'),
257
+ readable: formatDate(document.date, 'readable', siteTimezone(site), siteLocale(site)),
258
+ },
259
+ site: { name: site.title, url: url('/') },
260
+ });
261
+ }
262
+ /** The card: a linked title, the excerpt, who and when, and the site it is from. */
263
+ function card(input) {
264
+ const link = (href, text) => `<a href="${escapeXml(href)}">${escapeXml(text)}</a>`;
265
+ const { author, date } = input;
266
+ const byline = [
267
+ ...(author === undefined
268
+ ? []
269
+ : [author.url === undefined ? escapeXml(author.name) : link(author.url, author.name)]),
270
+ ...(date.iso === ''
271
+ ? []
272
+ : [`<time datetime="${escapeXml(date.iso)}">${escapeXml(date.readable)}</time>`]),
273
+ ];
274
+ return [
275
+ `<blockquote class="geekity-embed" cite="${escapeXml(input.href)}">`,
276
+ `<p><strong>${link(input.href, input.title)}</strong></p>`,
277
+ ...(input.excerpt === '' ? [] : [`<p>${escapeXml(input.excerpt)}</p>`]),
278
+ ...(byline.length === 0 ? [] : [`<p>${byline.join(', ')}</p>`]),
279
+ `<p>${link(input.site.url, input.site.name)}</p>`,
280
+ '</blockquote>',
281
+ ].join('');
282
+ }
283
+ function positiveInteger(value) {
284
+ if (value === undefined || !/^[0-9]+$/.test(value))
285
+ return undefined;
286
+ const parsed = Number(value);
287
+ return parsed > 0 ? parsed : undefined;
288
+ }
289
+ //# sourceMappingURL=oembed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oembed.js","sourceRoot":"","sources":["../../src/web/oembed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { ImageConfig } from '../images/paths.ts';
2
+ import type { ConditionalHeaders } from './negotiate.ts';
3
+ /**
4
+ * The OpenSearch description (TASK-204): what a browser reads to offer the
5
+ * site's own search from its address bar.
6
+ *
7
+ * A fixed route at the root for the reason the manifest is one, so no
8
+ * permalink can take it, and linked from the head of every page.
9
+ */
10
+ /** The description's URL. */
11
+ export declare const OPENSEARCH_PATH = "/opensearch.xml";
12
+ /**
13
+ * The description of one site, as XML.
14
+ *
15
+ * The short name is the title cut to 16 characters, and the description is
16
+ * the tagline, else the title, since the spec requires one. The image is
17
+ * `/favicon.ico`, whose 16 pixel frame is the size the spec suggests, and is
18
+ * left out on a site with no icon, where that URL answers 404. Every URL is
19
+ * absolute, because a browser keeps the template and runs it from its address
20
+ * bar, with no page to resolve a relative one against.
21
+ */
22
+ export declare function openSearchDescription(input: {
23
+ config: ImageConfig & {
24
+ baseUrl: string;
25
+ };
26
+ site: {
27
+ title: string;
28
+ tagline?: string | undefined;
29
+ icon?: unknown;
30
+ avatar?: unknown;
31
+ };
32
+ }): string;
33
+ /** The description as a response, or a 304 to a client that already holds it. */
34
+ export declare function openSearchResponse(body: string, conditional: ConditionalHeaders | undefined): Response;
35
+ //# sourceMappingURL=opensearch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opensearch.d.ts","sourceRoot":"","sources":["../../src/web/opensearch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAItD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGzD;;;;;;GAMG;AAEH,6BAA6B;AAC7B,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAQjD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,MAAM,EAAE,WAAW,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACzF,GAAG,MAAM,CAsBT;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,kBAAkB,GAAG,SAAS,GAC1C,QAAQ,CAUV"}
@@ -0,0 +1,65 @@
1
+ import { iconSetting, siteIconSource } from '../images/icons.js';
2
+ import { FAVICON_PATH } from './manifest.js';
3
+ import { escapeXml } from './feed-xml.js';
4
+ import { absoluteUrl, contentEtag, isNotModified } from './negotiate.js';
5
+ import { SEARCH_PATH } from './search.js';
6
+ /**
7
+ * The OpenSearch description (TASK-204): what a browser reads to offer the
8
+ * site's own search from its address bar.
9
+ *
10
+ * A fixed route at the root for the reason the manifest is one, so no
11
+ * permalink can take it, and linked from the head of every page.
12
+ */
13
+ /** The description's URL. */
14
+ export const OPENSEARCH_PATH = '/opensearch.xml';
15
+ const OPENSEARCH_CONTENT_TYPE = 'application/opensearchdescription+xml; charset=utf-8';
16
+ /** OpenSearch 1.1 caps these two, and a browser may refuse a longer one. */
17
+ const SHORT_NAME_MAX = 16;
18
+ const DESCRIPTION_MAX = 1024;
19
+ /**
20
+ * The description of one site, as XML.
21
+ *
22
+ * The short name is the title cut to 16 characters, and the description is
23
+ * the tagline, else the title, since the spec requires one. The image is
24
+ * `/favicon.ico`, whose 16 pixel frame is the size the spec suggests, and is
25
+ * left out on a site with no icon, where that URL answers 404. Every URL is
26
+ * absolute, because a browser keeps the template and runs it from its address
27
+ * bar, with no page to resolve a relative one against.
28
+ */
29
+ export function openSearchDescription(input) {
30
+ const { config, site } = input;
31
+ const url = (pathname) => absoluteUrl(pathname, config.baseUrl);
32
+ const tagline = site.tagline?.trim() ?? '';
33
+ const hasIcon = siteIconSource(config, iconSetting(site)) !== undefined;
34
+ const lines = [
35
+ '<?xml version="1.0" encoding="UTF-8"?>',
36
+ '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">',
37
+ ` <ShortName>${escapeXml(cut(site.title, SHORT_NAME_MAX))}</ShortName>`,
38
+ ` <Description>${escapeXml(cut(tagline || site.title, DESCRIPTION_MAX))}</Description>`,
39
+ ' <InputEncoding>UTF-8</InputEncoding>',
40
+ ...(hasIcon
41
+ ? [
42
+ ` <Image width="16" height="16" type="image/x-icon">${escapeXml(url(FAVICON_PATH))}</Image>`,
43
+ ]
44
+ : []),
45
+ ` <Url type="text/html" method="get" template="${escapeXml(url(SEARCH_PATH))}?q={searchTerms}"/>`,
46
+ ` <Url type="application/opensearchdescription+xml" rel="self" template="${escapeXml(url(OPENSEARCH_PATH))}"/>`,
47
+ '</OpenSearchDescription>',
48
+ ];
49
+ return `${lines.join('\n')}\n`;
50
+ }
51
+ /** The description as a response, or a 304 to a client that already holds it. */
52
+ export function openSearchResponse(body, conditional) {
53
+ const etag = contentEtag('opensearch', body);
54
+ const headers = new Headers({ etag, 'cache-control': 'no-cache' });
55
+ if (isNotModified(conditional, etag, undefined)) {
56
+ return new Response(null, { status: 304, headers });
57
+ }
58
+ headers.set('content-type', OPENSEARCH_CONTENT_TYPE);
59
+ return new Response(body, { headers });
60
+ }
61
+ /** `text` cut to at most `max` characters, never inside a surrogate pair. */
62
+ function cut(text, max) {
63
+ return [...text].slice(0, max).join('').trimEnd();
64
+ }
65
+ //# sourceMappingURL=opensearch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opensearch.js","sourceRoot":"","sources":["../../src/web/opensearch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEjE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;;GAMG;AAEH,6BAA6B;AAC7B,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD,MAAM,uBAAuB,GAAG,sDAAsD,CAAC;AAEvF,4EAA4E;AAC5E,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,eAAe,GAAG,IAAI,CAAC;AAE7B;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAGrC;IACC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAC/B,MAAM,GAAG,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAChF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,SAAS,CAAC;IAExE,MAAM,KAAK,GAAG;QACZ,wCAAwC;QACxC,sEAAsE;QACtE,gBAAgB,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,cAAc;QACxE,kBAAkB,SAAS,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC,gBAAgB;QACxF,wCAAwC;QACxC,GAAG,CAAC,OAAO;YACT,CAAC,CAAC;gBACE,uDAAuD,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,UAAU;aAC9F;YACH,CAAC,CAAC,EAAE,CAAC;QACP,kDAAkD,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,qBAAqB;QAClG,4EAA4E,SAAS,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,KAAK;QAChH,0BAA0B;KAC3B,CAAC;IACF,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,WAA2C;IAE3C,MAAM,IAAI,GAAG,WAAW,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC,CAAC;IAEnE,IAAI,aAAa,CAAC,WAAW,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;QAChD,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,uBAAuB,CAAC,CAAC;IACrD,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,6EAA6E;AAC7E,SAAS,GAAG,CAAC,IAAY,EAAE,GAAW;IACpC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;AACpD,CAAC"}