react-cheminfo 0.4.1 → 0.7.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 (174) hide show
  1. package/README.md +198 -34
  2. package/lib/citation/core/index.d.ts +1 -0
  3. package/lib/citation/core/index.d.ts.map +1 -1
  4. package/lib/citation/core/index.js +1 -0
  5. package/lib/citation/core/index.js.map +1 -1
  6. package/lib/citation/core/platformPaper.d.ts +14 -0
  7. package/lib/citation/core/platformPaper.d.ts.map +1 -0
  8. package/lib/citation/core/platformPaper.js +28 -0
  9. package/lib/citation/core/platformPaper.js.map +1 -0
  10. package/lib/color/core/index.d.ts +1 -1
  11. package/lib/color/core/index.d.ts.map +1 -1
  12. package/lib/color/core/index.js +1 -1
  13. package/lib/color/core/index.js.map +1 -1
  14. package/lib/core.d.ts +1 -0
  15. package/lib/core.d.ts.map +1 -1
  16. package/lib/core.js +1 -0
  17. package/lib/core.js.map +1 -1
  18. package/lib/ecosystem/core/sites.d.ts +1 -1
  19. package/lib/ecosystem/core/sites.d.ts.map +1 -1
  20. package/lib/ecosystem/core/sites.js +15 -5
  21. package/lib/ecosystem/core/sites.js.map +1 -1
  22. package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
  23. package/lib/ecosystem/ui/glyphs.js +3 -0
  24. package/lib/ecosystem/ui/glyphs.js.map +1 -1
  25. package/lib/orbital/core/index.d.ts +1 -1
  26. package/lib/orbital/core/index.d.ts.map +1 -1
  27. package/lib/orbital/core/index.js +1 -1
  28. package/lib/orbital/core/index.js.map +1 -1
  29. package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts +5 -0
  30. package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts.map +1 -1
  31. package/lib/orbital/ui/AtomicOrbitalCanvas.js +4 -3
  32. package/lib/orbital/ui/AtomicOrbitalCanvas.js.map +1 -1
  33. package/lib/orbital/ui/AtomicOrbitalViewer.d.ts +5 -0
  34. package/lib/orbital/ui/AtomicOrbitalViewer.d.ts.map +1 -1
  35. package/lib/orbital/ui/AtomicOrbitalViewer.js.map +1 -1
  36. package/lib/orbital/ui/axesGeometry.d.ts +27 -0
  37. package/lib/orbital/ui/axesGeometry.d.ts.map +1 -0
  38. package/lib/orbital/ui/axesGeometry.js +74 -0
  39. package/lib/orbital/ui/axesGeometry.js.map +1 -0
  40. package/lib/orbital/ui/camera.d.ts +7 -0
  41. package/lib/orbital/ui/camera.d.ts.map +1 -1
  42. package/lib/orbital/ui/camera.js +8 -1
  43. package/lib/orbital/ui/camera.js.map +1 -1
  44. package/lib/orbital/ui/renderAxes.d.ts +53 -0
  45. package/lib/orbital/ui/renderAxes.d.ts.map +1 -0
  46. package/lib/orbital/ui/renderAxes.js +110 -0
  47. package/lib/orbital/ui/renderAxes.js.map +1 -0
  48. package/lib/orbital/ui/viewer.d.ts +16 -0
  49. package/lib/orbital/ui/viewer.d.ts.map +1 -1
  50. package/lib/orbital/ui/viewer.js +24 -2
  51. package/lib/orbital/ui/viewer.js.map +1 -1
  52. package/lib/periodic/core/categories.d.ts +25 -0
  53. package/lib/periodic/core/categories.d.ts.map +1 -0
  54. package/lib/periodic/core/categories.js +70 -0
  55. package/lib/periodic/core/categories.js.map +1 -0
  56. package/lib/periodic/core/elements.d.ts +49 -0
  57. package/lib/periodic/core/elements.d.ts.map +1 -0
  58. package/lib/periodic/core/elements.js +175 -0
  59. package/lib/periodic/core/elements.js.map +1 -0
  60. package/lib/periodic/core/index.d.ts +6 -0
  61. package/lib/periodic/core/index.d.ts.map +1 -0
  62. package/lib/periodic/core/index.js +4 -0
  63. package/lib/periodic/core/index.js.map +1 -0
  64. package/lib/periodic/core/layout.d.ts +73 -0
  65. package/lib/periodic/core/layout.d.ts.map +1 -0
  66. package/lib/periodic/core/layout.js +132 -0
  67. package/lib/periodic/core/layout.js.map +1 -0
  68. package/lib/periodic/ui/CategoryLegend.d.ts +26 -0
  69. package/lib/periodic/ui/CategoryLegend.d.ts.map +1 -0
  70. package/lib/periodic/ui/CategoryLegend.js +53 -0
  71. package/lib/periodic/ui/CategoryLegend.js.map +1 -0
  72. package/lib/periodic/ui/ElementCell.d.ts +53 -0
  73. package/lib/periodic/ui/ElementCell.d.ts.map +1 -0
  74. package/lib/periodic/ui/ElementCell.js +56 -0
  75. package/lib/periodic/ui/ElementCell.js.map +1 -0
  76. package/lib/periodic/ui/PeriodicTable.d.ts +90 -0
  77. package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -0
  78. package/lib/periodic/ui/PeriodicTable.js +77 -0
  79. package/lib/periodic/ui/PeriodicTable.js.map +1 -0
  80. package/lib/periodic/ui/PeriodicTableChrome.d.ts +36 -0
  81. package/lib/periodic/ui/PeriodicTableChrome.d.ts.map +1 -0
  82. package/lib/periodic/ui/PeriodicTableChrome.js +72 -0
  83. package/lib/periodic/ui/PeriodicTableChrome.js.map +1 -0
  84. package/lib/periodic/ui/index.d.ts +7 -0
  85. package/lib/periodic/ui/index.d.ts.map +1 -0
  86. package/lib/periodic/ui/index.js +4 -0
  87. package/lib/periodic/ui/index.js.map +1 -0
  88. package/lib/seo/core/documentMeta.d.ts +2 -2
  89. package/lib/seo/core/documentMeta.js +4 -3
  90. package/lib/seo/core/documentMeta.js.map +1 -1
  91. package/lib/seo/core/index.d.ts +13 -4
  92. package/lib/seo/core/index.d.ts.map +1 -1
  93. package/lib/seo/core/index.js +8 -3
  94. package/lib/seo/core/index.js.map +1 -1
  95. package/lib/seo/core/noscript.d.ts +97 -0
  96. package/lib/seo/core/noscript.d.ts.map +1 -0
  97. package/lib/seo/core/noscript.js +93 -0
  98. package/lib/seo/core/noscript.js.map +1 -0
  99. package/lib/seo/core/pageMeta.d.ts +30 -14
  100. package/lib/seo/core/pageMeta.d.ts.map +1 -1
  101. package/lib/seo/core/pageMeta.js +40 -43
  102. package/lib/seo/core/pageMeta.js.map +1 -1
  103. package/lib/seo/core/robots.d.ts +55 -0
  104. package/lib/seo/core/robots.d.ts.map +1 -0
  105. package/lib/seo/core/robots.js +70 -0
  106. package/lib/seo/core/robots.js.map +1 -0
  107. package/lib/seo/core/routes.d.ts +73 -5
  108. package/lib/seo/core/routes.d.ts.map +1 -1
  109. package/lib/seo/core/routes.js +142 -16
  110. package/lib/seo/core/routes.js.map +1 -1
  111. package/lib/seo/core/siteFiles.d.ts +39 -43
  112. package/lib/seo/core/siteFiles.d.ts.map +1 -1
  113. package/lib/seo/core/siteFiles.js +53 -69
  114. package/lib/seo/core/siteFiles.js.map +1 -1
  115. package/lib/seo/core/startDocumentMeta.d.ts +44 -0
  116. package/lib/seo/core/startDocumentMeta.d.ts.map +1 -0
  117. package/lib/seo/core/startDocumentMeta.js +47 -0
  118. package/lib/seo/core/startDocumentMeta.js.map +1 -0
  119. package/lib/seo/core/structuredData.d.ts +48 -0
  120. package/lib/seo/core/structuredData.d.ts.map +1 -0
  121. package/lib/seo/core/structuredData.js +41 -0
  122. package/lib/seo/core/structuredData.js.map +1 -0
  123. package/lib/seo/core/template.d.ts +48 -0
  124. package/lib/seo/core/template.d.ts.map +1 -0
  125. package/lib/seo/core/template.js +53 -0
  126. package/lib/seo/core/template.js.map +1 -0
  127. package/lib/seo/vite/ogCard.d.ts +9 -1
  128. package/lib/seo/vite/ogCard.d.ts.map +1 -1
  129. package/lib/seo/vite/ogCard.js +14 -4
  130. package/lib/seo/vite/ogCard.js.map +1 -1
  131. package/lib/seo/vite/prerender.d.ts +38 -7
  132. package/lib/seo/vite/prerender.d.ts.map +1 -1
  133. package/lib/seo/vite/prerender.js +68 -30
  134. package/lib/seo/vite/prerender.js.map +1 -1
  135. package/lib/ui.d.ts +1 -0
  136. package/lib/ui.d.ts.map +1 -1
  137. package/lib/ui.js +1 -0
  138. package/lib/ui.js.map +1 -1
  139. package/package.json +2 -1
  140. package/src/citation/core/index.ts +1 -0
  141. package/src/citation/core/platformPaper.ts +32 -0
  142. package/src/color/core/index.ts +6 -1
  143. package/src/core.ts +1 -0
  144. package/src/ecosystem/core/sites.ts +16 -5
  145. package/src/ecosystem/ui/glyphs.tsx +19 -0
  146. package/src/orbital/core/index.ts +1 -1
  147. package/src/orbital/ui/AtomicOrbitalCanvas.tsx +9 -2
  148. package/src/orbital/ui/AtomicOrbitalViewer.tsx +5 -0
  149. package/src/orbital/ui/axesGeometry.ts +91 -0
  150. package/src/orbital/ui/camera.ts +9 -1
  151. package/src/orbital/ui/renderAxes.ts +190 -0
  152. package/src/orbital/ui/viewer.ts +32 -2
  153. package/src/periodic/core/categories.ts +83 -0
  154. package/src/periodic/core/elements.ts +217 -0
  155. package/src/periodic/core/index.ts +27 -0
  156. package/src/periodic/core/layout.ts +183 -0
  157. package/src/periodic/ui/CategoryLegend.tsx +105 -0
  158. package/src/periodic/ui/ElementCell.tsx +137 -0
  159. package/src/periodic/ui/PeriodicTable.tsx +226 -0
  160. package/src/periodic/ui/PeriodicTableChrome.tsx +170 -0
  161. package/src/periodic/ui/index.ts +6 -0
  162. package/src/seo/core/documentMeta.ts +5 -5
  163. package/src/seo/core/index.ts +19 -12
  164. package/src/seo/core/noscript.ts +195 -0
  165. package/src/seo/core/pageMeta.ts +54 -53
  166. package/src/seo/core/robots.ts +114 -0
  167. package/src/seo/core/routes.ts +181 -14
  168. package/src/seo/core/siteFiles.ts +58 -96
  169. package/src/seo/core/startDocumentMeta.ts +77 -0
  170. package/src/seo/core/structuredData.ts +80 -0
  171. package/src/seo/core/template.ts +54 -0
  172. package/src/seo/vite/ogCard.ts +15 -5
  173. package/src/seo/vite/prerender.ts +105 -58
  174. package/src/ui.ts +1 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Keep the tab and the canonical link in step with the page on screen.
