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.
- package/README.md +28 -28
- package/bin/check-seo.mjs +333 -0
- package/lib/about/core/about.d.ts +1 -30
- package/lib/about/core/about.d.ts.map +1 -1
- package/lib/about/core/about.js +0 -16
- package/lib/about/core/about.js.map +1 -1
- package/lib/about/core/index.d.ts +0 -1
- package/lib/about/core/index.d.ts.map +1 -1
- package/lib/about/core/index.js +0 -1
- package/lib/about/core/index.js.map +1 -1
- package/lib/about/ui/AboutBuild.d.ts +2 -5
- package/lib/about/ui/AboutBuild.d.ts.map +1 -1
- package/lib/about/ui/AboutBuild.js +7 -19
- package/lib/about/ui/AboutBuild.js.map +1 -1
- package/lib/about/ui/AboutPage.d.ts +2 -2
- package/lib/about/ui/AboutPage.d.ts.map +1 -1
- package/lib/about/ui/AboutPage.js +3 -14
- package/lib/about/ui/AboutPage.js.map +1 -1
- package/lib/about/ui/AboutVersion.d.ts +0 -4
- package/lib/about/ui/AboutVersion.d.ts.map +1 -1
- package/lib/about/ui/AboutVersion.js +3 -31
- package/lib/about/ui/AboutVersion.js.map +1 -1
- package/lib/download/core/index.d.ts +0 -5
- package/lib/download/core/index.d.ts.map +1 -1
- package/lib/download/core/index.js +0 -3
- package/lib/download/core/index.js.map +1 -1
- package/lib/download/ui/FigureDownload.d.ts +4 -55
- package/lib/download/ui/FigureDownload.d.ts.map +1 -1
- package/lib/download/ui/FigureDownload.js +6 -66
- package/lib/download/ui/FigureDownload.js.map +1 -1
- package/lib/download/ui/FigureDownloadPanel.d.ts +16 -2
- package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
- package/lib/download/ui/FigureDownloadPanel.js +11 -30
- package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
- package/lib/download/ui/figureDownloadHint.d.ts +30 -0
- package/lib/download/ui/figureDownloadHint.d.ts.map +1 -0
- package/lib/download/ui/figureDownloadHint.js +33 -0
- package/lib/download/ui/figureDownloadHint.js.map +1 -0
- package/lib/download/ui/index.d.ts +3 -10
- package/lib/download/ui/index.d.ts.map +1 -1
- package/lib/download/ui/index.js +1 -5
- package/lib/download/ui/index.js.map +1 -1
- package/lib/download/ui/useFigureActions.d.ts +50 -0
- package/lib/download/ui/useFigureActions.d.ts.map +1 -0
- package/lib/download/ui/useFigureActions.js +58 -0
- package/lib/download/ui/useFigureActions.js.map +1 -0
- package/lib/download/ui/useFigureDownload.d.ts +85 -0
- package/lib/download/ui/useFigureDownload.d.ts.map +1 -0
- package/lib/download/ui/useFigureDownload.js +75 -0
- package/lib/download/ui/useFigureDownload.js.map +1 -0
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +0 -40
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/core/types.d.ts +0 -10
- package/lib/ecosystem/core/types.d.ts.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/ir/core/irCommands.d.ts +2 -2
- package/lib/ir/core/irCommands.js +2 -2
- package/lib/ir/core/irCommands.js.map +1 -1
- package/lib/ir/ui/IrAboutDialog.d.ts.map +1 -1
- package/lib/ir/ui/IrAboutDialog.js +1 -1
- package/lib/ir/ui/IrAboutDialog.js.map +1 -1
- package/lib/ir/ui/IrCanvas.d.ts +6 -0
- package/lib/ir/ui/IrCanvas.d.ts.map +1 -1
- package/lib/ir/ui/IrCanvas.js +2 -2
- package/lib/ir/ui/IrCanvas.js.map +1 -1
- package/lib/ir/ui/IrShell.d.ts.map +1 -1
- package/lib/ir/ui/IrShell.js +16 -11
- package/lib/ir/ui/IrShell.js.map +1 -1
- package/lib/ir/ui/IrToolbar.d.ts +11 -1
- package/lib/ir/ui/IrToolbar.d.ts.map +1 -1
- package/lib/ir/ui/IrToolbar.js +12 -2
- package/lib/ir/ui/IrToolbar.js.map +1 -1
- package/lib/ir/ui/index.d.ts +1 -0
- package/lib/ir/ui/index.d.ts.map +1 -1
- package/lib/ir/ui/index.js.map +1 -1
- package/lib/language/core/index.d.ts +2 -0
- package/lib/language/core/index.d.ts.map +1 -1
- package/lib/language/core/index.js +1 -0
- package/lib/language/core/index.js.map +1 -1
- package/lib/language/core/languagePath.d.ts +52 -0
- package/lib/language/core/languagePath.d.ts.map +1 -0
- package/lib/language/core/languagePath.js +65 -0
- package/lib/language/core/languagePath.js.map +1 -0
- package/lib/locales/de.json +2 -4
- package/lib/locales/en.json +2 -4
- package/lib/locales/es.json +2 -4
- package/lib/locales/fr.json +2 -4
- package/lib/locales/it.json +2 -4
- package/lib/overlay/ui/OverlayPanel.d.ts +6 -0
- package/lib/overlay/ui/OverlayPanel.d.ts.map +1 -1
- package/lib/overlay/ui/OverlayPanel.js +2 -2
- package/lib/overlay/ui/OverlayPanel.js.map +1 -1
- package/lib/overlay/ui/overlayPanelStyles.d.ts +2 -1
- package/lib/overlay/ui/overlayPanelStyles.d.ts.map +1 -1
- package/lib/overlay/ui/overlayPanelStyles.js +3 -1
- package/lib/overlay/ui/overlayPanelStyles.js.map +1 -1
- package/lib/periodic/ui/ElementCell.d.ts +0 -6
- package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
- package/lib/periodic/ui/ElementCell.js +34 -30
- package/lib/periodic/ui/ElementCell.js.map +1 -1
- package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
- package/lib/periodic/ui/PeriodicTable.js +12 -15
- package/lib/periodic/ui/PeriodicTable.js.map +1 -1
- package/lib/seo/core/alternates.d.ts +40 -0
- package/lib/seo/core/alternates.d.ts.map +1 -0
- package/lib/seo/core/alternates.js +42 -0
- package/lib/seo/core/alternates.js.map +1 -0
- package/lib/seo/core/index.d.ts +3 -0
- package/lib/seo/core/index.d.ts.map +1 -1
- package/lib/seo/core/index.js +2 -0
- package/lib/seo/core/index.js.map +1 -1
- package/lib/seo/core/noscript.d.ts +9 -0
- package/lib/seo/core/noscript.d.ts.map +1 -1
- package/lib/seo/core/noscript.js +7 -2
- package/lib/seo/core/noscript.js.map +1 -1
- package/lib/seo/core/pageMeta.d.ts +23 -0
- package/lib/seo/core/pageMeta.d.ts.map +1 -1
- package/lib/seo/core/pageMeta.js +52 -3
- package/lib/seo/core/pageMeta.js.map +1 -1
- package/lib/seo/core/pageProse.d.ts +76 -0
- package/lib/seo/core/pageProse.d.ts.map +1 -0
- package/lib/seo/core/pageProse.js +92 -0
- package/lib/seo/core/pageProse.js.map +1 -0
- package/lib/seo/core/routeProblems.d.ts +33 -0
- package/lib/seo/core/routeProblems.d.ts.map +1 -0
- package/lib/seo/core/routeProblems.js +89 -0
- package/lib/seo/core/routeProblems.js.map +1 -0
- package/lib/seo/core/routes.d.ts +14 -0
- package/lib/seo/core/routes.d.ts.map +1 -1
- package/lib/seo/core/routes.js.map +1 -1
- package/lib/seo/core/siteFiles.d.ts.map +1 -1
- package/lib/seo/core/siteFiles.js +2 -1
- package/lib/seo/core/siteFiles.js.map +1 -1
- package/lib/seo/vite/prerender.d.ts +13 -0
- package/lib/seo/vite/prerender.d.ts.map +1 -1
- package/lib/seo/vite/prerender.js +17 -10
- package/lib/seo/vite/prerender.js.map +1 -1
- package/package.json +2 -1
- package/src/about/core/about.ts +1 -51
- package/src/about/core/index.ts +0 -1
- package/src/about/ui/AboutBuild.tsx +8 -37
- package/src/about/ui/AboutPage.tsx +8 -64
- package/src/about/ui/AboutVersion.tsx +5 -45
- package/src/download/core/index.ts +0 -25
- package/src/download/ui/FigureDownload.tsx +11 -148
- package/src/download/ui/FigureDownloadPanel.tsx +46 -45
- package/src/download/ui/figureDownloadHint.ts +52 -0
- package/src/download/ui/index.ts +6 -10
- package/src/download/ui/useFigureActions.ts +113 -0
- package/src/download/ui/useFigureDownload.tsx +191 -0
- package/src/ecosystem/core/sites.ts +0 -40
- package/src/ecosystem/core/types.ts +0 -10
- package/src/ir/core/irCommands.ts +2 -2
- package/src/ir/ui/IrAboutDialog.tsx +0 -13
- package/src/ir/ui/IrCanvas.tsx +8 -2
- package/src/ir/ui/IrShell.tsx +23 -25
- package/src/ir/ui/IrToolbar.tsx +36 -6
- package/src/ir/ui/index.ts +1 -0
- package/src/language/core/index.ts +2 -0
- package/src/language/core/languagePath.ts +78 -0
- package/src/locales/de.json +2 -4
- package/src/locales/en.json +2 -4
- package/src/locales/es.json +2 -4
- package/src/locales/fr.json +2 -4
- package/src/locales/it.json +2 -4
- package/src/overlay/ui/OverlayPanel.tsx +8 -2
- package/src/overlay/ui/overlayPanelStyles.ts +3 -0
- package/src/periodic/ui/ElementCell.tsx +34 -37
- package/src/periodic/ui/PeriodicTable.tsx +12 -17
- package/src/seo/core/alternates.ts +65 -0
- package/src/seo/core/index.ts +3 -0
- package/src/seo/core/noscript.ts +18 -2
- package/src/seo/core/pageMeta.ts +74 -3
- package/src/seo/core/pageProse.ts +146 -0
- package/src/seo/core/routeProblems.ts +111 -0
- package/src/seo/core/routes.ts +14 -0
- package/src/seo/core/siteFiles.ts +2 -1
- package/src/seo/vite/prerender.ts +30 -11
- package/lib/about/core/repository.d.ts +0 -14
- package/lib/about/core/repository.d.ts.map +0 -1
- package/lib/about/core/repository.js +0 -21
- package/lib/about/core/repository.js.map +0 -1
- package/lib/download/core/exportStyles.d.ts +0 -22
- package/lib/download/core/exportStyles.d.ts.map +0 -1
- package/lib/download/core/exportStyles.js +0 -19
- package/lib/download/core/exportStyles.js.map +0 -1
- package/lib/download/core/imageResolution.d.ts +0 -88
- package/lib/download/core/imageResolution.d.ts.map +0 -1
- package/lib/download/core/imageResolution.js +0 -77
- package/lib/download/core/imageResolution.js.map +0 -1
- package/lib/download/core/pictureExport.d.ts +0 -93
- package/lib/download/core/pictureExport.d.ts.map +0 -1
- package/lib/download/core/pictureExport.js +0 -116
- package/lib/download/core/pictureExport.js.map +0 -1
- package/lib/download/ui/ExportDialogFrame.d.ts +0 -46
- package/lib/download/ui/ExportDialogFrame.d.ts.map +0 -1
- package/lib/download/ui/ExportDialogFrame.js +0 -24
- package/lib/download/ui/ExportDialogFrame.js.map +0 -1
- package/lib/download/ui/ExportImageDialog.d.ts +0 -47
- package/lib/download/ui/ExportImageDialog.d.ts.map +0 -1
- package/lib/download/ui/ExportImageDialog.js +0 -25
- package/lib/download/ui/ExportImageDialog.js.map +0 -1
- package/lib/download/ui/ExportNameField.d.ts +0 -29
- package/lib/download/ui/ExportNameField.d.ts.map +0 -1
- package/lib/download/ui/ExportNameField.js +0 -99
- package/lib/download/ui/ExportNameField.js.map +0 -1
- package/lib/download/ui/ExportPictureSection.d.ts +0 -38
- package/lib/download/ui/ExportPictureSection.d.ts.map +0 -1
- package/lib/download/ui/ExportPictureSection.js +0 -48
- package/lib/download/ui/ExportPictureSection.js.map +0 -1
- package/lib/download/ui/useDrawingBox.d.ts +0 -23
- package/lib/download/ui/useDrawingBox.d.ts.map +0 -1
- package/lib/download/ui/useDrawingBox.js +0 -21
- package/lib/download/ui/useDrawingBox.js.map +0 -1
- package/src/about/core/repository.ts +0 -22
- package/src/download/core/exportStyles.ts +0 -20
- package/src/download/core/imageResolution.ts +0 -118
- package/src/download/core/pictureExport.ts +0 -207
- package/src/download/ui/ExportDialogFrame.tsx +0 -86
- package/src/download/ui/ExportImageDialog.tsx +0 -91
- package/src/download/ui/ExportNameField.tsx +0 -148
- package/src/download/ui/ExportPictureSection.tsx +0 -142
- package/src/download/ui/useDrawingBox.ts +0 -37
package/src/locales/it.json
CHANGED
|
@@ -86,10 +86,6 @@
|
|
|
86
86
|
"about.whatYouCanDo": "Cosa si può fare qui",
|
|
87
87
|
"about.builtOn": "Costruito su",
|
|
88
88
|
"about.howToCite": "Come citare",
|
|
89
|
-
"about.licenceAndSource": "Licenza e codice sorgente",
|
|
90
|
-
"about.licence": "{license}, © cheminfo. Si può usare in un corso, farne un fork, o riprenderne un pezzo per altro. Il codice è su {sources}.",
|
|
91
|
-
"about.foundAProblem": "Ha trovato un problema?",
|
|
92
|
-
"about.reportIt": "Ce lo dica: una segnalazione che indichi cosa ha scritto e cosa ha ottenuto è la correzione più rapida che esista. {tracker}.",
|
|
93
89
|
"about.citeWork": "Citare {what}",
|
|
94
90
|
"about.built": "Compilato {builtAt}.",
|
|
95
91
|
"about.builtFromCommit": "Compilato {builtAt} dal commit {commit}.",
|
|
@@ -189,6 +185,8 @@
|
|
|
189
185
|
"download.hintRasterSvg": "Un SVG che contiene un'immagine di {pixels}: una scena 3D non ha forma vettoriale.",
|
|
190
186
|
"download.hintSvg": "Si apre a {pixels}, e resta nitido a qualsiasi dimensione.",
|
|
191
187
|
"download.hintPng": "Salvato {pixels}.",
|
|
188
|
+
"download.hintCopied": "Copiato negli appunti: PNG di {pixels}.",
|
|
189
|
+
"download.copyUnsupported": "Questo browser non può mettere un'immagine negli appunti. Salvala invece.",
|
|
192
190
|
"download.size": "Dimensione",
|
|
193
191
|
"download.sizeHelp": "La forma con cui la figura viene disegnata per il file. Viene ricomposta, così gli assi e le etichette si adattano a quella forma invece di essere stirati. Una colonna di rivista è larga 8,5 cm, e il suo testo si stampa alla dimensione con cui si legge sullo schermo.",
|
|
194
192
|
"download.sizeScreen": "Come mostrata",
|
|
@@ -78,6 +78,12 @@ export interface OverlayPanelProps {
|
|
|
78
78
|
* @default derived from the type size — 88 at the compact size
|
|
79
79
|
*/
|
|
80
80
|
nameWidth?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Fixed width of the panel, for one whose hint changes with its choices:
|
|
83
|
+
* left to its content, it would resize under the pointer at every pick.
|
|
84
|
+
* @default undefined — as wide as its content
|
|
85
|
+
*/
|
|
86
|
+
width?: number | string;
|
|
81
87
|
/**
|
|
82
88
|
* Value of the `data-testid` attribute of the panel.
|
|
83
89
|
* @default undefined
|
|
@@ -104,7 +110,7 @@ export interface OverlayPanelProps {
|
|
|
104
110
|
export function OverlayPanel(props: OverlayPanelProps): ReactElement {
|
|
105
111
|
const { title, children, onReset, resetLabel, help } = props;
|
|
106
112
|
const t = useChromeT();
|
|
107
|
-
const { actions, hint, nameWidth, testId } = props;
|
|
113
|
+
const { actions, hint, nameWidth, width: panelWidth, testId } = props;
|
|
108
114
|
const { metrics } = useOverlaySurface();
|
|
109
115
|
const titleId = useId();
|
|
110
116
|
const reset = useOverlayInteraction();
|
|
@@ -120,7 +126,7 @@ export function OverlayPanel(props: OverlayPanelProps): ReactElement {
|
|
|
120
126
|
role="group"
|
|
121
127
|
aria-labelledby={titleId}
|
|
122
128
|
data-testid={testId}
|
|
123
|
-
style={overlayPanelSurfaceStyle(metrics)}
|
|
129
|
+
style={overlayPanelSurfaceStyle(metrics, panelWidth)}
|
|
124
130
|
>
|
|
125
131
|
<div style={overlayPanelHeaderStyle(metrics)}>
|
|
126
132
|
<span style={overlayPanelTitleStyle(metrics)}>
|
|
@@ -26,14 +26,17 @@ import { overlayFocusRing } from './overlayValueStyles.ts';
|
|
|
26
26
|
* The panel itself: a header, a body, and whatever the figure can be told to
|
|
27
27
|
* do at the foot.
|
|
28
28
|
* @param metrics - The measurements the card is drawn from.
|
|
29
|
+
* @param width - A fixed width, or `undefined` for as wide as its content.
|
|
29
30
|
* @returns The panel's rules.
|
|
30
31
|
*/
|
|
31
32
|
export function overlayPanelSurfaceStyle(
|
|
32
33
|
metrics: OverlayMetrics,
|
|
34
|
+
width?: number | string,
|
|
33
35
|
): CSSProperties {
|
|
34
36
|
return {
|
|
35
37
|
display: 'flex',
|
|
36
38
|
flexDirection: 'column',
|
|
39
|
+
width,
|
|
37
40
|
minWidth: OVERLAY_PANEL_MIN_WIDTH,
|
|
38
41
|
color: 'var(--text)',
|
|
39
42
|
fontSize: metrics.fontSize,
|
|
@@ -37,12 +37,6 @@ export interface ElementCellProps {
|
|
|
37
37
|
* @default '' — nothing is written
|
|
38
38
|
*/
|
|
39
39
|
detail?: string;
|
|
40
|
-
/**
|
|
41
|
-
* Whether the table this cell belongs to writes a third line at all. A table
|
|
42
|
-
* that writes none gives the room to the symbol, and its rows are shorter.
|
|
43
|
-
* @default whether this cell has one
|
|
44
|
-
*/
|
|
45
|
-
writesDetail?: boolean;
|
|
46
40
|
/**
|
|
47
41
|
* Whether the cell is the one the tools are pointed at.
|
|
48
42
|
* @default false
|
|
@@ -82,7 +76,6 @@ export function ElementCell(props: ElementCellProps): ReactElement {
|
|
|
82
76
|
row,
|
|
83
77
|
onSelect,
|
|
84
78
|
detail = '',
|
|
85
|
-
writesDetail = detail !== '',
|
|
86
79
|
isSelected = false,
|
|
87
80
|
isIncluded = true,
|
|
88
81
|
onHover,
|
|
@@ -137,7 +130,7 @@ export function ElementCell(props: ElementCellProps): ReactElement {
|
|
|
137
130
|
<span
|
|
138
131
|
style={{
|
|
139
132
|
...symbolStyle,
|
|
140
|
-
fontSize: symbolFontSize(symbol
|
|
133
|
+
fontSize: symbolFontSize(symbol),
|
|
141
134
|
}}
|
|
142
135
|
>
|
|
143
136
|
{symbol}
|
|
@@ -158,12 +151,10 @@ export function ElementCell(props: ElementCellProps): ReactElement {
|
|
|
158
151
|
* ones whose second letter is an `m` — which are written a tenth smaller
|
|
159
152
|
* rather than run into the edges of their cell.
|
|
160
153
|
* @param symbol - The chemical symbol.
|
|
161
|
-
* @param writesDetail - Whether the table writes a third line under it.
|
|
162
154
|
* @returns A CSS length.
|
|
163
155
|
*/
|
|
164
|
-
function symbolFontSize(symbol: string
|
|
165
|
-
|
|
166
|
-
return fitted(symbolWidthInEm(symbol), cap, '0.5rem');
|
|
156
|
+
function symbolFontSize(symbol: string): string {
|
|
157
|
+
return fitted(symbolWidthInEm(symbol), SYMBOL_CQW, '0.5rem');
|
|
167
158
|
}
|
|
168
159
|
|
|
169
160
|
/**
|
|
@@ -233,17 +224,24 @@ const TABULAR_EM = 0.6;
|
|
|
233
224
|
/** The size the symbol is written at wherever it fits, as a share of the table. */
|
|
234
225
|
const SYMBOL_CQW = '2.4cqw';
|
|
235
226
|
|
|
236
|
-
/** The same, in a table that writes nothing under the symbol. */
|
|
237
|
-
const SYMBOL_ALONE_CQW = '2.8cqw';
|
|
238
|
-
|
|
239
227
|
/**
|
|
240
228
|
* The tallest the third line is ever written, as a share of the table.
|
|
241
229
|
*
|
|
242
|
-
* Close to
|
|
243
|
-
*
|
|
244
|
-
*
|
|
230
|
+
* Close to two thirds of the symbol: the symbol is how a cell is found, but
|
|
231
|
+
* the value is what the table is being read for, and a value half the size of
|
|
232
|
+
* the symbol is the one thing on the cell a class cannot make out.
|
|
233
|
+
*/
|
|
234
|
+
const DETAIL_CQW = '1.6cqw';
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The band at each end of a cell: the atomic number at the top, whatever the
|
|
238
|
+
* tool writes at the bottom.
|
|
239
|
+
*
|
|
240
|
+
* They are one length, not two, and that is what makes the symbol between them
|
|
241
|
+
* sit at the centre of the cell rather than near it — a bottom band wider than
|
|
242
|
+
* the top one by a third lifted every symbol in the table two pixels.
|
|
245
243
|
*/
|
|
246
|
-
const
|
|
244
|
+
const EDGE_BAND = '1.8cqw';
|
|
247
245
|
|
|
248
246
|
/** How wide one column is, as a share of the table's width. */
|
|
249
247
|
const CELL_CQW = '4.85cqw';
|
|
@@ -251,49 +249,48 @@ const CELL_CQW = '4.85cqw';
|
|
|
251
249
|
/** What the border and the padding of a cell take off that width. */
|
|
252
250
|
const CELL_CHROME = '4px';
|
|
253
251
|
|
|
252
|
+
/**
|
|
253
|
+
* A cell is three bands of its own, and every cell has all three.
|
|
254
|
+
*
|
|
255
|
+
* The atomic number is in the top band, against its left edge; the symbol is
|
|
256
|
+
* centred in the middle one; whatever the tool writes is centred in the
|
|
257
|
+
* bottom one. The bands keep their height whether or not anything is written
|
|
258
|
+
* in the last of them, so a number sits at one height across the whole table
|
|
259
|
+
* and a symbol at one other — before this, both rode on a stack the cell
|
|
260
|
+
* centred and moved whenever a neighbour's value was written smaller.
|
|
261
|
+
*/
|
|
254
262
|
const cellStyle = {
|
|
255
|
-
alignItems: 'center',
|
|
256
263
|
border: '1px solid rgb(255 255 255 / 0.55)',
|
|
257
264
|
borderRadius: 3,
|
|
258
265
|
cursor: 'pointer',
|
|
259
|
-
display: '
|
|
260
|
-
flexDirection: 'column',
|
|
266
|
+
display: 'grid',
|
|
261
267
|
font: 'inherit',
|
|
262
|
-
|
|
268
|
+
gridTemplateRows: `${EDGE_BAND} 1fr ${EDGE_BAND}`,
|
|
269
|
+
justifyItems: 'center',
|
|
263
270
|
minWidth: 0,
|
|
264
271
|
overflow: 'hidden',
|
|
265
272
|
padding: '1px',
|
|
266
|
-
// The corner the atomic number is pinned into.
|
|
267
|
-
position: 'relative',
|
|
268
273
|
transition: 'opacity 120ms ease, filter 120ms ease',
|
|
269
274
|
} as const satisfies CSSProperties;
|
|
270
275
|
|
|
271
|
-
/**
|
|
272
|
-
* The atomic number sits out of the flow, in the corner.
|
|
273
|
-
*
|
|
274
|
-
* In the flow it rode on top of a stack the cell centres, so it moved down
|
|
275
|
-
* whenever what is written under the symbol was written smaller — the numbers
|
|
276
|
-
* of a row then sat at as many heights as the row had word lengths. Out of it,
|
|
277
|
-
* the number is in the same corner of every cell, and the symbol is centred in
|
|
278
|
-
* the cell rather than in what is left of it.
|
|
279
|
-
*/
|
|
280
276
|
const numberStyle = {
|
|
277
|
+
alignSelf: 'start',
|
|
281
278
|
fontSize: 'max(0.33rem, 1.1cqw)',
|
|
282
279
|
fontVariantNumeric: 'tabular-nums',
|
|
283
|
-
|
|
280
|
+
justifySelf: 'start',
|
|
284
281
|
lineHeight: 1,
|
|
285
282
|
opacity: 0.8,
|
|
286
|
-
position: 'absolute',
|
|
287
|
-
top: 'max(1px, 0.14cqw)',
|
|
288
283
|
} as const satisfies CSSProperties;
|
|
289
284
|
|
|
290
285
|
const symbolStyle = {
|
|
286
|
+
alignSelf: 'center',
|
|
291
287
|
fontWeight: 700,
|
|
292
288
|
letterSpacing: '-0.02em',
|
|
293
289
|
lineHeight: 1.02,
|
|
294
290
|
} as const satisfies CSSProperties;
|
|
295
291
|
|
|
296
292
|
const detailStyle = {
|
|
293
|
+
alignSelf: 'center',
|
|
297
294
|
fontVariantNumeric: 'tabular-nums',
|
|
298
295
|
lineHeight: 1,
|
|
299
296
|
maxWidth: '100%',
|
|
@@ -147,7 +147,6 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
|
|
|
147
147
|
} = props;
|
|
148
148
|
|
|
149
149
|
const t = useChromeT();
|
|
150
|
-
const writesDetail = detailOf !== undefined;
|
|
151
150
|
const gridRef = useRef<HTMLDivElement>(null);
|
|
152
151
|
const cameFromKeyRef = useRef(false);
|
|
153
152
|
const offset = headers ? 1 : 0;
|
|
@@ -178,7 +177,7 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
|
|
|
178
177
|
role="grid"
|
|
179
178
|
aria-label={t('periodic.table')}
|
|
180
179
|
data-testid="periodic-table"
|
|
181
|
-
style={gridStyle(headers
|
|
180
|
+
style={gridStyle(headers)}
|
|
182
181
|
onKeyDown={handleKeyDown}
|
|
183
182
|
>
|
|
184
183
|
{headers ? <HeaderStrips onSelectRange={onSelectRange} /> : null}
|
|
@@ -193,7 +192,6 @@ export function PeriodicTable(props: PeriodicTableProps): ReactElement {
|
|
|
193
192
|
symbol={element.symbol}
|
|
194
193
|
name={nameOf(element)}
|
|
195
194
|
detail={detailOf?.(element)}
|
|
196
|
-
writesDetail={writesDetail}
|
|
197
195
|
swatch={swatchOf(element)}
|
|
198
196
|
isSelected={element.symbol === selected}
|
|
199
197
|
isIncluded={isIncluded?.(element)}
|
|
@@ -218,8 +216,9 @@ function insetStyle(offset: number): CSSProperties {
|
|
|
218
216
|
return {
|
|
219
217
|
alignItems: 'center',
|
|
220
218
|
display: 'flex',
|
|
221
|
-
//
|
|
222
|
-
|
|
219
|
+
// A column of air on each side, so the writing reads as sitting in the
|
|
220
|
+
// block rather than as running into the cells beside it.
|
|
221
|
+
padding: '0 2.4cqw',
|
|
223
222
|
gridColumn: `${String(EMPTY_BLOCK.column + offset)} / span ${String(EMPTY_BLOCK.columnSpan)}`,
|
|
224
223
|
gridRow: `${String(EMPTY_BLOCK.row + offset)} / span ${String(EMPTY_BLOCK.rowSpan)}`,
|
|
225
224
|
// A share of the table, with a floor, exactly as a cell sizes its symbol:
|
|
@@ -251,14 +250,13 @@ const MIN_WIDTH = 280;
|
|
|
251
250
|
/**
|
|
252
251
|
* How tall one row of elements is, as a share of the table's width.
|
|
253
252
|
*
|
|
254
|
-
* A column is about 5.
|
|
255
|
-
*
|
|
256
|
-
* cell need to be
|
|
253
|
+
* A column is about 5.2% of that width, so a cell is a third taller than it is
|
|
254
|
+
* wide — the proportion of a wall chart, and the room the three bands of a
|
|
255
|
+
* cell need to be read from the back of a room. It is the same height in every
|
|
256
|
+
* table: a cell keeps the band it writes a value in whether or not that table
|
|
257
|
+
* writes one.
|
|
257
258
|
*/
|
|
258
|
-
const ROW_HEIGHT = '6.
|
|
259
|
-
|
|
260
|
-
/** The same, in a table that writes nothing under the symbol. */
|
|
261
|
-
const ROW_HEIGHT_ALONE = '4.9cqw';
|
|
259
|
+
const ROW_HEIGHT = '6.8cqw';
|
|
262
260
|
|
|
263
261
|
/** The band the two inner-transition series were lifted out across. */
|
|
264
262
|
const SERIES_GAP = 'max(6px, 1.1cqw)';
|
|
@@ -285,14 +283,11 @@ const baseGridStyle = {
|
|
|
285
283
|
* The grid the cells are placed on.
|
|
286
284
|
* @param headers - Whether a leading column and row hold the period and group
|
|
287
285
|
* numbers.
|
|
288
|
-
* @param writesDetail - Whether a cell writes a third line, which is what a
|
|
289
|
-
* row is tall enough for.
|
|
290
286
|
* @returns The style of the grid.
|
|
291
287
|
*/
|
|
292
|
-
function gridStyle(headers: boolean
|
|
293
|
-
const row = writesDetail ? ROW_HEIGHT : ROW_HEIGHT_ALONE;
|
|
288
|
+
function gridStyle(headers: boolean): CSSProperties {
|
|
294
289
|
// The eighth row is the gap the inner-transition series are lifted out into.
|
|
295
|
-
const rows = `repeat(7, ${
|
|
290
|
+
const rows = `repeat(7, ${ROW_HEIGHT}) ${SERIES_GAP} repeat(2, ${ROW_HEIGHT})`;
|
|
296
291
|
if (!headers) {
|
|
297
292
|
return {
|
|
298
293
|
...baseGridStyle,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tags that tell a search engine five addresses are one page written five
|
|
3
|
+
* times.
|
|
4
|
+
*
|
|
5
|
+
* Without them a translated site is read as five sites that happen to look
|
|
6
|
+
* alike: the French tutorial competes with the English one instead of being
|
|
7
|
+
* offered to a French reader, and a search engine picks one of them to keep.
|
|
8
|
+
* With them each address is served to the audience it was written for, and the
|
|
9
|
+
* four it was not stop diluting it.
|
|
10
|
+
*
|
|
11
|
+
* Every page names **every** language including itself — a set that does not
|
|
12
|
+
* list the page it is on is ignored whole — plus `x-default`, which is where a
|
|
13
|
+
* reader whose language we do not speak is sent.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Language } from '../../i18n/core/languages.ts';
|
|
17
|
+
import { DEFAULT_LANGUAGE } from '../../i18n/core/languages.ts';
|
|
18
|
+
import { withLanguagePath } from '../../language/core/languagePath.ts';
|
|
19
|
+
import { escapeAttribute } from '../../share/core/escape.ts';
|
|
20
|
+
|
|
21
|
+
/** Which page, in which languages, served from where. */
|
|
22
|
+
export interface AlternateOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Where the site is served, mount path included and no trailing slash, e.g.
|
|
25
|
+
* `https://3d.cheminfo.org`.
|
|
26
|
+
*/
|
|
27
|
+
origin: string;
|
|
28
|
+
/**
|
|
29
|
+
* The page, from the site's own root and with no language prefix on it, e.g.
|
|
30
|
+
* `/tutorial`.
|
|
31
|
+
*/
|
|
32
|
+
path: string;
|
|
33
|
+
/**
|
|
34
|
+
* Every language the site is written in. One language alone means there is
|
|
35
|
+
* nothing to point at, and no tag is written.
|
|
36
|
+
*/
|
|
37
|
+
languages: readonly Language[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The `rel="alternate"` block for one page of a translated site.
|
|
42
|
+
* @param options - The page, its languages and where the site is served.
|
|
43
|
+
* @returns The tags, one per line, or `''` when the site speaks one language.
|
|
44
|
+
*/
|
|
45
|
+
export function alternateLinkTags(options: AlternateOptions): string {
|
|
46
|
+
const { origin, path, languages } = options;
|
|
47
|
+
if (languages.length < 2) return '';
|
|
48
|
+
|
|
49
|
+
const tags: string[] = [];
|
|
50
|
+
for (const language of languages) {
|
|
51
|
+
tags.push(linkTag(language, address(origin, language, path)));
|
|
52
|
+
}
|
|
53
|
+
// A reader we have no language for gets the unprefixed address, which is the
|
|
54
|
+
// one every link handed out before the site was translated already points at.
|
|
55
|
+
tags.push(linkTag('x-default', address(origin, DEFAULT_LANGUAGE, path)));
|
|
56
|
+
return tags.join('\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function address(origin: string, language: Language, path: string): string {
|
|
60
|
+
return `${origin}${withLanguagePath(language, path)}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function linkTag(hreflang: string, href: string): string {
|
|
64
|
+
return `<link rel="alternate" hreflang="${escapeAttribute(hreflang)}" href="${escapeAttribute(href)}" />`;
|
|
65
|
+
}
|
package/src/seo/core/index.ts
CHANGED
|
@@ -10,10 +10,13 @@ export type {
|
|
|
10
10
|
export { noscriptIndex } from './noscript.ts';
|
|
11
11
|
export type { PageMetaOptions } from './pageMeta.ts';
|
|
12
12
|
export { injectPageMeta, pageDocumentMeta, pageHeadTags } from './pageMeta.ts';
|
|
13
|
+
export type { PageContent, PageSection, PageTable } from './pageProse.ts';
|
|
14
|
+
export { pageProseHtml } from './pageProse.ts';
|
|
13
15
|
export type { PlainDescriptionOptions } from './plainDescription.ts';
|
|
14
16
|
export { plainDescription, plainProse } from './plainDescription.ts';
|
|
15
17
|
export type { RobotsDisallow } from './robots.ts';
|
|
16
18
|
export { robotsTxt } from './robots.ts';
|
|
19
|
+
export { routeProblems, withheldPhrase } from './routeProblems.ts';
|
|
17
20
|
export type { RouteMeta } from './routes.ts';
|
|
18
21
|
export { assertRoutes, homeRoute, pageMetaFor, routeFor } from './routes.ts';
|
|
19
22
|
export type { SiteFilesOptions } from './siteFiles.ts';
|
package/src/seo/core/noscript.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { ECOSYSTEM_SITES, siteUrl } from '../../ecosystem/core/sites.ts';
|
|
|
12
12
|
import { joinBasePath } from '../../router/core/basePath.ts';
|
|
13
13
|
import { escapeAttribute, escapeText } from '../../share/core/escape.ts';
|
|
14
14
|
|
|
15
|
+
import type { PageContent } from './pageProse.ts';
|
|
16
|
+
import { pageProseHtml } from './pageProse.ts';
|
|
15
17
|
import type { RouteMeta } from './routes.ts';
|
|
16
18
|
import type { SiteFilesOptions } from './siteFiles.ts';
|
|
17
19
|
import { mountPathOf, resolveSite } from './siteFiles.ts';
|
|
@@ -93,6 +95,14 @@ export interface NoscriptOptions
|
|
|
93
95
|
extends Omit<SiteFilesOptions, 'routes'>, Omit<NoscriptText, 'routes'> {
|
|
94
96
|
/** The addresses it links, each with the label it is linked under. */
|
|
95
97
|
routes: readonly NoscriptRoute[];
|
|
98
|
+
/**
|
|
99
|
+
* What the page being written says, above the crawl path. The block is the
|
|
100
|
+
* only text a crawler is handed before anything runs, so a page with nothing
|
|
101
|
+
* of its own here is indexed on its title and on this site's menu — which
|
|
102
|
+
* every other page of the site carries too.
|
|
103
|
+
* @default undefined — the block is the menu alone
|
|
104
|
+
*/
|
|
105
|
+
content?: PageContent;
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
/**
|
|
@@ -111,14 +121,20 @@ export function noscriptIndex(options: NoscriptOptions): string {
|
|
|
111
121
|
const site = resolveSite(options.site);
|
|
112
122
|
const hrefs = options.hrefs ?? 'absolute';
|
|
113
123
|
const mount = hrefs === 'absolute' ? mountPathOf(options) : '';
|
|
114
|
-
const
|
|
124
|
+
const content = options.content;
|
|
125
|
+
// The page's own name answers the title it is indexed under, so it wins over
|
|
126
|
+
// a heading the site writes for every one of its pages at once.
|
|
127
|
+
const heading = escapeText(
|
|
128
|
+
content?.heading ?? options.heading ?? siteDisplayName(site),
|
|
129
|
+
);
|
|
115
130
|
const intro = escapeText(
|
|
116
131
|
options.intro ??
|
|
117
132
|
`${site.tagline} This tool needs JavaScript; these are the pages it offers:`,
|
|
118
133
|
);
|
|
134
|
+
const prose = content === undefined ? '' : pageProseHtml(content, ' ');
|
|
119
135
|
|
|
120
136
|
return `<noscript>
|
|
121
|
-
<h1>${heading}</h1
|
|
137
|
+
<h1>${heading}</h1>${prose}
|
|
122
138
|
<p>${intro}</p>${pageList(options.routes, mount, hrefs, ' ')}${familyList(site.id, options.ecosystem)}
|
|
123
139
|
</noscript>`;
|
|
124
140
|
}
|
package/src/seo/core/pageMeta.ts
CHANGED
|
@@ -14,14 +14,26 @@
|
|
|
14
14
|
|
|
15
15
|
import { siteDisplayName } from '../../ecosystem/core/lookup.ts';
|
|
16
16
|
import type { SiteId, SiteRecord } from '../../ecosystem/core/sites.ts';
|
|
17
|
+
import type { Language } from '../../i18n/core/languages.ts';
|
|
18
|
+
import { DEFAULT_LANGUAGE } from '../../i18n/core/languages.ts';
|
|
19
|
+
import {
|
|
20
|
+
readLanguagePath,
|
|
21
|
+
withLanguagePath,
|
|
22
|
+
} from '../../language/core/languagePath.ts';
|
|
23
|
+
import { withoutQueryOrFragment } from '../../router/core/address.ts';
|
|
24
|
+
import { stripBasePath } from '../../router/core/basePath.ts';
|
|
17
25
|
import { escapeAttribute, escapeText } from '../../share/core/escape.ts';
|
|
18
26
|
|
|
27
|
+
import { alternateLinkTags } from './alternates.ts';
|
|
19
28
|
import type { DocumentMeta } from './documentMeta.ts';
|
|
20
29
|
import type { RouteMeta } from './routes.ts';
|
|
21
30
|
import { pageMetaFor } from './routes.ts';
|
|
22
31
|
import { mountPathOf, originOf, resolveSite } from './siteFiles.ts';
|
|
23
32
|
import { PAGE_HEAD_MARKER, fill } from './template.ts';
|
|
24
33
|
|
|
34
|
+
// A scheme and an authority: what `location.href` hands out.
|
|
35
|
+
const ABSOLUTE_URL = /^[a-z][\d+.a-z-]*:\/\//i;
|
|
36
|
+
|
|
25
37
|
/** Which site is being served, and what it answers. */
|
|
26
38
|
export interface PageMetaOptions {
|
|
27
39
|
/** The site, named or passed. */
|
|
@@ -48,6 +60,17 @@ export interface PageMetaOptions {
|
|
|
48
60
|
* @default '/og.png'
|
|
49
61
|
*/
|
|
50
62
|
image?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Every language the site is written in, the default one included.
|
|
65
|
+
*
|
|
66
|
+
* The language is read off the address — `/fr/tutorial` is the French
|
|
67
|
+
* tutorial — so the canonical, the card and the `hreflang` set are written
|
|
68
|
+
* for the page actually being served, and the routes passed are the table in
|
|
69
|
+
* that language. A site writing one language leaves this out and nothing
|
|
70
|
+
* about its head changes.
|
|
71
|
+
* @default [the default language]
|
|
72
|
+
*/
|
|
73
|
+
languages?: readonly Language[];
|
|
51
74
|
}
|
|
52
75
|
|
|
53
76
|
/**
|
|
@@ -74,12 +97,27 @@ export function injectPageMeta(html: string, options: PageMetaOptions): string {
|
|
|
74
97
|
*/
|
|
75
98
|
export function pageHeadTags(options: PageMetaOptions): string {
|
|
76
99
|
const name = siteDisplayName(resolveSite(options.site));
|
|
77
|
-
const
|
|
100
|
+
const route = routeMetaOf(options);
|
|
101
|
+
const description = route.description;
|
|
78
102
|
const origin = originOf(options);
|
|
79
103
|
const { title, canonical } = pageDocumentMeta(options);
|
|
80
104
|
const image = absolute(options.image ?? '/og.png', origin);
|
|
105
|
+
const alternates = alternateLinkTags({
|
|
106
|
+
origin,
|
|
107
|
+
path: route.path,
|
|
108
|
+
languages: languagesOf(options),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// A maintenance screen says so on the page itself. `robots.txt` cannot: it
|
|
112
|
+
// stops the crawl, and an address nobody crawled is still listed from
|
|
113
|
+
// whatever links to it, with no description to show for it.
|
|
114
|
+
const robots =
|
|
115
|
+
route.indexed === false
|
|
116
|
+
? ['<meta name="robots" content="noindex, nofollow" />']
|
|
117
|
+
: [];
|
|
81
118
|
|
|
82
119
|
return [
|
|
120
|
+
...robots,
|
|
83
121
|
`<title>${escapeText(title)}</title>`,
|
|
84
122
|
`<meta name="description" content="${escapeAttribute(description)}" />`,
|
|
85
123
|
`<link rel="canonical" href="${escapeAttribute(canonical)}" />`,
|
|
@@ -90,6 +128,7 @@ export function pageHeadTags(options: PageMetaOptions): string {
|
|
|
90
128
|
`<meta property="og:url" content="${escapeAttribute(canonical)}" />`,
|
|
91
129
|
`<meta property="og:image" content="${escapeAttribute(image)}" />`,
|
|
92
130
|
'<meta name="twitter:card" content="summary_large_image" />',
|
|
131
|
+
...(alternates === '' ? [] : [alternates]),
|
|
93
132
|
].join('\n');
|
|
94
133
|
}
|
|
95
134
|
|
|
@@ -112,15 +151,47 @@ export function pageDocumentMeta(
|
|
|
112
151
|
return {
|
|
113
152
|
title: `${meta.title} — ${siteDisplayName(site)}`,
|
|
114
153
|
description: meta.description,
|
|
115
|
-
canonical: `${originOf(options)}${meta.path}`,
|
|
154
|
+
canonical: `${originOf(options)}${withLanguagePath(pageLanguage(options), meta.path)}`,
|
|
116
155
|
};
|
|
117
156
|
}
|
|
118
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The language an address is being served in.
|
|
160
|
+
*
|
|
161
|
+
* Read off the address itself rather than passed beside it, so the catalog a
|
|
162
|
+
* server picks and the canonical it writes cannot disagree.
|
|
163
|
+
* @param options - Which site, which address, and the languages it speaks.
|
|
164
|
+
* @returns The language named by the address, or the default one.
|
|
165
|
+
* @throws {Error} When the deployment names an origin that is not an absolute
|
|
166
|
+
* address.
|
|
167
|
+
*/
|
|
168
|
+
export function pageLanguage(options: PageMetaOptions): Language {
|
|
169
|
+
return readLanguagePath(ownPath(options), languagesOf(options)).language;
|
|
170
|
+
}
|
|
171
|
+
|
|
119
172
|
// A server behind a mount is handed the address the browser asked for, and the
|
|
120
173
|
// route table is written from the site's own root, so the mount the origin
|
|
121
174
|
// carries is taken off it before the table is read.
|
|
122
175
|
function routeMetaOf(options: PageMetaOptions): RouteMeta {
|
|
123
|
-
|
|
176
|
+
const { path } = readLanguagePath(ownPath(options), languagesOf(options));
|
|
177
|
+
return pageMetaFor(options.routes, path);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// The address from the site's own root, the mount taken off, with the query
|
|
181
|
+
// string and the fragment gone: what the language prefix is read off. An app
|
|
182
|
+
// handing over `location.href` is answered too, so the one reading serves a
|
|
183
|
+
// server, a build and a click alike.
|
|
184
|
+
function ownPath(options: PageMetaOptions): string {
|
|
185
|
+
const url = options.url;
|
|
186
|
+
const path =
|
|
187
|
+
ABSOLUTE_URL.test(url) && URL.canParse(url)
|
|
188
|
+
? new URL(url).pathname
|
|
189
|
+
: withoutQueryOrFragment(url);
|
|
190
|
+
return stripBasePath(mountPathOf(options), path);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function languagesOf(options: PageMetaOptions): readonly Language[] {
|
|
194
|
+
return options.languages ?? [DEFAULT_LANGUAGE];
|
|
124
195
|
}
|
|
125
196
|
|
|
126
197
|
function absolute(target: string, origin: string): string {
|