@redocly/theme 0.61.0-next.1 → 0.61.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Catalog/CatalogEntity/CatalogEntity.js +5 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.js +9 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistoryButton.d.ts +6 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistoryButton.js +144 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.d.ts +8 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +161 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityRevisionItem.d.ts +8 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityRevisionItem.js +67 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.d.ts +9 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.js +212 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityMetadata.js +2 -25
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.js +12 -1
- package/lib/components/Catalog/CatalogEntity/ShowMoreButton.d.ts +8 -0
- package/lib/components/Catalog/CatalogEntity/ShowMoreButton.js +35 -0
- package/lib/components/Catalog/CatalogTableView/CatalogTableViewRow.d.ts +2 -0
- package/lib/components/Catalog/CatalogTableView/CatalogTableViewRow.js +4 -1
- package/lib/components/Catalog/variables.js +112 -0
- package/lib/components/ColorModeSwitcher/ColorModeIcon.d.ts +2 -1
- package/lib/components/ColorModeSwitcher/ColorModeIcon.js +3 -2
- package/lib/components/ColorModeSwitcher/ColorModeSwitcher.js +1 -4
- package/lib/components/Menu/variables.js +1 -0
- package/lib/components/Product/utils.d.ts +1 -0
- package/lib/components/Product/utils.js +10 -0
- package/lib/components/Tooltip/Tooltip.js +2 -0
- package/lib/core/constants/catalog.d.ts +1 -0
- package/lib/core/constants/catalog.js +2 -1
- package/lib/core/constants/common.d.ts +4 -0
- package/lib/core/constants/common.js +5 -1
- package/lib/core/hooks/catalog/use-catalog-entity-details.d.ts +3 -1
- package/lib/core/hooks/catalog/use-catalog-entity-details.js +12 -5
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/hooks/search/use-recent-searches.js +14 -41
- package/lib/core/hooks/use-color-switcher.d.ts +0 -1
- package/lib/core/hooks/use-color-switcher.js +19 -13
- package/lib/core/hooks/use-page-actions.js +37 -6
- package/lib/core/hooks/use-product-picker.js +12 -2
- package/lib/core/hooks/use-store.d.ts +17 -0
- package/lib/core/hooks/use-store.js +64 -0
- package/lib/core/hooks/use-telemetry-fallback.d.ts +2 -0
- package/lib/core/hooks/use-telemetry-fallback.js +2 -0
- package/lib/core/types/catalog.d.ts +33 -4
- package/lib/core/types/common.d.ts +2 -0
- package/lib/core/types/hooks.d.ts +14 -3
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/utils/build-revision-url.d.ts +1 -0
- package/lib/core/utils/build-revision-url.js +15 -0
- package/lib/core/utils/date.d.ts +14 -0
- package/lib/core/utils/date.js +39 -0
- package/lib/core/utils/index.d.ts +2 -0
- package/lib/core/utils/index.js +2 -0
- package/lib/core/utils/load-and-navigate.js +7 -2
- package/lib/core/utils/transform-revisions-to-version-history.d.ts +8 -0
- package/lib/core/utils/transform-revisions-to-version-history.js +110 -0
- package/lib/icons/NavaidMilitaryIcon/NavaidMilitaryIcon.d.ts +9 -0
- package/lib/icons/NavaidMilitaryIcon/NavaidMilitaryIcon.js +26 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/package.json +5 -5
- package/src/components/Catalog/CatalogEntity/CatalogEntity.tsx +7 -1
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.tsx +12 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistoryButton.tsx +147 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +180 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityRevisionItem.tsx +93 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.tsx +284 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityMetadata.tsx +3 -25
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.tsx +15 -2
- package/src/components/Catalog/CatalogEntity/ShowMoreButton.tsx +47 -0
- package/src/components/Catalog/CatalogTableView/CatalogTableViewRow.tsx +6 -1
- package/src/components/Catalog/variables.ts +112 -0
- package/src/components/ColorModeSwitcher/ColorModeIcon.tsx +5 -3
- package/src/components/ColorModeSwitcher/ColorModeSwitcher.tsx +2 -7
- package/src/components/Menu/variables.ts +1 -0
- package/src/components/Product/utils.ts +6 -0
- package/src/components/Tooltip/Tooltip.tsx +2 -0
- package/src/core/constants/catalog.ts +2 -0
- package/src/core/constants/common.ts +5 -0
- package/src/core/hooks/__mocks__/use-theme-hooks.ts +1 -0
- package/src/core/hooks/catalog/use-catalog-entity-details.ts +22 -6
- package/src/core/hooks/index.ts +1 -0
- package/src/core/hooks/search/use-recent-searches.ts +38 -65
- package/src/core/hooks/use-color-switcher.ts +29 -15
- package/src/core/hooks/use-page-actions.ts +63 -6
- package/src/core/hooks/use-product-picker.ts +12 -0
- package/src/core/hooks/use-store.ts +95 -0
- package/src/core/hooks/use-telemetry-fallback.ts +2 -0
- package/src/core/types/catalog.ts +38 -10
- package/src/core/types/common.ts +4 -0
- package/src/core/types/hooks.ts +23 -4
- package/src/core/types/l10n.ts +10 -0
- package/src/core/utils/build-revision-url.ts +16 -0
- package/src/core/utils/date.ts +33 -0
- package/src/core/utils/index.ts +2 -0
- package/src/core/utils/load-and-navigate.ts +6 -1
- package/src/core/utils/transform-revisions-to-version-history.ts +163 -0
- package/src/icons/NavaidMilitaryIcon/NavaidMilitaryIcon.tsx +43 -0
- package/src/index.ts +2 -0
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import type { JSX } from 'react';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useColorSwitcher } from '@redocly/theme/core/hooks';
|
|
6
6
|
import { ColorModeIcon } from '@redocly/theme/components/ColorModeSwitcher/ColorModeIcon';
|
|
7
7
|
import { Button } from '@redocly/theme/components/Button/Button';
|
|
8
8
|
|
|
@@ -11,12 +11,7 @@ export type ColorModeSwitcherProps = {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export function ColorModeSwitcher({ className }: ColorModeSwitcherProps): JSX.Element | null {
|
|
14
|
-
const { isSwitcherHidden,
|
|
15
|
-
useColorSwitcher();
|
|
16
|
-
|
|
17
|
-
useMount(() => {
|
|
18
|
-
initActiveColorMode();
|
|
19
|
-
});
|
|
14
|
+
const { isSwitcherHidden, switchColorMode, activeColorMode } = useColorSwitcher();
|
|
20
15
|
|
|
21
16
|
if (isSwitcherHidden) {
|
|
22
17
|
return null;
|
|
@@ -40,6 +40,7 @@ export const menu = css`
|
|
|
40
40
|
--menu-item-padding-vertical: var(--spacing-unit); // @presenter Spacing
|
|
41
41
|
--menu-item-padding-horizontal: var(--spacing-xxs); // @presenter Spacing
|
|
42
42
|
--menu-item-nested-offset: var(--spacing-sm); // @presenter Spacing
|
|
43
|
+
--menu-header-container-gap: var(--spacing-sm); // @presenter Spacing
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* @tokens Menu item label
|
|
@@ -15,6 +15,7 @@ export const useThemeHooks = vi.fn(() => ({
|
|
|
15
15
|
useTelemetry: vi.fn(() => ({
|
|
16
16
|
send: vi.fn(),
|
|
17
17
|
sendCodeSnippetReportedMessage: vi.fn(),
|
|
18
|
+
sendPageActionsButtonClickedMessage: vi.fn(),
|
|
18
19
|
})),
|
|
19
20
|
useBreadcrumbs: vi.fn().mockReturnValue({ breadcrumbs: [], siblings: undefined }),
|
|
20
21
|
useBanner: vi.fn(() => ({
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import type { CatalogEntityConfig, EntitiesCatalogConfig } from '@redocly/config';
|
|
2
2
|
import type { BffCatalogEntity } from '@redocly/theme/core/types';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { withPathPrefix } from '../../utils/urls';
|
|
5
5
|
|
|
6
6
|
type Props = {
|
|
7
7
|
catalogConfig: CatalogEntityConfig;
|
|
8
8
|
entitiesCatalogConfig?: EntitiesCatalogConfig;
|
|
9
|
+
revision?: string | null;
|
|
10
|
+
version?: string | null;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
type BaseEntity = Pick<BffCatalogEntity, 'key' | 'type'>;
|
|
12
14
|
|
|
13
|
-
export function useCatalogEntityDetails({
|
|
15
|
+
export function useCatalogEntityDetails({
|
|
16
|
+
catalogConfig,
|
|
17
|
+
entitiesCatalogConfig,
|
|
18
|
+
revision,
|
|
19
|
+
version,
|
|
20
|
+
}: Props) {
|
|
14
21
|
const getCatalogSpecificConfigByEntityTypeIncluded = (entity: BaseEntity) => {
|
|
15
22
|
if (!entitiesCatalogConfig) {
|
|
16
23
|
return;
|
|
@@ -22,14 +29,23 @@ export function useCatalogEntityDetails({ catalogConfig, entitiesCatalogConfig }
|
|
|
22
29
|
};
|
|
23
30
|
|
|
24
31
|
const getEntityDetailsLink = (entity: BaseEntity) => {
|
|
25
|
-
const pathPrefix = getPathPrefix();
|
|
26
32
|
const catalogSpecificConfig = getCatalogSpecificConfigByEntityTypeIncluded(entity);
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
const basePath =
|
|
35
|
+
!catalogSpecificConfig || !entitiesCatalogConfig
|
|
36
|
+
? withPathPrefix(`/catalogs/${catalogConfig.slug}/entities/${entity.key}`)
|
|
37
|
+
: withPathPrefix(`/catalogs/${catalogSpecificConfig.slug}/entities/${entity.key}`);
|
|
38
|
+
|
|
39
|
+
const params = new URLSearchParams();
|
|
40
|
+
if (revision) {
|
|
41
|
+
params.set('revision', revision);
|
|
42
|
+
}
|
|
43
|
+
if (version !== undefined) {
|
|
44
|
+
params.set('version', version ?? '');
|
|
30
45
|
}
|
|
46
|
+
params.set('search', '');
|
|
31
47
|
|
|
32
|
-
return `${
|
|
48
|
+
return `${basePath}?${params.toString()}`;
|
|
33
49
|
};
|
|
34
50
|
|
|
35
51
|
return { getEntityDetailsLink };
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -1,83 +1,56 @@
|
|
|
1
|
-
import { useCallback
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
2
|
|
|
3
|
+
import { createStore, useStore } from '../use-store';
|
|
3
4
|
import { isBrowser } from '../../utils/js-utils';
|
|
4
5
|
|
|
5
6
|
const RECENT_SEARCHES_KEY = 'recentSearches';
|
|
6
7
|
const RECENT_SEARCHES_LIMIT = 5;
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const getSnapshot = (): string[] => {
|
|
13
|
-
if (!isBrowser()) return [];
|
|
14
|
-
|
|
15
|
-
if (cachedSnapshot) return cachedSnapshot;
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const stored = localStorage.getItem(RECENT_SEARCHES_KEY);
|
|
19
|
-
cachedSnapshot = stored ? JSON.parse(stored) : [];
|
|
20
|
-
return cachedSnapshot;
|
|
21
|
-
} catch (e) {
|
|
22
|
-
cachedSnapshot = [];
|
|
23
|
-
return cachedSnapshot;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const updateItems = (value: string, isAdd: boolean) => {
|
|
28
|
-
if (!isBrowser()) return;
|
|
29
|
-
|
|
30
|
-
const currentItems = getSnapshot();
|
|
31
|
-
const valueIndex = currentItems.indexOf(value);
|
|
32
|
-
|
|
33
|
-
if (valueIndex !== -1) {
|
|
34
|
-
currentItems.splice(valueIndex, 1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (isAdd) {
|
|
38
|
-
currentItems.unshift(value);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const limitedItems = currentItems.slice(0, RECENT_SEARCHES_LIMIT);
|
|
42
|
-
|
|
43
|
-
localStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(limitedItems));
|
|
44
|
-
cachedSnapshot = limitedItems;
|
|
45
|
-
|
|
46
|
-
subscribers.forEach((callback) => callback());
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const subscribe = (callback: () => void) => {
|
|
50
|
-
subscribers.add(callback);
|
|
51
|
-
return () => subscribers.delete(callback);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
getSnapshot,
|
|
56
|
-
subscribe,
|
|
57
|
-
updateItems,
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const recentSearchesStore = createRecentSearchesStore();
|
|
9
|
+
const recentSearchesStore = createStore<string[]>({
|
|
10
|
+
storageKey: RECENT_SEARCHES_KEY,
|
|
11
|
+
});
|
|
62
12
|
|
|
63
13
|
export const useRecentSearches = (): {
|
|
64
14
|
items: string[];
|
|
65
15
|
addSearchHistoryItem: (value: string) => void;
|
|
66
16
|
removeSearchHistoryItem: (value: string) => void;
|
|
67
17
|
} => {
|
|
68
|
-
const items =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
() =>
|
|
18
|
+
const [items, setItems] = useStore<string[]>(recentSearchesStore, []);
|
|
19
|
+
|
|
20
|
+
const updateItems = useCallback(
|
|
21
|
+
(value: string, isAdd: boolean) => {
|
|
22
|
+
if (!isBrowser()) return;
|
|
23
|
+
|
|
24
|
+
const currentItems = [...items];
|
|
25
|
+
const valueIndex = currentItems.indexOf(value);
|
|
26
|
+
if (valueIndex !== -1) {
|
|
27
|
+
currentItems.splice(valueIndex, 1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (isAdd) {
|
|
31
|
+
currentItems.unshift(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const limitedItems = currentItems.slice(0, RECENT_SEARCHES_LIMIT);
|
|
35
|
+
|
|
36
|
+
setItems(limitedItems);
|
|
37
|
+
},
|
|
38
|
+
[items, setItems],
|
|
72
39
|
);
|
|
73
40
|
|
|
74
|
-
const addSearchHistoryItem = useCallback(
|
|
75
|
-
|
|
76
|
-
|
|
41
|
+
const addSearchHistoryItem = useCallback(
|
|
42
|
+
(value: string) => {
|
|
43
|
+
updateItems(value, true);
|
|
44
|
+
},
|
|
45
|
+
[updateItems],
|
|
46
|
+
);
|
|
77
47
|
|
|
78
|
-
const removeSearchHistoryItem = useCallback(
|
|
79
|
-
|
|
80
|
-
|
|
48
|
+
const removeSearchHistoryItem = useCallback(
|
|
49
|
+
(value: string) => {
|
|
50
|
+
updateItems(value, false);
|
|
51
|
+
},
|
|
52
|
+
[updateItems],
|
|
53
|
+
);
|
|
81
54
|
|
|
82
55
|
return { items, addSearchHistoryItem, removeSearchHistoryItem };
|
|
83
56
|
};
|
|
@@ -1,23 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useMount } from '@redocly/theme/core/hooks';
|
|
2
4
|
|
|
3
5
|
import { useThemeConfig } from './use-theme-config';
|
|
4
6
|
import { useThemeHooks } from './use-theme-hooks';
|
|
7
|
+
import { createStore, useStore } from './use-store';
|
|
8
|
+
import { DEFAULT_COLOR_MODES } from '../constants';
|
|
9
|
+
|
|
10
|
+
const COLOR_MODE_KEY = 'colorSchema';
|
|
11
|
+
const colorModeStore = createStore<string>({
|
|
12
|
+
storageKey: COLOR_MODE_KEY,
|
|
13
|
+
});
|
|
5
14
|
|
|
6
15
|
export const useColorSwitcher = () => {
|
|
7
16
|
const themeSettings = useThemeConfig();
|
|
8
17
|
const { useTelemetry } = useThemeHooks();
|
|
9
18
|
const telemetry = useTelemetry();
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
19
|
+
const themeColorMode = themeSettings.colorMode;
|
|
20
|
+
|
|
21
|
+
const modes = useMemo(
|
|
22
|
+
() => themeColorMode?.modes || [DEFAULT_COLOR_MODES.LIGHT, DEFAULT_COLOR_MODES.DARK],
|
|
23
|
+
[themeColorMode],
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const defaultMode = modes[0] || DEFAULT_COLOR_MODES.LIGHT;
|
|
14
27
|
|
|
15
|
-
const
|
|
28
|
+
const [activeColorMode, setActiveColorMode] = useStore<string>(colorModeStore, defaultMode);
|
|
29
|
+
|
|
30
|
+
useMount(() => {
|
|
16
31
|
const activeMode = Array.from(document.documentElement.classList).find((c) =>
|
|
17
32
|
modes.includes(c),
|
|
18
33
|
);
|
|
19
|
-
setActiveColorMode(activeMode ||
|
|
20
|
-
};
|
|
34
|
+
setActiveColorMode(activeMode || defaultMode);
|
|
35
|
+
});
|
|
21
36
|
|
|
22
37
|
const switchColorMode = (mode?: string): void => {
|
|
23
38
|
if (mode && !modes.includes(mode)) {
|
|
@@ -25,23 +40,22 @@ export const useColorSwitcher = () => {
|
|
|
25
40
|
}
|
|
26
41
|
|
|
27
42
|
const activeIndex = modes.indexOf(activeColorMode);
|
|
28
|
-
// If specific mode is provided, use it, otherwise cycle through modes
|
|
29
43
|
const newMode = mode || (activeIndex < modes.length - 1 ? modes[activeIndex + 1] : modes[0]);
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
const root = document.documentElement;
|
|
46
|
+
|
|
47
|
+
modes.forEach((mode) => root.classList.remove(mode));
|
|
48
|
+
root.classList.add(newMode, 'notransition');
|
|
33
49
|
|
|
34
50
|
window.requestAnimationFrame(() => {
|
|
35
|
-
|
|
51
|
+
root.classList.remove('notransition');
|
|
36
52
|
});
|
|
37
53
|
telemetry.sendColorModeSwitchedMessage({ from: activeColorMode, to: newMode });
|
|
38
|
-
|
|
39
54
|
setActiveColorMode(newMode);
|
|
40
55
|
};
|
|
41
56
|
|
|
42
57
|
return {
|
|
43
|
-
isSwitcherHidden:
|
|
44
|
-
initActiveColorMode,
|
|
58
|
+
isSwitcherHidden: themeColorMode?.hide,
|
|
45
59
|
switchColorMode,
|
|
46
60
|
activeColorMode,
|
|
47
61
|
};
|
|
@@ -17,6 +17,14 @@ import { ClipboardService } from '../utils/clipboard-service';
|
|
|
17
17
|
import { IS_BROWSER } from '../utils/dom';
|
|
18
18
|
import { generateMCPDeepLink } from '../utils/mcp';
|
|
19
19
|
|
|
20
|
+
function createPageActionResource(pageSlug: string, pageUrl: string) {
|
|
21
|
+
return {
|
|
22
|
+
id: pageSlug,
|
|
23
|
+
object: 'page' as const,
|
|
24
|
+
uri: pageUrl,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
const DEFAULT_ENABLED_ACTIONS = [
|
|
21
29
|
'copy',
|
|
22
30
|
'view',
|
|
@@ -41,10 +49,12 @@ export function usePageActions(
|
|
|
41
49
|
mcpUrl?: string,
|
|
42
50
|
actions?: PageActionType[],
|
|
43
51
|
): PageAction[] {
|
|
44
|
-
const { useTranslate, usePageData, usePageProps, usePageSharedData } =
|
|
52
|
+
const { useTranslate, usePageData, usePageProps, usePageSharedData, useTelemetry } =
|
|
53
|
+
useThemeHooks();
|
|
45
54
|
const { translate } = useTranslate();
|
|
46
55
|
const themeConfig = useThemeConfig();
|
|
47
56
|
const pageProps = usePageProps();
|
|
57
|
+
const telemetry = useTelemetry();
|
|
48
58
|
const openApiSharedData = usePageSharedData<
|
|
49
59
|
{ options: { excludeFromSearch: boolean } } | undefined
|
|
50
60
|
>('openAPIDocsStore');
|
|
@@ -67,9 +77,25 @@ export function usePageActions(
|
|
|
67
77
|
? { serverName: mcpConfig.serverName, url: mcpUrl || '' }
|
|
68
78
|
: { serverName: mcpConfig.serverName, url: mcpConfig.serverUrl || '' };
|
|
69
79
|
|
|
70
|
-
|
|
80
|
+
const isDocsMcp = !requiresMcpUrl;
|
|
81
|
+
|
|
82
|
+
const origin = IS_BROWSER ? window.location.origin : '';
|
|
83
|
+
const pageUrl = `${origin}${pageSlug}`;
|
|
84
|
+
|
|
85
|
+
return createMCPAction({
|
|
86
|
+
clientType,
|
|
87
|
+
mcpConfig: config,
|
|
88
|
+
translate,
|
|
89
|
+
onClickCallback: isDocsMcp
|
|
90
|
+
? () =>
|
|
91
|
+
telemetry.sendPageActionsButtonClickedMessage({
|
|
92
|
+
...createPageActionResource(pageSlug, pageUrl),
|
|
93
|
+
action_type: `docs-mcp-${clientType}` as const,
|
|
94
|
+
})
|
|
95
|
+
: undefined,
|
|
96
|
+
});
|
|
71
97
|
},
|
|
72
|
-
[mcpUrl, mcpConfig, translate],
|
|
98
|
+
[mcpUrl, mcpConfig, translate, telemetry, pageSlug],
|
|
73
99
|
);
|
|
74
100
|
|
|
75
101
|
const result: PageAction[] = useMemo(() => {
|
|
@@ -81,13 +107,14 @@ export function usePageActions(
|
|
|
81
107
|
? window.location.origin
|
|
82
108
|
: ((globalThis as { SSR_HOSTNAME?: string })['SSR_HOSTNAME'] ?? '');
|
|
83
109
|
const normalizedSlug = pageSlug.startsWith('/') ? pageSlug : '/' + pageSlug;
|
|
110
|
+
const pageUrl = `${origin}${normalizedSlug}`;
|
|
84
111
|
const mdPageUrl = new URL(
|
|
85
112
|
origin + normalizedSlug + (normalizedSlug === '/' ? 'index.html.md' : '.md'),
|
|
86
113
|
).toString();
|
|
87
114
|
|
|
88
115
|
const actionHandlers: Record<PageActionType, () => PageAction | null> = {
|
|
89
|
-
'docs-mcp-cursor': createMCPHandler('cursor'),
|
|
90
|
-
'docs-mcp-vscode': createMCPHandler('vscode'),
|
|
116
|
+
'docs-mcp-cursor': createMCPHandler('cursor', false),
|
|
117
|
+
'docs-mcp-vscode': createMCPHandler('vscode', false),
|
|
91
118
|
'mcp-cursor': createMCPHandler('cursor', true),
|
|
92
119
|
'mcp-vscode': createMCPHandler('vscode', true),
|
|
93
120
|
|
|
@@ -104,6 +131,10 @@ export function usePageActions(
|
|
|
104
131
|
}
|
|
105
132
|
const text = await result.text();
|
|
106
133
|
ClipboardService.copyCustom(text);
|
|
134
|
+
telemetry.sendPageActionsButtonClickedMessage({
|
|
135
|
+
...createPageActionResource(pageSlug, pageUrl),
|
|
136
|
+
action_type: 'copy',
|
|
137
|
+
});
|
|
107
138
|
} catch (error) {
|
|
108
139
|
console.error(error);
|
|
109
140
|
}
|
|
@@ -116,6 +147,12 @@ export function usePageActions(
|
|
|
116
147
|
description: translate('page.actions.viewAsMdDescription', 'Open this page as Markdown'),
|
|
117
148
|
iconComponent: MarkdownFullIcon,
|
|
118
149
|
link: mdPageUrl,
|
|
150
|
+
onClick: () => {
|
|
151
|
+
telemetry.sendPageActionsButtonClickedMessage({
|
|
152
|
+
...createPageActionResource(pageSlug, pageUrl),
|
|
153
|
+
action_type: 'view',
|
|
154
|
+
});
|
|
155
|
+
},
|
|
119
156
|
}),
|
|
120
157
|
|
|
121
158
|
chatgpt: () => {
|
|
@@ -128,6 +165,12 @@ export function usePageActions(
|
|
|
128
165
|
description: translate('page.actions.chatGptDescription', 'Get insights from ChatGPT'),
|
|
129
166
|
iconComponent: ChatGptIcon,
|
|
130
167
|
link: getExternalAiPromptLink('https://chat.openai.com', mdPageUrl),
|
|
168
|
+
onClick: () => {
|
|
169
|
+
telemetry.sendPageActionsButtonClickedMessage({
|
|
170
|
+
...createPageActionResource(pageSlug, pageUrl),
|
|
171
|
+
action_type: 'chatgpt',
|
|
172
|
+
});
|
|
173
|
+
},
|
|
131
174
|
};
|
|
132
175
|
},
|
|
133
176
|
|
|
@@ -141,6 +184,12 @@ export function usePageActions(
|
|
|
141
184
|
description: translate('page.actions.claudeDescription', 'Get insights from Claude'),
|
|
142
185
|
iconComponent: ClaudeIcon,
|
|
143
186
|
link: getExternalAiPromptLink('https://claude.ai/new', mdPageUrl),
|
|
187
|
+
onClick: () => {
|
|
188
|
+
telemetry.sendPageActionsButtonClickedMessage({
|
|
189
|
+
...createPageActionResource(pageSlug, pageUrl),
|
|
190
|
+
action_type: 'claude',
|
|
191
|
+
});
|
|
192
|
+
},
|
|
144
193
|
};
|
|
145
194
|
},
|
|
146
195
|
};
|
|
@@ -156,6 +205,7 @@ export function usePageActions(
|
|
|
156
205
|
translate,
|
|
157
206
|
isPublic,
|
|
158
207
|
createMCPHandler,
|
|
208
|
+
telemetry,
|
|
159
209
|
]);
|
|
160
210
|
|
|
161
211
|
return result;
|
|
@@ -172,12 +222,19 @@ type CreateMCPActionParams = {
|
|
|
172
222
|
clientType: MCPClientType;
|
|
173
223
|
mcpConfig: McpConnectionParams;
|
|
174
224
|
translate: (key: string, defaultValue: string) => string;
|
|
225
|
+
onClickCallback?: () => void;
|
|
175
226
|
};
|
|
176
227
|
|
|
177
|
-
function createMCPAction({
|
|
228
|
+
function createMCPAction({
|
|
229
|
+
clientType,
|
|
230
|
+
mcpConfig,
|
|
231
|
+
translate,
|
|
232
|
+
onClickCallback,
|
|
233
|
+
}: CreateMCPActionParams): PageAction {
|
|
178
234
|
const url = generateMCPDeepLink(clientType, mcpConfig);
|
|
179
235
|
const sharedProps = {
|
|
180
236
|
onClick: () => {
|
|
237
|
+
onClickCallback?.();
|
|
181
238
|
window.open(url, '_blank');
|
|
182
239
|
},
|
|
183
240
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useNavigate } from 'react-router-dom';
|
|
2
2
|
|
|
3
|
+
import { getProductClassName } from '@redocly/theme/components/Product/utils';
|
|
4
|
+
|
|
3
5
|
import { useThemeHooks } from './use-theme-hooks';
|
|
4
6
|
import { withPathPrefix } from '../utils';
|
|
5
7
|
|
|
@@ -13,6 +15,16 @@ export function useProductPicker() {
|
|
|
13
15
|
function setProduct(product: typeof currentProduct) {
|
|
14
16
|
if (!product) return;
|
|
15
17
|
telemetry.sendProductPickedMessage({ product: product.slug });
|
|
18
|
+
if (typeof document === 'undefined') return;
|
|
19
|
+
|
|
20
|
+
if (product.name) {
|
|
21
|
+
const root = document.documentElement;
|
|
22
|
+
Array.from(root.classList)
|
|
23
|
+
.filter((c) => c.startsWith('product-'))
|
|
24
|
+
.forEach((c) => root.classList.remove(c));
|
|
25
|
+
root.classList.add(getProductClassName(product.name));
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
loadAndNavigate({ navigate, to: withPathPrefix(product.link) });
|
|
17
29
|
}
|
|
18
30
|
return {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
|
|
3
|
+
import { isBrowser } from '../utils/js-utils';
|
|
4
|
+
|
|
5
|
+
type Store<T> = {
|
|
6
|
+
getValue: (defaultValue: T) => T;
|
|
7
|
+
setValue: (next: T) => void;
|
|
8
|
+
subscribe: (callback: () => void) => Unsubscribe;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type Unsubscribe = () => void;
|
|
12
|
+
|
|
13
|
+
type CreateStoreProps<T> = {
|
|
14
|
+
storageKey: string;
|
|
15
|
+
storageType?: 'localStorage' | 'sessionStorage';
|
|
16
|
+
serializer?: {
|
|
17
|
+
parse: (value: string) => T;
|
|
18
|
+
serialize: (value: T) => string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function createStore<T>({
|
|
23
|
+
storageKey,
|
|
24
|
+
storageType = 'localStorage',
|
|
25
|
+
serializer = {
|
|
26
|
+
parse: (value: string) => JSON.parse(value) as T,
|
|
27
|
+
serialize: (value: T) => JSON.stringify(value),
|
|
28
|
+
},
|
|
29
|
+
}: CreateStoreProps<T>): Store<T> {
|
|
30
|
+
const subscribers = new Set<() => void>();
|
|
31
|
+
let cachedValue: T;
|
|
32
|
+
|
|
33
|
+
const shouldSerialize = (value: unknown) => typeof value !== 'string';
|
|
34
|
+
|
|
35
|
+
const getValue = (defaultValue: T): T => {
|
|
36
|
+
if (!isBrowser()) return defaultValue;
|
|
37
|
+
if (cachedValue !== undefined) return cachedValue;
|
|
38
|
+
|
|
39
|
+
const value = window[storageType].getItem(storageKey);
|
|
40
|
+
if (!value) {
|
|
41
|
+
cachedValue = defaultValue;
|
|
42
|
+
return cachedValue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!shouldSerialize(defaultValue)) {
|
|
46
|
+
cachedValue = value as T;
|
|
47
|
+
return cachedValue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
cachedValue = JSON.parse(value) as T;
|
|
52
|
+
} catch {
|
|
53
|
+
cachedValue = defaultValue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return cachedValue;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const setValue = (next: T): void => {
|
|
60
|
+
if (!isBrowser()) return;
|
|
61
|
+
try {
|
|
62
|
+
window[storageType].setItem(
|
|
63
|
+
storageKey,
|
|
64
|
+
shouldSerialize(next) ? serializer.serialize(next) : (next as string),
|
|
65
|
+
);
|
|
66
|
+
cachedValue = next;
|
|
67
|
+
subscribers.forEach((callback) => callback());
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const subscribe = (callback: () => void): Unsubscribe => {
|
|
74
|
+
subscribers.add(callback);
|
|
75
|
+
return () => {
|
|
76
|
+
subscribers.delete(callback);
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
getValue,
|
|
82
|
+
setValue,
|
|
83
|
+
subscribe,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function useStore<T>(store: Store<T>, defaultValue: T) {
|
|
88
|
+
const value = useSyncExternalStore(
|
|
89
|
+
store.subscribe,
|
|
90
|
+
() => store.getValue(defaultValue),
|
|
91
|
+
() => defaultValue,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return [value, store.setValue] as const;
|
|
95
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
export const useTelemetryFallback = () => ({
|
|
4
4
|
send: () => {},
|
|
5
5
|
sendPageViewedMessage: () => {},
|
|
6
|
+
sendPageActionsButtonClickedMessage: () => {},
|
|
6
7
|
sendErrorMessage: () => {},
|
|
7
8
|
sendClientErrorMessage: () => {},
|
|
8
9
|
sendBreadcrumbClickedMessage: () => {},
|
|
@@ -63,6 +64,7 @@ export const useTelemetryFallback = () => ({
|
|
|
63
64
|
sendAsyncapiDocsMessageClickedMessage: () => {},
|
|
64
65
|
sendAsyncapiDocsServerModalOpenedMessage: () => {},
|
|
65
66
|
sendAsyncapiDocsDownloadDefinitionClickedMessage: () => {},
|
|
67
|
+
sendAsyncapiDocsReferencedInClickedMessage: () => {},
|
|
66
68
|
sendGraphqlDocsViewedMessage: () => {},
|
|
67
69
|
sendGraphqlDocsPerformanceMetricsMessage: () => {},
|
|
68
70
|
sendGraphqlDocsReferencedInLinkClickedMessage: () => {},
|