3
+ *
4
+ * The server, or the build that wrote one file per address, already titled the
5
+ * page it handed out; this is what a move inside the app changes, and what a
6
+ * crawler that renders the page reads afterwards. Every site did the same three
7
+ * things around it — read the address it is on, look it up in its route table,
8
+ * write the head — so all three live here, and a site says only where its
9
+ * address is read and how a change to it is noticed.
10
+ */
11
+ import type { PageMetaOptions } from './pageMeta.ts';
12
+ /** Where a site's address is read, and how a change to it is noticed. */
13
+ export interface StartDocumentMetaOptions extends Omit<PageMetaOptions, 'url' | 'image'> {
14
+ /**
15
+ * The address on screen, query string included: a path, or the absolute
16
+ * address read off the page. It is read again on every write, so a `follow`
17
+ * that tracks what it reads — a signals `effect` — notices the next page.
18
+ */
19
+ url: () => string;
20
+ /**
21
+ * How a change of page is noticed: `effect` from `@preact/signals-react`
22
+ * follows whichever signals `url` reads and hands back the function that
23
+ * stops it. Left out, the head is written once, which is what a site calling
24
+ * this from its own `popstate` handler wants.
25
+ * @default undefined — the head is written once
26
+ */
27
+ follow?: (write: () => void) => () => void;
28
+ }
29
+ /**
30
+ * Write the head of the page on screen, and keep it in step as the page
31
+ * changes.
32
+ *
33
+ * Nothing happens where there is no document — a prerender script, a unit test
34
+ * of the route table — so this is safe to call from a module either of them
35
+ * imports.
36
+ * @param options - The site, its routes, where its address is read and how a
37
+ * change to it is noticed.
38
+ * @returns The function that stops following, which does nothing when nothing
39
+ * was followed.
40
+ * @throws {Error} When the site answers no route, or names an origin that is
41
+ * not an absolute address.
42
+ */
43
+ export declare function startDocumentMeta(options: StartDocumentMetaOptions): () => void;
44
+ //# sourceMappingURL=startDocumentMeta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startDocumentMeta.d.ts","sourceRoot":"","sources":["../../../src/seo/core/startDocumentMeta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrD,yEAAyE;AACzE,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CACpD,eAAe,EACf,KAAK,GAAG,OAAO,CAChB;IACC;;;;OAIG;IACH,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;CAC5C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,wBAAwB,GAChC,MAAM,IAAI,CAeZ"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Keep the tab and the canonical link in step with the page on screen.
3
+ *
4
+ * The server, or the build that wrote one file per address, already titled the
5
+ * page it handed out; this is what a move inside the app changes, and what a
6
+ * crawler that renders the page reads afterwards. Every site did the same three
7
+ * things around it — read the address it is on, look it up in its route table,
8
+ * write the head — so all three live here, and a site says only where its
9
+ * address is read and how a change to it is noticed.
10
+ */
11
+ import { writeDocumentMeta } from "./documentMeta.js";
12
+ import { pageDocumentMeta } from "./pageMeta.js";
13
+ /**
14
+ * Write the head of the page on screen, and keep it in step as the page
15
+ * changes.
16
+ *
17
+ * Nothing happens where there is no document — a prerender script, a unit test
18
+ * of the route table — so this is safe to call from a module either of them
19
+ * imports.
20
+ * @param options - The site, its routes, where its address is read and how a
21
+ * change to it is noticed.
22
+ * @returns The function that stops following, which does nothing when nothing
23
+ * was followed.
24
+ * @throws {Error} When the site answers no route, or names an origin that is
25
+ * not an absolute address.
26
+ */
27
+ export function startDocumentMeta(options) {
28
+ if (typeof document === 'undefined')
29
+ return stopNothing;
30
+ const write = () => {
31
+ writeDocumentMeta(pageDocumentMeta({
32
+ site: options.site,
33
+ routes: options.routes,
34
+ url: options.url(),
35
+ origin: options.origin,
36
+ }));
37
+ };
38
+ return (options.follow ?? writeOnce)(write);
39
+ }
40
+ function writeOnce(write) {
41
+ write();
42
+ return stopNothing;
43
+ }
44
+ function stopNothing() {
45
+ // Nothing was followed, so there is nothing to stop.
46
+ }
47
+ //# sourceMappingURL=startDocumentMeta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startDocumentMeta.js","sourceRoot":"","sources":["../../../src/seo/core/startDocumentMeta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAuBjD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAiC;IAEjC,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,WAAW,CAAC;IAExD,MAAM,KAAK,GAAG,GAAS,EAAE;QACvB,iBAAiB,CACf,gBAAgB,CAAC;YACf,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CACH,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,SAAS,CAAC,KAAiB;IAClC,KAAK,EAAE,CAAC;IACR,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,WAAW;IAClB,qDAAqD;AACvD,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * One `application/ld+json` block describing the tool.
3
+ *
4
+ * It is the same on every page of a site — what varies per page is the head —
5
+ * so it is written into the built page once rather than per route.
6
+ */
7
+ import type { SiteFilesOptions } from './siteFiles.ts';
8
+ /** What the structured-data block says the tool is. */
9
+ export interface StructuredDataOptions extends SiteFilesOptions {
10
+ /**
11
+ * The schema.org application category.
12
+ * @default 'EducationalApplication'
13
+ */
14
+ category?: string;
15
+ /**
16
+ * What the tool needs to run.
17
+ * @default 'Any modern browser'
18
+ */
19
+ operatingSystem?: string;
20
+ /**
21
+ * What the tool does, in the words a search result is read in. A site whose
22
+ * indexed sentence says more than the line its tile in the family menu
23
+ * carries writes it here.
24
+ * @default the site's tagline
25
+ */
26
+ description?: string;
27
+ /**
28
+ * What a browser has to offer for the tool to run. Ours run in the page.
29
+ * @default 'Requires JavaScript'
30
+ */
31
+ browserRequirements?: string;
32
+ /**
33
+ * The currency the price is quoted in. The price is zero either way, but the
34
+ * pair has to agree with the audience the site is read by.
35
+ * @default 'EUR'
36
+ */
37
+ currency?: string;
38
+ }
39
+ /**
40
+ * The structured-data block, ready to put in the head.
41
+ *
42
+ * It always says the tool is free: the price is zero, and a block that leaves
43
+ * that implicit is one a rich result declines to show.
44
+ * @param options - The site, and what kind of application it is.
45
+ * @returns The script tag.
46
+ */
47
+ export declare function structuredDataScript(options: StructuredDataOptions): string;
48
+ //# sourceMappingURL=structuredData.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structuredData.d.ts","sourceRoot":"","sources":["../../../src/seo/core/structuredData.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAMvD,uDAAuD;AACvD,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC7D;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAwB3E"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * One `application/ld+json` block describing the tool.
3
+ *
4
+ * It is the same on every page of a site — what varies per page is the head —
5
+ * so it is written into the built page once rather than per route.
6
+ */
7
+ import { siteDisplayName } from "../../ecosystem/core/lookup.js";
8
+ import { originOf, resolveSite } from "./siteFiles.js";
9
+ /** The sequence that must not appear raw inside a script element. */
10
+ const SCRIPT_SAFE_LESS_THAN = String.raw `\u003c`;
11
+ /**
12
+ * The structured-data block, ready to put in the head.
13
+ *
14
+ * It always says the tool is free: the price is zero, and a block that leaves
15
+ * that implicit is one a rich result declines to show.
16
+ * @param options - The site, and what kind of application it is.
17
+ * @returns The script tag.
18
+ */
19
+ export function structuredDataScript(options) {
20
+ const site = resolveSite(options.site);
21
+ const data = {
22
+ '@context': 'https://schema.org',
23
+ '@type': 'WebApplication',
24
+ name: siteDisplayName(site),
25
+ url: `${originOf(options)}/`,
26
+ description: options.description ?? site.tagline,
27
+ applicationCategory: options.category ?? 'EducationalApplication',
28
+ operatingSystem: options.operatingSystem ?? 'Any modern browser',
29
+ browserRequirements: options.browserRequirements ?? 'Requires JavaScript',
30
+ offers: {
31
+ '@type': 'Offer',
32
+ price: '0',
33
+ priceCurrency: options.currency ?? 'EUR',
34
+ },
35
+ isAccessibleForFree: true,
36
+ publisher: { '@type': 'Organization', name: 'cheminfo' },
37
+ };
38
+ const json = JSON.stringify(data, null, 2).replaceAll('<', SCRIPT_SAFE_LESS_THAN);
39
+ return `<script type="application/ld+json">\n${json}\n</script>`;
40
+ }
41
+ //# sourceMappingURL=structuredData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structuredData.js","sourceRoot":"","sources":["../../../src/seo/core/structuredData.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGjE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEvD,qEAAqE;AACrE,MAAM,qBAAqB,GAAG,MAAM,CAAC,GAAG,CAAA,QAAQ,CAAC;AAkCjD;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA8B;IACjE,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG;QACX,UAAU,EAAE,oBAAoB;QAChC,OAAO,EAAE,gBAAgB;QACzB,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC;QAC3B,GAAG,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG;QAC5B,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO;QAChD,mBAAmB,EAAE,OAAO,CAAC,QAAQ,IAAI,wBAAwB;QACjE,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,oBAAoB;QAChE,mBAAmB,EAAE,OAAO,CAAC,mBAAmB,IAAI,qBAAqB;QACzE,MAAM,EAAE;YACN,OAAO,EAAE,OAAO;YAChB,KAAK,EAAE,GAAG;YACV,aAAa,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;SACzC;QACD,mBAAmB,EAAE,IAAI;QACzB,SAAS,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,EAAE;KACzD,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,CACnD,GAAG,EACH,qBAAqB,CACtB,CAAC;IACF,OAAO,wCAAwC,IAAI,aAAa,CAAC;AACnE,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Where a built page lets its head and its body be written.
3
+ *
4
+ * Every address of a site needs its own title, description, canonical and card,
5
+ * and a static build ships one `index.html`. Rather than look for the tags a
6
+ * page already carries and operate on them, the template says where they go:
7
+ * two comments, replaced by what the build or the server writes for the address
8
+ * being answered.
9
+ *
10
+ * ```html
11
+ * <head>
12
+ * <meta charset="utf-8" />
13
+ * <link rel="icon" href="%BASE_URL%favicon.svg" />
14
+ * <!--cheminfo:head-->
15
+ * </head>
16
+ * <body>
17
+ * <div id="root"></div>
18
+ * <!--cheminfo:body-->
19
+ * </body>
20
+ * ```
21
+ *
22
+ * The template carries no title and no description of its own, so nothing can
23
+ * be duplicated and nothing has to be taken back out. Nothing is parsed and
24
+ * nothing is searched for but the marker, so what the rest of the page holds — a
25
+ * byte order mark, an implicit head, a `</head>` its prose displays or a script
26
+ * quotes, an unterminated comment — cannot reach the result.
27
+ */
28
+ /** Where the head a crawler reads is written. */
29
+ export declare const PAGE_HEAD_MARKER = "<!--cheminfo:head-->";
30
+ /** Where the crawl path a visitor with no JavaScript reads is written. */
31
+ export declare const PAGE_BODY_MARKER = "<!--cheminfo:body-->";
32
+ /**
33
+ * Write content in the place the template kept for it.
34
+ *
35
+ * The marker is consumed, so a page is always filled from the template and
36
+ * never from a filled page: applying this twice throws rather than writing a
37
+ * second head, which is why idempotence is not something the caller has to
38
+ * defend.
39
+ * @param html - The template.
40
+ * @param marker - {@link PAGE_HEAD_MARKER} or {@link PAGE_BODY_MARKER}.
41
+ * @param content - The markup to write, taken as written: no `$&`, `$1` or
42
+ * `$<name>` is expanded.
43
+ * @returns The page.
44
+ * @throws {Error} When the template carries no such marker, rather than
45
+ * silently shipping a page with no head.
46
+ */
47
+ export declare function fill(html: string, marker: string, content: string): string;
48
+ //# sourceMappingURL=template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../../src/seo/core/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAI1E"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Where a built page lets its head and its body be written.
3
+ *
4
+ * Every address of a site needs its own title, description, canonical and card,
5
+ * and a static build ships one `index.html`. Rather than look for the tags a
6
+ * page already carries and operate on them, the template says where they go:
7
+ * two comments, replaced by what the build or the server writes for the address
8
+ * being answered.
9
+ *
10
+ * ```html
11
+ * <head>
12
+ * <meta charset="utf-8" />
13
+ * <link rel="icon" href="%BASE_URL%favicon.svg" />
14
+ * <!--cheminfo:head-->
15
+ * </head>
16
+ * <body>
17
+ * <div id="root"></div>
18
+ * <!--cheminfo:body-->
19
+ * </body>
20
+ * ```
21
+ *
22
+ * The template carries no title and no description of its own, so nothing can
23
+ * be duplicated and nothing has to be taken back out. Nothing is parsed and
24
+ * nothing is searched for but the marker, so what the rest of the page holds — a
25
+ * byte order mark, an implicit head, a `</head>` its prose displays or a script
26
+ * quotes, an unterminated comment — cannot reach the result.
27
+ */
28
+ /** Where the head a crawler reads is written. */
29
+ export const PAGE_HEAD_MARKER = '<!--cheminfo:head-->';
30
+ /** Where the crawl path a visitor with no JavaScript reads is written. */
31
+ export const PAGE_BODY_MARKER = '<!--cheminfo:body-->';
32
+ /**
33
+ * Write content in the place the template kept for it.
34
+ *
35
+ * The marker is consumed, so a page is always filled from the template and
36
+ * never from a filled page: applying this twice throws rather than writing a
37
+ * second head, which is why idempotence is not something the caller has to
38
+ * defend.
39
+ * @param html - The template.
40
+ * @param marker - {@link PAGE_HEAD_MARKER} or {@link PAGE_BODY_MARKER}.
41
+ * @param content - The markup to write, taken as written: no `$&`, `$1` or
42
+ * `$<name>` is expanded.
43
+ * @returns The page.
44
+ * @throws {Error} When the template carries no such marker, rather than
45
+ * silently shipping a page with no head.
46
+ */
47
+ export function fill(html, marker, content) {
48
+ const at = html.indexOf(marker);
49
+ if (at === -1)
50
+ throw new Error(`the page carries no ${marker}`);
51
+ return html.slice(0, at) + content + html.slice(at + marker.length);
52
+ }
53
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/seo/core/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,iDAAiD;AACjD,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAEvD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAEvD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,MAAc,EAAE,OAAe;IAChE,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,KAAK,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,EAAE,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AACtE,CAAC"}
@@ -4,6 +4,10 @@
4
4
  * The card is the site's own mark, its two colours and its name, all read from
