@docusaurus/theme-common 2.0.0-beta.15a2b59f9 → 2.0.0-beta.17

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 (209) hide show
  1. package/lib/components/Collapsible/index.d.ts +36 -0
  2. package/lib/components/Collapsible/index.d.ts.map +1 -0
  3. package/lib/components/Collapsible/index.js +143 -0
  4. package/lib/components/Collapsible/index.js.map +1 -0
  5. package/lib/components/Details/index.d.ts +12 -0
  6. package/lib/components/Details/index.d.ts.map +1 -0
  7. package/lib/components/Details/index.js +66 -0
  8. package/lib/components/Details/index.js.map +1 -0
  9. package/lib/components/Details/styles.module.css +58 -0
  10. package/{src/utils/docsUtils.ts → lib/hooks/styles.css} +3 -4
  11. package/lib/hooks/useHideableNavbar.d.ts +13 -0
  12. package/lib/hooks/useHideableNavbar.d.ts.map +1 -0
  13. package/lib/hooks/useHideableNavbar.js +59 -0
  14. package/lib/hooks/useHideableNavbar.js.map +1 -0
  15. package/lib/hooks/useKeyboardNavigation.d.ts +10 -0
  16. package/lib/hooks/useKeyboardNavigation.d.ts.map +1 -0
  17. package/lib/hooks/useKeyboardNavigation.js +31 -0
  18. package/lib/hooks/useKeyboardNavigation.js.map +1 -0
  19. package/lib/hooks/useLockBodyScroll.d.ts +8 -0
  20. package/lib/hooks/useLockBodyScroll.d.ts.map +1 -0
  21. package/lib/hooks/useLockBodyScroll.js +16 -0
  22. package/lib/hooks/useLockBodyScroll.js.map +1 -0
  23. package/lib/hooks/usePrismTheme.d.ts +9 -0
  24. package/lib/hooks/usePrismTheme.d.ts.map +1 -0
  25. package/lib/hooks/usePrismTheme.js +18 -0
  26. package/lib/hooks/usePrismTheme.js.map +1 -0
  27. package/lib/hooks/useSearchPage.d.ts +14 -0
  28. package/lib/hooks/useSearchPage.d.ts.map +1 -0
  29. package/lib/hooks/useSearchPage.js +42 -0
  30. package/lib/hooks/useSearchPage.js.map +1 -0
  31. package/lib/hooks/useWindowSize.d.ts +15 -0
  32. package/lib/hooks/useWindowSize.d.ts.map +1 -0
  33. package/lib/hooks/useWindowSize.js +56 -0
  34. package/lib/hooks/useWindowSize.js.map +1 -0
  35. package/lib/index.d.ts +32 -3
  36. package/lib/index.d.ts.map +1 -0
  37. package/lib/index.js +26 -2
  38. package/lib/index.js.map +1 -0
  39. package/lib/utils/ThemeClassNames.d.ts +40 -12
  40. package/lib/utils/ThemeClassNames.d.ts.map +1 -0
  41. package/lib/utils/ThemeClassNames.js +41 -3
  42. package/lib/utils/ThemeClassNames.js.map +1 -0
  43. package/lib/utils/announcementBarUtils.d.ts +6 -5
  44. package/lib/utils/announcementBarUtils.d.ts.map +1 -0
  45. package/lib/utils/announcementBarUtils.js +20 -21
  46. package/lib/utils/announcementBarUtils.js.map +1 -0
  47. package/lib/utils/codeBlockUtils.d.ts +10 -0
  48. package/lib/utils/codeBlockUtils.d.ts.map +1 -0
  49. package/lib/utils/codeBlockUtils.js +125 -3
  50. package/lib/utils/codeBlockUtils.js.map +1 -0
  51. package/lib/utils/colorModeUtils.d.ts +18 -0
  52. package/lib/utils/colorModeUtils.d.ts.map +1 -0
  53. package/lib/utils/colorModeUtils.js +107 -0
  54. package/lib/utils/colorModeUtils.js.map +1 -0
  55. package/lib/utils/docSidebarItemsExpandedState.d.ts +17 -0
  56. package/lib/utils/docSidebarItemsExpandedState.d.ts.map +1 -0
  57. package/lib/utils/docSidebarItemsExpandedState.js +23 -0
  58. package/lib/utils/docSidebarItemsExpandedState.js.map +1 -0
  59. package/lib/utils/docsPreferredVersion/DocsPreferredVersionProvider.d.ts +3 -2
  60. package/lib/utils/docsPreferredVersion/DocsPreferredVersionProvider.d.ts.map +1 -0
  61. package/lib/utils/docsPreferredVersion/DocsPreferredVersionProvider.js +9 -11
  62. package/lib/utils/docsPreferredVersion/DocsPreferredVersionProvider.js.map +1 -0
  63. package/lib/utils/docsPreferredVersion/DocsPreferredVersionStorage.d.ts +2 -1
  64. package/lib/utils/docsPreferredVersion/DocsPreferredVersionStorage.d.ts.map +1 -0
  65. package/lib/utils/docsPreferredVersion/DocsPreferredVersionStorage.js +2 -3
  66. package/lib/utils/docsPreferredVersion/DocsPreferredVersionStorage.js.map +1 -0
  67. package/lib/utils/docsPreferredVersion/useDocsPreferredVersion.d.ts +12 -3
  68. package/lib/utils/docsPreferredVersion/useDocsPreferredVersion.d.ts.map +1 -0
  69. package/lib/utils/docsPreferredVersion/useDocsPreferredVersion.js +3 -3
  70. package/lib/utils/docsPreferredVersion/useDocsPreferredVersion.js.map +1 -0
  71. package/lib/utils/docsUtils.d.ts +26 -0
  72. package/lib/utils/docsUtils.d.ts.map +1 -0
  73. package/lib/utils/docsUtils.js +136 -1
  74. package/lib/utils/docsUtils.js.map +1 -0
  75. package/lib/utils/generalUtils.d.ts +7 -0
  76. package/lib/utils/generalUtils.d.ts.map +1 -0
  77. package/lib/utils/generalUtils.js +3 -2
  78. package/lib/utils/generalUtils.js.map +1 -0
  79. package/lib/utils/historyUtils.d.ts +23 -0
  80. package/lib/utils/historyUtils.d.ts.map +1 -0
  81. package/lib/utils/historyUtils.js +41 -0
  82. package/lib/utils/historyUtils.js.map +1 -0
  83. package/lib/utils/jsUtils.d.ts +23 -0
  84. package/lib/utils/jsUtils.d.ts.map +1 -0
  85. package/lib/utils/jsUtils.js +29 -0
  86. package/lib/utils/jsUtils.js.map +1 -0
  87. package/lib/utils/mobileSecondaryMenu.d.ts +21 -0
  88. package/lib/utils/mobileSecondaryMenu.d.ts.map +1 -0
  89. package/lib/utils/mobileSecondaryMenu.js +51 -0
  90. package/lib/utils/mobileSecondaryMenu.js.map +1 -0
  91. package/lib/utils/pathUtils.d.ts +1 -0
  92. package/lib/utils/pathUtils.d.ts.map +1 -0
  93. package/lib/utils/pathUtils.js +5 -4
  94. package/lib/utils/pathUtils.js.map +1 -0
  95. package/lib/utils/reactUtils.d.ts +31 -0
  96. package/lib/utils/reactUtils.d.ts.map +1 -0
  97. package/lib/utils/reactUtils.js +43 -0
  98. package/lib/utils/reactUtils.js.map +1 -0
  99. package/lib/utils/regexpUtils.d.ts +11 -0
  100. package/lib/utils/regexpUtils.d.ts.map +1 -0
  101. package/lib/utils/regexpUtils.js +17 -0
  102. package/lib/utils/regexpUtils.js.map +1 -0
  103. package/lib/utils/routesUtils.d.ts +14 -0
  104. package/lib/utils/routesUtils.d.ts.map +1 -0
  105. package/lib/utils/routesUtils.js +41 -0
  106. package/lib/utils/routesUtils.js.map +1 -0
  107. package/lib/utils/scrollUtils.d.ts +53 -0
  108. package/lib/utils/scrollUtils.d.ts.map +1 -0
  109. package/lib/utils/scrollUtils.js +135 -0
  110. package/lib/utils/scrollUtils.js.map +1 -0
  111. package/lib/utils/searchUtils.d.ts +1 -0
  112. package/lib/utils/searchUtils.d.ts.map +1 -0
  113. package/lib/utils/searchUtils.js +1 -0
  114. package/lib/utils/searchUtils.js.map +1 -0
  115. package/lib/utils/storageUtils.d.ts +5 -0
  116. package/lib/utils/storageUtils.d.ts.map +1 -0
  117. package/lib/utils/storageUtils.js +39 -14
  118. package/lib/utils/storageUtils.js.map +1 -0
  119. package/lib/utils/tabGroupChoiceUtils.d.ts +19 -0
  120. package/lib/utils/tabGroupChoiceUtils.d.ts.map +1 -0
  121. package/lib/utils/tabGroupChoiceUtils.js +55 -0
  122. package/lib/utils/tabGroupChoiceUtils.js.map +1 -0
  123. package/lib/utils/tagsUtils.d.ts +19 -0
  124. package/lib/utils/tagsUtils.d.ts.map +1 -0
  125. package/lib/utils/tagsUtils.js +33 -0
  126. package/lib/utils/tagsUtils.js.map +1 -0
  127. package/lib/utils/tocUtils.d.ts +20 -0
  128. package/lib/utils/tocUtils.d.ts.map +1 -0
  129. package/lib/utils/tocUtils.js +73 -0
  130. package/lib/utils/tocUtils.js.map +1 -0
  131. package/lib/utils/useAlternatePageUtils.d.ts +1 -0
  132. package/lib/utils/useAlternatePageUtils.d.ts.map +1 -0
  133. package/lib/utils/useAlternatePageUtils.js +3 -1
  134. package/lib/utils/useAlternatePageUtils.js.map +1 -0
  135. package/lib/utils/useContextualSearchFilters.d.ts +12 -0
  136. package/lib/utils/useContextualSearchFilters.d.ts.map +1 -0
  137. package/lib/utils/useContextualSearchFilters.js +36 -0
  138. package/lib/utils/useContextualSearchFilters.js.map +1 -0
  139. package/lib/utils/useLocalPathname.d.ts +8 -0
  140. package/lib/utils/useLocalPathname.d.ts.map +1 -0
  141. package/lib/utils/useLocalPathname.js +17 -0
  142. package/lib/utils/useLocalPathname.js.map +1 -0
  143. package/lib/utils/useLocationChange.d.ts +2 -1
  144. package/lib/utils/useLocationChange.d.ts.map +1 -0
  145. package/lib/utils/useLocationChange.js +12 -10
  146. package/lib/utils/useLocationChange.js.map +1 -0
  147. package/lib/utils/usePluralForm.d.ts +1 -0
  148. package/lib/utils/usePluralForm.d.ts.map +1 -0
  149. package/lib/utils/usePluralForm.js +28 -24
  150. package/lib/utils/usePluralForm.js.map +1 -0
  151. package/lib/utils/usePrevious.d.ts +1 -0
  152. package/lib/utils/usePrevious.d.ts.map +1 -0
  153. package/lib/utils/usePrevious.js +4 -2
  154. package/lib/utils/usePrevious.js.map +1 -0
  155. package/lib/utils/useTOCHighlight.d.ts +14 -0
  156. package/lib/utils/useTOCHighlight.d.ts.map +1 -0
  157. package/lib/utils/useTOCHighlight.js +126 -0
  158. package/lib/utils/useTOCHighlight.js.map +1 -0
  159. package/lib/utils/useThemeConfig.d.ts +35 -17
  160. package/lib/utils/useThemeConfig.d.ts.map +1 -0
  161. package/lib/utils/useThemeConfig.js +1 -0
  162. package/lib/utils/useThemeConfig.js.map +1 -0
  163. package/package.json +19 -13
  164. package/src/components/Collapsible/index.tsx +247 -0
  165. package/src/components/Details/index.tsx +102 -0
  166. package/src/components/Details/styles.module.css +58 -0
  167. package/src/hooks/styles.css +10 -0
  168. package/src/hooks/useHideableNavbar.ts +77 -0
  169. package/src/hooks/useKeyboardNavigation.ts +37 -0
  170. package/src/hooks/useLockBodyScroll.ts +18 -0
  171. package/src/hooks/usePrismTheme.ts +20 -0
  172. package/src/hooks/useSearchPage.ts +66 -0
  173. package/src/hooks/useWindowSize.ts +70 -0
  174. package/src/index.ts +95 -3
  175. package/src/types.d.ts +0 -2
  176. package/src/utils/ThemeClassNames.ts +46 -4
  177. package/src/utils/announcementBarUtils.tsx +27 -22
  178. package/src/utils/codeBlockUtils.ts +153 -2
  179. package/src/utils/colorModeUtils.tsx +158 -0
  180. package/src/utils/docSidebarItemsExpandedState.tsx +40 -0
  181. package/src/utils/docsPreferredVersion/DocsPreferredVersionProvider.tsx +17 -16
  182. package/src/utils/docsPreferredVersion/DocsPreferredVersionStorage.ts +3 -4
  183. package/src/utils/docsPreferredVersion/useDocsPreferredVersion.ts +18 -14
  184. package/src/utils/docsUtils.tsx +231 -0
  185. package/src/utils/generalUtils.ts +3 -2
  186. package/src/utils/historyUtils.ts +51 -0
  187. package/src/utils/jsUtils.ts +36 -0
  188. package/src/utils/mobileSecondaryMenu.tsx +114 -0
  189. package/src/utils/pathUtils.ts +6 -4
  190. package/src/utils/reactUtils.tsx +53 -0
  191. package/src/utils/regexpUtils.ts +23 -0
  192. package/src/utils/routesUtils.ts +56 -0
  193. package/src/utils/scrollUtils.tsx +235 -0
  194. package/src/utils/storageUtils.ts +37 -12
  195. package/src/utils/tabGroupChoiceUtils.tsx +89 -0
  196. package/src/utils/tagsUtils.ts +48 -0
  197. package/src/utils/tocUtils.ts +108 -0
  198. package/src/utils/useAlternatePageUtils.ts +4 -3
  199. package/src/utils/useContextualSearchFilters.ts +53 -0
  200. package/src/utils/useLocalPathname.ts +20 -0
  201. package/src/utils/useLocationChange.ts +13 -11
  202. package/src/utils/usePluralForm.ts +31 -26
  203. package/src/utils/usePrevious.ts +3 -2
  204. package/src/utils/useTOCHighlight.ts +183 -0
  205. package/src/utils/useThemeConfig.ts +37 -17
  206. package/lib/.tsbuildinfo +0 -1
  207. package/src/utils/__tests__/codeBlockUtils.test.ts +0 -54
  208. package/src/utils/__tests__/pathUtils.test.ts +0 -32
  209. package/tsconfig.json +0 -10
