@docusaurus/theme-common 3.7.0 → 3.8.0-canary-6327
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/lib/components/Collapsible/index.d.ts +0 -6
- package/lib/components/Collapsible/index.d.ts.map +1 -1
- package/lib/components/Collapsible/index.js +3 -17
- package/lib/components/Collapsible/index.js.map +1 -1
- package/lib/components/Details/index.d.ts.map +1 -1
- package/lib/components/Details/index.js +1 -2
- package/lib/components/Details/index.js.map +1 -1
- package/lib/contexts/colorMode.d.ts +6 -7
- package/lib/contexts/colorMode.d.ts.map +1 -1
- package/lib/contexts/colorMode.js +111 -67
- package/lib/contexts/colorMode.js.map +1 -1
- package/lib/contexts/navbarMobileSidebar.d.ts.map +1 -1
- package/lib/contexts/navbarMobileSidebar.js +21 -12
- package/lib/contexts/navbarMobileSidebar.js.map +1 -1
- package/lib/hooks/useCodeWordWrap.d.ts +2 -1
- package/lib/hooks/useCodeWordWrap.d.ts.map +1 -1
- package/lib/hooks/useCodeWordWrap.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/internal.d.ts +2 -3
- package/lib/internal.d.ts.map +1 -1
- package/lib/internal.js +5 -3
- package/lib/internal.js.map +1 -1
- package/lib/utils/ThemeClassNames.d.ts +21 -1
- package/lib/utils/ThemeClassNames.d.ts.map +1 -1
- package/lib/utils/ThemeClassNames.js +21 -2
- package/lib/utils/ThemeClassNames.js.map +1 -1
- package/lib/utils/codeBlockUtils.d.ts +66 -34
- package/lib/utils/codeBlockUtils.d.ts.map +1 -1
- package/lib/utils/codeBlockUtils.js +136 -30
- package/lib/utils/codeBlockUtils.js.map +1 -1
- package/lib/utils/metadataUtils.d.ts.map +1 -1
- package/lib/utils/metadataUtils.js +37 -20
- package/lib/utils/metadataUtils.js.map +1 -1
- package/lib/utils/reactUtils.d.ts.map +1 -1
- package/lib/utils/reactUtils.js +4 -1
- package/lib/utils/reactUtils.js.map +1 -1
- package/lib/utils/scrollUtils.d.ts.map +1 -1
- package/lib/utils/scrollUtils.js +1 -0
- package/lib/utils/scrollUtils.js.map +1 -1
- package/lib/utils/storageUtils.js +3 -3
- package/lib/utils/storageUtils.js.map +1 -1
- package/lib/utils/titleFormatterUtils.d.ts +59 -0
- package/lib/utils/titleFormatterUtils.d.ts.map +1 -0
- package/lib/utils/titleFormatterUtils.js +51 -0
- package/lib/utils/titleFormatterUtils.js.map +1 -0
- package/lib/utils/useThemeConfig.d.ts +3 -2
- package/lib/utils/useThemeConfig.d.ts.map +1 -1
- package/lib/utils/useThemeConfig.js.map +1 -1
- package/package.json +9 -10
- package/src/components/Collapsible/index.tsx +1 -29
- package/src/components/Details/index.tsx +0 -1
- package/src/contexts/colorMode.tsx +164 -83
- package/src/contexts/navbarMobileSidebar.tsx +33 -13
- package/src/hooks/useCodeWordWrap.ts +4 -2
- package/src/index.ts +1 -1
- package/src/internal.ts +14 -3
- package/src/utils/ThemeClassNames.ts +25 -2
- package/src/utils/{codeBlockUtils.ts → codeBlockUtils.tsx} +255 -57
- package/src/utils/metadataUtils.tsx +57 -27
- package/src/utils/reactUtils.tsx +4 -1
- package/src/utils/scrollUtils.tsx +1 -0
- package/src/utils/storageUtils.ts +3 -3
- package/src/utils/titleFormatterUtils.tsx +122 -0
- package/src/utils/useThemeConfig.ts +4 -2
- package/lib/utils/generalUtils.d.ts +0 -11
- package/lib/utils/generalUtils.d.ts.map +0 -1
- package/lib/utils/generalUtils.js +0 -18
- package/lib/utils/generalUtils.js.map +0 -1
- package/src/utils/generalUtils.ts +0 -19
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {CSSProperties} from 'react';
|
|
8
|
+
import type {CSSProperties, ReactNode} from 'react';
|
|
9
|
+
import {createContext, useContext, useMemo} from 'react';
|
|
10
|
+
import clsx from 'clsx';
|
|
9
11
|
import rangeParser from 'parse-numeric-range';
|
|
12
|
+
import {ReactContextError} from './reactUtils';
|
|
10
13
|
import type {PrismTheme, PrismThemeEntry} from 'prism-react-renderer';
|
|
14
|
+
import type {WordWrap} from '../hooks/useCodeWordWrap';
|
|
11
15
|
|
|
12
16
|
const codeBlockTitleRegex = /title=(?<quote>["'])(?<title>.*?)\1/;
|
|
13
17
|
const metastringLinesRangeRegex = /\{(?<range>[\d,-]+)\}/;
|
|
@@ -151,69 +155,81 @@ export function parseCodeBlockTitle(metastring?: string): string {
|
|
|
151
155
|
return metastring?.match(codeBlockTitleRegex)?.groups!.title ?? '';
|
|
152
156
|
}
|
|
153
157
|
|
|
158
|
+
function getMetaLineNumbersStart(metastring?: string): number | undefined {
|
|
159
|
+
const showLineNumbersMeta = metastring
|
|
160
|
+
?.split(' ')
|
|
161
|
+
.find((str) => str.startsWith('showLineNumbers'));
|
|
162
|
+
|
|
163
|
+
if (showLineNumbersMeta) {
|
|
164
|
+
if (showLineNumbersMeta.startsWith('showLineNumbers=')) {
|
|
165
|
+
const value = showLineNumbersMeta.replace('showLineNumbers=', '');
|
|
166
|
+
return parseInt(value, 10);
|
|
167
|
+
}
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function getLineNumbersStart({
|
|
175
|
+
showLineNumbers,
|
|
176
|
+
metastring,
|
|
177
|
+
}: {
|
|
178
|
+
showLineNumbers: boolean | number | undefined;
|
|
179
|
+
metastring: string | undefined;
|
|
180
|
+
}): number | undefined {
|
|
181
|
+
const defaultStart = 1;
|
|
182
|
+
if (typeof showLineNumbers === 'boolean') {
|
|
183
|
+
return showLineNumbers ? defaultStart : undefined;
|
|
184
|
+
}
|
|
185
|
+
if (typeof showLineNumbers === 'number') {
|
|
186
|
+
return showLineNumbers;
|
|
187
|
+
}
|
|
188
|
+
return getMetaLineNumbersStart(metastring);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// TODO Docusaurus v4: remove, only kept for internal retro-compatibility
|
|
192
|
+
// See https://github.com/facebook/docusaurus/pull/11153
|
|
154
193
|
export function containsLineNumbers(metastring?: string): boolean {
|
|
155
194
|
return Boolean(metastring?.includes('showLineNumbers'));
|
|
156
195
|
}
|
|
157
196
|
|
|
197
|
+
type ParseCodeLinesParam = {
|
|
198
|
+
/**
|
|
199
|
+
* The full metastring, as received from MDX. Line ranges declared here
|
|
200
|
+
* start at 1.
|
|
201
|
+
*/
|
|
202
|
+
metastring: string | undefined;
|
|
203
|
+
/**
|
|
204
|
+
* Language of the code block, used to determine which kinds of magic
|
|
205
|
+
* comment styles to enable.
|
|
206
|
+
*/
|
|
207
|
+
language: string | undefined;
|
|
208
|
+
/**
|
|
209
|
+
* Magic comment types that we should try to parse. Each entry would
|
|
210
|
+
* correspond to one class name to apply to each line.
|
|
211
|
+
*/
|
|
212
|
+
magicComments: MagicCommentConfig[];
|
|
213
|
+
};
|
|
214
|
+
|
|
158
215
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* Returns undefined if there is no language class name.
|
|
216
|
+
* The highlighted lines, 0-indexed. e.g. `{ 0: ["highlight", "sample"] }`
|
|
217
|
+
* means the 1st line should have `highlight` and `sample` as class names.
|
|
162
218
|
*/
|
|
163
|
-
|
|
164
|
-
const languageClassName = className
|
|
165
|
-
.split(' ')
|
|
166
|
-
.find((str) => str.startsWith('language-'));
|
|
167
|
-
return languageClassName?.replace(/language-/, '');
|
|
168
|
-
}
|
|
219
|
+
type CodeLineClassNames = {[lineIndex: number]: string[]};
|
|
169
220
|
|
|
170
221
|
/**
|
|
171
|
-
*
|
|
172
|
-
* clean content and the highlighted lines marked by the comments or metastring.
|
|
173
|
-
*
|
|
174
|
-
* If the metastring contains a range, the `content` will be returned as-is
|
|
175
|
-
* without any parsing. The returned `lineClassNames` will be a map from that
|
|
176
|
-
* number range to the first magic comment config entry (which _should_ be for
|
|
177
|
-
* line highlight directives.)
|
|
178
|
-
*
|
|
179
|
-
* @param content The raw code with magic comments. Trailing newline will be
|
|
180
|
-
* trimmed upfront.
|
|
181
|
-
* @param options Options for parsing behavior.
|
|
222
|
+
* Code lines after applying magic comments or metastring highlight ranges
|
|
182
223
|
*/
|
|
183
|
-
|
|
184
|
-
content: string,
|
|
185
|
-
options: {
|
|
186
|
-
/**
|
|
187
|
-
* The full metastring, as received from MDX. Line ranges declared here
|
|
188
|
-
* start at 1.
|
|
189
|
-
*/
|
|
190
|
-
metastring: string | undefined;
|
|
191
|
-
/**
|
|
192
|
-
* Language of the code block, used to determine which kinds of magic
|
|
193
|
-
* comment styles to enable.
|
|
194
|
-
*/
|
|
195
|
-
language: string | undefined;
|
|
196
|
-
/**
|
|
197
|
-
* Magic comment types that we should try to parse. Each entry would
|
|
198
|
-
* correspond to one class name to apply to each line.
|
|
199
|
-
*/
|
|
200
|
-
magicComments: MagicCommentConfig[];
|
|
201
|
-
},
|
|
202
|
-
): {
|
|
203
|
-
/**
|
|
204
|
-
* The highlighted lines, 0-indexed. e.g. `{ 0: ["highlight", "sample"] }`
|
|
205
|
-
* means the 1st line should have `highlight` and `sample` as class names.
|
|
206
|
-
*/
|
|
207
|
-
lineClassNames: {[lineIndex: number]: string[]};
|
|
208
|
-
/**
|
|
209
|
-
* If there's number range declared in the metastring, the code block is
|
|
210
|
-
* returned as-is (no parsing); otherwise, this is the clean code with all
|
|
211
|
-
* magic comments stripped away.
|
|
212
|
-
*/
|
|
224
|
+
type ParsedCodeLines = {
|
|
213
225
|
code: string;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
226
|
+
lineClassNames: CodeLineClassNames;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
function parseCodeLinesFromMetastring(
|
|
230
|
+
code: string,
|
|
231
|
+
{metastring, magicComments}: ParseCodeLinesParam,
|
|
232
|
+
): ParsedCodeLines | null {
|
|
217
233
|
// Highlighted lines specified in props: don't parse the content
|
|
218
234
|
if (metastring && metastringLinesRangeRegex.test(metastring)) {
|
|
219
235
|
const linesRange = metastring.match(metastringLinesRangeRegex)!.groups!
|
|
@@ -229,6 +245,14 @@ export function parseLines(
|
|
|
229
245
|
.map((n) => [n - 1, [metastringRangeClassName]] as [number, string[]]);
|
|
230
246
|
return {lineClassNames: Object.fromEntries(lines), code};
|
|
231
247
|
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function parseCodeLinesFromContent(
|
|
252
|
+
code: string,
|
|
253
|
+
params: ParseCodeLinesParam,
|
|
254
|
+
): ParsedCodeLines {
|
|
255
|
+
const {language, magicComments} = params;
|
|
232
256
|
if (language === undefined) {
|
|
233
257
|
return {lineClassNames: {}, code};
|
|
234
258
|
}
|
|
@@ -237,7 +261,7 @@ export function parseLines(
|
|
|
237
261
|
magicComments,
|
|
238
262
|
);
|
|
239
263
|
// Go through line by line
|
|
240
|
-
const lines = code.split(
|
|
264
|
+
const lines = code.split(/\r?\n/);
|
|
241
265
|
const blocks = Object.fromEntries(
|
|
242
266
|
magicComments.map((d) => [d.className, {start: 0, range: ''}]),
|
|
243
267
|
);
|
|
@@ -278,7 +302,7 @@ export function parseLines(
|
|
|
278
302
|
}
|
|
279
303
|
lines.splice(lineNumber, 1);
|
|
280
304
|
}
|
|
281
|
-
|
|
305
|
+
|
|
282
306
|
const lineClassNames: {[lineIndex: number]: string[]} = {};
|
|
283
307
|
Object.entries(blocks).forEach(([className, {range}]) => {
|
|
284
308
|
rangeParser(range).forEach((l) => {
|
|
@@ -286,7 +310,145 @@ export function parseLines(
|
|
|
286
310
|
lineClassNames[l]!.push(className);
|
|
287
311
|
});
|
|
288
312
|
});
|
|
289
|
-
|
|
313
|
+
|
|
314
|
+
return {code: lines.join('\n'), lineClassNames};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Parses the code content, strips away any magic comments, and returns the
|
|
319
|
+
* clean content and the highlighted lines marked by the comments or metastring.
|
|
320
|
+
*
|
|
321
|
+
* If the metastring contains a range, the `content` will be returned as-is
|
|
322
|
+
* without any parsing. The returned `lineClassNames` will be a map from that
|
|
323
|
+
* number range to the first magic comment config entry (which _should_ be for
|
|
324
|
+
* line highlight directives.)
|
|
325
|
+
*/
|
|
326
|
+
export function parseLines(
|
|
327
|
+
code: string,
|
|
328
|
+
params: ParseCodeLinesParam,
|
|
329
|
+
): ParsedCodeLines {
|
|
330
|
+
// Historical behavior: we remove last line break
|
|
331
|
+
const newCode = code.replace(/\r?\n$/, '');
|
|
332
|
+
// Historical behavior: we try one strategy after the other
|
|
333
|
+
// we don't support mixing metastring ranges + magic comments
|
|
334
|
+
return (
|
|
335
|
+
parseCodeLinesFromMetastring(newCode, {...params}) ??
|
|
336
|
+
parseCodeLinesFromContent(newCode, {...params})
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Gets the language name from the class name (set by MDX).
|
|
342
|
+
* e.g. `"language-javascript"` => `"javascript"`.
|
|
343
|
+
* Returns undefined if there is no language class name.
|
|
344
|
+
*/
|
|
345
|
+
export function parseClassNameLanguage(
|
|
346
|
+
className: string | undefined,
|
|
347
|
+
): string | undefined {
|
|
348
|
+
if (!className) {
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
const languageClassName = className
|
|
352
|
+
.split(' ')
|
|
353
|
+
.find((str) => str.startsWith('language-'));
|
|
354
|
+
return languageClassName?.replace(/language-/, '');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Prism languages are always lowercase
|
|
358
|
+
// We want to fail-safe and allow both "php" and "PHP"
|
|
359
|
+
// See https://github.com/facebook/docusaurus/issues/9012
|
|
360
|
+
function normalizeLanguage(language: string | undefined): string | undefined {
|
|
361
|
+
return language?.toLowerCase();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function getLanguage(params: {
|
|
365
|
+
language: string | undefined;
|
|
366
|
+
className: string | undefined;
|
|
367
|
+
defaultLanguage: string | undefined;
|
|
368
|
+
}): string {
|
|
369
|
+
return (
|
|
370
|
+
normalizeLanguage(
|
|
371
|
+
params.language ??
|
|
372
|
+
parseClassNameLanguage(params.className) ??
|
|
373
|
+
params.defaultLanguage,
|
|
374
|
+
) ?? 'text'
|
|
375
|
+
); // There's always a language, required by Prism;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* This ensures that we always have the code block language as className
|
|
380
|
+
* For MDX code blocks this is provided automatically by MDX
|
|
381
|
+
* For JSX code blocks, the language gets added by this function
|
|
382
|
+
* This ensures both cases lead to a consistent HTML output
|
|
383
|
+
*/
|
|
384
|
+
function ensureLanguageClassName({
|
|
385
|
+
className,
|
|
386
|
+
language,
|
|
387
|
+
}: {
|
|
388
|
+
className: string | undefined;
|
|
389
|
+
language: string;
|
|
390
|
+
}): string {
|
|
391
|
+
return clsx(
|
|
392
|
+
className,
|
|
393
|
+
language &&
|
|
394
|
+
!className?.includes(`language-${language}`) &&
|
|
395
|
+
`language-${language}`,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export interface CodeBlockMetadata {
|
|
400
|
+
codeInput: string; // Including magic comments
|
|
401
|
+
code: string; // Rendered code, excluding magic comments
|
|
402
|
+
className: string; // There's always a "language-<lang>" className
|
|
403
|
+
language: string;
|
|
404
|
+
title: ReactNode;
|
|
405
|
+
lineNumbersStart: number | undefined;
|
|
406
|
+
lineClassNames: CodeLineClassNames;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function createCodeBlockMetadata(params: {
|
|
410
|
+
code: string;
|
|
411
|
+
className: string | undefined;
|
|
412
|
+
language: string | undefined;
|
|
413
|
+
defaultLanguage: string | undefined;
|
|
414
|
+
metastring: string | undefined;
|
|
415
|
+
magicComments: MagicCommentConfig[];
|
|
416
|
+
title: ReactNode;
|
|
417
|
+
showLineNumbers: boolean | number | undefined;
|
|
418
|
+
}): CodeBlockMetadata {
|
|
419
|
+
const language = getLanguage({
|
|
420
|
+
language: params.language,
|
|
421
|
+
defaultLanguage: params.defaultLanguage,
|
|
422
|
+
className: params.className,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const {lineClassNames, code} = parseLines(params.code, {
|
|
426
|
+
metastring: params.metastring,
|
|
427
|
+
magicComments: params.magicComments,
|
|
428
|
+
language,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const className = ensureLanguageClassName({
|
|
432
|
+
className: params.className,
|
|
433
|
+
language,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const title = parseCodeBlockTitle(params.metastring) || params.title;
|
|
437
|
+
|
|
438
|
+
const lineNumbersStart = getLineNumbersStart({
|
|
439
|
+
showLineNumbers: params.showLineNumbers,
|
|
440
|
+
metastring: params.metastring,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
codeInput: params.code,
|
|
445
|
+
code,
|
|
446
|
+
className,
|
|
447
|
+
language,
|
|
448
|
+
title,
|
|
449
|
+
lineNumbersStart,
|
|
450
|
+
lineClassNames,
|
|
451
|
+
};
|
|
290
452
|
}
|
|
291
453
|
|
|
292
454
|
export function getPrismCssVariables(prismTheme: PrismTheme): CSSProperties {
|
|
@@ -304,3 +466,39 @@ export function getPrismCssVariables(prismTheme: PrismTheme): CSSProperties {
|
|
|
304
466
|
});
|
|
305
467
|
return properties;
|
|
306
468
|
}
|
|
469
|
+
|
|
470
|
+
type CodeBlockContextValue = {
|
|
471
|
+
metadata: CodeBlockMetadata;
|
|
472
|
+
wordWrap: WordWrap;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const CodeBlockContext = createContext<CodeBlockContextValue | null>(null);
|
|
476
|
+
|
|
477
|
+
export function CodeBlockContextProvider({
|
|
478
|
+
metadata,
|
|
479
|
+
wordWrap,
|
|
480
|
+
children,
|
|
481
|
+
}: {
|
|
482
|
+
metadata: CodeBlockMetadata;
|
|
483
|
+
wordWrap: WordWrap;
|
|
484
|
+
children: ReactNode;
|
|
485
|
+
}): ReactNode {
|
|
486
|
+
// Should we optimize this in 2 contexts?
|
|
487
|
+
// Unlike metadata, wordWrap is stateful and likely to trigger re-renders
|
|
488
|
+
const value: CodeBlockContextValue = useMemo(() => {
|
|
489
|
+
return {metadata, wordWrap};
|
|
490
|
+
}, [metadata, wordWrap]);
|
|
491
|
+
return (
|
|
492
|
+
<CodeBlockContext.Provider value={value}>
|
|
493
|
+
{children}
|
|
494
|
+
</CodeBlockContext.Provider>
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function useCodeBlockContext(): CodeBlockContextValue {
|
|
499
|
+
const value = useContext(CodeBlockContext);
|
|
500
|
+
if (value === null) {
|
|
501
|
+
throw new ReactContextError('CodeBlockContextProvider');
|
|
502
|
+
}
|
|
503
|
+
return value;
|
|
504
|
+
}
|
|
@@ -10,7 +10,7 @@ import clsx from 'clsx';
|
|
|
10
10
|
import Head from '@docusaurus/Head';
|
|
11
11
|
import useRouteContext from '@docusaurus/useRouteContext';
|
|
12
12
|
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
|
|
13
|
-
import {useTitleFormatter} from './
|
|
13
|
+
import {useTitleFormatter} from './titleFormatterUtils';
|
|
14
14
|
|
|
15
15
|
type PageMetadataProps = {
|
|
16
16
|
readonly title?: string;
|
|
@@ -20,6 +20,55 @@ type PageMetadataProps = {
|
|
|
20
20
|
readonly children?: ReactNode;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
function TitleMetadata({title}: {title: string}) {
|
|
24
|
+
const titleFormatter = useTitleFormatter();
|
|
25
|
+
const formattedTitle = titleFormatter.format(title);
|
|
26
|
+
return (
|
|
27
|
+
<Head>
|
|
28
|
+
<title>{formattedTitle}</title>
|
|
29
|
+
<meta property="og:title" content={formattedTitle} />
|
|
30
|
+
</Head>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function DescriptionMetadata({description}: {description: string}) {
|
|
35
|
+
return (
|
|
36
|
+
<Head>
|
|
37
|
+
<meta name="description" content={description} />
|
|
38
|
+
<meta property="og:description" content={description} />
|
|
39
|
+
</Head>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ImageMetadata({image}: {image: string}) {
|
|
44
|
+
const {withBaseUrl} = useBaseUrlUtils();
|
|
45
|
+
const pageImage = withBaseUrl(image, {absolute: true});
|
|
46
|
+
return (
|
|
47
|
+
<Head>
|
|
48
|
+
<meta property="og:image" content={pageImage} />
|
|
49
|
+
<meta name="twitter:image" content={pageImage} />
|
|
50
|
+
</Head>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function KeywordsMetadata({
|
|
55
|
+
keywords,
|
|
56
|
+
}: {
|
|
57
|
+
keywords: PageMetadataProps['keywords'];
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<Head>
|
|
61
|
+
<meta
|
|
62
|
+
name="keywords"
|
|
63
|
+
content={
|
|
64
|
+
// https://github.com/microsoft/TypeScript/issues/17002
|
|
65
|
+
(Array.isArray(keywords) ? keywords.join(',') : keywords) as string
|
|
66
|
+
}
|
|
67
|
+
/>
|
|
68
|
+
</Head>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
23
72
|
/**
|
|
24
73
|
* Helper component to manipulate page metadata and override site defaults.
|
|
25
74
|
* Works in the same way as Helmet.
|
|
@@ -31,33 +80,14 @@ export function PageMetadata({
|
|
|
31
80
|
image,
|
|
32
81
|
children,
|
|
33
82
|
}: PageMetadataProps): ReactNode {
|
|
34
|
-
const pageTitle = useTitleFormatter(title);
|
|
35
|
-
const {withBaseUrl} = useBaseUrlUtils();
|
|
36
|
-
const pageImage = image ? withBaseUrl(image, {absolute: true}) : undefined;
|
|
37
|
-
|
|
38
83
|
return (
|
|
39
|
-
|
|
40
|
-
{title && <title
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
{
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
{keywords && (
|
|
47
|
-
<meta
|
|
48
|
-
name="keywords"
|
|
49
|
-
content={
|
|
50
|
-
// https://github.com/microsoft/TypeScript/issues/17002
|
|
51
|
-
(Array.isArray(keywords) ? keywords.join(',') : keywords) as string
|
|
52
|
-
}
|
|
53
|
-
/>
|
|
54
|
-
)}
|
|
55
|
-
|
|
56
|
-
{pageImage && <meta property="og:image" content={pageImage} />}
|
|
57
|
-
{pageImage && <meta name="twitter:image" content={pageImage} />}
|
|
58
|
-
|
|
59
|
-
{children}
|
|
60
|
-
</Head>
|
|
84
|
+
<>
|
|
85
|
+
{title && <TitleMetadata title={title} />}
|
|
86
|
+
{description && <DescriptionMetadata description={description} />}
|
|
87
|
+
{keywords && <KeywordsMetadata keywords={keywords} />}
|
|
88
|
+
{image && <ImageMetadata image={image} />}
|
|
89
|
+
{children && <Head>{children}</Head>}
|
|
90
|
+
</>
|
|
61
91
|
);
|
|
62
92
|
}
|
|
63
93
|
|
package/src/utils/reactUtils.tsx
CHANGED
|
@@ -50,6 +50,9 @@ export function usePrevious<T>(value: T): T | undefined {
|
|
|
50
50
|
ref.current = value;
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
+
// TODO need to fix this React Compiler lint error
|
|
54
|
+
// probably requires changing the API though
|
|
55
|
+
// eslint-disable-next-line react-compiler/react-compiler
|
|
53
56
|
return ref.current;
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -81,7 +84,7 @@ export function useShallowMemoObject<O extends object>(obj: O): O {
|
|
|
81
84
|
const deps = Object.entries(obj);
|
|
82
85
|
// Sort by keys to make it order-insensitive
|
|
83
86
|
deps.sort((a, b) => a[0].localeCompare(b[0]));
|
|
84
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
87
|
+
// eslint-disable-next-line react-compiler/react-compiler,react-hooks/exhaustive-deps
|
|
85
88
|
return useMemo(() => obj, deps.flat());
|
|
86
89
|
}
|
|
87
90
|
|
|
@@ -124,6 +124,7 @@ export function useScrollPosition(
|
|
|
124
124
|
window.addEventListener('scroll', handleScroll, opts);
|
|
125
125
|
|
|
126
126
|
return () => window.removeEventListener('scroll', handleScroll, opts);
|
|
127
|
+
// eslint-disable-next-line react-compiler/react-compiler
|
|
127
128
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
128
129
|
}, [dynamicEffect, scrollEventsEnabledRef, ...deps]);
|
|
129
130
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {useCallback,
|
|
8
|
+
import {useCallback, useState, useSyncExternalStore} from 'react';
|
|
9
9
|
import SiteStorage from '@generated/site-storage';
|
|
10
10
|
|
|
11
11
|
export type StorageType = (typeof SiteStorage)['type'] | 'none';
|
|
@@ -208,12 +208,12 @@ export function useStorageSlot(
|
|
|
208
208
|
options?: {persistence?: StorageType},
|
|
209
209
|
): [string | null, StorageSlot] {
|
|
210
210
|
// Not ideal but good enough: assumes storage slot config is constant
|
|
211
|
-
const storageSlot =
|
|
211
|
+
const [storageSlot] = useState(() => {
|
|
212
212
|
if (key === null) {
|
|
213
213
|
return NoopStorageSlot;
|
|
214
214
|
}
|
|
215
215
|
return createStorageSlot(key, options);
|
|
216
|
-
})
|
|
216
|
+
});
|
|
217
217
|
|
|
218
218
|
const listen: StorageSlot['listen'] = useCallback(
|
|
219
219
|
(onChange) => {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {createContext, useContext} from 'react';
|
|
9
|
+
import type {ReactNode} from 'react';
|
|
10
|
+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
11
|
+
import useRouteContext from '@docusaurus/useRouteContext';
|
|
12
|
+
import {ReactContextError} from './reactUtils';
|
|
13
|
+
|
|
14
|
+
type TitleFormatterParams = {
|
|
15
|
+
/**
|
|
16
|
+
* The page title to format
|
|
17
|
+
* Usually provided with these APIs:
|
|
18
|
+
* - <PageMetadata title={title}>
|
|
19
|
+
* - useTitleFormatter().format(title)
|
|
20
|
+
*/
|
|
21
|
+
title: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The siteConfig.title value
|
|
25
|
+
*/
|
|
26
|
+
siteTitle: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The siteConfig.titleDelimiter value
|
|
30
|
+
*/
|
|
31
|
+
titleDelimiter: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The plugin that created the page you are on
|
|
35
|
+
*/
|
|
36
|
+
plugin: {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* This is the full formatting function, including all useful params
|
|
44
|
+
* Can be customized through React context with the provider
|
|
45
|
+
*/
|
|
46
|
+
export type TitleFormatterFn = (params: TitleFormatterParams) => string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The default formatter is provided in params for convenience
|
|
50
|
+
*/
|
|
51
|
+
export type TitleFormatterFnWithDefault = (
|
|
52
|
+
params: TitleFormatterParams & {
|
|
53
|
+
defaultFormatter: (params: TitleFormatterParams) => string;
|
|
54
|
+
},
|
|
55
|
+
) => string;
|
|
56
|
+
|
|
57
|
+
export const TitleFormatterFnDefault: TitleFormatterFn = ({
|
|
58
|
+
title,
|
|
59
|
+
siteTitle,
|
|
60
|
+
titleDelimiter,
|
|
61
|
+
}): string => {
|
|
62
|
+
const trimmedTitle = title?.trim();
|
|
63
|
+
if (!trimmedTitle || trimmedTitle === siteTitle) {
|
|
64
|
+
return siteTitle;
|
|
65
|
+
}
|
|
66
|
+
return `${trimmedTitle} ${titleDelimiter} ${siteTitle}`;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* This is the simpler API exposed to theme/users
|
|
71
|
+
*/
|
|
72
|
+
type TitleFormatter = {format: (title: string) => string};
|
|
73
|
+
|
|
74
|
+
const TitleFormatterContext = createContext<TitleFormatterFnWithDefault | null>(
|
|
75
|
+
null,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
export function TitleFormatterProvider({
|
|
79
|
+
formatter,
|
|
80
|
+
children,
|
|
81
|
+
}: {
|
|
82
|
+
children: ReactNode;
|
|
83
|
+
formatter: TitleFormatterFnWithDefault;
|
|
84
|
+
}): ReactNode {
|
|
85
|
+
return (
|
|
86
|
+
<TitleFormatterContext.Provider value={formatter}>
|
|
87
|
+
{children}
|
|
88
|
+
</TitleFormatterContext.Provider>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function useTitleFormatterContext() {
|
|
93
|
+
const value = useContext(TitleFormatterContext);
|
|
94
|
+
if (value === null) {
|
|
95
|
+
throw new ReactContextError('TitleFormatterProvider');
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Returns a function to format the page title
|
|
102
|
+
*/
|
|
103
|
+
export function useTitleFormatter(): TitleFormatter {
|
|
104
|
+
const formatter = useTitleFormatterContext();
|
|
105
|
+
const {siteConfig} = useDocusaurusContext();
|
|
106
|
+
const {title: siteTitle, titleDelimiter} = siteConfig;
|
|
107
|
+
|
|
108
|
+
// Unfortunately we can only call this hook here, not in the provider
|
|
109
|
+
// Route context can't be accessed in any provider applied above the router
|
|
110
|
+
const {plugin} = useRouteContext();
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
format: (title: string) =>
|
|
114
|
+
formatter({
|
|
115
|
+
title,
|
|
116
|
+
siteTitle,
|
|
117
|
+
titleDelimiter,
|
|
118
|
+
plugin,
|
|
119
|
+
defaultFormatter: TitleFormatterFnDefault,
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -9,12 +9,13 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
|
9
9
|
import type {PrismTheme} from 'prism-react-renderer';
|
|
10
10
|
import type {DeepPartial} from 'utility-types';
|
|
11
11
|
import type {MagicCommentConfig} from './codeBlockUtils';
|
|
12
|
+
import type {ColorMode} from '../contexts/colorMode';
|
|
12
13
|
|
|
13
14
|
export type DocsVersionPersistence = 'localStorage' | 'none';
|
|
14
15
|
|
|
15
16
|
// TODO improve types, use unions
|
|
16
17
|
export type NavbarItem = {
|
|
17
|
-
type?: string
|
|
18
|
+
type?: string;
|
|
18
19
|
items?: NavbarItem[];
|
|
19
20
|
label?: string;
|
|
20
21
|
position?: 'left' | 'right';
|
|
@@ -44,7 +45,7 @@ export type Navbar = {
|
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
export type ColorModeConfig = {
|
|
47
|
-
defaultMode:
|
|
48
|
+
defaultMode: ColorMode;
|
|
48
49
|
disableSwitch: boolean;
|
|
49
50
|
respectPrefersColorScheme: boolean;
|
|
50
51
|
};
|
|
@@ -103,6 +104,7 @@ export type TableOfContents = {
|
|
|
103
104
|
maxHeadingLevel: number;
|
|
104
105
|
};
|
|
105
106
|
|
|
107
|
+
// TODO Docusaurus v4: use interface + declaration merging to enhance
|
|
106
108
|
// Theme config after validation/normalization
|
|
107
109
|
export type ThemeConfig = {
|
|
108
110
|
docs: {
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the MIT license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Formats the page's title based on relevant site config and other contexts.
|
|
9
|
-
*/
|
|
10
|
-
export declare function useTitleFormatter(title?: string | undefined): string;
|
|
11
|
-
//# sourceMappingURL=generalUtils.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generalUtils.d.ts","sourceRoot":"","sources":["../../src/utils/generalUtils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAMpE"}
|