react-cheminfo 0.34.0 → 0.36.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 (225) hide show
  1. package/README.md +28 -28
  2. package/bin/check-seo.mjs +333 -0
  3. package/lib/about/core/about.d.ts +1 -30
  4. package/lib/about/core/about.d.ts.map +1 -1
  5. package/lib/about/core/about.js +0 -16
  6. package/lib/about/core/about.js.map +1 -1
  7. package/lib/about/core/index.d.ts +0 -1
  8. package/lib/about/core/index.d.ts.map +1 -1
  9. package/lib/about/core/index.js +0 -1
  10. package/lib/about/core/index.js.map +1 -1
  11. package/lib/about/ui/AboutBuild.d.ts +2 -5
  12. package/lib/about/ui/AboutBuild.d.ts.map +1 -1
  13. package/lib/about/ui/AboutBuild.js +7 -19
  14. package/lib/about/ui/AboutBuild.js.map +1 -1
  15. package/lib/about/ui/AboutPage.d.ts +2 -2
  16. package/lib/about/ui/AboutPage.d.ts.map +1 -1
  17. package/lib/about/ui/AboutPage.js +3 -14
  18. package/lib/about/ui/AboutPage.js.map +1 -1
  19. package/lib/about/ui/AboutVersion.d.ts +0 -4
  20. package/lib/about/ui/AboutVersion.d.ts.map +1 -1
  21. package/lib/about/ui/AboutVersion.js +3 -31
  22. package/lib/about/ui/AboutVersion.js.map +1 -1
  23. package/lib/download/core/index.d.ts +0 -5
  24. package/lib/download/core/index.d.ts.map +1 -1
  25. package/lib/download/core/index.js +0 -3
  26. package/lib/download/core/index.js.map +1 -1
  27. package/lib/download/ui/FigureDownload.d.ts +4 -55
  28. package/lib/download/ui/FigureDownload.d.ts.map +1 -1
  29. package/lib/download/ui/FigureDownload.js +6 -66
  30. package/lib/download/ui/FigureDownload.js.map +1 -1
  31. package/lib/download/ui/FigureDownloadPanel.d.ts +16 -2
  32. package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
  33. package/lib/download/ui/FigureDownloadPanel.js +11 -30
  34. package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
  35. package/lib/download/ui/figureDownloadHint.d.ts +30 -0
  36. package/lib/download/ui/figureDownloadHint.d.ts.map +1 -0
  37. package/lib/download/ui/figureDownloadHint.js +33 -0
  38. package/lib/download/ui/figureDownloadHint.js.map +1 -0
  39. package/lib/download/ui/index.d.ts +3 -10
  40. package/lib/download/ui/index.d.ts.map +1 -1
  41. package/lib/download/ui/index.js +1 -5
  42. package/lib/download/ui/index.js.map +1 -1
  43. package/lib/download/ui/useFigureActions.d.ts +50 -0
  44. package/lib/download/ui/useFigureActions.d.ts.map +1 -0
  45. package/lib/download/ui/useFigureActions.js +58 -0
  46. package/lib/download/ui/useFigureActions.js.map +1 -0
  47. package/lib/download/ui/useFigureDownload.d.ts +85 -0
  48. package/lib/download/ui/useFigureDownload.d.ts.map +1 -0
  49. package/lib/download/ui/useFigureDownload.js +75 -0
  50. package/lib/download/ui/useFigureDownload.js.map +1 -0
  51. package/lib/ecosystem/core/sites.d.ts.map +1 -1
  52. package/lib/ecosystem/core/sites.js +0 -40
  53. package/lib/ecosystem/core/sites.js.map +1 -1
  54. package/lib/ecosystem/core/types.d.ts +0 -10
  55. package/lib/ecosystem/core/types.d.ts.map +1 -1
  56. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  57. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  58. package/lib/ir/core/irCommands.d.ts +2 -2
  59. package/lib/ir/core/irCommands.js +2 -2
  60. package/lib/ir/core/irCommands.js.map +1 -1
  61. package/lib/ir/ui/IrAboutDialog.d.ts.map +1 -1
  62. package/lib/ir/ui/IrAboutDialog.js +1 -1
  63. package/lib/ir/ui/IrAboutDialog.js.map +1 -1
  64. package/lib/ir/ui/IrCanvas.d.ts +6 -0
  65. package/lib/ir/ui/IrCanvas.d.ts.map +1 -1
  66. package/lib/ir/ui/IrCanvas.js +2 -2
  67. package/lib/ir/ui/IrCanvas.js.map +1 -1
  68. package/lib/ir/ui/IrShell.d.ts.map +1 -1
  69. package/lib/ir/ui/IrShell.js +16 -11
  70. package/lib/ir/ui/IrShell.js.map +1 -1
  71. package/lib/ir/ui/IrToolbar.d.ts +11 -1
  72. package/lib/ir/ui/IrToolbar.d.ts.map +1 -1
  73. package/lib/ir/ui/IrToolbar.js +12 -2
  74. package/lib/ir/ui/IrToolbar.js.map +1 -1
  75. package/lib/ir/ui/index.d.ts +1 -0
  76. package/lib/ir/ui/index.d.ts.map +1 -1
  77. package/lib/ir/ui/index.js.map +1 -1
  78. package/lib/language/core/index.d.ts +2 -0
  79. package/lib/language/core/index.d.ts.map +1 -1
  80. package/lib/language/core/index.js +1 -0
  81. package/lib/language/core/index.js.map +1 -1
  82. package/lib/language/core/languagePath.d.ts +52 -0
  83. package/lib/language/core/languagePath.d.ts.map +1 -0
  84. package/lib/language/core/languagePath.js +65 -0
  85. package/lib/language/core/languagePath.js.map +1 -0
  86. package/lib/locales/de.json +2 -4
  87. package/lib/locales/en.json +2 -4
  88. package/lib/locales/es.json +2 -4
  89. package/lib/locales/fr.json +2 -4
  90. package/lib/locales/it.json +2 -4
  91. package/lib/overlay/ui/OverlayPanel.d.ts +6 -0
  92. package/lib/overlay/ui/OverlayPanel.d.ts.map +1 -1
  93. package/lib/overlay/ui/OverlayPanel.js +2 -2
  94. package/lib/overlay/ui/OverlayPanel.js.map +1 -1
  95. package/lib/overlay/ui/overlayPanelStyles.d.ts +2 -1
  96. package/lib/overlay/ui/overlayPanelStyles.d.ts.map +1 -1
  97. package/lib/overlay/ui/overlayPanelStyles.js +3 -1
  98. package/lib/overlay/ui/overlayPanelStyles.js.map +1 -1
  99. package/lib/periodic/ui/ElementCell.d.ts +0 -6
  100. package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
  101. package/lib/periodic/ui/ElementCell.js +34 -30
  102. package/lib/periodic/ui/ElementCell.js.map +1 -1
  103. package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
  104. package/lib/periodic/ui/PeriodicTable.js +12 -15
  105. package/lib/periodic/ui/PeriodicTable.js.map +1 -1
  106. package/lib/seo/core/alternates.d.ts +40 -0
  107. package/lib/seo/core/alternates.d.ts.map +1 -0
  108. package/lib/seo/core/alternates.js +42 -0
  109. package/lib/seo/core/alternates.js.map +1 -0
  110. package/lib/seo/core/index.d.ts +3 -0
  111. package/lib/seo/core/index.d.ts.map +1 -1
  112. package/lib/seo/core/index.js +2 -0
  113. package/lib/seo/core/index.js.map +1 -1
  114. package/lib/seo/core/noscript.d.ts +9 -0
  115. package/lib/seo/core/noscript.d.ts.map +1 -1
  116. package/lib/seo/core/noscript.js +7 -2
  117. package/lib/seo/core/noscript.js.map +1 -1
  118. package/lib/seo/core/pageMeta.d.ts +23 -0
  119. package/lib/seo/core/pageMeta.d.ts.map +1 -1
  120. package/lib/seo/core/pageMeta.js +52 -3
  121. package/lib/seo/core/pageMeta.js.map +1 -1
  122. package/lib/seo/core/pageProse.d.ts +76 -0
  123. package/lib/seo/core/pageProse.d.ts.map +1 -0
  124. package/lib/seo/core/pageProse.js +92 -0
  125. package/lib/seo/core/pageProse.js.map +1 -0
  126. package/lib/seo/core/routeProblems.d.ts +33 -0
  127. package/lib/seo/core/routeProblems.d.ts.map +1 -0
  128. package/lib/seo/core/routeProblems.js +89 -0
  129. package/lib/seo/core/routeProblems.js.map +1 -0
  130. package/lib/seo/core/routes.d.ts +14 -0
  131. package/lib/seo/core/routes.d.ts.map +1 -1
  132. package/lib/seo/core/routes.js.map +1 -1
  133. package/lib/seo/core/siteFiles.d.ts.map +1 -1
  134. package/lib/seo/core/siteFiles.js +2 -1
  135. package/lib/seo/core/siteFiles.js.map +1 -1
  136. package/lib/seo/vite/prerender.d.ts +13 -0
  137. package/lib/seo/vite/prerender.d.ts.map +1 -1
  138. package/lib/seo/vite/prerender.js +17 -10
  139. package/lib/seo/vite/prerender.js.map +1 -1
  140. package/package.json +2 -1
  141. package/src/about/core/about.ts +1 -51
  142. package/src/about/core/index.ts +0 -1
  143. package/src/about/ui/AboutBuild.tsx +8 -37
  144. package/src/about/ui/AboutPage.tsx +8 -64
  145. package/src/about/ui/AboutVersion.tsx +5 -45
  146. package/src/download/core/index.ts +0 -25
  147. package/src/download/ui/FigureDownload.tsx +11 -148
  148. package/src/download/ui/FigureDownloadPanel.tsx +46 -45
  149. package/src/download/ui/figureDownloadHint.ts +52 -0
  150. package/src/download/ui/index.ts +6 -10
  151. package/src/download/ui/useFigureActions.ts +113 -0
  152. package/src/download/ui/useFigureDownload.tsx +191 -0
  153. package/src/ecosystem/core/sites.ts +0 -40
  154. package/src/ecosystem/core/types.ts +0 -10
  155. package/src/ir/core/irCommands.ts +2 -2
  156. package/src/ir/ui/IrAboutDialog.tsx +0 -13
  157. package/src/ir/ui/IrCanvas.tsx +8 -2
  158. package/src/ir/ui/IrShell.tsx +23 -25
  159. package/src/ir/ui/IrToolbar.tsx +36 -6
  160. package/src/ir/ui/index.ts +1 -0
  161. package/src/language/core/index.ts +2 -0
  162. package/src/language/core/languagePath.ts +78 -0
  163. package/src/locales/de.json +2 -4
  164. package/src/locales/en.json +2 -4
  165. package/src/locales/es.json +2 -4
  166. package/src/locales/fr.json +2 -4
  167. package/src/locales/it.json +2 -4
  168. package/src/overlay/ui/OverlayPanel.tsx +8 -2
  169. package/src/overlay/ui/overlayPanelStyles.ts +3 -0
  170. package/src/periodic/ui/ElementCell.tsx +34 -37
  171. package/src/periodic/ui/PeriodicTable.tsx +12 -17
  172. package/src/seo/core/alternates.ts +65 -0
  173. package/src/seo/core/index.ts +3 -0
  174. package/src/seo/core/noscript.ts +18 -2
  175. package/src/seo/core/pageMeta.ts +74 -3
  176. package/src/seo/core/pageProse.ts +146 -0
  177. package/src/seo/core/routeProblems.ts +111 -0
  178. package/src/seo/core/routes.ts +14 -0
  179. package/src/seo/core/siteFiles.ts +2 -1
  180. package/src/seo/vite/prerender.ts +30 -11
  181. package/lib/about/core/repository.d.ts +0 -14
  182. package/lib/about/core/repository.d.ts.map +0 -1
  183. package/lib/about/core/repository.js +0 -21
  184. package/lib/about/core/repository.js.map +0 -1
  185. package/lib/download/core/exportStyles.d.ts +0 -22
  186. package/lib/download/core/exportStyles.d.ts.map +0 -1
  187. package/lib/download/core/exportStyles.js +0 -19
  188. package/lib/download/core/exportStyles.js.map +0 -1
  189. package/lib/download/core/imageResolution.d.ts +0 -88
  190. package/lib/download/core/imageResolution.d.ts.map +0 -1
  191. package/lib/download/core/imageResolution.js +0 -77
  192. package/lib/download/core/imageResolution.js.map +0 -1
  193. package/lib/download/core/pictureExport.d.ts +0 -93
  194. package/lib/download/core/pictureExport.d.ts.map +0 -1
  195. package/lib/download/core/pictureExport.js +0 -116
  196. package/lib/download/core/pictureExport.js.map +0 -1
  197. package/lib/download/ui/ExportDialogFrame.d.ts +0 -46
  198. package/lib/download/ui/ExportDialogFrame.d.ts.map +0 -1
  199. package/lib/download/ui/ExportDialogFrame.js +0 -24
  200. package/lib/download/ui/ExportDialogFrame.js.map +0 -1
  201. package/lib/download/ui/ExportImageDialog.d.ts +0 -47
  202. package/lib/download/ui/ExportImageDialog.d.ts.map +0 -1
  203. package/lib/download/ui/ExportImageDialog.js +0 -25
  204. package/lib/download/ui/ExportImageDialog.js.map +0 -1
  205. package/lib/download/ui/ExportNameField.d.ts +0 -29
  206. package/lib/download/ui/ExportNameField.d.ts.map +0 -1
  207. package/lib/download/ui/ExportNameField.js +0 -99
  208. package/lib/download/ui/ExportNameField.js.map +0 -1
  209. package/lib/download/ui/ExportPictureSection.d.ts +0 -38
  210. package/lib/download/ui/ExportPictureSection.d.ts.map +0 -1
  211. package/lib/download/ui/ExportPictureSection.js +0 -48
  212. package/lib/download/ui/ExportPictureSection.js.map +0 -1
  213. package/lib/download/ui/useDrawingBox.d.ts +0 -23
  214. package/lib/download/ui/useDrawingBox.d.ts.map +0 -1
  215. package/lib/download/ui/useDrawingBox.js +0 -21
  216. package/lib/download/ui/useDrawingBox.js.map +0 -1
  217. package/src/about/core/repository.ts +0 -22
  218. package/src/download/core/exportStyles.ts +0 -20
  219. package/src/download/core/imageResolution.ts +0 -118
  220. package/src/download/core/pictureExport.ts +0 -207
  221. package/src/download/ui/ExportDialogFrame.tsx +0 -86
  222. package/src/download/ui/ExportImageDialog.tsx +0 -91
  223. package/src/download/ui/ExportNameField.tsx +0 -148
  224. package/src/download/ui/ExportPictureSection.tsx +0 -142
  225. package/src/download/ui/useDrawingBox.ts +0 -37
