@docusaurus/theme-common 3.7.0 → 3.8.0-canary-6324

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 (70) hide show
  1. package/lib/components/Collapsible/index.d.ts +0 -6
  2. package/lib/components/Collapsible/index.d.ts.map +1 -1
  3. package/lib/components/Collapsible/index.js +3 -17
  4. package/lib/components/Collapsible/index.js.map +1 -1
  5. package/lib/components/Details/index.d.ts.map +1 -1
  6. package/lib/components/Details/index.js +1 -2
  7. package/lib/components/Details/index.js.map +1 -1
  8. package/lib/contexts/colorMode.d.ts +6 -7
  9. package/lib/contexts/colorMode.d.ts.map +1 -1
  10. package/lib/contexts/colorMode.js +111 -67
  11. package/lib/contexts/colorMode.js.map +1 -1
  12. package/lib/contexts/navbarMobileSidebar.d.ts.map +1 -1
  13. package/lib/contexts/navbarMobileSidebar.js +21 -12
  14. package/lib/contexts/navbarMobileSidebar.js.map +1 -1
  15. package/lib/hooks/useCodeWordWrap.d.ts +2 -1
  16. package/lib/hooks/useCodeWordWrap.d.ts.map +1 -1
  17. package/lib/hooks/useCodeWordWrap.js.map +1 -1
  18. package/lib/index.js +1 -1
  19. package/lib/index.js.map +1 -1
  20. package/lib/internal.d.ts +2 -3
  21. package/lib/internal.d.ts.map +1 -1
  22. package/lib/internal.js +5 -3
  23. package/lib/internal.js.map +1 -1
  24. package/lib/utils/ThemeClassNames.d.ts +21 -1
  25. package/lib/utils/ThemeClassNames.d.ts.map +1 -1
  26. package/lib/utils/ThemeClassNames.js +21 -2
  27. package/lib/utils/ThemeClassNames.js.map +1 -1
  28. package/lib/utils/codeBlockUtils.d.ts +66 -34
  29. package/lib/utils/codeBlockUtils.d.ts.map +1 -1
  30. package/lib/utils/codeBlockUtils.js +136 -30
  31. package/lib/utils/codeBlockUtils.js.map +1 -1
  32. package/lib/utils/metadataUtils.d.ts.map +1 -1
  33. package/lib/utils/metadataUtils.js +37 -20
  34. package/lib/utils/metadataUtils.js.map +1 -1
  35. package/lib/utils/reactUtils.d.ts.map +1 -1
  36. package/lib/utils/reactUtils.js +4 -1
  37. package/lib/utils/reactUtils.js.map +1 -1
  38. package/lib/utils/scrollUtils.d.ts.map +1 -1
  39. package/lib/utils/scrollUtils.js +1 -0
  40. package/lib/utils/scrollUtils.js.map +1 -1
  41. package/lib/utils/storageUtils.js +3 -3
  42. package/lib/utils/storageUtils.js.map +1 -1
  43. package/lib/utils/titleFormatterUtils.d.ts +59 -0
  44. package/lib/utils/titleFormatterUtils.d.ts.map +1 -0
  45. package/lib/utils/titleFormatterUtils.js +51 -0
  46. package/lib/utils/titleFormatterUtils.js.map +1 -0
  47. package/lib/utils/useThemeConfig.d.ts +3 -2
  48. package/lib/utils/useThemeConfig.d.ts.map +1 -1
  49. package/lib/utils/useThemeConfig.js.map +1 -1
  50. package/package.json +9 -10
  51. package/src/components/Collapsible/index.tsx +1 -29
  52. package/src/components/Details/index.tsx +0 -1
  53. package/src/contexts/colorMode.tsx +164 -83
  54. package/src/contexts/navbarMobileSidebar.tsx +33 -13
  55. package/src/hooks/useCodeWordWrap.ts +4 -2
  56. package/src/index.ts +1 -1
  57. package/src/internal.ts +14 -3
  58. package/src/utils/ThemeClassNames.ts +25 -2
  59. package/src/utils/{codeBlockUtils.ts → codeBlockUtils.tsx} +255 -57
  60. package/src/utils/metadataUtils.tsx +57 -27
  61. package/src/utils/reactUtils.tsx +4 -1
  62. package/src/utils/scrollUtils.tsx +1 -0
  63. package/src/utils/storageUtils.ts +3 -3
  64. package/src/utils/titleFormatterUtils.tsx +122 -0
  65. package/src/utils/useThemeConfig.ts +4 -2
  66. package/lib/utils/generalUtils.d.ts +0 -11
  67. package/lib/utils/generalUtils.d.ts.map +0 -1
  68. package/lib/utils/generalUtils.js +0 -18
  69. package/lib/utils/generalUtils.js.map +0 -1
  70. 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
- * Gets the language name from the class name (set by MDX).
160
- * e.g. `"language-javascript"` => `"javascript"`.
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
- export function parseLanguage(className: string): string | undefined {
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
- * Parses the code content, strips away any magic comments, and returns the
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
- export function parseLines(
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
- let code = content.replace(/\n$/, '');
216
- const {language, magicComments, metastring} = options;
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('\n');
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
- code = lines.join('\n');
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
- return {lineClassNames, code};
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 './generalUtils';
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
- <Head>
40
- {title && <title>{pageTitle}</title>}
41
- {title && <meta property="og:title" content={pageTitle} />}
42
-
43
- {description && <meta name="description" content={description} />}
44
- {description && <meta property="og:description" content={description} />}
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
 
@@ -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, useRef, useSyncExternalStore} from 'react';
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 = useRef(() => {
211
+ const [storageSlot] = useState(() => {
212
212
  if (key === null) {
213
213
  return NoopStorageSlot;
214
214
  }
215
215
  return createStorageSlot(key, options);
216
- }).current();
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 | undefined;
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: 'light' | 'dark';
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"}