@@ -10,12 +10,13 @@ import React, {
10
10
  useEffect,
11
11
  useCallback,
12
12
  useMemo,
13
- ReactNode,
13
+ type ReactNode,
14
14
  useContext,
15
15
  createContext,
16
16
  } from 'react';
17
- import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
17
+ import useIsBrowser from '@docusaurus/useIsBrowser';
18
18
  import {createStorageSlot} from './storageUtils';
19
+ import {ReactContextError} from './reactUtils';
19
20
  import {useThemeConfig} from './useThemeConfig';
20
21
 
21
22
  export const AnnouncementBarDismissStorageKey =
@@ -33,21 +34,21 @@ const setDismissedInStorage = (bool: boolean) =>
33
34
  AnnouncementBarDismissStorage.set(String(bool));
34
35
 
35
36
  type AnnouncementBarAPI = {
36
- readonly isClosed: boolean;
37
+ readonly isActive: boolean;
37
38
  readonly close: () => void;
38
39
  };
39
40
 
40
41
  const useAnnouncementBarContextValue = (): AnnouncementBarAPI => {
41
42
  const {announcementBar} = useThemeConfig();
42
- const {isClient} = useDocusaurusContext();
43
+ const isBrowser = useIsBrowser();
43
44
 
44
- const [isClosed, setClosed] = useState(() => {
45
- return isClient
46
- ? // On client navigation: init with localstorage value
45
+ const [isClosed, setClosed] = useState(() =>
46
+ isBrowser
47
+ ? // On client navigation: init with local storage value
47
48
  isDismissedInStorage()
48
49
  : // On server/hydration: always visible to prevent layout shifts (will be hidden with css if needed)
49
- false;
50
- });
50
+ false,
51
+ );
51
52
  // Update state after hydration
52
53
  useEffect(() => {
53
54
  setClosed(isDismissedInStorage());
@@ -68,6 +69,7 @@ const useAnnouncementBarContextValue = (): AnnouncementBarAPI => {
68
69
 
69
70
  // retrocompatibility due to spelling mistake of default id
70
71
  // see https://github.com/facebook/docusaurus/issues/3338
72
+ // cSpell:ignore annoucement
71
73
  if (viewedId === 'annoucement-bar') {
72
74
  viewedId = 'announcement-bar';
73
75
  }
@@ -83,33 +85,36 @@ const useAnnouncementBarContextValue = (): AnnouncementBarAPI => {
83
85
  if (isNewAnnouncement || !isDismissedInStorage()) {
84
86
  setClosed(false);
85
87
  }
86
- }, []);
88
+ }, [announcementBar]);
87
89
 
88
- return useMemo(() => {
89
- return {
90
- isClosed,
90
+ return useMemo(
91
+ () => ({
92
+ isActive: !!announcementBar && !isClosed,
91
93
  close: handleClose,
92
- };
93
- }, [isClosed]);
94
+ }),
95
+ [announcementBar, isClosed, handleClose],
96
+ );
94
97
  };