@@ -0,0 +1,146 @@
1
+ /**
2
+ * The text a page is indexed on, written into the HTML before anything runs.
3
+ *
4
+ * A prerendered site writes one file per address, and each of them carried the
5
+ * same body: the site's crawl path, and nothing else. A search engine clusters
6
+ * pages by the text it is handed, so a hundred and eighteen element pages
7
+ * differing only in their title are a hundred and seventeen duplicates — and
8
+ * which one it keeps is not ours to choose. A page heavy enough that the
9
+ * renderer never gets to it is indexed on that body alone.
10
+ *
11
+ * So what a site writes here is the text its running app already shows, drawn
12
+ * from the same data: the prose is not written twice, it is written where the
13
+ * build can reach it. Text only, and escaped — the addresses a reader follows
14
+ * are the crawl path's business.
15
+ */
16
+
17
+ import { escapeText } from '../../share/core/escape.ts';
18
+
19
+ /** A table of facts under the prose: the rows a page would show anyway. */
20
+ export interface PageTable {
21
+ /** Header cells, left to right. */
22
+ columns: readonly string[];
23
+ /** Body rows, each as many cells as there are columns. */
24
+ rows: ReadonlyArray<readonly string[]>;
25
+ /**
26
+ * The sentence under the table, saying where the numbers come from.
27
+ * @default undefined — the table stands on its own
28
+ */
29
+ caption?: string;
30
+ }
31
+
32
+ /** One run of the page's text: a heading, prose, and the facts under it. */
33
+ export interface PageSection {
34
+ /**
35
+ * The heading it opens with, as an `h2`.
36
+ * @default undefined — the run carries no heading
37
+ */
38
+ heading?: string;
39
+ /**
40
+ * The paragraphs under it, each taken as written.
41
+ * @default undefined — no prose
42
+ */
43
+ paragraphs?: readonly string[];
44
+ /**
45
+ * A list under the prose, one item per line.
46
+ * @default undefined — no list
47
+ */
48
+ list?: readonly string[];
49
+ /**
50
+ * A table under the prose.
51
+ * @default undefined — no table
52
+ */
53
+ table?: PageTable;
54
+ }
55
+
56
+ /** The whole of one page's text, as the page itself would say it. */
57
+ export interface PageContent extends PageSection {
58
+ /**
59
+ * The page's own name, written as its `h1`. It answers the title the page is
60
+ * indexed under, so a reader who searched for it reads the same words again.
61
+ */
62
+ heading: string;
63
+ /**
64
+ * The runs under the opening prose, in reading order.
65
+ * @default undefined — the page is its opening prose
66
+ */
67
+ sections?: readonly PageSection[];
68
+ }
69
+
70
+ /**
71
+ * The page's text as HTML, everything below its heading.
72
+ *
73
+ * The heading itself is left to the caller, which already writes the one `h1`
74
+ * the page carries — `noscriptIndex` does.
75
+ * @param content - What the page says.
76
+ * @param indent - The indentation every line is written at.
77
+ * @default ' '
78
+ * @returns The HTML, opening with a newline, or `''` when the page says nothing
79
+ * below its heading.
80
+ */
81
+ export function pageProseHtml(content: PageContent, indent = ' '): string {
82
+ // The page's own heading is the caller's `h1`; what is left of its opening run
83
+ // is prose like any section's.
84
+ const opening: PageSection = {
85
+ paragraphs: content.paragraphs,
86
+ list: content.list,
87
+ table: content.table,
88
+ };
89
+ const parts = [
90
+ sectionHtml(opening, indent),
91
+ ...(content.sections ?? []).map((section) => sectionHtml(section, indent)),
92
+ ];
93
+ return parts.join('');
94
+ }
95
+
96
+ function sectionHtml(section: PageSection, indent: string): string {
97
+ const lines: string[] = [];
98
+ if (section.heading !== undefined && section.heading.trim() !== '') {
99
+ lines.push(`${indent}<h2>${escapeText(section.heading)}</h2>`);
100
+ }
101
+ for (const paragraph of section.paragraphs ?? []) {
102
+ if (paragraph.trim() === '') continue;
103
+ lines.push(`${indent}<p>${escapeText(paragraph)}</p>`);
104
+ }
105
+ const list = listHtml(section.list, indent);
106
+ if (list !== '') lines.push(list);
107
+ const table = tableHtml(section.table, indent);
108
+ if (table !== '') lines.push(table);
109
+ return lines.length === 0 ? '' : `\n${lines.join('\n')}`;
110
+ }
111
+
112
+ function listHtml(list: readonly string[] | undefined, indent: string): string {
113
+ // A list with no item is not a list: `<ul>` holds at least one `<li>`.
114
+ if (list === undefined || list.length === 0) return '';
115
+ const items = list
116
+ .map((item) => `${indent} <li>${escapeText(item)}</li>`)
117
+ .join('\n');
118
+ return `${indent}<ul>\n${items}\n${indent}</ul>`;
119
+ }
120
+
121
+ function tableHtml(table: PageTable | undefined, indent: string): string {
122
+ // A table with no row says nothing, and an empty `<tbody>` is not markup a
123
+ // crawler is owed.
124
+ if (table === undefined || table.rows.length === 0) return '';
125
+ const caption =
126
+ table.caption === undefined || table.caption.trim() === ''
127
+ ? ''
128
+ : `\n${indent} <caption>${escapeText(table.caption)}</caption>`;
129
+ const head = table.columns
130
+ .map((column) => `<th>${escapeText(column)}</th>`)
131
+ .join('');
132
+ const body = table.rows
133
+ .map(
134
+ (row) =>
135
+ `${indent} <tr>${row.map((cell) => `<td>${escapeText(cell)}</td>`).join('')}</tr>`,
136
+ )
137
+ .join('\n');
138
+ return `${indent}<table>${caption}
139
+ ${indent} <thead>
140
+ ${indent} <tr>${head}</tr>
141
+ ${indent} </thead>
142
+ ${indent} <tbody>
143
+ ${body}
144
+ ${indent} </tbody>
145
+ ${indent}</table>`;
146
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * What is wrong with the prose a site is indexed under.
3
+ *
4
+ * A route table is the only place a search result is written, and it is written
5
+ * once and read for years, so the limits that decide whether a result is
6
+ * readable are checked rather than trusted: a title Google cuts in half, a
7
+ * description too short to say anything or long enough to be clipped, two pages
8
+ * sharing a sentence — which is how a site asks to be deduplicated down to one
9
+ * result — and a snippet promising something the page does not carry.
10
+ *
11
+ * The paths are `assertRoutes`' business; this is about the words.
12
+ */
13
+
14
+ import type { RouteMeta } from './routes.ts';
15
+
16
+ /** The longest title that survives a search result whole. */
17
+ const TITLE_LIMIT = 60;
18
+ /** The shortest description that says anything. */
19
+ const DESCRIPTION_MIN = 110;
20
+ /** The longest description a result shows without clipping it. */
21
+ const DESCRIPTION_MAX = 160;
22
+
23
+ /**
24
+ * What a site never says about itself, in the words it would say it in. A
25
+ * snippet naming a repository, a tracker or a licence is both a promise the
26
+ * page does not keep and a thing we do not publish.
27
+ */
28
+ const WITHHELD =
29
+ /\b(?:licence|license|licensing|open[ -]source|repositor(?:y|ies)|issue tracker|source code|report a (?:problem|bug|issue))\b/i;
30
+
31
+ /**
32
+ * The phrase in a snippet that names something a site does not publish.
33
+ *
34
+ * Shared with the build-time checker, which reads the same sentences back out
35
+ * of the pages they were written into: one list of words, checked where they
36
+ * are authored and again where they landed.
37
+ * @param text - A title or a description.
38
+ * @returns The phrase, or `undefined` when there is none.
39
+ */
40
+ export function withheldPhrase(text: string): string | undefined {
41
+ return WITHHELD.exec(text)?.[0];
42
+ }
43
+
44
+ /**
45
+ * Check the table a site is indexed under, before a build reads it.
46
+ *
47
+ * Returns the problems rather than throwing, so a site asserts an empty list in
48
+ * its own test and reads every one of them at once.
49
+ * @param routes - Every address the site answers.
50
+ * @returns One line per problem, empty when the table is fit to ship.
51
+ */
52
+ export function routeProblems(routes: readonly RouteMeta[]): string[] {
53
+ const problems: string[] = [];
54
+
55
+ if (routes.length === 0) {
56
+ problems.push('the table is empty: a site answers at least one route.');
57
+ return problems;
58
+ }
59
+
60
+ const titles = new Map<string, string>();
61
+ const descriptions = new Map<string, string>();
62
+
63
+ for (const route of routes) {
64
+ const at = route.path;
65
+
66
+ if (route.title.trim() === '') {
67
+ problems.push(`${at}: the title is empty.`);
68
+ } else if (route.title.length > TITLE_LIMIT) {
69
+ problems.push(
70
+ `${at}: the title is ${route.title.length} characters, and the site name is appended to it — at most ${TITLE_LIMIT} survives a result whole.`,
71
+ );
72
+ }
73
+
74
+ const length = route.description.length;
75
+ if (length < DESCRIPTION_MIN) {
76
+ problems.push(
77
+ `${at}: the description is ${length} characters — at least ${DESCRIPTION_MIN}, or the result says half of what the page is.`,
78
+ );
79
+ } else if (length > DESCRIPTION_MAX) {
80
+ problems.push(
81
+ `${at}: the description is ${length} characters — at most ${DESCRIPTION_MAX}, or the sentence is cut off in the result itself.`,
82
+ );
83
+ }
84
+
85
+ const titleFirst = titles.get(route.title);
86
+ if (titleFirst === undefined) {
87
+ titles.set(route.title, at);
88
+ } else {
89
+ problems.push(`${at}: the title repeats the one at ${titleFirst}.`);
90
+ }
91
+
92
+ const descriptionFirst = descriptions.get(route.description);
93
+ if (descriptionFirst === undefined) {
94
+ descriptions.set(route.description, at);
95
+ } else {
96
+ problems.push(
97
+ `${at}: the description repeats the one at ${descriptionFirst}.`,
98
+ );
99
+ }
100
+
101
+ const withheld =
102
+ withheldPhrase(route.title) ?? withheldPhrase(route.description);
103
+ if (withheld !== undefined) {
104
+ problems.push(
105
+ `${at}: the snippet says "${withheld}" — a site names no repository, tracker or licence of ours.`,
106
+ );
107
+ }
108
+ }
109
+
110
+ return problems;
111
+ }
@@ -42,6 +42,20 @@ export interface RouteMeta {
42
42
  * @default undefined — the link stands on its own
43
43
  */
44
44
  note?: string;
45
+ /**
46
+ * Whether a search engine is meant to list the page.
47
+ *
48
+ * A maintenance screen — a curation queue, an import run, an admin table — is
49
+ * a real address a signed-in person opens, so it belongs in the table the
50
+ * router and the tab title read. It is not a result anybody wants: it says
51
+ * nothing a visitor searched for, and a chemist who lands on it has been sent
52
+ * to the wrong place. Such a route is left out of the sitemap and the page
53
+ * answers `noindex`, which is how a page is kept out of the index — never a
54
+ * `Disallow`, which only stops the crawl and still lets the address be listed
55
+ * from a link somewhere else.
56
+ * @default true
57
+ */
58
+ indexed?: boolean;
45
59
  /**
46
60
  * Whether the route also answers every address beneath it, so a section
47
61
  * carrying more pages than a table can hold — an entry per structure, per
@@ -50,10 +50,11 @@ export interface SiteFilesOptions {
50
50
  */
51
51
  export function sitemapXml(options: SiteFilesOptions): string {
52
52
  const origin = originOf(options);
53
- if (options.routes.length === 0) {
53
+ if (options.routes.every((route) => route.indexed === false)) {
54
54
  throw new Error('a sitemap lists at least one address');
55
55
  }
56
56
  const entries = options.routes
57
+ .filter((route) => route.indexed !== false)
57
58
  .map(
58
59
  (route) =>
59
60
  ` <url><loc>${escapeText(`${origin}${route.path}`)}</loc></url>`,
@@ -27,6 +27,7 @@ import { trimTrailingSlash } from '../../router/core/address.ts';
27
27
  import type { NoscriptText } from '../core/noscript.ts';
28
28
  import { noscriptIndex } from '../core/noscript.ts';
29
29
  import { pageHeadTags } from '../core/pageMeta.ts';
30
+ import type { PageContent } from '../core/pageProse.ts';
30
31
  import type { RobotsDisallow } from '../core/robots.ts';
31
32
  import { robotsTxt } from '../core/robots.ts';
32
33
  import type { RouteMeta } from '../core/routes.ts';
@@ -91,6 +92,18 @@ export interface PrerenderOptions {
91
92
  * @default true
92
93
  */
93
94
  noscript?: boolean | NoscriptText;
95
+ /**
96
+ * What each page says for itself, above the crawl path: its own heading, its
97
+ * prose and the facts it would show anyway, read from the same data the app
98
+ * renders from.
99
+ *
100
+ * Without it every address ships the same body — the site's menu — and a
101
+ * search engine handed a hundred identical bodies keeps one of them. It is a
102
+ * function of the route rather than a field of it, so the prose stays out of
103
+ * the bundle the browser downloads: only the build ever calls it.
104
+ * @default undefined — every page carries the menu alone
105
+ */
106
+ content?: (route: RouteMeta) => PageContent | undefined;
94
107
  }
95
108
 
96
109
  /**
@@ -104,18 +117,18 @@ export function cheminfoPrerender(options: PrerenderOptions): Plugin {
104
117
  const { site, routes, origin, robots = [] } = options;
105
118
  assertRoutes(routes);
106
119
  const structuredData = structuredDataOf(options);
107
- const crawlPath = crawlPathOf(options);
108
120
 
109
121
  let out = 'dist';
110
122
  let serve = false;
111
123
  let logger: Logger | null = null;
112
124
 
113
- const page = (template: string, url: string) => {
125
+ const page = (template: string, route: RouteMeta) => {
114
126
  const head = fill(
115
127
  template,
116
128
  PAGE_HEAD_MARKER,
117
- `${pageHeadTags({ site, routes, origin, url })}${structuredData}`,
129
+ `${pageHeadTags({ site, routes, origin, url: route.path })}${structuredData}`,
118
130
  );
131
+ const crawlPath = crawlPathOf(options, route);
119
132
  return crawlPath === '' ? head : fill(head, PAGE_BODY_MARKER, crawlPath);
120
133
  };
121
134
 
@@ -132,17 +145,16 @@ export function cheminfoPrerender(options: PrerenderOptions): Plugin {
132
145
  // from the home route rather than shipped with its markers showing.
133
146
  transformIndexHtml: {
134
147
  order: 'post',
135
- handler: (html: string) =>
136
- serve ? page(html, homeRoute(routes).path) : html,
148
+ handler: (html: string) => (serve ? page(html, homeRoute(routes)) : html),
137
149
  },
138
150
 
139
151
  closeBundle() {
140
152
  if (serve) return;
141
153
  const template = readFileSync(join(out, 'index.html'), 'utf8');
142
154
 
143
- const write = (url: string, file: string) => {
155
+ const write = (route: RouteMeta, file: string) => {
144
156
  mkdirSync(dirname(file), { recursive: true });
145
- writeFileSync(file, page(template, url));
157
+ writeFileSync(file, page(template, route));
146
158
  };
147
159
 
148
160
  let root = false;
@@ -150,7 +162,7 @@ export function cheminfoPrerender(options: PrerenderOptions): Plugin {
150
162
  const address = trimTrailingSlash(route.path);
151
163
  if (address === '/') root = true;
152
164
  write(
153
- route.path,
165
+ route,
154
166
  address === '/'
155
167
  ? join(out, 'index.html')
156
168
  : join(out, address.slice(1), 'index.html'),
@@ -159,7 +171,7 @@ export function cheminfoPrerender(options: PrerenderOptions): Plugin {
159
171
  // The file a static server hands out for the mount itself. A table naming
160
172
  // no root would otherwise leave the template vite built, and ship a site
161
173
  // whose front page carries its markers instead of a head.
162
- if (!root) write(homeRoute(routes).path, join(out, 'index.html'));
174
+ if (!root) write(homeRoute(routes), join(out, 'index.html'));
163
175
 
164
176
  writeFileSync(
165
177
  join(out, 'sitemap.xml'),
@@ -196,9 +208,16 @@ function structuredDataOf(options: PrerenderOptions): string {
196
208
  })}`;
197
209
  }
198
210
 
199
- function crawlPathOf(options: PrerenderOptions): string {
211
+ function crawlPathOf(options: PrerenderOptions, route: RouteMeta): string {
200
212
  const { site, routes, origin, noscript = true } = options;
201
213
  if (noscript === false) return '';
214
+ const content = options.content?.(route);
202
215
  const { routes: listed, ...prose } = noscript === true ? {} : noscript;
203
- return noscriptIndex({ site, origin, ...prose, routes: listed ?? routes });
216
+ return noscriptIndex({
217
+ site,
218
+ origin,
219
+ ...prose,
220
+ routes: listed ?? routes,
221
+ content,
222
+ });
204
223
  }
@@ -1,14 +0,0 @@
1
- /**
2
- * What can be linked to inside a repository, and when.
3
- *
4
- * Only GitHub has the addresses the About page builds — a release tag, a
5
- * commit — and only a repository a visitor can open is worth pointing them at.
6
- */
7
- /**
8
- * The sources as a base a release or a commit can be appended to.
9
- * @param repository - Where the sources live.
10
- * @returns The address without its trailing slash, or `undefined` when it is
11
- * not a GitHub repository and nothing inside it can be addressed.
12
- */
13
- export declare function githubSources(repository: string): string | undefined;
14
- //# sourceMappingURL=repository.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../../src/about/core/repository.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKpE"}
@@ -1,21 +0,0 @@
1
- /**
2
- * What can be linked to inside a repository, and when.
3
- *
4
- * Only GitHub has the addresses the About page builds — a release tag, a
5
- * commit — and only a repository a visitor can open is worth pointing them at.
6
- */
7
- /** Where our repositories live, and the only host whose paths we know. */
8
- const GITHUB = 'https://github.com/';
9
- /**
10
- * The sources as a base a release or a commit can be appended to.
11
- * @param repository - Where the sources live.
12
- * @returns The address without its trailing slash, or `undefined` when it is
13
- * not a GitHub repository and nothing inside it can be addressed.
14
- */
15
- export function githubSources(repository) {
16
- const sources = repository.endsWith('/')
17
- ? repository.slice(0, -1)
18
- : repository;
19
- return sources.startsWith(GITHUB) ? sources : undefined;
20
- }
21
- //# sourceMappingURL=repository.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"repository.js","sourceRoot":"","sources":["../../../src/about/core/repository.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,0EAA0E;AAC1E,MAAM,MAAM,GAAG,qBAAqB,CAAC;AAErC;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC;QACtC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACzB,CAAC,CAAC,UAAU,CAAC;IACf,OAAO,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1D,CAAC"}
@@ -1,22 +0,0 @@
1
- /**
2
- * A row of buttons with the name box among them, wrapping when the dialog is
3
- * too narrow for them.
4
- */
5
- export declare const exportButtonsStyle: {
6
- readonly display: "flex";
7
- readonly flexWrap: "wrap";
8
- readonly alignItems: "center";
9
- readonly gap: 6;
10
- };
11
- /** One group of an export dialog: what it offers, in a column. */
12
- export declare const exportSectionStyle: {
13
- readonly display: "flex";
14
- readonly flexDirection: "column";
15
- readonly gap: 8;
16
- };
17
- /** Text that says something about a control rather than being one. */
18
- export declare const exportNoteStyle: {
19
- readonly fontSize: 12;
20
- readonly opacity: 0.7;
21
- };
22
- //# sourceMappingURL=exportStyles.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"exportStyles.d.ts","sourceRoot":"","sources":["../../../src/download/core/exportStyles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AAEX,kEAAkE;AAClE,eAAO,MAAM,kBAAkB;;;;CAIrB,CAAC;AAEX,sEAAsE;AACtE,eAAO,MAAM,eAAe;;;CAA0C,CAAC"}
@@ -1,19 +0,0 @@
1
- /**
2
- * A row of buttons with the name box among them, wrapping when the dialog is
3
- * too narrow for them.
4
- */
5
- export const exportButtonsStyle = {
6
- display: 'flex',
7
- flexWrap: 'wrap',
8
- alignItems: 'center',
9
- gap: 6,
10
- };
11
- /** One group of an export dialog: what it offers, in a column. */
12
- export const exportSectionStyle = {
13
- display: 'flex',
14
- flexDirection: 'column',
15
- gap: 8,
16
- };
17
- /** Text that says something about a control rather than being one. */
18
- export const exportNoteStyle = { fontSize: 12, opacity: 0.7 };
19
- //# sourceMappingURL=exportStyles.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"exportStyles.js","sourceRoot":"","sources":["../../../src/download/core/exportStyles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,OAAO,EAAE,MAAM;IACf,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,QAAQ;IACpB,GAAG,EAAE,CAAC;CACE,CAAC;AAEX,kEAAkE;AAClE,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,OAAO,EAAE,MAAM;IACf,aAAa,EAAE,QAAQ;IACvB,GAAG,EAAE,CAAC;CACE,CAAC;AAEX,sEAAsE;AACtE,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAW,CAAC"}
@@ -1,88 +0,0 @@
1
- /** How big a picture is written out, as a multiple of the size it is drawn at. */
2
- export interface ImageResolution {
3
- /** Identifies the resolution in a picker. */
4
- id: string;
5
- /** What the option reads. */
6
- label: string;
7
- /** How many pixels each drawing unit becomes. */
8
- scale: number;
9
- }
10
- /**
11
- * The sizes a picture leaves the editor at.
12
- *
13
- * A drawing unit is about a screen pixel, so the multipliers read as what the
14
- * picture is for rather than as numbers: what is on screen, the same again on a
15
- * retina display, and the two steps up a figure needs to survive being printed
16
- * or thrown on a wall.
17
- */
18
- export declare const IMAGE_RESOLUTIONS: readonly [{
19
- readonly id: "screen";
20
- readonly label: "Screen (1×)";
21
- readonly scale: 1;
22
- }, {
23
- readonly id: "retina";
24
- readonly label: "Retina (2×)";
25
- readonly scale: 2;
26
- }, {
27
- readonly id: "print";
28
- readonly label: "Print (4×)";
29
- readonly scale: 4;
30
- }, {
31
- readonly id: "poster";
32
- readonly label: "Poster (8×)";
33
- readonly scale: 8;
34
- }];
35
- /** One of the resolutions offered. */
36
- export type ImageResolutionId = (typeof IMAGE_RESOLUTIONS)[number]['id'];
37
- /**
38
- * The resolution a picture is written out at unless another is picked: what is
39
- * on screen, doubled, which is what a picture pasted into a document wants.
40
- */
41
- export declare const DEFAULT_IMAGE_RESOLUTION: {
42
- readonly id: "retina";
43
- readonly label: "Retina (2×)";
44
- readonly scale: 2;
45
- };
46
- /** How big a picture comes out, in pixels. */
47
- export interface ImageSize {
48
- /** Width of the picture, in pixels. */
49
- width: number;
50
- /** Height of the picture, in pixels. */
51
- height: number;
52
- /**
53
- * What each drawing unit was actually worth. Below the resolution that was
54
- * asked for when it would have made a picture too big to rasterize.
55
- */
56
- scale: number;
57
- }
58
- /**
59
- * How big a rectangle of the drawing comes out at a given resolution.
60
- *
61
- * The resolution is a wish rather than an instruction: a wide glycan asked for
62
- * at 8× would run past what a browser can rasterize and come back blank, so the
63
- * scale is brought down until the picture fits instead. The scale it settled on
64
- * comes back with the size, so what will actually be written can be shown
65
- * before it is.
66
- * @param box - The rectangle being written out.
67
- * @param box.width - How wide it is, in drawing units.
68
- * @param box.height - How tall it is, in drawing units.
69
- * @param scale - How many pixels each drawing unit should become.
70
- * @returns The size, and the scale it was reached at.
71
- */
72
- export declare function imageSize(box: {
73
- width: number;
74
- height: number;
75
- }, scale: number): ImageSize;
76
- /**
77
- * The resolution an identifier stands for.
78
- * @param id - What was picked.
79
- * @returns The resolution, falling back to the default one.
80
- */
81
- export declare function imageResolution(id: string): ImageResolution;
82
- /**
83
- * A size, written the way it is shown next to the resolution it comes from.
84
- * @param size - The size to write, `null` when nothing is drawn.
85
- * @returns The text, e.g. `1440 × 620 px`.
86
- */
87
- export declare function formatImageSize(size: ImageSize | null): string;
88
- //# sourceMappingURL=imageResolution.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"imageResolution.d.ts","sourceRoot":"","sources":["../../../src/download/core/imageResolution.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;EAKiB,CAAC;AAEhD,sCAAsC;AACtC,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAEzE;;;GAGG;AACH,eAAO,MAAM,wBAAwB;;;;CAAuB,CAAC;AAa7D,8CAA8C;AAC9C,MAAM,WAAW,SAAS;IACxB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACtC,KAAK,EAAE,MAAM,GACZ,SAAS,CAoBX;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,CAK3D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,GAAG,MAAM,CAG9D"}
@@ -1,77 +0,0 @@
1
- /**
2
- * The sizes a picture leaves the editor at.
3
- *
4
- * A drawing unit is about a screen pixel, so the multipliers read as what the
5
- * picture is for rather than as numbers: what is on screen, the same again on a
6
- * retina display, and the two steps up a figure needs to survive being printed
7
- * or thrown on a wall.
8
- */
9
- export const IMAGE_RESOLUTIONS = [
10
- { id: 'screen', label: 'Screen (1×)', scale: 1 },
11
- { id: 'retina', label: 'Retina (2×)', scale: 2 },
12
- { id: 'print', label: 'Print (4×)', scale: 4 },
13
- { id: 'poster', label: 'Poster (8×)', scale: 8 },
14
- ];
15
- /**
16
- * The resolution a picture is written out at unless another is picked: what is
17
- * on screen, doubled, which is what a picture pasted into a document wants.
18
- */
19
- export const DEFAULT_IMAGE_RESOLUTION = IMAGE_RESOLUTIONS[1];
20
- /**
21
- * Longest side a browser rasterizes reliably.
22
- *
23
- * Above roughly this, Safari and Chrome quietly hand back an empty canvas
24
- * instead of refusing, so the picture would be saved blank.
25
- */
26
- const MAXIMUM_SIDE = 8192;
27
- /** Most pixels a rasterized picture is allowed to hold. */
28
- const MAXIMUM_PIXELS = 32 * 1024 * 1024;
29
- /**
30
- * How big a rectangle of the drawing comes out at a given resolution.
31
- *
32
- * The resolution is a wish rather than an instruction: a wide glycan asked for
33
- * at 8× would run past what a browser can rasterize and come back blank, so the
34
- * scale is brought down until the picture fits instead. The scale it settled on
35
- * comes back with the size, so what will actually be written can be shown
36
- * before it is.
37
- * @param box - The rectangle being written out.
38
- * @param box.width - How wide it is, in drawing units.
39
- * @param box.height - How tall it is, in drawing units.
40
- * @param scale - How many pixels each drawing unit should become.
41
- * @returns The size, and the scale it was reached at.
42
- */
43
- export function imageSize(box, scale) {
44
- const wanted = Number.isFinite(scale) && scale > 0 ? scale : 1;
45
- const width = Math.max(box.width, 1);
46
- const height = Math.max(box.height, 1);
47
- const limit = Math.min(MAXIMUM_SIDE / width, MAXIMUM_SIDE / height, Math.sqrt(MAXIMUM_PIXELS / (width * height)));
48
- const used = Math.min(wanted, limit);
49
- // A picture held back by the limit is rounded down rather than to the nearest
50
- // pixel, so rounding cannot put it back over what it was held to.
51
- const toPixels = used < wanted ? Math.floor : Math.round;
52
- return {
53
- width: Math.max(1, toPixels(width * used)),
54
- height: Math.max(1, toPixels(height * used)),
55
- scale: used,
56
- };
57
- }
58
- /**
59
- * The resolution an identifier stands for.
60
- * @param id - What was picked.
61
- * @returns The resolution, falling back to the default one.
62
- */
63
- export function imageResolution(id) {
64
- return (IMAGE_RESOLUTIONS.find((resolution) => resolution.id === id) ??
65
- DEFAULT_IMAGE_RESOLUTION);
66
- }
67
- /**
68
- * A size, written the way it is shown next to the resolution it comes from.
69
- * @param size - The size to write, `null` when nothing is drawn.
70
- * @returns The text, e.g. `1440 × 620 px`.
71
- */
72
- export function formatImageSize(size) {
73
- if (!size)
74
- return 'nothing to export';
75
- return `${size.width} × ${size.height} px`;
76
- }
77
- //# sourceMappingURL=imageResolution.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"imageResolution.js","sourceRoot":"","sources":["../../../src/download/core/imageResolution.ts"],"names":[],"mappings":"AAUA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE;IAChD,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE;IAChD,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE;IAC9C,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE;CACH,CAAC;AAKhD;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B,2DAA2D;AAC3D,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAexC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,SAAS,CACvB,GAAsC,EACtC,KAAa;IAEb,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAEvC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,YAAY,GAAG,KAAK,EACpB,YAAY,GAAG,MAAM,EACrB,IAAI,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAC7C,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACrC,8EAA8E;IAC9E,kEAAkE;IAClE,MAAM,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;IAEzD,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;QAC1C,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QAC5C,KAAK,EAAE,IAAI;KACZ,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,OAAO,CACL,iBAAiB,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC;QAC5D,wBAAwB,CACzB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAsB;IACpD,IAAI,CAAC,IAAI;QAAE,OAAO,mBAAmB,CAAC;IACtC,OAAO,GAAG,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,MAAM,KAAK,CAAC;AAC7C,CAAC"}