@redocly/theme 0.61.0-next.2 → 0.61.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.
Files changed (71) hide show
  1. package/lib/components/Catalog/CatalogEntity/CatalogEntity.js +5 -0
  2. package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.js +9 -0
  3. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistoryButton.d.ts +6 -0
  4. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistoryButton.js +144 -0
  5. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.d.ts +8 -0
  6. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +161 -0
  7. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityRevisionItem.d.ts +8 -0
  8. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityRevisionItem.js +67 -0
  9. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.d.ts +9 -0
  10. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.js +212 -0
  11. package/lib/components/Catalog/CatalogEntity/CatalogEntityMetadata.js +2 -25
  12. package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.js +12 -1
  13. package/lib/components/Catalog/CatalogEntity/ShowMoreButton.d.ts +8 -0
  14. package/lib/components/Catalog/CatalogEntity/ShowMoreButton.js +35 -0
  15. package/lib/components/Catalog/CatalogTableView/CatalogTableViewRow.d.ts +2 -0
  16. package/lib/components/Catalog/CatalogTableView/CatalogTableViewRow.js +4 -1
  17. package/lib/components/Catalog/variables.js +112 -0
  18. package/lib/components/Menu/variables.js +1 -0
  19. package/lib/components/Tooltip/Tooltip.js +2 -0
  20. package/lib/core/constants/catalog.d.ts +1 -0
  21. package/lib/core/constants/catalog.js +2 -1
  22. package/lib/core/hooks/catalog/use-catalog-entity-details.d.ts +3 -1
  23. package/lib/core/hooks/catalog/use-catalog-entity-details.js +12 -5
  24. package/lib/core/hooks/use-color-switcher.js +7 -8
  25. package/lib/core/hooks/use-page-actions.js +37 -6
  26. package/lib/core/hooks/use-telemetry-fallback.d.ts +2 -0
  27. package/lib/core/hooks/use-telemetry-fallback.js +2 -0
  28. package/lib/core/types/catalog.d.ts +33 -4
  29. package/lib/core/types/hooks.d.ts +14 -3
  30. package/lib/core/types/l10n.d.ts +1 -1
  31. package/lib/core/utils/build-revision-url.d.ts +1 -0
  32. package/lib/core/utils/build-revision-url.js +15 -0
  33. package/lib/core/utils/date.d.ts +14 -0
  34. package/lib/core/utils/date.js +39 -0
  35. package/lib/core/utils/index.d.ts +2 -0
  36. package/lib/core/utils/index.js +2 -0
  37. package/lib/core/utils/transform-revisions-to-version-history.d.ts +8 -0
  38. package/lib/core/utils/transform-revisions-to-version-history.js +110 -0
  39. package/lib/icons/NavaidMilitaryIcon/NavaidMilitaryIcon.d.ts +9 -0
  40. package/lib/icons/NavaidMilitaryIcon/NavaidMilitaryIcon.js +26 -0
  41. package/lib/index.d.ts +2 -0
  42. package/lib/index.js +2 -0
  43. package/package.json +6 -6
  44. package/src/components/Catalog/CatalogEntity/CatalogEntity.tsx +7 -1
  45. package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.tsx +12 -0
  46. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistoryButton.tsx +147 -0
  47. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +180 -0
  48. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityRevisionItem.tsx +93 -0
  49. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.tsx +284 -0
  50. package/src/components/Catalog/CatalogEntity/CatalogEntityMetadata.tsx +3 -25
  51. package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.tsx +15 -2
  52. package/src/components/Catalog/CatalogEntity/ShowMoreButton.tsx +47 -0
  53. package/src/components/Catalog/CatalogTableView/CatalogTableViewRow.tsx +6 -1
  54. package/src/components/Catalog/variables.ts +112 -0
  55. package/src/components/Menu/variables.ts +1 -0
  56. package/src/components/Tooltip/Tooltip.tsx +2 -0
  57. package/src/core/constants/catalog.ts +2 -0
  58. package/src/core/hooks/__mocks__/use-theme-hooks.ts +1 -0
  59. package/src/core/hooks/catalog/use-catalog-entity-details.ts +22 -6
  60. package/src/core/hooks/use-color-switcher.ts +13 -10
  61. package/src/core/hooks/use-page-actions.ts +63 -6
  62. package/src/core/hooks/use-telemetry-fallback.ts +2 -0
  63. package/src/core/types/catalog.ts +38 -10
  64. package/src/core/types/hooks.ts +23 -4
  65. package/src/core/types/l10n.ts +10 -0
  66. package/src/core/utils/build-revision-url.ts +16 -0
  67. package/src/core/utils/date.ts +33 -0
  68. package/src/core/utils/index.ts +2 -0
  69. package/src/core/utils/transform-revisions-to-version-history.ts +163 -0
  70. package/src/icons/NavaidMilitaryIcon/NavaidMilitaryIcon.tsx +43 -0
  71. package/src/index.ts +2 -0