5
5
  * its record — so it is generated rather than hand-drawn. A mark redrawn in the
6
6
  * card is a mark that drifts from the one the site shows.
7
+ *
8
+ * React is reached for only when a card is actually drawn, and never at module
9
+ * load: this module sits in the same entry point as the prerender plugin, which
10
+ * a site must be able to import with none of the optional peers installed.
7
11
  */
8
12
  import type { EcosystemSite, SiteId } from '../../ecosystem/core/sites.ts';
9
13
  /** The width every card is drawn at. */
@@ -26,8 +30,12 @@ export interface OgCardOptions {
26
30
  * Screenshot it at {@link OG_WIDTH} × {@link OG_HEIGHT} — a headless browser is
27
31
  * the only thing here that can rasterise it, and every site already has one for
28
32
  * its end-to-end tests.
33
+ *
34
+ * Drawing the mark is the one thing here that needs React, so React is loaded
35
+ * on the call rather than by the module. The card is drawn by a build script
36
+ * that already awaits a browser, so the await costs it nothing.
29
37
  * @param options - Which site, and what it says.
30
38
  * @returns A complete HTML document.
31
39
  */
32
- export declare function ogCardHtml(options: OgCardOptions): string;
40
+ export declare function ogCardHtml(options: OgCardOptions): Promise<string>;
33
41
  //# sourceMappingURL=ogCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ogCard.d.ts","sourceRoot":"","sources":["../../../src/seo/vite/ogCard.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAI3E,wCAAwC;AACxC,eAAO,MAAM,QAAQ,OAAO,CAAC;AAE7B,yCAAyC;AACzC,eAAO,MAAM,SAAS,MAAM,CAAC;AAE7B,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,IAAI,EAAE,aAAa,GAAG,MAAM,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAiDzD"}
1
+ {"version":3,"file":"ogCard.d.ts","sourceRoot":"","sources":["../../../src/seo/vite/ogCard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAG3E,wCAAwC;AACxC,eAAO,MAAM,QAAQ,OAAO,CAAC;AAE7B,yCAAyC;AACzC,eAAO,MAAM,SAAS,MAAM,CAAC;AAE7B,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,IAAI,EAAE,aAAa,GAAG,MAAM,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAuDxE"}
@@ -4,11 +4,12 @@
4
4
  * The card is the site's own mark, its two colours and its name, all read from
5
5
  * its record — so it is generated rather than hand-drawn. A mark redrawn in the
6
6
  * card is a mark that drifts from the one the site shows.
7
+ *
8
+ * React is reached for only when a card is actually drawn, and never at module
9
+ * load: this module sits in the same entry point as the prerender plugin, which
10
+ * a site must be able to import with none of the optional peers installed.
7
11
  */
8
- import { createElement } from 'react';
9
- import { renderToStaticMarkup } from 'react-dom/server';
10
12
  import { siteById } from "../../ecosystem/core/lookup.js";
11
- import { SiteMark } from "../../ecosystem/ui/marks.js";
12
13
  import { escapeText } from "../../share/core/escape.js";
13
14
  /** The width every card is drawn at. */
14
15
  export const OG_WIDTH = 1200;
@@ -20,12 +21,21 @@ export const OG_HEIGHT = 630;
20
21
  * Screenshot it at {@link OG_WIDTH} × {@link OG_HEIGHT} — a headless browser is
21
22
  * the only thing here that can rasterise it, and every site already has one for
22
23
  * its end-to-end tests.
24
+ *
25
+ * Drawing the mark is the one thing here that needs React, so React is loaded
26
+ * on the call rather than by the module. The card is drawn by a build script
27
+ * that already awaits a browser, so the await costs it nothing.
23
28
  * @param options - Which site, and what it says.
24
29
  * @returns A complete HTML document.
25
30
  */
26
- export function ogCardHtml(options) {
31
+ export async function ogCardHtml(options) {
27
32
  const site = typeof options.site === 'string' ? siteById(options.site) : options.site;
28
33
  const description = options.description ?? site.tagline;
34
+ const [{ createElement }, { renderToStaticMarkup }, { SiteMark }] = await Promise.all([
35
+ import('react'),
36
+ import('react-dom/server'),
37
+ import("../../ecosystem/ui/marks.js"),
38
+ ]);
29
39
  const mark = renderToStaticMarkup(createElement(SiteMark, { site, size: 132, colors: 'literal' }));
30
40
  const dot = site.name.dot === true ? '<span class="dot">.</span>' : '';
31
41
  return `<!doctype html>
@@ -1 +1 @@
1
- {"version":3,"file":"ogCard.js","sourceRoot":"","sources":["../../../src/seo/vite/ogCard.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAE1D,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA8B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAExD,wCAAwC;AACxC,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC;AAE7B,yCAAyC;AACzC,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAa7B;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,OAAsB;IAC/C,MAAM,IAAI,GACR,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3E,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO,CAAC;IACxD,MAAM,IAAI,GAAG,oBAAoB,CAC/B,aAAa,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAChE,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvE,OAAO;;;;;;;;iBAQQ,QAAQ;kBACP,SAAS;;;;;;;;;;;uBAWJ,IAAI,CAAC,KAAK;sBACX,IAAI,CAAC,QAAQ;;;;;;;sBAOb,IAAI,CAAC,QAAQ;;;;;MAK7B,IAAI;6BACmB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,qBAAqB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;;SAEzG,UAAU,CAAC,WAAW,CAAC;;;CAG/B,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"ogCard.js","sourceRoot":"","sources":["../../../src/seo/vite/ogCard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAExD,wCAAwC;AACxC,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC;AAE7B,yCAAyC;AACzC,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAa7B;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAsB;IACrD,MAAM,IAAI,GACR,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3E,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO,CAAC;IACxD,MAAM,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,oBAAoB,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,GAC/D,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,MAAM,CAAC,OAAO,CAAC;QACf,MAAM,CAAC,kBAAkB,CAAC;QAC1B,MAAM,CAAC,6BAA8B,CAAC;KACvC,CAAC,CAAC;IACL,MAAM,IAAI,GAAG,oBAAoB,CAC/B,aAAa,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAChE,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvE,OAAO;;;;;;;;iBAQQ,QAAQ;kBACP,SAAS;;;;;;;;;;;uBAWJ,IAAI,CAAC,KAAK;sBACX,IAAI,CAAC,QAAQ;;;;;;;sBAOb,IAAI,CAAC,QAAQ;;;;;MAK7B,IAAI;6BACmB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,qBAAqB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;;SAEzG,UAAU,CAAC,WAAW,CAAC;;;CAG/B,CAAC;AACF,CAAC"}
@@ -6,6 +6,11 @@
6
6
  * crawler gets whatever came off the wire. Without this every address carries
7
7
  * the same title and a search engine folds the whole site into one result.
8
8
  *
9
+ * The build's `index.html` is the template: it declares where its head and its
10
+ * crawl path go with `<!--cheminfo:head-->` and `<!--cheminfo:body-->`, and
11
+ * every file written here is filled from it. The dev server is filled the same
12
+ * way, from the home route, so what a developer opens is what the build ships.
13
+ *
9
14
  * These files are also what makes the server's catch-all fallback unnecessary.
10
15
  * Every address the tool answers is on disk, so an address that is *not* on
11
16
  * disk is genuinely not a page and must 404 rather than serving the tool under
@@ -13,6 +18,8 @@
13
18
  */
14
19
  import type { Plugin } from 'vite';
15
20
  import type { EcosystemSite, SiteId } from '../../ecosystem/core/sites.ts';
21
+ import type { NoscriptText } from '../core/noscript.ts';
22
+ import type { RobotsDisallow } from '../core/robots.ts';
16
23
  import type { RouteMeta } from '../core/routes.ts';
17
24
  /** What the build needs to know to write the site's addresses. */
18
25
  export interface PrerenderOptions {
@@ -21,16 +28,20 @@ export interface PrerenderOptions {
21
28
  /** Every address it answers, each with its title and description. */
22
29
  routes: readonly RouteMeta[];
23
30
  /**
24
- * Origin every absolute address is built on.
31
+ * Where the site is served, mount path included. Every absolute address is
32
+ * built on it, and `robots.txt` and the `noscript` index write their paths
33
+ * under its mount. The files on disk are laid out from the build's own root
34
+ * either way: it is the server that puts them under the mount.
25
35
  * @default `https://<the site's host>`
26
36
  */
27
37
  origin?: string;
28
38
  /**
29
- * Address prefixes `robots.txt` keeps out of the index, e.g. `/v1/`. Set to
30
- * `false` to write no `robots.txt` at all, for a site that ships its own.
39
+ * Addresses `robots.txt` keeps out of the index, e.g. `/v1/`, each optionally
40
+ * with the sentence saying why. Set to `false` to write no `robots.txt` at
41
+ * all, for a site that ships its own.
31
42
  * @default []
32
43
  */
33
- robots?: false | readonly string[];
44
+ robots?: false | ReadonlyArray<string | RobotsDisallow>;
34
45
  /**
35
46
  * The schema.org category of the structured-data block, or `false` to write
36
47
  * none.
@@ -43,16 +54,36 @@ export interface PrerenderOptions {
43
54
  */
44
55
  operatingSystem?: string;
45
56
  /**
46
- * Whether the built page carries a `noscript` index of the addresses. It is
47
- * the only crawl path through a site whose body is an empty root element.
57
+ * What the tool does, in the structured-data block.
58
+ * @default the site's tagline
59
+ */
60
+ description?: string;
61
+ /**
62
+ * What a browser has to offer for the tool to run.
63
+ * @default 'Requires JavaScript'
64
+ */
65
+ browserRequirements?: string;
66
+ /**
67
+ * The currency the zero price is quoted in.
68
+ * @default 'EUR'
69
+ */
70
+ currency?: string;
71
+ /**
72
+ * Whether the built page carries a `noscript` index of the addresses, what it
73
+ * says, which pages it lists and how it writes their addresses. It is the
74
+ * only crawl path through a site whose body is an empty root element, so
75
+ * `false` leaves one without any — and is also what lets a template ship no
76
+ * `<!--cheminfo:body-->` at all.
48
77
  * @default true
49
78
  */
50
- noscript?: boolean;
79
+ noscript?: boolean | NoscriptText;
51
80
  }
52
81
  /**
53
82
  * Prerender every routed address of a cheminfo site.
54
83
  * @param options - The site, its routes, and what a crawler is told.
55
84
  * @returns The Vite plugin.
85
+ * @throws {Error} When the route table names an address twice or names one that
86
+ * is not a path, or when the origin is not an absolute address.
56
87
  */
57
88
  export declare function cheminfoPrerender(options: PrerenderOptions): Plugin;
58
89
  //# sourceMappingURL=prerender.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prerender.d.ts","sourceRoot":"","sources":["../../../src/seo/vite/prerender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAE3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAQnD,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,IAAI,EAAE,aAAa,GAAG,MAAM,CAAC;IAC7B,qEAAqE;IACrE,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,SAAS,MAAM,EAAE,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC1B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CA+EnE"}
1
+ {"version":3,"file":"prerender.d.ts","sourceRoot":"","sources":["../../../src/seo/vite/prerender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAMnD,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,IAAI,EAAE,aAAa,GAAG,MAAM,CAAC;IAC7B,qEAAqE;IACrE,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,aAAa,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC;IACxD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC1B;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,YAAY,CAAC;CACnC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CA6EnE"}
@@ -6,6 +6,11 @@
6
6
  * crawler gets whatever came off the wire. Without this every address carries
7
7
  * the same title and a search engine folds the whole site into one result.
8
8
  *
9
+ * The build's `index.html` is the template: it declares where its head and its
10
+ * crawl path go with `<!--cheminfo:head-->` and `<!--cheminfo:body-->`, and
11
+ * every file written here is filled from it. The dev server is filled the same
12
+ * way, from the home route, so what a developer opens is what the build ships.
13
+ *
9
14
  * These files are also what makes the server's catch-all fallback unnecessary.
10
15
  * Every address the tool answers is on disk, so an address that is *not* on
11
16
  * disk is genuinely not a page and must 404 rather than serving the tool under
@@ -13,52 +18,67 @@
13
18
  */
14
19
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
15
20
  import { dirname, join, resolve } from 'node:path';
16
- import { injectPageMeta, insertBeforeHeadEnd } from "../core/pageMeta.js";
17
- import { noscriptIndex, robotsTxt, sitemapXml, structuredDataScript, } from "../core/siteFiles.js";
21
+ import { noscriptIndex } from "../core/noscript.js";
22
+ import { pageHeadTags } from "../core/pageMeta.js";
23
+ import { robotsTxt } from "../core/robots.js";
24
+ import { assertRoutes, homeRoute, trimTrailingSlash } from "../core/routes.js";
25
+ import { sitemapXml } from "../core/siteFiles.js";
26
+ import { structuredDataScript } from "../core/structuredData.js";
27
+ import { PAGE_BODY_MARKER, PAGE_HEAD_MARKER, fill } from "../core/template.js";
18
28
  /**
19
29
  * Prerender every routed address of a cheminfo site.
20
30
  * @param options - The site, its routes, and what a crawler is told.
21
31
  * @returns The Vite plugin.
32
+ * @throws {Error} When the route table names an address twice or names one that
33
+ * is not a path, or when the origin is not an absolute address.
22
34
  */
23
35
  export function cheminfoPrerender(options) {
24
- const { site, routes, origin, robots = [], category, operatingSystem, noscript = true, } = options;
36
+ const { site, routes, origin, robots = [] } = options;
37
+ assertRoutes(routes);
38
+ const structuredData = structuredDataOf(options);
39
+ const crawlPath = crawlPathOf(options);
25
40
  let out = 'dist';
41
+ let serve = false;
26
42
  let logger = null;
43
+ const page = (template, url) => {
44
+ const head = fill(template, PAGE_HEAD_MARKER, `${pageHeadTags({ site, routes, origin, url })}${structuredData}`);
45
+ return crawlPath === '' ? head : fill(head, PAGE_BODY_MARKER, crawlPath);
46
+ };
27
47
  return {
28
48
  name: 'cheminfo:prerender',
29
- apply: 'build',
30
49
  configResolved(config) {
50
+ serve = config.command === 'serve';
31
51
  out = resolve(config.root, config.build.outDir);
32
52
  logger = config.logger;
33
53
  },
54
+ // A dev run has no build to prerender, so the page vite serves is filled
55
+ // from the home route rather than shipped with its markers showing.
34
56
  transformIndexHtml: {
35
57
  order: 'post',
36
- handler(html) {
37
- let page = html;
38
- if (category !== false) {
39
- page = insertBeforeHeadEnd(page, structuredDataScript({
40
- site,
41
- routes,
42
- origin,
43
- category,
44
- operatingSystem,
45
- }));
46
- }
47
- if (noscript) {
48
- page = insertBeforeBodyEnd(page, noscriptIndex({ site, routes, origin }));
49
- }
50
- return page;
51
- },
58
+ handler: (html) => serve ? page(html, homeRoute(routes).path) : html,
52
59
  },
53
60
  closeBundle() {
54
- const index = readFileSync(join(out, 'index.html'), 'utf8');
61
+ if (serve)
62
+ return;
63
+ const template = readFileSync(join(out, 'index.html'), 'utf8');
64
+ const write = (url, file) => {
65
+ mkdirSync(dirname(file), { recursive: true });
66
+ writeFileSync(file, page(template, url));
67
+ };
68
+ let root = false;
55
69
  for (const route of routes) {
56
- const file = route.path === '/'
70
+ const address = trimTrailingSlash(route.path);
71
+ if (address === '/')
72
+ root = true;
73
+ write(route.path, address === '/'
57
74
  ? join(out, 'index.html')
58
- : join(out, route.path.slice(1), 'index.html');
59
- mkdirSync(dirname(file), { recursive: true });
60
- writeFileSync(file, injectPageMeta(index, { site, routes, origin, url: route.path }));
75
+ : join(out, address.slice(1), 'index.html'));
61
76
  }
77
+ // The file a static server hands out for the mount itself. A table naming
78
+ // no root would otherwise leave the template vite built, and ship a site
79
+ // whose front page carries its markers instead of a head.
80
+ if (!root)
81
+ write(homeRoute(routes).path, join(out, 'index.html'));
62
82
  writeFileSync(join(out, 'sitemap.xml'), sitemapXml({ site, routes, origin }));
63
83
  if (robots !== false) {
64
84
  writeFileSync(join(out, 'robots.txt'), robotsTxt({ site, routes, origin }, robots));
@@ -67,10 +87,28 @@ export function cheminfoPrerender(options) {
67
87
  },
68
88
  };
69
89
  }
70
- function insertBeforeBodyEnd(html, addition) {
71
- const body = html.lastIndexOf('</body>');
72
- if (body === -1)
73
- return `${html}\n${addition}\n`;
74
- return `${html.slice(0, body)}${addition}\n${html.slice(body)}`;
90
+ function structuredDataOf(options) {
91
+ const { category, ...rest } = options;
92
+ if (category === false)
93
+ return '';
94
+ const { site, routes, origin } = rest;
95
+ const { operatingSystem, description, browserRequirements, currency } = rest;
96
+ return `\n${structuredDataScript({
97
+ site,
98
+ routes,
99
+ origin,
100
+ category,
101
+ operatingSystem,
102
+ description,
103
+ browserRequirements,
104
+ currency,
105
+ })}`;
106
+ }
107
+ function crawlPathOf(options) {
108
+ const { site, routes, origin, noscript = true } = options;
109
+ if (noscript === false)
110
+ return '';
111
+ const { routes: listed, ...prose } = noscript === true ? {} : noscript;
112
+ return noscriptIndex({ site, origin, ...prose, routes: listed ?? routes });
75
113
  }
76
114
  //# sourceMappingURL=prerender.js.map