@azerion/bunosaurus-theme-common 0.0.0-stage → 4.0.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 +2 -2
- package/lib/components/Collapsible/index.d.ts +59 -0
- package/lib/components/Collapsible/index.d.ts.map +1 -0
- package/lib/components/Collapsible/index.js +145 -0
- package/lib/components/Collapsible/index.js.map +1 -0
- package/lib/components/Details/index.d.ts +20 -0
- package/lib/components/Details/index.d.ts.map +1 -0
- package/lib/components/Details/index.js +86 -0
- package/lib/components/Details/index.js.map +1 -0
- package/lib/components/Details/styles.module.css +66 -0
- package/lib/components/ThemedComponent/index.d.ts +31 -0
- package/lib/components/ThemedComponent/index.d.ts.map +1 -0
- package/lib/components/ThemedComponent/index.js +47 -0
- package/lib/components/ThemedComponent/index.js.map +1 -0
- package/lib/components/ThemedComponent/styles.module.css +26 -0
- package/lib/contexts/announcementBar.d.ts +21 -0
- package/lib/contexts/announcementBar.d.ts.map +1 -0
- package/lib/contexts/announcementBar.js +72 -0
- package/lib/contexts/announcementBar.js.map +1 -0
- package/lib/contexts/colorMode.d.ts +23 -0
- package/lib/contexts/colorMode.d.ts.map +1 -0
- package/lib/contexts/colorMode.js +163 -0
- package/lib/contexts/colorMode.js.map +1 -0
- package/lib/contexts/navbarMobileSidebar.d.ts +31 -0
- package/lib/contexts/navbarMobileSidebar.d.ts.map +1 -0
- package/lib/contexts/navbarMobileSidebar.js +65 -0
- package/lib/contexts/navbarMobileSidebar.js.map +1 -0
- package/lib/contexts/navbarSecondaryMenu/content.d.ts +37 -0
- package/lib/contexts/navbarSecondaryMenu/content.d.ts.map +1 -0
- package/lib/contexts/navbarSecondaryMenu/content.js +50 -0
- package/lib/contexts/navbarSecondaryMenu/content.js.map +1 -0
- package/lib/contexts/navbarSecondaryMenu/display.d.ts +24 -0
- package/lib/contexts/navbarSecondaryMenu/display.d.ts.map +1 -0
- package/lib/contexts/navbarSecondaryMenu/display.js +62 -0
- package/lib/contexts/navbarSecondaryMenu/display.js.map +1 -0
- package/lib/hooks/useBackToTopButton.d.ts +27 -0
- package/lib/hooks/useBackToTopButton.d.ts.map +1 -0
- package/lib/hooks/useBackToTopButton.js +50 -0
- package/lib/hooks/useBackToTopButton.js.map +1 -0
- package/lib/hooks/useCodeWordWrap.d.ts +15 -0
- package/lib/hooks/useCodeWordWrap.d.ts.map +1 -0
- package/lib/hooks/useCodeWordWrap.js +67 -0
- package/lib/hooks/useCodeWordWrap.js.map +1 -0
- package/lib/hooks/useHideableNavbar.d.ts +17 -0
- package/lib/hooks/useHideableNavbar.d.ts.map +1 -0
- package/lib/hooks/useHideableNavbar.js +65 -0
- package/lib/hooks/useHideableNavbar.js.map +1 -0
- package/lib/hooks/useLockBodyScroll.d.ts +12 -0
- package/lib/hooks/useLockBodyScroll.d.ts.map +1 -0
- package/lib/hooks/useLockBodyScroll.js +20 -0
- package/lib/hooks/useLockBodyScroll.js.map +1 -0
- package/lib/hooks/useMutationObserver.d.ts +4 -0
- package/lib/hooks/useMutationObserver.d.ts.map +1 -0
- package/lib/hooks/useMutationObserver.js +29 -0
- package/lib/hooks/useMutationObserver.js.map +1 -0
- package/lib/hooks/usePrismTheme.d.ts +13 -0
- package/lib/hooks/usePrismTheme.d.ts.map +1 -0
- package/lib/hooks/usePrismTheme.js +21 -0
- package/lib/hooks/usePrismTheme.js.map +1 -0
- package/lib/hooks/useTOCHighlight.d.ts +25 -0
- package/lib/hooks/useTOCHighlight.d.ts.map +1 -0
- package/lib/hooks/useTOCHighlight.js +130 -0
- package/lib/hooks/useTOCHighlight.js.map +1 -0
- package/lib/hooks/useWindowSize.d.ts +27 -0
- package/lib/hooks/useWindowSize.d.ts.map +1 -0
- package/lib/hooks/useWindowSize.js +49 -0
- package/lib/hooks/useWindowSize.js.map +1 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +37 -0
- package/lib/index.js.map +1 -0
- package/lib/internal.d.ts +35 -0
- package/lib/internal.d.ts.map +1 -0
- package/lib/internal.js +45 -0
- package/lib/internal.js.map +1 -0
- package/lib/translations/blogTranslations.d.ts +20 -0
- package/lib/translations/blogTranslations.d.ts.map +1 -0
- package/lib/translations/blogTranslations.js +52 -0
- package/lib/translations/blogTranslations.js.map +1 -0
- package/lib/translations/contentVisibilityTranslations.d.ts +12 -0
- package/lib/translations/contentVisibilityTranslations.d.ts.map +1 -0
- package/lib/translations/contentVisibilityTranslations.js +30 -0
- package/lib/translations/contentVisibilityTranslations.js.map +1 -0
- package/lib/translations/docsTranslations.d.ts +8 -0
- package/lib/translations/docsTranslations.d.ts.map +1 -0
- package/lib/translations/docsTranslations.js +17 -0
- package/lib/translations/docsTranslations.js.map +1 -0
- package/lib/utils/IntlUtils.d.ts +2 -0
- package/lib/utils/IntlUtils.d.ts.map +1 -0
- package/lib/utils/IntlUtils.js +20 -0
- package/lib/utils/IntlUtils.js.map +1 -0
- package/lib/utils/ThemeClassNames.d.ts +99 -0
- package/lib/utils/ThemeClassNames.d.ts.map +1 -0
- package/lib/utils/ThemeClassNames.js +107 -0
- package/lib/utils/ThemeClassNames.js.map +1 -0
- package/lib/utils/accessibilityUtils.d.ts +8 -0
- package/lib/utils/accessibilityUtils.d.ts.map +1 -0
- package/lib/utils/accessibilityUtils.js +10 -0
- package/lib/utils/accessibilityUtils.js.map +1 -0
- package/lib/utils/admonitionUtils.d.ts +12 -0
- package/lib/utils/admonitionUtils.d.ts.map +1 -0
- package/lib/utils/admonitionUtils.js +32 -0
- package/lib/utils/admonitionUtils.js.map +1 -0
- package/lib/utils/anchorUtils.d.ts +16 -0
- package/lib/utils/anchorUtils.d.ts.map +1 -0
- package/lib/utils/anchorUtils.js +26 -0
- package/lib/utils/anchorUtils.js.map +1 -0
- package/lib/utils/anchorUtils.module.css +14 -0
- package/lib/utils/codeBlockUtils.d.ts +101 -0
- package/lib/utils/codeBlockUtils.d.ts.map +1 -0
- package/lib/utils/codeBlockUtils.js +310 -0
- package/lib/utils/codeBlockUtils.js.map +1 -0
- package/lib/utils/emojiUtils.d.ts +20 -0
- package/lib/utils/emojiUtils.d.ts.map +1 -0
- package/lib/utils/emojiUtils.js +32 -0
- package/lib/utils/emojiUtils.js.map +1 -0
- package/lib/utils/errorBoundaryUtils.d.ts +31 -0
- package/lib/utils/errorBoundaryUtils.d.ts.map +1 -0
- package/lib/utils/errorBoundaryUtils.js +48 -0
- package/lib/utils/errorBoundaryUtils.js.map +1 -0
- package/lib/utils/errorBoundaryUtils.module.css +16 -0
- package/lib/utils/footerUtils.d.ts +13 -0
- package/lib/utils/footerUtils.d.ts.map +1 -0
- package/lib/utils/footerUtils.js +14 -0
- package/lib/utils/footerUtils.js.map +1 -0
- package/lib/utils/historyUtils.d.ts +39 -0
- package/lib/utils/historyUtils.d.ts.map +1 -0
- package/lib/utils/historyUtils.js +148 -0
- package/lib/utils/historyUtils.js.map +1 -0
- package/lib/utils/jsUtils.d.ts +24 -0
- package/lib/utils/jsUtils.d.ts.map +1 -0
- package/lib/utils/jsUtils.js +43 -0
- package/lib/utils/jsUtils.js.map +1 -0
- package/lib/utils/metadataUtils.d.ts +38 -0
- package/lib/utils/metadataUtils.d.ts.map +1 -0
- package/lib/utils/metadataUtils.js +87 -0
- package/lib/utils/metadataUtils.js.map +1 -0
- package/lib/utils/navbarUtils.d.ts +21 -0
- package/lib/utils/navbarUtils.d.ts.map +1 -0
- package/lib/utils/navbarUtils.js +35 -0
- package/lib/utils/navbarUtils.js.map +1 -0
- package/lib/utils/reactUtils.d.ts +60 -0
- package/lib/utils/reactUtils.d.ts.map +1 -0
- package/lib/utils/reactUtils.js +91 -0
- package/lib/utils/reactUtils.js.map +1 -0
- package/lib/utils/regexpUtils.d.ts +12 -0
- package/lib/utils/regexpUtils.d.ts.map +1 -0
- package/lib/utils/regexpUtils.js +18 -0
- package/lib/utils/regexpUtils.js.map +1 -0
- package/lib/utils/routesUtils.d.ts +26 -0
- package/lib/utils/routesUtils.d.ts.map +1 -0
- package/lib/utils/routesUtils.js +54 -0
- package/lib/utils/routesUtils.js.map +1 -0
- package/lib/utils/scrollUtils.d.ts +83 -0
- package/lib/utils/scrollUtils.d.ts.map +1 -0
- package/lib/utils/scrollUtils.js +201 -0
- package/lib/utils/scrollUtils.js.map +1 -0
- package/lib/utils/searchUtils.d.ts +8 -0
- package/lib/utils/searchUtils.d.ts.map +1 -0
- package/lib/utils/searchUtils.js +8 -0
- package/lib/utils/searchUtils.js.map +1 -0
- package/lib/utils/skipToContentUtils.d.ts +20 -0
- package/lib/utils/skipToContentUtils.d.ts.map +1 -0
- package/lib/utils/skipToContentUtils.js +75 -0
- package/lib/utils/skipToContentUtils.js.map +1 -0
- package/lib/utils/storageUtils.d.ts +36 -0
- package/lib/utils/storageUtils.d.ts.map +1 -0
- package/lib/utils/storageUtils.js +193 -0
- package/lib/utils/storageUtils.js.map +1 -0
- package/lib/utils/tabsUtils.d.ts +55 -0
- package/lib/utils/tabsUtils.d.ts.map +1 -0
- package/lib/utils/tabsUtils.js +179 -0
- package/lib/utils/tabsUtils.js.map +1 -0
- package/lib/utils/tagsUtils.d.ts +20 -0
- package/lib/utils/tagsUtils.d.ts.map +1 -0
- package/lib/utils/tagsUtils.js +41 -0
- package/lib/utils/tagsUtils.js.map +1 -0
- package/lib/utils/titleFormatterUtils.d.ts +59 -0
- package/lib/utils/titleFormatterUtils.d.ts.map +1 -0
- package/lib/utils/titleFormatterUtils.js +49 -0
- package/lib/utils/titleFormatterUtils.js.map +1 -0
- package/lib/utils/tocUtils.d.ts +36 -0
- package/lib/utils/tocUtils.d.ts.map +1 -0
- package/lib/utils/tocUtils.js +84 -0
- package/lib/utils/tocUtils.js.map +1 -0
- package/lib/utils/useAlternatePageUtils.d.ts +32 -0
- package/lib/utils/useAlternatePageUtils.d.ts.map +1 -0
- package/lib/utils/useAlternatePageUtils.js +42 -0
- package/lib/utils/useAlternatePageUtils.js.map +1 -0
- package/lib/utils/useLocalPathname.d.ts +13 -0
- package/lib/utils/useLocalPathname.d.ts.map +1 -0
- package/lib/utils/useLocalPathname.js +19 -0
- package/lib/utils/useLocalPathname.js.map +1 -0
- package/lib/utils/useLocationChange.d.ts +17 -0
- package/lib/utils/useLocationChange.d.ts.map +1 -0
- package/lib/utils/useLocationChange.js +31 -0
- package/lib/utils/useLocationChange.js.map +1 -0
- package/lib/utils/usePluralForm.d.ts +21 -0
- package/lib/utils/usePluralForm.d.ts.map +1 -0
- package/lib/utils/usePluralForm.js +93 -0
- package/lib/utils/usePluralForm.js.map +1 -0
- package/lib/utils/useThemeConfig.d.ts +120 -0
- package/lib/utils/useThemeConfig.d.ts.map +1 -0
- package/lib/utils/useThemeConfig.js +14 -0
- package/lib/utils/useThemeConfig.js.map +1 -0
- package/package.json +58 -4
- package/src/components/Collapsible/index.tsx +250 -0
- package/src/components/Details/index.tsx +130 -0
- package/src/components/Details/styles.module.css +66 -0
- package/src/components/ThemedComponent/index.tsx +75 -0
- package/src/components/ThemedComponent/styles.module.css +26 -0
- package/src/contexts/announcementBar.tsx +117 -0
- package/src/contexts/colorMode.tsx +257 -0
- package/src/contexts/navbarMobileSidebar.tsx +119 -0
- package/src/contexts/navbarSecondaryMenu/content.tsx +100 -0
- package/src/contexts/navbarSecondaryMenu/display.tsx +102 -0
- package/src/hooks/useBackToTopButton.ts +73 -0
- package/src/hooks/useCodeWordWrap.ts +107 -0
- package/src/hooks/useHideableNavbar.ts +81 -0
- package/src/hooks/useLockBodyScroll.ts +21 -0
- package/src/hooks/useMutationObserver.ts +38 -0
- package/src/hooks/usePrismTheme.ts +24 -0
- package/src/hooks/useTOCHighlight.ts +189 -0
- package/src/hooks/useWindowSize.ts +67 -0
- package/src/index.ts +112 -0
- package/src/internal.ts +107 -0
- package/src/translations/blogTranslations.tsx +89 -0
- package/src/translations/contentVisibilityTranslations.tsx +51 -0
- package/src/translations/docsTranslations.tsx +28 -0
- package/src/types.d.ts +8 -0
- package/src/utils/IntlUtils.ts +27 -0
- package/src/utils/ThemeClassNames.ts +119 -0
- package/src/utils/accessibilityUtils.ts +10 -0
- package/src/utils/admonitionUtils.tsx +44 -0
- package/src/utils/anchorUtils.module.css +14 -0
- package/src/utils/anchorUtils.ts +31 -0
- package/src/utils/codeBlockUtils.tsx +494 -0
- package/src/utils/emojiUtils.ts +40 -0
- package/src/utils/errorBoundaryUtils.module.css +16 -0
- package/src/utils/errorBoundaryUtils.tsx +71 -0
- package/src/utils/footerUtils.ts +18 -0
- package/src/utils/historyUtils.ts +199 -0
- package/src/utils/jsUtils.ts +54 -0
- package/src/utils/metadataUtils.tsx +145 -0
- package/src/utils/navbarUtils.tsx +45 -0
- package/src/utils/reactUtils.tsx +120 -0
- package/src/utils/regexpUtils.ts +24 -0
- package/src/utils/routesUtils.ts +75 -0
- package/src/utils/scrollUtils.tsx +303 -0
- package/src/utils/searchUtils.ts +8 -0
- package/src/utils/skipToContentUtils.tsx +108 -0
- package/src/utils/storageUtils.ts +260 -0
- package/src/utils/tabsUtils.tsx +307 -0
- package/src/utils/tagsUtils.tsx +61 -0
- package/src/utils/titleFormatterUtils.tsx +120 -0
- package/src/utils/tocUtils.ts +119 -0
- package/src/utils/useAlternatePageUtils.ts +80 -0
- package/src/utils/useLocalPathname.ts +22 -0
- package/src/utils/useLocationChange.ts +41 -0
- package/src/utils/usePluralForm.ts +131 -0
- package/src/utils/useThemeConfig.ts +147 -0
|
@@ -0,0 +1,307 @@
|
|
|
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
|
+
isValidElement,
|
|
10
|
+
useCallback,
|
|
11
|
+
useLayoutEffect,
|
|
12
|
+
useState,
|
|
13
|
+
useMemo,
|
|
14
|
+
createContext,
|
|
15
|
+
type ReactNode,
|
|
16
|
+
type ReactElement,
|
|
17
|
+
} from 'react';
|
|
18
|
+
import {useHistory} from '@docusaurus/router';
|
|
19
|
+
import {useQueryStringValue} from '@docusaurus/theme-common/internal';
|
|
20
|
+
import {duplicates, useStorageSlot} from '../index';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* TabValue is the "config" of a given Tab
|
|
24
|
+
* Provided through <Tabs> "values" prop or through the children <TabItem> props
|
|
25
|
+
*/
|
|
26
|
+
export interface TabValue {
|
|
27
|
+
readonly value: string;
|
|
28
|
+
readonly label?: string;
|
|
29
|
+
readonly attributes?: {[key: string]: unknown};
|
|
30
|
+
readonly default?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TabsProps {
|
|
34
|
+
readonly lazy?: boolean;
|
|
35
|
+
readonly block?: boolean;
|
|
36
|
+
readonly children: ReactNode;
|
|
37
|
+
readonly defaultValue?: string | null;
|
|
38
|
+
readonly values?: readonly TabValue[];
|
|
39
|
+
readonly groupId?: string;
|
|
40
|
+
readonly className?: string;
|
|
41
|
+
readonly queryString?: string | boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TabItemProps {
|
|
45
|
+
readonly children: ReactNode;
|
|
46
|
+
readonly value: string;
|
|
47
|
+
readonly default?: boolean;
|
|
48
|
+
readonly label?: string;
|
|
49
|
+
readonly className?: string;
|
|
50
|
+
readonly attributes?: {[key: string]: unknown};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function sanitizeTabsChildren(children: ReactNode): ReactNode {
|
|
54
|
+
return React.Children.toArray(children).filter((child) => child !== '\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractChildrenTabValues(children: ReactNode): TabValue[] {
|
|
58
|
+
// ✅ <TabItem value="red"/> => true
|
|
59
|
+
// ✅ <CustomTabItem value="red"/> => true
|
|
60
|
+
// ❌ <RedTabItem value="tab-value"/> => requires <Tabs values> prop
|
|
61
|
+
function isTabItemWithValueProp(
|
|
62
|
+
comp: ReactElement,
|
|
63
|
+
): comp is ReactElement<TabItemProps> {
|
|
64
|
+
const {props} = comp;
|
|
65
|
+
return !!props && typeof props === 'object' && 'value' in props;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const elements = React.Children.toArray(children).flatMap((child) => {
|
|
69
|
+
// Historical case, not sure when it happens, do we really need this?
|
|
70
|
+
if (!child) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
if (isValidElement(child) && isTabItemWithValueProp(child)) {
|
|
74
|
+
return [child];
|
|
75
|
+
}
|
|
76
|
+
// child.type.name will give non-sensical values in prod because of
|
|
77
|
+
// minification, but we assume it won't throw in prod.
|
|
78
|
+
const badChildTypeName =
|
|
79
|
+
// @ts-expect-error: guarding against unexpected cases
|
|
80
|
+
typeof child.type === 'string' ? child.type : child.type.name;
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Docusaurus error: Bad <Tabs> child <${badChildTypeName}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.
|
|
83
|
+
If you do not want to pass on a "value" prop to the direct children of <Tabs>, you can also pass an explicit <Tabs values={...}> prop.`,
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return elements.map(
|
|
88
|
+
({props: {value, label, attributes, default: isDefault}}) => ({
|
|
89
|
+
value,
|
|
90
|
+
label,
|
|
91
|
+
attributes,
|
|
92
|
+
default: isDefault,
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function ensureNoDuplicateValue(values: readonly TabValue[]) {
|
|
98
|
+
const dup = duplicates(values, (a, b) => a.value === b.value);
|
|
99
|
+
if (dup.length > 0) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Docusaurus error: Duplicate values "${dup
|
|
102
|
+
.map((a) => `'${a.value}'`)
|
|
103
|
+
.join(', ')}" found in <Tabs>. Every value needs to be unique.`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function useTabValues(
|
|
109
|
+
props: Pick<TabsProps, 'values' | 'children'>,
|
|
110
|
+
): readonly TabValue[] {
|
|
111
|
+
const {values: valuesProp, children} = props;
|
|
112
|
+
return useMemo(() => {
|
|
113
|
+
const values = valuesProp ?? extractChildrenTabValues(children);
|
|
114
|
+
ensureNoDuplicateValue(values);
|
|
115
|
+
return values;
|
|
116
|
+
}, [valuesProp, children]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isValidValue({
|
|
120
|
+
value,
|
|
121
|
+
tabValues,
|
|
122
|
+
}: {
|
|
123
|
+
value: string | null | undefined;
|
|
124
|
+
tabValues: readonly TabValue[];
|
|
125
|
+
}) {
|
|
126
|
+
return tabValues.some((a) => a.value === value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getInitialStateValue({
|
|
130
|
+
defaultValue,
|
|
131
|
+
tabValues,
|
|
132
|
+
}: {
|
|
133
|
+
defaultValue: TabsProps['defaultValue'];
|
|
134
|
+
tabValues: readonly TabValue[];
|
|
135
|
+
}): string {
|
|
136
|
+
if (tabValues.length === 0) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
'Docusaurus error: the <Tabs> component requires at least one <TabItem> children component',
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (defaultValue) {
|
|
142
|
+
// Warn user about passing incorrect defaultValue as prop.
|
|
143
|
+
if (!isValidValue({value: defaultValue, tabValues})) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Docusaurus error: The <Tabs> has a defaultValue "${defaultValue}" but none of its children has the corresponding value. Available values are: ${tabValues
|
|
146
|
+
.map((a) => a.value)
|
|
147
|
+
.join(
|
|
148
|
+
', ',
|
|
149
|
+
)}. If you intend to show no default tab, use defaultValue={null} instead.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return defaultValue;
|
|
153
|
+
}
|
|
154
|
+
const defaultTabValue =
|
|
155
|
+
tabValues.find((tabValue) => tabValue.default) ?? tabValues[0];
|
|
156
|
+
if (!defaultTabValue) {
|
|
157
|
+
throw new Error('Unexpected error: 0 tabValues');
|
|
158
|
+
}
|
|
159
|
+
return defaultTabValue.value;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getStorageKey(groupId: string | undefined) {
|
|
163
|
+
if (!groupId) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return `docusaurus.tab.${groupId}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getQueryStringKey({
|
|
170
|
+
queryString = false,
|
|
171
|
+
groupId,
|
|
172
|
+
}: Pick<TabsProps, 'queryString' | 'groupId'>) {
|
|
173
|
+
if (typeof queryString === 'string') {
|
|
174
|
+
return queryString;
|
|
175
|
+
}
|
|
176
|
+
if (queryString === false) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
if (queryString === true && !groupId) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return groupId ?? null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function useTabQueryString({
|
|
188
|
+
queryString = false,
|
|
189
|
+
groupId,
|
|
190
|
+
}: Pick<TabsProps, 'queryString' | 'groupId'>) {
|
|
191
|
+
const history = useHistory();
|
|
192
|
+
const key = getQueryStringKey({queryString, groupId});
|
|
193
|
+
const value = useQueryStringValue(key);
|
|
194
|
+
|
|
195
|
+
const setValue = useCallback(
|
|
196
|
+
(newValue: string) => {
|
|
197
|
+
if (!key) {
|
|
198
|
+
return; // no-op
|
|
199
|
+
}
|
|
200
|
+
const searchParams = new URLSearchParams(history.location.search);
|
|
201
|
+
searchParams.set(key, newValue);
|
|
202
|
+
history.replace({...history.location, search: searchParams.toString()});
|
|
203
|
+
},
|
|
204
|
+
[key, history],
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return [value, setValue] as const;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function useTabStorage({groupId}: Pick<TabsProps, 'groupId'>) {
|
|
211
|
+
const key = getStorageKey(groupId);
|
|
212
|
+
const [value, storageSlot] = useStorageSlot(key);
|
|
213
|
+
|
|
214
|
+
const setValue = useCallback(
|
|
215
|
+
(newValue: string) => {
|
|
216
|
+
if (!key) {
|
|
217
|
+
return; // no-op
|
|
218
|
+
}
|
|
219
|
+
storageSlot.set(newValue);
|
|
220
|
+
},
|
|
221
|
+
[key, storageSlot],
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
return [value, setValue] as const;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
type TabsContextValue = {
|
|
228
|
+
selectedValue: string;
|
|
229
|
+
selectValue: (value: string) => void;
|
|
230
|
+
tabValues: readonly TabValue[];
|
|
231
|
+
lazy: boolean;
|
|
232
|
+
// TODO Docusaurus v4: remove this "block" concept?
|
|
233
|
+
// TIL about it, and afaik we never used nor documented it
|
|
234
|
+
// See https://infima.dev/docs/components/tabs#block
|
|
235
|
+
block: boolean;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export function useTabsContextValue(props: TabsProps): TabsContextValue {
|
|
239
|
+
const {defaultValue, queryString = false, groupId} = props;
|
|
240
|
+
const tabValues = useTabValues(props);
|
|
241
|
+
|
|
242
|
+
const [selectedValue, setSelectedValue] = useState(() =>
|
|
243
|
+
getInitialStateValue({defaultValue, tabValues}),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const [queryStringValue, setQueryString] = useTabQueryString({
|
|
247
|
+
queryString,
|
|
248
|
+
groupId,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const [storageValue, setStorageValue] = useTabStorage({
|
|
252
|
+
groupId,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// We sync valid querystring/storage value to state on change + hydration
|
|
256
|
+
const valueToSync = (() => {
|
|
257
|
+
const value = queryStringValue ?? storageValue;
|
|
258
|
+
if (!isValidValue({value, tabValues})) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return value;
|
|
262
|
+
})();
|
|
263
|
+
// Sync in a layout/sync effect is important, for useScrollPositionBlocker
|
|
264
|
+
// See https://github.com/facebook/docusaurus/issues/8625
|
|
265
|
+
useLayoutEffect(() => {
|
|
266
|
+
if (valueToSync) {
|
|
267
|
+
setSelectedValue(valueToSync);
|
|
268
|
+
}
|
|
269
|
+
}, [valueToSync]);
|
|
270
|
+
|
|
271
|
+
const selectValue = useCallback(
|
|
272
|
+
(newValue: string) => {
|
|
273
|
+
if (!isValidValue({value: newValue, tabValues})) {
|
|
274
|
+
throw new Error(`Can't select invalid tab value=${newValue}`);
|
|
275
|
+
}
|
|
276
|
+
setSelectedValue(newValue);
|
|
277
|
+
setQueryString(newValue);
|
|
278
|
+
setStorageValue(newValue);
|
|
279
|
+
},
|
|
280
|
+
[setQueryString, setStorageValue, tabValues],
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
selectedValue,
|
|
285
|
+
selectValue,
|
|
286
|
+
tabValues,
|
|
287
|
+
lazy: props.lazy ?? false,
|
|
288
|
+
block: props.block ?? false,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const TabsContext = createContext<TabsContextValue | null>(null);
|
|
293
|
+
|
|
294
|
+
export function useTabs(): TabsContextValue {
|
|
295
|
+
const contextValue = React.useContext(TabsContext);
|
|
296
|
+
if (!contextValue) {
|
|
297
|
+
throw new Error('useTabsContext() must be used within a Tabs component');
|
|
298
|
+
}
|
|
299
|
+
return contextValue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function TabsProvider(props: {
|
|
303
|
+
children: ReactNode;
|
|
304
|
+
value: TabsContextValue;
|
|
305
|
+
}): ReactNode {
|
|
306
|
+
return <TabsContext value={props.value}>{props.children}</TabsContext>;
|
|
307
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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 type {ReactNode} from 'react';
|
|
9
|
+
import Translate, {translate} from '@docusaurus/Translate';
|
|
10
|
+
import type {TagsListItem} from '@docusaurus/utils';
|
|
11
|
+
|
|
12
|
+
export const translateTagsPageTitle = (): string =>
|
|
13
|
+
translate({
|
|
14
|
+
id: 'theme.tags.tagsPageTitle',
|
|
15
|
+
message: 'Tags',
|
|
16
|
+
description: 'The title of the tag list page',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export function TagsPageLinkLabel(): ReactNode {
|
|
20
|
+
return (
|
|
21
|
+
<Translate
|
|
22
|
+
id="theme.tags.tagsPageLink"
|
|
23
|
+
description="The label of the link targeting the tag list page">
|
|
24
|
+
View all tags
|
|
25
|
+
</Translate>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type TagLetterEntry = {letter: string; tags: TagsListItem[]};
|
|
30
|
+
|
|
31
|
+
function getTagLetter(tag: string): string {
|
|
32
|
+
return tag[0]!.toUpperCase();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Takes a list of tags (as provided by the content plugins), and groups them by
|
|
37
|
+
* their initials.
|
|
38
|
+
*/
|
|
39
|
+
export function listTagsByLetters(
|
|
40
|
+
tags: readonly TagsListItem[],
|
|
41
|
+
): TagLetterEntry[] {
|
|
42
|
+
const groups: {[initial: string]: TagsListItem[]} = {};
|
|
43
|
+
Object.values(tags).forEach((tag) => {
|
|
44
|
+
const initial = getTagLetter(tag.label);
|
|
45
|
+
groups[initial] ??= [];
|
|
46
|
+
groups[initial]!.push(tag);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
Object.entries(groups)
|
|
51
|
+
// Sort letters
|
|
52
|
+
.sort(([letter1], [letter2]) => letter1.localeCompare(letter2))
|
|
53
|
+
.map(([letter, letterTags]) => {
|
|
54
|
+
// Sort tags inside a letter
|
|
55
|
+
const sortedTags = letterTags.sort((tag1, tag2) =>
|
|
56
|
+
tag1.label.localeCompare(tag2.label),
|
|
57
|
+
);
|
|
58
|
+
return {letter, tags: sortedTags};
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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 value={formatter}>{children}</TitleFormatterContext>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function useTitleFormatterContext() {
|
|
91
|
+
const value = useContext(TitleFormatterContext);
|
|
92
|
+
if (value === null) {
|
|
93
|
+
throw new ReactContextError('TitleFormatterProvider');
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns a function to format the page title
|
|
100
|
+
*/
|
|
101
|
+
export function useTitleFormatter(): TitleFormatter {
|
|
102
|
+
const formatter = useTitleFormatterContext();
|
|
103
|
+
const {siteConfig} = useDocusaurusContext();
|
|
104
|
+
const {title: siteTitle, titleDelimiter} = siteConfig;
|
|
105
|
+
|
|
106
|
+
// Unfortunately we can only call this hook here, not in the provider
|
|
107
|
+
// Route context can't be accessed in any provider applied above the router
|
|
108
|
+
const {plugin} = useRouteContext();
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
format: (title: string) =>
|
|
112
|
+
formatter({
|
|
113
|
+
title,
|
|
114
|
+
siteTitle,
|
|
115
|
+
titleDelimiter,
|
|
116
|
+
plugin,
|
|
117
|
+
defaultFormatter: TitleFormatterFnDefault,
|
|
118
|
+
}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
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 {useMemo} from 'react';
|
|
9
|
+
import type {TOCItem} from '@docusaurus/mdx-loader';
|
|
10
|
+
|
|
11
|
+
export type TOCTreeNode = {
|
|
12
|
+
readonly value: string;
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly level: number;
|
|
15
|
+
readonly children: readonly TOCTreeNode[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function treeifyTOC(flatTOC: readonly TOCItem[]): TOCTreeNode[] {
|
|
19
|
+
const headings = flatTOC.map((heading) => ({
|
|
20
|
+
...heading,
|
|
21
|
+
parentIndex: -1,
|
|
22
|
+
children: [] as TOCTreeNode[],
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
// Keep track of which previous index would be the current heading's direct
|
|
26
|
+
// parent. Each entry <i> is the last index of the `headings` array at heading
|
|
27
|
+
// level <i>. We will modify these indices as we iterate through all headings.
|
|
28
|
+
// e.g. if an ### H3 was last seen at index 2, then prevIndexForLevel[3] === 2
|
|
29
|
+
// indices 0 and 1 will remain unused.
|
|
30
|
+
const prevIndexForLevel = Array<number>(7).fill(-1);
|
|
31
|
+
|
|
32
|
+
headings.forEach((curr, currIndex) => {
|
|
33
|
+
// Take the last seen index for each ancestor level. the highest index will
|
|
34
|
+
// be the direct ancestor of the current heading.
|
|
35
|
+
const ancestorLevelIndexes = prevIndexForLevel.slice(2, curr.level);
|
|
36
|
+
curr.parentIndex = Math.max(...ancestorLevelIndexes);
|
|
37
|
+
// Mark that curr.level was last seen at the current index.
|
|
38
|
+
prevIndexForLevel[curr.level] = currIndex;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const rootNodes: TOCTreeNode[] = [];
|
|
42
|
+
|
|
43
|
+
// For a given parentIndex, add each Node into that parent's `children` array
|
|
44
|
+
headings.forEach((heading) => {
|
|
45
|
+
const {parentIndex, ...rest} = heading;
|
|
46
|
+
if (parentIndex >= 0) {
|
|
47
|
+
headings[parentIndex]!.children.push(rest);
|
|
48
|
+
} else {
|
|
49
|
+
rootNodes.push(rest);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return rootNodes;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Takes a flat TOC list (from the MDX loader) and treeifies it into what the
|
|
57
|
+
* TOC components expect. Memoized for performance.
|
|
58
|
+
*/
|
|
59
|
+
export function useTreeifiedTOC(toc: TOCItem[]): readonly TOCTreeNode[] {
|
|
60
|
+
return useMemo(() => treeifyTOC(toc), [toc]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function filterTOC({
|
|
64
|
+
toc,
|
|
65
|
+
minHeadingLevel,
|
|
66
|
+
maxHeadingLevel,
|
|
67
|
+
}: {
|
|
68
|
+
toc: readonly TOCTreeNode[];
|
|
69
|
+
minHeadingLevel: number;
|
|
70
|
+
maxHeadingLevel: number;
|
|
71
|
+
}): TOCTreeNode[] {
|
|
72
|
+
function isValid(item: TOCTreeNode) {
|
|
73
|
+
return item.level >= minHeadingLevel && item.level <= maxHeadingLevel;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return toc.flatMap((item) => {
|
|
77
|
+
const filteredChildren = filterTOC({
|
|
78
|
+
toc: item.children,
|
|
79
|
+
minHeadingLevel,
|
|
80
|
+
maxHeadingLevel,
|
|
81
|
+
});
|
|
82
|
+
if (isValid(item)) {
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
...item,
|
|
86
|
+
children: filteredChildren,
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
return filteredChildren;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Takes a flat TOC list (from the MDX loader) and treeifies it into what the
|
|
96
|
+
* TOC components expect, applying the `minHeadingLevel` and `maxHeadingLevel`.
|
|
97
|
+
* Memoized for performance.
|
|
98
|
+
*
|
|
99
|
+
* **Important**: this is not the same as `useTreeifiedTOC(toc.filter(...))`,
|
|
100
|
+
* because we have to filter the TOC after it has been treeified. This is mostly
|
|
101
|
+
* to ensure that weird TOC structures preserve their semantics. For example, an
|
|
102
|
+
* h3-h2-h4 sequence should not be treeified as an "h3 > h4" hierarchy with
|
|
103
|
+
* min=3, max=4, but should rather be "[h3, h4]" (since the h2 heading has split
|
|
104
|
+
* the two headings and they are not parent-children)
|
|
105
|
+
*/
|
|
106
|
+
export function useFilteredAndTreeifiedTOC({
|
|
107
|
+
toc,
|
|
108
|
+
minHeadingLevel,
|
|
109
|
+
maxHeadingLevel,
|
|
110
|
+
}: {
|
|
111
|
+
toc: readonly TOCItem[];
|
|
112
|
+
minHeadingLevel: number;
|
|
113
|
+
maxHeadingLevel: number;
|
|
114
|
+
}): readonly TOCTreeNode[] {
|
|
115
|
+
return useMemo(
|
|
116
|
+
() => filterTOC({toc: treeifyTOC(toc), minHeadingLevel, maxHeadingLevel}),
|
|
117
|
+
[toc, minHeadingLevel, maxHeadingLevel],
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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 useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
9
|
+
import {useLocation} from '@docusaurus/router';
|
|
10
|
+
import {applyTrailingSlash} from '@docusaurus/utils-common';
|
|
11
|
+
import type {I18nLocaleConfig} from '@docusaurus/types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Permits to obtain the url of the current page in another locale, useful to
|
|
15
|
+
* generate hreflang meta headers etc...
|
|
16
|
+
*
|
|
17
|
+
* @see https://developers.google.com/search/docs/advanced/crawling/localized-versions
|
|
18
|
+
*/
|
|
19
|
+
export function useAlternatePageUtils(): {
|
|
20
|
+
/**
|
|
21
|
+
* Everything (pathname, base URL, etc.) is read from the context. Just tell
|
|
22
|
+
* it which locale to link to and it will give you the alternate link for the
|
|
23
|
+
* current page.
|
|
24
|
+
*/
|
|
25
|
+
createUrl: ({
|
|
26
|
+
/** The locale name to link to. */
|
|
27
|
+
locale,
|
|
28
|
+
/**
|
|
29
|
+
* For hreflang SEO headers, we need it to be fully qualified (full
|
|
30
|
+
* protocol/domain/path...); but for locale dropdowns, using a pathname is
|
|
31
|
+
* good enough.
|
|
32
|
+
*/
|
|
33
|
+
fullyQualified,
|
|
34
|
+
}: {
|
|
35
|
+
locale: string;
|
|
36
|
+
fullyQualified: boolean;
|
|
37
|
+
}) => string;
|
|
38
|
+
} {
|
|
39
|
+
const {
|
|
40
|
+
siteConfig: {baseUrl, trailingSlash},
|
|
41
|
+
i18n: {localeConfigs},
|
|
42
|
+
} = useDocusaurusContext();
|
|
43
|
+
|
|
44
|
+
// TODO using useLocation().pathname is not a super idea
|
|
45
|
+
// See https://github.com/facebook/docusaurus/issues/9170
|
|
46
|
+
const {pathname} = useLocation();
|
|
47
|
+
|
|
48
|
+
const canonicalPathname = applyTrailingSlash(pathname, {
|
|
49
|
+
trailingSlash,
|
|
50
|
+
baseUrl,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Canonical pathname, without the baseUrl of the current locale
|
|
54
|
+
const pathnameSuffix = canonicalPathname.replace(baseUrl, '');
|
|
55
|
+
|
|
56
|
+
function getLocaleConfig(locale: string): I18nLocaleConfig {
|
|
57
|
+
const localeConfig = localeConfigs[locale];
|
|
58
|
+
if (!localeConfig) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Unexpected Docusaurus bug, no locale config found for locale=${locale}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return localeConfig;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createUrl({
|
|
67
|
+
locale,
|
|
68
|
+
fullyQualified,
|
|
69
|
+
}: {
|
|
70
|
+
locale: string;
|
|
71
|
+
fullyQualified: boolean;
|
|
72
|
+
}) {
|
|
73
|
+
const localeConfig = getLocaleConfig(locale);
|
|
74
|
+
const newUrl = `${fullyQualified ? localeConfig.url : ''}`;
|
|
75
|
+
const newBaseUrl = localeConfig.baseUrl;
|
|
76
|
+
return `${newUrl}${newBaseUrl}${pathnameSuffix}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {createUrl};
|
|
80
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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 {useLocation} from '@docusaurus/router';
|
|
9
|
+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the pathname of current route, without the optional site baseUrl.
|
|
13
|
+
* - `/docs/myDoc` => `/docs/myDoc`
|
|
14
|
+
* - `/baseUrl/docs/myDoc` => `/docs/myDoc`
|
|
15
|
+
*/
|
|
16
|
+
export function useLocalPathname(): string {
|
|
17
|
+
const {
|
|
18
|
+
siteConfig: {baseUrl},
|
|
19
|
+
} = useDocusaurusContext();
|
|
20
|
+
const {pathname} = useLocation();
|
|
21
|
+
return pathname.replace(baseUrl, '/');
|
|
22
|
+
}
|