@@ -19,6 +19,8 @@ export type BaseEntity = {
19
19
  type: string;
20
20
  title: string;
21
21
  summary?: string | null;
22
+ revision?: string | null;
23
+ version?: string | null;
22
24
  };
23
25
 
24
26
  export type CatalogTableViewRowProps<T extends BaseEntity> = {
@@ -95,14 +97,17 @@ export const CatalogTableViewRow = <T extends BaseEntity>({
95
97
  const { getEntityDetailsLink } = useCatalogEntityDetails({
96
98
  catalogConfig,
97
99
  entitiesCatalogConfig,
100
+ revision: entity.revision,
101
+ version: entity.version,
98
102
  });
103
+ const link = getEntityDetailsLink({ key: entity.key, type: entity.type });
99
104
 
100
105
  return (
101
106
  <TableRow
102
107
  key={entity.id}
103
108
  $columnsWidths={columns.map((column) => column.width || '1fr')}
104
109
  $columnsMinWidths={columns.map((column) => column.minWidth || 'auto')}
105
- to={getEntityDetailsLink(entity) + '?search='}
110
+ to={link}
106
111
  onClick={() => onRowClick(entity)}
107
112
  style={{ color: 'var(--catalog-page-wrapper-text-color)' }}
108
113
  data-component-name="Catalog/CatalogTableView/CatalogTableViewRow"
@@ -235,6 +235,13 @@ export const catalog = css`
235
235
  --catalog-metadata-column-half-gap: var(--spacing-sm);
236
236
  --catalog-metadata-heading-size: var(--font-size-md);
237
237
 
238
+ /**
239
+ * @tokens Catalog show more button
240
+ */
241
+ --catalog-show-more-button-gap: 0.5rem;
242
+ --catalog-show-more-button-color: var(--text-color-primary);
243
+ --catalog-show-more-button-color-hover: var(--text-color-secondary);
244
+
238
245
  /**
239
246
  * @tokens Catalog arrow circle
240
247
  */
@@ -427,5 +434,110 @@ export const catalog = css`
427
434
  --catalog-entity-relations-edge-label-font-size: 12px;
428
435
  --catalog-entity-relations-edge-label-line-height: 1.2;
429
436
  // @tokens End
437
+
438
+ /**
439
+ * @tokens Catalog history button
440
+ */
441
+ --catalog-history-separator-border-color: var(--border-color-secondary);
442
+ --catalog-history-separator-margin-bottom: var(--spacing-sm);
443
+ --catalog-history-item-padding: var(--spacing-xxs);
444
+ --catalog-history-item-margin-bottom: calc(var(--spacing-xxs) * -1);
445
+ --catalog-history-item-gap: var(--spacing-xxs);
446
+ --catalog-history-item-border-radius: var(--menu-item-label-border-radius);
447
+ --catalog-history-item-bg-color-hover: var(--menu-item-bg-color-hover);
448
+ --catalog-history-icon-size: 16px;
449
+ --catalog-history-text-font-family: var(--font-family-base);
450
+ --catalog-history-text-font-size: var(--font-size-base);
451
+ --catalog-history-text-font-weight: var(--font-weight-regular);
452
+ --catalog-history-text-line-height: var(--line-height-base);
453
+ --catalog-history-text-color: var(--text-color-primary);
454
+ --catalog-history-pill-gap: var(--spacing-xxs);
455
+ --catalog-history-pill-padding-vertical: 1px;
456
+ --catalog-history-pill-padding-horizontal: var(--spacing-xs);
457
+ --catalog-history-pill-border-color: var(--border-color-primary);
458
+ --catalog-history-pill-border-radius: 12px;
459
+ --catalog-history-pill-icon-size: 14px;
460
+ --catalog-history-pill-icon-color: var(--color-green-8);
461
+ --catalog-history-pill-font-family: var(--font-family-base);
462
+ --catalog-history-pill-font-size: var(--font-size-base);
463
+ --catalog-history-pill-line-height: var(--line-height-base);
464
+ --catalog-history-pill-text-color: var(--text-color-primary);
465
+ // @tokens End
466
+
467
+ /**
468
+ * @tokens Catalog history sidebar
469
+ */
470
+ --catalog-history-sidebar-width: 320px;
471
+ --catalog-history-sidebar-bg-color: var(--sidebar-bg-color);
472
+ --catalog-history-sidebar-border-color: var(--border-color-secondary);
473
+
474
+ --catalog-history-sidebar-header-padding: var(--spacing-base) var(--spacing-sm) var(--spacing-base);
475
+ --catalog-history-sidebar-header-font-family: var(--font-family-base);
476
+ --catalog-history-sidebar-header-font-size: var(--font-size-base);
477
+ --catalog-history-sidebar-header-font-weight: var(--font-weight-semibold);
478
+ --catalog-history-sidebar-header-line-height: var(--line-height-base);
479
+ --catalog-history-sidebar-header-letter-spacing: 0;
480
+ --catalog-history-sidebar-header-color: var(--text-color-primary);
481
+
482
+ --catalog-history-sidebar-close-button-size: 32px;
483
+ --catalog-history-sidebar-close-button-border-radius: var(--border-radius-md);
484
+ --catalog-history-sidebar-close-button-bg-color-hover: var(--color-hover-base);
485
+ --catalog-history-sidebar-close-icon-color: var(--text-color-primary);
486
+
487
+ --catalog-history-sidebar-content-padding: 0 var(--spacing-sm);
488
+ --catalog-history-sidebar-content-margin-top: calc(var(--spacing-xxs) * -1);
489
+
490
+ --catalog-history-sidebar-version-header-padding: var(--spacing-xs) var(--spacing-xxs);
491
+ --catalog-history-sidebar-version-header-border-radius: var(--border-radius-md);
492
+ --catalog-history-sidebar-version-header-bg-color-hover: var(--color-hover-base);
493
+
494
+ --catalog-history-sidebar-version-icon-size: 32px;
495
+ --catalog-history-sidebar-version-icon-color: var(--text-color-secondary);
496
+ --catalog-history-sidebar-version-icon-color-current: var(--color-static-white);
497
+ --catalog-history-sidebar-version-icon-margin-right: var(--spacing-xs);
498
+ --catalog-history-sidebar-chevron-icon-color: var(--text-color-secondary);
499
+ --catalog-history-sidebar-version-info-gap: 2px;
500
+
501
+ --catalog-history-sidebar-version-title-font-family: var(--font-family-base);
502
+ --catalog-history-sidebar-version-title-font-size: var(--font-size-base);
503
+ --catalog-history-sidebar-version-title-font-weight: var(--font-weight-medium);
504
+ --catalog-history-sidebar-version-title-line-height: var(--line-height-base);
505
+ --catalog-history-sidebar-version-title-color: var(--color-warm-grey-7);
506
+
507
+ --catalog-history-sidebar-version-date-font-family: var(--font-family-base);
508
+ --catalog-history-sidebar-version-date-font-size: var(--font-size-sm);
509
+ --catalog-history-sidebar-version-date-font-weight: var(--font-weight-regular);
510
+ --catalog-history-sidebar-version-date-line-height: var(--line-height-sm);
511
+ --catalog-history-sidebar-version-date-color: var(--color-warm-grey-7);
512
+
513
+ --catalog-history-sidebar-revisions-padding-left: 40px;
514
+
515
+ --catalog-history-sidebar-revision-item-padding: var(--spacing-xs) var(--spacing-sm);
516
+ --catalog-history-sidebar-revision-item-border-radius: var(--border-radius-md);
517
+ --catalog-history-sidebar-revision-item-bg-color-hover: var(--bg-color-hover);
518
+
519
+ --catalog-history-sidebar-revision-name-font-family: var(--font-family-base);
520
+ --catalog-history-sidebar-revision-name-font-size: var(--font-size-base);
521
+ --catalog-history-sidebar-revision-name-font-weight: var(--font-weight-regular);
522
+ --catalog-history-sidebar-revision-name-line-height: var(--line-height-base);
523
+ --catalog-history-sidebar-revision-name-color: var(--text-color-primary);
524
+
525
+ --catalog-history-sidebar-revision-date-font-family: var(--font-family-base);
526
+ --catalog-history-sidebar-revision-date-font-size: var(--font-size-sm);
527
+ --catalog-history-sidebar-revision-date-font-weight: var(--font-weight-regular);
528
+ --catalog-history-sidebar-revision-date-line-height: var(--line-height-sm);
529
+ --catalog-history-sidebar-revision-date-color: var(--color-warm-grey-7);
530
+
531
+ --catalog-history-sidebar-revision-line-color: var(--border-color-secondary);
532
+ --catalog-history-sidebar-revision-item-bg-color-active: var(--bg-color-active);
533
+
534
+ --catalog-history-sidebar-revisions-limit-message-color: var(--color-warm-grey-6);
535
+
536
+ /**
537
+ * @tokens Catalog history tag border radius
538
+ */
539
+ --catalog-history-sidebar-version-tag-border-radius: var(--border-radius-xl);
540
+ --catalog-history-sidebar-revision-tag-border-radius: var(--border-radius-xl);
541
+ // @tokens End
430
542
  `;
431
543
  /* eslint-enable theme/no-raw-colors-in-styles */
@@ -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
@@ -110,6 +110,8 @@ export function TooltipComponent({
110
110
  onMouseEnter: handleOpen,
111
111
  onMouseLeave: handleClose,
112
112
  onClick: handleClose,
113
+ onFocus: handleOpen,
114
+ onBlur: handleClose,
113
115
  };
114
116
 
115
117
  return (
@@ -84,3 +84,5 @@ export enum GraphCustomNodeType {
84
84
  export enum GraphCustomEdgeType {
85
85
  CatalogEdge = 'catalogEdge',
86
86
  }
87
+
88
+ export const VERSION_NOT_SPECIFIED = 'not_specified_version';
@@ -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 { getPathPrefix } from '../../utils/urls';
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({ catalogConfig, entitiesCatalogConfig }: Props) {
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
- if (!catalogSpecificConfig || !entitiesCatalogConfig) {
29
- return `${pathPrefix}/catalogs/${catalogConfig.slug}/entities/${entity.key}`;
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 `${pathPrefix}/catalogs/${catalogSpecificConfig.slug}/entities/${entity.key}`;
48
+ return `${basePath}?${params.toString()}`;
33
49
  };
34
50
 
35
51
  return { getEntityDetailsLink };
@@ -1,10 +1,11 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
+ import { useMount } from '@redocly/theme/core/hooks';
4
+
3
5
  import { useThemeConfig } from './use-theme-config';
4
6
  import { useThemeHooks } from './use-theme-hooks';
5
7
  import { createStore, useStore } from './use-store';
6
8
  import { DEFAULT_COLOR_MODES } from '../constants';
7
- import { isBrowser } from '../utils/js-utils';
8
9
 
9
10
  const COLOR_MODE_KEY = 'colorSchema';
10
11
  const colorModeStore = createStore<string>({
@@ -21,15 +22,17 @@ export const useColorSwitcher = () => {
21
22
  () => themeColorMode?.modes || [DEFAULT_COLOR_MODES.LIGHT, DEFAULT_COLOR_MODES.DARK],
22
23
  [themeColorMode],
23
24
  );
24
- const documentMode = useMemo(() => {
25
- if (!isBrowser()) return;
26
- return Array.from(document.documentElement.classList).find((c) => modes.includes(c));
27
- }, [modes]);
28
-
29
- const [activeColorMode, setActiveColorMode] = useStore<string>(
30
- colorModeStore,
31
- documentMode ?? modes[0] ?? DEFAULT_COLOR_MODES.LIGHT,
32
- );
25
+
26
+ const defaultMode = modes[0] || DEFAULT_COLOR_MODES.LIGHT;
27
+
28
+ const [activeColorMode, setActiveColorMode] = useStore<string>(colorModeStore, defaultMode);
29
+
30
+ useMount(() => {
31
+ const activeMode = Array.from(document.documentElement.classList).find((c) =>
32
+ modes.includes(c),
33
+ );
34
+ setActiveColorMode(activeMode || defaultMode);
35
+ });
33
36
 
34
37
  const switchColorMode = (mode?: string): void => {
35
38
  if (mode && !modes.includes(mode)) {
@@ -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 } = useThemeHooks();
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
- return createMCPAction({ clientType, mcpConfig: config, translate });
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({ clientType, mcpConfig, translate }: CreateMCPActionParams): PageAction {
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
  };
@@ -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: () => {},
@@ -40,16 +40,6 @@ export type UseCatalogResponse = {
40
40
  collapsedSidebar: boolean;
41
41
  };
42
42
 
43
- export type UseCatalogFetchCatalogEntities = (
44
- params: CatalogApiParams,
45
- initialData?: BffCatalogEntityList,
46
- ) => CatalogApiResults<BffCatalogEntity>;
47
-
48
- export type UseCatalogFetchCatalogEntitiesRelations = (
49
- params: CatalogApiParams & { entityKey: string },
50
- initialData?: BffCatalogRelatedEntityList,
51
- ) => CatalogApiResults<BffCatalogRelatedEntity>;
52
-
53
43
  export type UseCatalogSortResponse = {
54
44
  sortOption: SortOption | null;
55
45
  setSortOption: (option: SortOption | null) => void;
@@ -128,6 +118,7 @@ export type CatalogApiParams = {
128
118
  filter?: string;
129
119
  sort?: string;
130
120
  search?: string;
121
+ entityVersion?: string | null;
131
122
  };
132
123
 
133
124
  export type UseCatalogEntitiesProps = {
@@ -170,6 +161,7 @@ export type BffCatalogRelatedEntity = {
170
161
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
171
162
  metadata?: Record<string, any>;
172
163
  version?: string | null;
164
+ revision?: string | null;
173
165
  readonly createdAt: string | null;
174
166
  readonly updatedAt: string | null;
175
167
  };
@@ -207,6 +199,7 @@ export type BffCatalogEntity = {
207
199
  readonly revision: string;
208
200
  readonly isCurrent?: boolean | null;
209
201
  readonly isDefaultVersion?: boolean | null;
202
+ readonly isDeleted?: boolean | null;
210
203
  readonly createdAt: string;
211
204
  readonly updatedAt: string;
212
205
  };
@@ -229,6 +222,41 @@ export type Page = {
229
222
  total: number;
230
223
  };
231
224
 
225
+ export type BffCatalogEntityRevision = {
226
+ readonly version: string | null;
227
+ readonly revision: string | null;
228
+ readonly isCurrent: boolean | null;
229
+ readonly isDefaultVersion: boolean | null;
230
+ readonly createdAt: string | null;
231
+ readonly updatedAt: string | null;
232
+ };
233
+
234
+ export type BffCatalogEntityRevisionList = BffCatalogList<BffCatalogEntityRevision>;
235
+
232
236
  export type EntityRelationType = (typeof ENTITY_RELATION_TYPES)[number];
233
237
 
234
238
  export type ListType = 'default' | 'team' | 'api-operation' | 'data-schema';
239
+
240
+ export type CatalogEntityRevision = {
241
+ name: string;
242
+ date: string;
243
+ revisionDate: string | null;
244
+ isActive?: boolean;
245
+ isCurrent?: boolean;
246
+ };
247
+
248
+ export type CatalogEntityVersionHistoryGroup = {
249
+ version: string;
250
+ date: string;
251
+ isCurrent?: boolean;
252
+ isExpanded?: boolean;
253
+ revisions?: CatalogEntityRevision[];
254
+ singleRevisionDate?: string | null;
255
+ hasCurrentRevisionFromBackend?: boolean;
256
+ isDefaultVersion?: boolean;
257
+ };
258
+
259
+ export type SidebarConnectedEntity = {
260
+ key: string;
261
+ version?: string;
262
+ };
@@ -9,6 +9,7 @@ import type {
9
9
  import type { ShikiTransformer } from '@shikijs/types';
10
10
  import type { Callback, TFunction as TFunc } from 'i18next';
11
11
  import type { To, Location, NavigateFunction } from 'react-router-dom';
12
+ import type { UseQueryResult } from '@tanstack/react-query';
12
13
  import type { CatalogConfig, ProductUiConfig } from '@redocly/theme/config';
13
14
  import type {
14
15
  UseCatalogResponse,
@@ -16,8 +17,8 @@ import type {
16
17
  UseCatalogSortResponse,
17
18
  UseCatalogSearchResponse,
18
19
  UseCatalogProps,
19
- UseCatalogFetchCatalogEntities,
20
- UseCatalogFetchCatalogEntitiesRelations,
20
+ CatalogApiParams,
21
+ CatalogApiResults,
21
22
  } from './catalog';
22
23
  import type { UserMenuData } from './user-menu';
23
24
  import type { ItemState } from './sidebar';
@@ -33,6 +34,14 @@ import type { TFunction } from './l10n';
33
34
  import type { BreadcrumbItem } from './breadcrumb';
34
35
  import type { DrilldownMenuItemDetails } from './sidebar';
35
36
 
37
+ import {
38
+ BffCatalogEntity,
39
+ BffCatalogEntityList,
40
+ BffCatalogRelatedEntity,
41
+ BffCatalogRelatedEntityList,
42
+ BffCatalogEntityRevision,
43
+ BffCatalogEntityRevisionList,
44
+ } from './catalog';
36
45
  import { AiSearchError } from '../constants/search';
37
46
 
38
47
  export type ThemeHooks = {
@@ -147,8 +156,18 @@ export type ThemeHooks = {
147
156
  useCatalog: (props?: UseCatalogProps) => UseCatalogResponse;
148
157
  useCatalogSort: () => UseCatalogSortResponse;
149
158
  useCatalogSearch: () => UseCatalogSearchResponse;
150
- useFetchCatalogEntities: UseCatalogFetchCatalogEntities;
151
- useFetchCatalogEntitiesRelations: UseCatalogFetchCatalogEntitiesRelations;
159
+ useFetchCatalogEntities: (
160
+ params: CatalogApiParams,
161
+ initialData?: BffCatalogEntityList,
162
+ ) => CatalogApiResults<BffCatalogEntity>;
163
+ useFetchCatalogEntitiesRelations: (
164
+ params: CatalogApiParams & { entityKey: string },
165
+ initialData?: BffCatalogRelatedEntityList,
166
+ ) => CatalogApiResults<BffCatalogRelatedEntity>;
167
+ useFetchCatalogEntityRevisions: (params: { entityKey: string; version?: string | null }) => {
168
+ query: UseQueryResult<BffCatalogEntityRevisionList, Error>;
169
+ items: BffCatalogEntityRevision[];
170
+ };
152
171
  useCatalogClassic: (config: CatalogConfig) => FilteredCatalog;
153
172
  useTelemetry: () => Omit<
154
173
  AsyncApiRealmUI.Telemetry,
@@ -170,6 +170,16 @@ export type TranslationKey =
170
170
  | 'catalog.links.label'
171
171
  | 'catalog.metadata.domains'
172
172
  | 'catalog.metadata.owners'
173
+ | 'catalog.history.button.label'
174
+ | 'catalog.history.sidebar.title'
175
+ | 'catalog.history.sidebar.close'
176
+ | 'catalog.history.version.label'
177
+ | 'catalog.history.version.notSpecified'
178
+ | 'catalog.history.version.default'
179
+ | 'catalog.history.revisions.limitMessage'
180
+ | 'catalog.history.revision.current'
181
+ | 'catalog.history.revisions.showLess'
182
+ | 'catalog.history.revisions.showMore'
173
183
  | 'sidebar.menu.backLabel'
174
184
  | 'sidebar.menu.backToLabel'
175
185
  | 'sidebar.actions.show'
@@ -0,0 +1,16 @@
1
+ export function buildRevisionUrl(
2
+ basePath: string,
3
+ revisionDate: string | null | undefined,
4
+ version?: string | null,
5
+ ): string | undefined {
6
+ if (!revisionDate) {
7
+ return undefined;
8
+ }
9
+
10
+ const params = new URLSearchParams();
11
+ params.set('revision', revisionDate);
12
+ if (version) {
13
+ params.set('version', version);
14
+ }
15
+ return `${basePath}?${params.toString()}`;
16
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Formats a date string to localized short date format (e.g., "Dec 12, 2025")
3
+ * @param dateString - ISO date string or null
4
+ * @param locale - Locale string (e.g., "en-US")
5
+ * @returns Formatted date string or empty string if dateString is null
6
+ */
7
+ export function toLocalizedShortDate(dateString: string | null, locale: string): string {
8
+ if (!dateString) return '';
9
+ const date = new Date(dateString);
10
+ return date.toLocaleDateString(locale, {
11
+ month: 'short',
12
+ day: 'numeric',
13
+ year: 'numeric',
14
+ });
15
+ }
16
+
17
+ /**
18
+ * Formats a date string to localized short date-time format (e.g., "Dec 12, 3:45 PM")
19
+ * @param dateString - ISO date string or null
20
+ * @param locale - Locale string (e.g., "en-US")
21
+ * @returns Formatted date-time string or empty string if dateString is null
22
+ */
23
+ export function toLocalizedShortDateTime(dateString: string | null, locale: string): string {
24
+ if (!dateString) return '';
25
+ const date = new Date(dateString);
26
+ return date.toLocaleString(locale, {
27
+ month: 'short',
28
+ day: 'numeric',
29
+ hour: 'numeric',
30
+ minute: '2-digit',
31
+ hour12: true,
32
+ });
33
+ }
@@ -41,3 +41,5 @@ export * from './Dynamic';
41
41
  export * from './tabs';
42
42
  export * from './get-operation-color';
43
43
  export * from './frontmatter-translate';
44
+ export * from './transform-revisions-to-version-history';
45
+ export * from './build-revision-url';