95
98
 
96
99
  const AnnouncementBarContext = createContext<AnnouncementBarAPI | null>(null);
97
100
 
98
- export const AnnouncementBarProvider = ({children}: {children: ReactNode}) => {
101
+ export function AnnouncementBarProvider({
102
+ children,
103
+ }: {
104
+ children: ReactNode;
105
+ }): JSX.Element {
99
106
  const value = useAnnouncementBarContextValue();
100
107
  return (
101
108
  <AnnouncementBarContext.Provider value={value}>
102
109
  {children}
103
110
  </AnnouncementBarContext.Provider>
104
111
  );
105
- };
112
+ }
106
113
 
107
- export const useAnnouncementBar = (): AnnouncementBarAPI => {
114
+ export function useAnnouncementBar(): AnnouncementBarAPI {
108
115
  const api = useContext(AnnouncementBarContext);
109
116
  if (!api) {
110
- throw new Error(
111
- 'useAnnouncementBar(): AnnouncementBar not found in React context: make sure to use the AnnouncementBarProvider on top of the tree',
112
- );
117
+ throw new ReactContextError('AnnouncementBarProvider');
113
118
  }
114
119
  return api;
115
- };
120
+ }
@@ -5,8 +5,159 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- const codeBlockTitleRegex = /title=(["'])(.*?)\1/;
8
+ import rangeParser from 'parse-numeric-range';
9
+
10
+ const codeBlockTitleRegex = /title=(?<quote>["'])(?<title>.*?)\1/;
11
+ const highlightLinesRangeRegex = /{(?<range>[\d,-]+)}/;
12
+
13
+ const commentTypes = ['js', 'jsBlock', 'jsx', 'python', 'html'] as const;
14
+ type CommentType = typeof commentTypes[number];
15
+
16
+ type CommentPattern = {
17
+ start: string;
18
+ end: string;
19
+ };
20
+
21
+ // Supported types of highlight comments
22
+ const commentPatterns: Record<CommentType, CommentPattern> = {
23
+ js: {
24
+ start: '\\/\\/',
25
+ end: '',
26
+ },
27
+ jsBlock: {
28
+ start: '\\/\\*',
29
+ end: '\\*\\/',
30
+ },
31
+ jsx: {
32
+ start: '\\{\\s*\\/\\*',
33
+ end: '\\*\\/\\s*\\}',
34
+ },
35
+ python: {
36
+ start: '#',
37
+ end: '',
38
+ },
39
+ html: {
40
+ start: '<!--',
41
+ end: '-->',
42
+ },
43
+ };
44
+
45
+ const magicCommentDirectives = [
46
+ 'highlight-next-line',
47
+ 'highlight-start',
48
+ 'highlight-end',
49
+ ];
50
+
51
+ const getMagicCommentDirectiveRegex = (
52
+ languages: readonly CommentType[] = commentTypes,
53
+ ) => {
54
+ // to be more reliable, the opening and closing comment must match
55
+ const commentPattern = languages
56
+ .map((lang) => {
57
+ const {start, end} = commentPatterns[lang];
58
+ return `(?:${start}\\s*(${magicCommentDirectives.join('|')})\\s*${end})`;
59
+ })
60
+ .join('|');
61
+ // white space is allowed, but otherwise it should be on it's own line
62
+ return new RegExp(`^\\s*(?:${commentPattern})\\s*$`);
63
+ };
64
+
65
+ // select comment styles based on language
66
+ const magicCommentDirectiveRegex = (lang: string) => {
67
+ switch (lang) {
68
+ case 'js':
69
+ case 'javascript':
70
+ case 'ts':
71
+ case 'typescript':
72
+ return getMagicCommentDirectiveRegex(['js', 'jsBlock']);
73
+
74
+ case 'jsx':
75
+ case 'tsx':
76
+ return getMagicCommentDirectiveRegex(['js', 'jsBlock', 'jsx']);
77
+
78
+ case 'html':
79
+ return getMagicCommentDirectiveRegex(['js', 'jsBlock', 'html']);
80
+
81
+ case 'python':
82
+ case 'py':
83
+ return getMagicCommentDirectiveRegex(['python']);
84
+
85
+ default:
86
+ // all comment types
87
+ return getMagicCommentDirectiveRegex();
88
+ }
89
+ };
9
90
 
10
91
  export function parseCodeBlockTitle(metastring?: string): string {
11
- return metastring?.match(codeBlockTitleRegex)?.[2] ?? '';
92
+ return metastring?.match(codeBlockTitleRegex)?.groups!.title ?? '';
93
+ }
94
+
95
+ export function parseLanguage(className: string): string | undefined {
96
+ const languageClassName = className
97
+ .split(' ')
98
+ .find((str) => str.startsWith('language-'));
99
+ return languageClassName?.replace(/language-/, '');
100
+ }
101
+
102
+ /**
103
+ * @param metastring The highlight range declared here starts at 1
104
+ * @returns Note: all line numbers start at 0, not 1
105
+ */
106
+ export function parseLines(
107
+ content: string,
108
+ metastring?: string,
109
+ language?: string,
110
+ ): {
111
+ highlightLines: number[];
112
+ code: string;
113
+ } {
114
+ let code = content.replace(/\n$/, '');
115
+ // Highlighted lines specified in props: don't parse the content
116
+ if (metastring && highlightLinesRangeRegex.test(metastring)) {
117
+ const highlightLinesRange = metastring.match(highlightLinesRangeRegex)!
118
+ .groups!.range;
119
+ const highlightLines = rangeParser(highlightLinesRange)
120
+ .filter((n) => n > 0)
121
+ .map((n) => n - 1);
122
+ return {highlightLines, code};
123
+ }
124
+ if (language === undefined) {
125
+ return {highlightLines: [], code};
126
+ }
127
+ const directiveRegex = magicCommentDirectiveRegex(language);
128
+ // go through line by line
129
+ const lines = code.split('\n');
130
+ let highlightBlockStart: number;
131
+ let highlightRange = '';
132
+ // loop through lines
133
+ for (let lineNumber = 0; lineNumber < lines.length; ) {
134
+ const line = lines[lineNumber];
135
+ const match = line.match(directiveRegex);
136
+ if (match !== null) {
137
+ const directive = match.slice(1).find((item) => item !== undefined);
138
+ switch (directive) {
139
+ case 'highlight-next-line':
140
+ highlightRange += `${lineNumber},`;
141
+ break;
142
+
143
+ case 'highlight-start':
144
+ highlightBlockStart = lineNumber;
145
+ break;
146
+
147
+ case 'highlight-end':
148
+ highlightRange += `${highlightBlockStart!}-${lineNumber - 1},`;
149
+ break;
150
+
151
+ default:
152
+ break;
153
+ }
154
+ lines.splice(lineNumber, 1);
155
+ } else {
156
+ // lines without directives are unchanged
157
+ lineNumber += 1;
158
+ }
159
+ }
160
+ const highlightLines = rangeParser(highlightRange);
161
+ code = lines.join('\n');
162
+ return {highlightLines, code};
12
163
  }
@@ -0,0 +1,158 @@
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 React, {
9
+ useState,
10
+ useCallback,
11
+ useEffect,
12
+ useContext,
13
+ useMemo,
14
+ type ReactNode,
15
+ } from 'react';
16
+ import {ReactContextError} from './reactUtils';
17
+
18
+ import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
19
+ import {createStorageSlot} from './storageUtils';
20
+ import {useThemeConfig} from './useThemeConfig';
21
+
22
+ type ColorModeContextValue = {
23
+ readonly isDarkTheme: boolean;
24
+ readonly setLightTheme: () => void;
25
+ readonly setDarkTheme: () => void;
26
+ };
27
+
28
+ const ThemeStorageKey = 'theme';
29
+ const ThemeStorage = createStorageSlot(ThemeStorageKey);
30
+
31
+ const themes = {
32
+ light: 'light',
33
+ dark: 'dark',
34
+ } as const;
35
+
36
+ type Themes = typeof themes[keyof typeof themes];
37
+
38
+ // Ensure to always return a valid theme even if input is invalid
39
+ const coerceToTheme = (theme?: string | null): Themes =>
40
+ theme === themes.dark ? themes.dark : themes.light;
41
+
42
+ const getInitialTheme = (defaultMode: Themes | undefined): Themes => {
43
+ if (!ExecutionEnvironment.canUseDOM) {
44
+ return coerceToTheme(defaultMode);
45
+ }
46
+ return coerceToTheme(document.documentElement.getAttribute('data-theme'));
47
+ };
48
+
49
+ const storeTheme = (newTheme: Themes) => {
50
+ ThemeStorage.set(coerceToTheme(newTheme));
51
+ };
52
+
53
+ function useColorModeContextValue(): ColorModeContextValue {
54
+ const {
55
+ colorMode: {defaultMode, disableSwitch, respectPrefersColorScheme},
56
+ } = useThemeConfig();
57
+ const [theme, setTheme] = useState(getInitialTheme(defaultMode));
58
+
59
+ const setLightTheme = useCallback(() => {
60
+ setTheme(themes.light);
61
+ storeTheme(themes.light);
62
+ }, []);
63
+ const setDarkTheme = useCallback(() => {
64
+ setTheme(themes.dark);
65
+ storeTheme(themes.dark);
66
+ }, []);
67
+
68
+ useEffect(() => {
69
+ document.documentElement.setAttribute('data-theme', coerceToTheme(theme));
70
+ }, [theme]);
71
+
72
+ useEffect(() => {
73
+ if (disableSwitch) {
74
+ return undefined;
75
+ }
76
+ const onChange = (e: StorageEvent) => {
77
+ if (e.key !== ThemeStorageKey) {
78
+ return;
79
+ }
80
+ try {
81
+ const storedTheme = ThemeStorage.get();
82
+ if (storedTheme !== null) {
83
+ setTheme(coerceToTheme(storedTheme));
84
+ }
85
+ } catch (err) {
86
+ console.error(err);
87
+ }
88
+ };
89
+ window.addEventListener('storage', onChange);
90
+ return () => {
91
+ window.removeEventListener('storage', onChange);
92
+ };
93
+ }, [disableSwitch, setTheme]);
94
+
95
+ // PCS is coerced to light mode when printing, which causes the color mode to
96
+ // be reset to dark when exiting print mode, disregarding user settings. When
97
+ // the listener fires only because of a print/screen switch, we don't change
98
+ // color mode. See https://github.com/facebook/docusaurus/pull/6490
99
+ const previousMediaIsPrint = React.useRef(false);
100
+
101
+ useEffect(() => {
102
+ if (disableSwitch && !respectPrefersColorScheme) {
103
+ return undefined;
104
+ }
105
+ const mql = window.matchMedia('(prefers-color-scheme: dark)');
106
+ const onChange = ({matches}: MediaQueryListEvent) => {
107
+ if (window.matchMedia('print').matches || previousMediaIsPrint.current) {
108
+ previousMediaIsPrint.current = window.matchMedia('print').matches;
109
+ return;
110
+ }
111
+ setTheme(matches ? themes.dark : themes.light);
112
+ };
113
+ mql.addListener(onChange);
114
+ return () => {
115
+ mql.removeListener(onChange);
116
+ };
117
+ }, [disableSwitch, respectPrefersColorScheme]);
118
+
119
+ return {
120
+ isDarkTheme: theme === themes.dark,
121
+ setLightTheme,
122
+ setDarkTheme,
123
+ };
124
+ }
125
+
126
+ const ColorModeContext = React.createContext<ColorModeContextValue | undefined>(
127
+ undefined,
128
+ );
129
+
130
+ export function ColorModeProvider({
131
+ children,
132
+ }: {
133
+ children: ReactNode;
134
+ }): JSX.Element {
135
+ const {isDarkTheme, setLightTheme, setDarkTheme} = useColorModeContextValue();
136
+ const contextValue = useMemo(
137
+ () => ({isDarkTheme, setLightTheme, setDarkTheme}),
138
+ [isDarkTheme, setLightTheme, setDarkTheme],
139
+ );
140
+ return (
141
+ <ColorModeContext.Provider value={contextValue}>
142
+ {children}
143
+ </ColorModeContext.Provider>
144
+ );
145
+ }
146
+
147
+ export function useColorMode(): ColorModeContextValue {
148
+ const context = useContext<ColorModeContextValue | undefined>(
149
+ ColorModeContext,
150
+ );
151
+ if (context == null) {
152
+ throw new ReactContextError(
153
+ 'ColorModeProvider',
154
+ 'Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.',
155
+ );
156
+ }
157
+ return context;
158
+ }
@@ -0,0 +1,40 @@
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 React, {type ReactNode, useMemo, useState, useContext} from 'react';
9
+ import {ReactContextError} from './reactUtils';
10
+
11
+ const EmptyContext: unique symbol = Symbol('EmptyContext');
12
+ const Context = React.createContext<
13
+ DocSidebarItemsExpandedState | typeof EmptyContext
14
+ >(EmptyContext);
15
+ type DocSidebarItemsExpandedState = {
16
+ expandedItem: number | null;
17
+ setExpandedItem: (a: number | null) => void;
18
+ };
19
+
20
+ export function DocSidebarItemsExpandedStateProvider({
21
+ children,
22
+ }: {
23
+ children: ReactNode;
24
+ }): JSX.Element {
25
+ const [expandedItem, setExpandedItem] = useState<number | null>(null);
26
+ const contextValue = useMemo(
27
+ () => ({expandedItem, setExpandedItem}),
28
+ [expandedItem],
29
+ );
30
+
31
+ return <Context.Provider value={contextValue}>{children}</Context.Provider>;
32
+ }
33
+
34
+ export function useDocSidebarItemsExpandedState(): DocSidebarItemsExpandedState {
35
+ const contextValue = useContext(Context);
36
+ if (contextValue === EmptyContext) {
37
+ throw new ReactContextError('DocSidebarItemsExpandedStateProvider');
38
+ }
39
+ return contextValue;
40
+ }
@@ -4,18 +4,23 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+
7
8
  import React, {
8
9
  createContext,
9
- ReactNode,
10
+ type ReactNode,
10
11
  useContext,
11
12
  useEffect,
12
13
  useMemo,
13
14
  useState,
14
15
  } from 'react';
15
- import {useThemeConfig, DocsVersionPersistence} from '../useThemeConfig';
16
+ import {useThemeConfig, type DocsVersionPersistence} from '../useThemeConfig';
16
17
  import {isDocsPluginEnabled} from '../docsUtils';
18
+ import {ReactContextError} from '../reactUtils';
17
19
 
18
- import {useAllDocsData} from '@theme/hooks/useDocs';
20
+ import {
21
+ useAllDocsData,
22
+ type GlobalPluginData,
23
+ } from '@docusaurus/plugin-content-docs/client';
19
24
 
20
25
  import DocsPreferredVersionStorage from './DocsPreferredVersionStorage';
21
26
 
@@ -34,7 +39,7 @@ type DocsPreferredVersionState = Record<
34
39
  DocsPreferredVersionPluginState
35
40
  >;
36
41
 
37
- // Initial state is always null as we can't read localstorage from node SSR
42
+ // Initial state is always null as we can't read local storage from node SSR
38
43
  function getInitialState(pluginIds: string[]): DocsPreferredVersionState {
39
44
  const initialState: DocsPreferredVersionState = {};
40
45
  pluginIds.forEach((pluginId) => {
@@ -54,7 +59,7 @@ function readStorageState({
54
59
  }: {
55
60
  pluginIds: string[];
56
61
  versionPersistence: DocsVersionPersistence;
57
- allDocsData: any; // TODO find a way to type it :(
62
+ allDocsData: Record<string, GlobalPluginData>;
58
63
  }): DocsPreferredVersionState {
59
64
  // The storage value we read might be stale,
60
65
  // and belong to a version that does not exist in the site anymore
@@ -68,14 +73,13 @@ function readStorageState({
68
73
  );
69
74
  const pluginData = allDocsData[pluginId];
70
75
  const versionExists = pluginData.versions.some(
71
- (version: any) => version.name === preferredVersionNameUnsafe,
76
+ (version) => version.name === preferredVersionNameUnsafe,
72
77
  );
73
78
  if (versionExists) {
74
79
  return {preferredVersionName: preferredVersionNameUnsafe};
75
- } else {
76
- DocsPreferredVersionStorage.clear(pluginId, versionPersistence);
77
- return {preferredVersionName: null};
78
80
  }
81
+ DocsPreferredVersionStorage.clear(pluginId, versionPersistence);
82
+ return {preferredVersionName: null};
79
83
  }
80
84
 
81
85
  const initialState: DocsPreferredVersionState = {};
@@ -120,7 +124,7 @@ function useContextValue() {
120
124
  return {
121
125
  savePreferredVersion,
122
126
  };
123
- }, [setState]);
127
+ }, [versionPersistence]);
124
128
 
125
129
  return [state, api] as const;
126
130
  }
@@ -132,7 +136,7 @@ const Context = createContext<DocsPreferredVersionContextValue | null>(null);
132
136
  export function DocsPreferredVersionContextProvider({
133
137
  children,
134
138
  }: {
135
- children: ReactNode;
139
+ children: JSX.Element;
136
140
  }): JSX.Element {
137
141
  if (isDocsPluginEnabled) {
138
142
  return (
@@ -140,9 +144,8 @@ export function DocsPreferredVersionContextProvider({
140
144
  {children}
141
145
  </DocsPreferredVersionContextProviderUnsafe>
142
146
  );
143
- } else {
144
- return <>{children}</>;
145
147
  }
148
+ return children;
146
149
  }
147
150
 
148
151
  function DocsPreferredVersionContextProviderUnsafe({
@@ -157,9 +160,7 @@ function DocsPreferredVersionContextProviderUnsafe({
157
160
  export function useDocsPreferredVersionContext(): DocsPreferredVersionContextValue {
158
161
  const value = useContext(Context);
159
162
  if (!value) {
160
- throw new Error(
161
- 'Can\'t find docs preferred context, maybe you forgot to use the "DocsPreferredVersionContextProvider"?',
162
- );
163
+ throw new ReactContextError('DocsPreferredVersionContextProvider');
163
164
  }
164
165
  return value;
165
166
  }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import {createStorageSlot} from '../storageUtils';
9
- import {DocsVersionPersistence} from '../useThemeConfig';
9
+ import type {DocsVersionPersistence} from '../useThemeConfig';
10
10
 
11
11
  const storageKey = (pluginId: string) => `docs-preferred-version-${pluginId}`;
12
12
 
@@ -22,9 +22,8 @@ const DocsPreferredVersionStorage = {
22
22
  read: (
23
23
  pluginId: string,
24
24
  persistence: DocsVersionPersistence,
25
- ): string | null => {
26
- return createStorageSlot(storageKey(pluginId), {persistence}).get();
27
- },
25
+ ): string | null =>
26
+ createStorageSlot(storageKey(pluginId), {persistence}).get(),
28
27
 
29
28
  clear: (pluginId: string, persistence: DocsVersionPersistence): void => {
30
29
  createStorageSlot(storageKey(pluginId), {persistence}).del();
@@ -4,40 +4,47 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+
7
8
  import {useCallback} from 'react';
8
9
  import {useDocsPreferredVersionContext} from './DocsPreferredVersionProvider';
9
- import {useAllDocsData, useDocsData} from '@theme/hooks/useDocs';
10
+ import {
11
+ useAllDocsData,
12
+ useDocsData,
13
+ type GlobalVersion,
14
+ } from '@docusaurus/plugin-content-docs/client';
10
15
 
11
16
  import {DEFAULT_PLUGIN_ID} from '@docusaurus/constants';
12
17
 
13
- // TODO improve typing
14
-
15
18
  // Note, the preferredVersion attribute will always be null before mount
16
19
  export function useDocsPreferredVersion(
17
20
  pluginId: string | undefined = DEFAULT_PLUGIN_ID,
18
- ) {
21
+ ): {
22
+ preferredVersion: GlobalVersion | null | undefined;
23
+ savePreferredVersionName: (versionName: string) => void;
24
+ } {
19
25
  const docsData = useDocsData(pluginId);
20
26
  const [state, api] = useDocsPreferredVersionContext();
21
27
 
22
28
  const {preferredVersionName} = state[pluginId];
23
29
 
24
30
  const preferredVersion = preferredVersionName
25
- ? docsData.versions.find(
26
- (version: any) => version.name === preferredVersionName,
27
- )
31
+ ? docsData.versions.find((version) => version.name === preferredVersionName)
28
32
  : null;
29
33
 
30
34
  const savePreferredVersionName = useCallback(
31
35
  (versionName: string) => {
32
36
  api.savePreferredVersion(pluginId, versionName);
33
37
  },
34
- [api],
38
+ [api, pluginId],
35
39
  );
36
40
 
37
41
  return {preferredVersion, savePreferredVersionName} as const;
38
42
  }
39
43
 
40
- export function useDocsPreferredVersionByPluginId(): Record<string, any> {
44
+ export function useDocsPreferredVersionByPluginId(): Record<
45
+ string,
46
+ GlobalVersion | null | undefined
47
+ > {
41
48
  const allDocsData = useAllDocsData();
42
49
  const [state] = useDocsPreferredVersionContext();
43
50
 
@@ -47,17 +54,14 @@ export function useDocsPreferredVersionByPluginId(): Record<string, any> {
47
54
 
48
55
  return preferredVersionName
49
56
  ? docsData.versions.find(
50
- (version: any) => version.name === preferredVersionName,
57
+ (version) => version.name === preferredVersionName,
51
58
  )
52
59
  : null;
53
60
  }
54
61
 
55
62
  const pluginIds = Object.keys(allDocsData);
56
63
 
57
- const result: Record<
58
- string,
59
- any // TODO find a way to type this properly!
60
- > = {};
64
+ const result: Record<string, GlobalVersion | null | undefined> = {};
61
65
  pluginIds.forEach((pluginId) => {
62
66
  result[pluginId] = getPluginIdPreferredVersion(pluginId);
63
67
  });