@redocly/theme 0.48.0 → 0.48.1
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/Feedback/Mood.js +14 -9
- package/lib/components/Search/SearchDialog.js +9 -3
- package/lib/components/Search/variables.js +4 -0
- package/lib/components/Segmented/Segmented.d.ts +4 -4
- package/lib/components/Segmented/Segmented.js +4 -7
- package/lib/components/Tag/Tag.d.ts +1 -0
- package/lib/components/Tag/Tag.js +3 -2
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.d.ts +1 -1
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +10 -5
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.d.ts +1 -2
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-steps.js +20 -15
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough.d.ts +2 -7
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough.js +10 -3
- package/lib/core/hooks/code-walkthrough/use-renderable-files.d.ts +9 -0
- package/lib/core/hooks/code-walkthrough/use-renderable-files.js +28 -0
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/utils/download-code-walkthrough.js +9 -1
- package/lib/core/utils/find-closest-common-directory.d.ts +6 -0
- package/lib/core/utils/find-closest-common-directory.js +51 -0
- package/lib/core/utils/get-file-icon.js +6 -0
- package/lib/core/utils/index.d.ts +1 -0
- package/lib/core/utils/index.js +1 -0
- package/lib/core/utils/replace-inputs-with-value.d.ts +1 -1
- package/lib/core/utils/replace-inputs-with-value.js +9 -10
- package/lib/icons/DocumentJavaIcon/DocumentJavaIcon.d.ts +9 -0
- package/lib/icons/DocumentJavaIcon/DocumentJavaIcon.js +22 -0
- package/lib/icons/DocumentJavaIcon/index.d.ts +1 -0
- package/lib/icons/DocumentJavaIcon/index.js +6 -0
- package/lib/icons/DocumentPythonIcon/DocumentPythonIcon.d.ts +9 -0
- package/lib/icons/DocumentPythonIcon/DocumentPythonIcon.js +23 -0
- package/lib/icons/DocumentPythonIcon/index.d.ts +1 -0
- package/lib/icons/DocumentPythonIcon/index.js +6 -0
- package/lib/icons/DocumentShellIcon/DocumentShellIcon.d.ts +9 -0
- package/lib/icons/DocumentShellIcon/DocumentShellIcon.js +22 -0
- package/lib/icons/DocumentShellIcon/index.d.ts +1 -0
- package/lib/icons/DocumentShellIcon/index.js +6 -0
- package/lib/icons/__tests__/IconTestUtils.d.ts +7 -0
- package/lib/icons/__tests__/IconTestUtils.js +33 -0
- package/lib/layouts/CodeWalkthroughLayout.js +4 -1
- package/lib/markdoc/components/CodeWalkthrough/CodeContainer.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodeFilters.js +15 -2
- package/lib/markdoc/components/CodeWalkthrough/CodePanel.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodePanelHeader.js +29 -23
- package/lib/markdoc/components/CodeWalkthrough/CodePanelPreview.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodePanelToolbar.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodeStep.js +6 -3
- package/lib/markdoc/components/CodeWalkthrough/CodeToggle.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +1 -1
- package/lib/markdoc/components/CodeWalkthrough/Input.js +4 -2
- package/lib/markdoc/tags/code-walkthrough.js +5 -0
- package/package.json +3 -3
- package/src/components/Feedback/Mood.tsx +16 -7
- package/src/components/Search/SearchDialog.tsx +52 -36
- package/src/components/Search/variables.ts +4 -0
- package/src/components/Segmented/Segmented.tsx +10 -10
- package/src/components/Tag/Tag.tsx +1 -1
- package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +9 -3
- package/src/core/hooks/code-walkthrough/use-code-walkthrough-steps.ts +30 -18
- package/src/core/hooks/code-walkthrough/use-code-walkthrough.ts +13 -13
- package/src/core/hooks/code-walkthrough/use-renderable-files.ts +51 -0
- package/src/core/hooks/index.ts +1 -0
- package/src/core/types/l10n.ts +3 -1
- package/src/core/utils/download-code-walkthrough.ts +14 -2
- package/src/core/utils/find-closest-common-directory.ts +51 -0
- package/src/core/utils/get-file-icon.ts +7 -0
- package/src/core/utils/index.ts +1 -0
- package/src/core/utils/replace-inputs-with-value.ts +12 -9
- package/src/icons/DocumentJavaIcon/DocumentJavaIcon.tsx +33 -0
- package/src/icons/DocumentJavaIcon/index.ts +1 -0
- package/src/icons/DocumentPythonIcon/DocumentPythonIcon.tsx +37 -0
- package/src/icons/DocumentPythonIcon/index.ts +1 -0
- package/src/icons/DocumentShellIcon/DocumentShellIcon.tsx +33 -0
- package/src/icons/DocumentShellIcon/index.ts +1 -0
- package/src/icons/__tests__/IconTestUtils.tsx +31 -0
- package/src/layouts/CodeWalkthroughLayout.tsx +5 -1
- package/src/markdoc/components/CodeWalkthrough/CodeContainer.tsx +1 -0
- package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +19 -3
- package/src/markdoc/components/CodeWalkthrough/CodePanel.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodePanelHeader.tsx +64 -47
- package/src/markdoc/components/CodeWalkthrough/CodePanelPreview.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodePanelToolbar.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodeStep.tsx +6 -2
- package/src/markdoc/components/CodeWalkthrough/CodeToggle.tsx +1 -1
- package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +4 -1
- package/src/markdoc/components/CodeWalkthrough/Input.tsx +4 -2
- package/src/markdoc/tags/code-walkthrough.ts +5 -0
|
@@ -153,7 +153,7 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
153
153
|
}
|
|
154
154
|
}}
|
|
155
155
|
>
|
|
156
|
-
{translate('search.
|
|
156
|
+
{translate('search.ai.button', 'Search with AI')}
|
|
157
157
|
</SearchAiButton>
|
|
158
158
|
) : null}
|
|
159
159
|
{showSearchFilterButton && (
|
|
@@ -236,41 +236,50 @@ export function SearchDialog({ onClose, className }: SearchDialogProps): JSX.Ele
|
|
|
236
236
|
/>
|
|
237
237
|
)}
|
|
238
238
|
</SearchDialogBody>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
<
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
239
|
+
<SearchDialogFooter>
|
|
240
|
+
{mode === 'ai-dialog' ? (
|
|
241
|
+
<AiDisclaimer>
|
|
242
|
+
{translate(
|
|
243
|
+
'search.ai.disclaimer',
|
|
244
|
+
'AI search might provide incomplete or incorrect results. Verify important information.',
|
|
245
|
+
)}
|
|
246
|
+
</AiDisclaimer>
|
|
247
|
+
) : (
|
|
248
|
+
<>
|
|
249
|
+
<SearchShortcuts>
|
|
250
|
+
<SearchShortcut
|
|
251
|
+
data-translation-key="search.keys.navigate"
|
|
252
|
+
combination="Tab"
|
|
253
|
+
text={translate('search.keys.navigate', 'to navigate')}
|
|
254
|
+
/>
|
|
255
|
+
<SearchShortcut
|
|
256
|
+
data-translation-key="search.keys.select"
|
|
257
|
+
combination="⏎"
|
|
258
|
+
text={translate('search.keys.select', 'to select')}
|
|
259
|
+
/>
|
|
260
|
+
<SearchShortcut
|
|
261
|
+
data-translation-key="search.keys.exit"
|
|
262
|
+
combination="Esc"
|
|
263
|
+
text={translate('search.keys.exit', 'to exit')}
|
|
264
|
+
/>
|
|
265
|
+
</SearchShortcuts>
|
|
266
|
+
{isSearchLoading && (
|
|
267
|
+
<SearchLoading>
|
|
268
|
+
<SpinnerLoader size="16px" color="var(--search-input-icon-color)" />
|
|
269
|
+
{translate('search.loading', 'Loading...')}
|
|
270
|
+
</SearchLoading>
|
|
271
|
+
)}
|
|
272
|
+
<SearchCancelButton
|
|
273
|
+
data-translation-key="search.cancel"
|
|
274
|
+
variant="secondary"
|
|
275
|
+
size="small"
|
|
276
|
+
onClick={onClose}
|
|
277
|
+
>
|
|
278
|
+
{translate('search.cancel', 'Cancel')}
|
|
279
|
+
</SearchCancelButton>
|
|
280
|
+
</>
|
|
281
|
+
)}
|
|
282
|
+
</SearchDialogFooter>
|
|
274
283
|
</SearchDialogWrapper>
|
|
275
284
|
</SearchOverlay>
|
|
276
285
|
);
|
|
@@ -439,3 +448,10 @@ const SearchHeaderButtons = styled.div`
|
|
|
439
448
|
padding-left: var(--search-header-buttons-padding-left);
|
|
440
449
|
border-left: var(--search-header-buttons-border-left);
|
|
441
450
|
`;
|
|
451
|
+
|
|
452
|
+
const AiDisclaimer = styled.div`
|
|
453
|
+
font-size: var(--search-ai-disclaimer-font-size);
|
|
454
|
+
line-height: var(--search-ai-disclaimer-line-height);
|
|
455
|
+
color: var(--search-ai-disclaimer-text-color);
|
|
456
|
+
margin: 0 auto;
|
|
457
|
+
`;
|
|
@@ -176,5 +176,9 @@ export const search = css`
|
|
|
176
176
|
--search-ai-resource-tag-text-color: var(--text-color-secondary);
|
|
177
177
|
--search-ai-resource-tag-icon-color: var(--text-color-secondary);
|
|
178
178
|
|
|
179
|
+
--search-ai-disclaimer-font-size: var(--font-size-sm);
|
|
180
|
+
--search-ai-disclaimer-line-height: var(--line-height-sm);
|
|
181
|
+
--search-ai-disclaimer-text-color: var(--text-color-secondary);
|
|
182
|
+
|
|
179
183
|
// @tokens End
|
|
180
184
|
`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
2
|
import styled, { css } from 'styled-components';
|
|
3
3
|
|
|
4
|
-
import type { ReactElement } from 'react';
|
|
4
|
+
import type { ForwardedRef, ReactElement } from 'react';
|
|
5
5
|
import type { SelectOption } from '@redocly/theme/core/types/select';
|
|
6
6
|
|
|
7
7
|
import { typedMemo } from '@redocly/theme/core/hoc/typedMemo';
|
|
@@ -14,15 +14,13 @@ export type SegmentedProps<T> = {
|
|
|
14
14
|
size?: 'regular' | 'small';
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
function SegmentedComponent<T>(
|
|
18
|
-
options,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
className = '',
|
|
22
|
-
size = 'regular',
|
|
23
|
-
}: SegmentedProps<T>): ReactElement {
|
|
17
|
+
function SegmentedComponent<T>(
|
|
18
|
+
{ options, onChange, value, className = '', size = 'regular' }: SegmentedProps<T>,
|
|
19
|
+
ref?: ForwardedRef<HTMLDivElement>,
|
|
20
|
+
): ReactElement {
|
|
24
21
|
return (
|
|
25
22
|
<SegmentedGroup
|
|
23
|
+
ref={ref}
|
|
26
24
|
data-component-name="Segmented/Segmented"
|
|
27
25
|
className={`tag-grey ${size} ${className}`}
|
|
28
26
|
role="tablist"
|
|
@@ -43,7 +41,9 @@ function SegmentedComponent<T>({
|
|
|
43
41
|
);
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
export const Segmented = typedMemo(SegmentedComponent)
|
|
44
|
+
export const Segmented = typedMemo(forwardRef(SegmentedComponent)) as <T>(
|
|
45
|
+
props: SegmentedProps<T> & { ref?: ForwardedRef<HTMLDivElement> },
|
|
46
|
+
) => ReactElement;
|
|
47
47
|
|
|
48
48
|
const SegmentedGroup = styled.div`
|
|
49
49
|
display: flex;
|
|
@@ -60,6 +60,7 @@ export function useCodeWalkthroughControls(
|
|
|
60
60
|
filters: Record<string, CodeWalkthroughFilter>,
|
|
61
61
|
inputs: InputsMarkdocAttr,
|
|
62
62
|
toggles: TogglesMarkdocAttr,
|
|
63
|
+
enableDeepLink: boolean,
|
|
63
64
|
): WalkthroughControlsState {
|
|
64
65
|
const location = useLocation();
|
|
65
66
|
const navigate = useNavigate();
|
|
@@ -72,7 +73,7 @@ export function useCodeWalkthroughControls(
|
|
|
72
73
|
...toggle,
|
|
73
74
|
render: true,
|
|
74
75
|
type: 'toggle',
|
|
75
|
-
value: searchParams.get(id) === 'true',
|
|
76
|
+
value: enableDeepLink ? searchParams.get(id) === 'true' : false,
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
return initialState;
|
|
@@ -116,7 +117,7 @@ export function useCodeWalkthroughControls(
|
|
|
116
117
|
...input,
|
|
117
118
|
render: true,
|
|
118
119
|
type: 'input',
|
|
119
|
-
value: searchParams.get(id)
|
|
120
|
+
value: enableDeepLink ? (searchParams.get(id) ?? input.value) : input.value,
|
|
120
121
|
};
|
|
121
122
|
}
|
|
122
123
|
return initialState;
|
|
@@ -156,11 +157,12 @@ export function useCodeWalkthroughControls(
|
|
|
156
157
|
const initialState: FiltersState = {};
|
|
157
158
|
|
|
158
159
|
for (const [id, filter] of Object.entries(filters)) {
|
|
160
|
+
const defaultValue = filter?.items?.[0]?.value || '';
|
|
159
161
|
initialState[id] = {
|
|
160
162
|
...filter,
|
|
161
163
|
render: true,
|
|
162
164
|
type: 'filter',
|
|
163
|
-
value: searchParams.get(id)
|
|
165
|
+
value: enableDeepLink ? (searchParams.get(id) ?? defaultValue) : defaultValue,
|
|
164
166
|
};
|
|
165
167
|
}
|
|
166
168
|
|
|
@@ -270,6 +272,10 @@ export function useCodeWalkthroughControls(
|
|
|
270
272
|
* Update the URL search params with the current state of the filters and inputs
|
|
271
273
|
*/
|
|
272
274
|
useEffect(() => {
|
|
275
|
+
if (!enableDeepLink) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
273
279
|
const newSearchParams = new URLSearchParams(Array.from(searchParams.entries()));
|
|
274
280
|
|
|
275
281
|
for (const [id, { value }] of Object.entries(state)) {
|
|
@@ -8,9 +8,9 @@ import { ACTIVE_STEP_QUERY_PARAM } from '@redocly/theme/core/constants';
|
|
|
8
8
|
|
|
9
9
|
type ActiveStep = string | null;
|
|
10
10
|
type CodeWalkthroughStep = CodeWalkthroughStepAttr & {
|
|
11
|
-
active?: boolean;
|
|
12
11
|
compRef?: HTMLElement;
|
|
13
12
|
};
|
|
13
|
+
|
|
14
14
|
export type WalkthroughStepsState = {
|
|
15
15
|
activeStep: ActiveStep;
|
|
16
16
|
setActiveStep: (stepId: ActiveStep) => void;
|
|
@@ -20,7 +20,10 @@ export type WalkthroughStepsState = {
|
|
|
20
20
|
filtersElementRef?: React.RefObject<HTMLDivElement>;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export function useCodeWalkthroughSteps(
|
|
23
|
+
export function useCodeWalkthroughSteps(
|
|
24
|
+
steps: CodeWalkthroughStep[],
|
|
25
|
+
enableDeepLink: boolean,
|
|
26
|
+
): WalkthroughStepsState {
|
|
24
27
|
const location = useLocation();
|
|
25
28
|
const navigate = useNavigate();
|
|
26
29
|
const searchParams = useMemo(() => new URLSearchParams(location.search), [location.search]);
|
|
@@ -32,7 +35,7 @@ export function useCodeWalkthroughSteps(steps: CodeWalkthroughStep[]): Walkthrou
|
|
|
32
35
|
const observedElementsRef = useRef(new Set<HTMLElement>());
|
|
33
36
|
|
|
34
37
|
const [activeStep, setActiveStep] = useState<ActiveStep>(
|
|
35
|
-
searchParams.get(ACTIVE_STEP_QUERY_PARAM),
|
|
38
|
+
enableDeepLink ? searchParams.get(ACTIVE_STEP_QUERY_PARAM) : null,
|
|
36
39
|
);
|
|
37
40
|
|
|
38
41
|
const register = useCallback(
|
|
@@ -74,28 +77,23 @@ export function useCodeWalkthroughSteps(steps: CodeWalkthroughStep[]): Walkthrou
|
|
|
74
77
|
return;
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
const
|
|
80
|
+
const renderedSteps = steps.filter((step) => Boolean(step.compRef));
|
|
81
|
+
|
|
82
|
+
if (renderedSteps.length < 2) {
|
|
83
|
+
setActiveStep(renderedSteps[0]?.id || null);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
78
87
|
for (const entry of entries) {
|
|
79
|
-
const
|
|
80
|
-
const stepKey = Number((target as HTMLElement)?.dataset?.stepKey);
|
|
81
|
-
const stepActive = (target as HTMLElement)?.dataset?.stepActive === 'true';
|
|
88
|
+
const stepKey = Number((entry.target as HTMLElement)?.dataset?.stepKey);
|
|
82
89
|
|
|
83
90
|
if (!Number.isInteger(stepKey) || stepKey < 0) {
|
|
84
91
|
continue;
|
|
85
92
|
}
|
|
86
93
|
|
|
94
|
+
const { intersectionRatio, boundingClientRect, rootBounds, isIntersecting } = entry;
|
|
87
95
|
const step = steps[stepKey];
|
|
88
|
-
step.active = stepActive;
|
|
89
|
-
stepsEntries.push(entry);
|
|
90
|
-
}
|
|
91
96
|
|
|
92
|
-
for (const stepEntry of stepsEntries) {
|
|
93
|
-
const { target, intersectionRatio, boundingClientRect, rootBounds, isIntersecting } =
|
|
94
|
-
stepEntry;
|
|
95
|
-
const stepKey = Number((target as HTMLElement)?.dataset?.stepKey);
|
|
96
|
-
const step = steps[stepKey];
|
|
97
|
-
|
|
98
|
-
const renderedSteps = steps.filter((step) => Boolean(step.compRef));
|
|
99
97
|
const stepIndex = renderedSteps.findIndex(
|
|
100
98
|
(renderedStep) => renderedStep.stepKey === step.stepKey,
|
|
101
99
|
);
|
|
@@ -105,6 +103,16 @@ export function useCodeWalkthroughSteps(steps: CodeWalkthroughStep[]): Walkthrou
|
|
|
105
103
|
rootBounds?.bottom !== undefined && boundingClientRect.top < rootBounds.top;
|
|
106
104
|
const stepGoesIn = isIntersecting;
|
|
107
105
|
|
|
106
|
+
if (
|
|
107
|
+
intersectionRatio > 0.8 &&
|
|
108
|
+
intersectionRatio < 1 &&
|
|
109
|
+
intersectionAtTop &&
|
|
110
|
+
activeStep === null
|
|
111
|
+
) {
|
|
112
|
+
setActiveStep(step.id);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
108
116
|
if (intersectionRatio < 1 && intersectionRatio !== 0 && intersectionAtTop) {
|
|
109
117
|
let newStep: string | null = null;
|
|
110
118
|
|
|
@@ -128,7 +136,7 @@ export function useCodeWalkthroughSteps(steps: CodeWalkthroughStep[]): Walkthrou
|
|
|
128
136
|
const filtersElementHeight = filtersElementRef.current?.clientHeight || 0;
|
|
129
137
|
const navbarHeight = document.querySelector('nav')?.clientHeight || 0;
|
|
130
138
|
const newObserver = new IntersectionObserver(observerCallback, {
|
|
131
|
-
threshold: [0.8],
|
|
139
|
+
threshold: [0.8, 0.85, 0.9, 0.95],
|
|
132
140
|
rootMargin: `-${filtersElementHeight + navbarHeight}px 0px 0px 0px`,
|
|
133
141
|
});
|
|
134
142
|
|
|
@@ -145,6 +153,10 @@ export function useCodeWalkthroughSteps(steps: CodeWalkthroughStep[]): Walkthrou
|
|
|
145
153
|
* Update the URL search params with the current state of the filters and inputs
|
|
146
154
|
*/
|
|
147
155
|
useEffect(() => {
|
|
156
|
+
if (!enableDeepLink) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
148
160
|
const newSearchParams = new URLSearchParams(Array.from(searchParams.entries()));
|
|
149
161
|
|
|
150
162
|
if (activeStep) {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
CodeWalkthroughFileset,
|
|
3
2
|
CodeWalkthroughFile,
|
|
4
3
|
CodeWalkthroughStepAttr,
|
|
5
|
-
|
|
6
|
-
InputsMarkdocAttr,
|
|
7
|
-
TogglesMarkdocAttr,
|
|
4
|
+
CodeWalkthroughAttr,
|
|
8
5
|
} from '@redocly/config';
|
|
9
6
|
|
|
10
7
|
import {
|
|
@@ -23,16 +20,19 @@ export type WalkthroughState = {
|
|
|
23
20
|
|
|
24
21
|
export function useCodeWalkthrough(
|
|
25
22
|
steps: CodeWalkthroughStepAttr[],
|
|
26
|
-
attributes:
|
|
27
|
-
filters: Record<string, CodeWalkthroughFilter>;
|
|
28
|
-
filesets: CodeWalkthroughFileset[];
|
|
29
|
-
inputs: InputsMarkdocAttr;
|
|
30
|
-
toggles: TogglesMarkdocAttr;
|
|
31
|
-
},
|
|
23
|
+
attributes: Omit<CodeWalkthroughAttr, 'steps' | 'preview'>,
|
|
32
24
|
): WalkthroughState {
|
|
33
|
-
const { filters, filesets, inputs, toggles } = attributes;
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
const { filters, filesets, inputs, toggles, __idx } = attributes;
|
|
26
|
+
/*
|
|
27
|
+
We only enable deep linking for the first CodeWalkthrough,
|
|
28
|
+
because we don't expect more than one on the same page.
|
|
29
|
+
Any subsequent walkthroughs have it disabled to avoid
|
|
30
|
+
collisions/conflicts in the URL.
|
|
31
|
+
*/
|
|
32
|
+
const enableDeepLink = __idx === 1;
|
|
33
|
+
|
|
34
|
+
const stepsState = useCodeWalkthroughSteps(steps, enableDeepLink);
|
|
35
|
+
const controlsState = useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink);
|
|
36
36
|
|
|
37
37
|
const files: CodeWalkthroughFile[] = filesets
|
|
38
38
|
.filter((fileset) => controlsState.areConditionsMet(fileset))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { CodeWalkthroughFile } from '@redocly/config';
|
|
4
|
+
import type { IconProps } from '@redocly/theme/icons/types';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
getFileIconByExt,
|
|
8
|
+
removeLeadingSlash,
|
|
9
|
+
findClosestCommonDirectory,
|
|
10
|
+
} from '@redocly/theme/core/utils';
|
|
11
|
+
|
|
12
|
+
export type RenderableFile = CodeWalkthroughFile & {
|
|
13
|
+
FileIcon: React.FunctionComponent<IconProps>;
|
|
14
|
+
parentFolder: string;
|
|
15
|
+
isNameDuplicate: boolean;
|
|
16
|
+
inRootDir: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function useRenderableFiles(files: CodeWalkthroughFile[]): RenderableFile[] {
|
|
20
|
+
return useMemo(
|
|
21
|
+
function () {
|
|
22
|
+
const filePaths = files.map(({ path }) => path);
|
|
23
|
+
const rootDir = findClosestCommonDirectory(filePaths);
|
|
24
|
+
|
|
25
|
+
const renderableFiles = files.map((file) => {
|
|
26
|
+
const FileIcon = getFileTypeIcon(file.basename);
|
|
27
|
+
const parentFolder = file.path.split('/').slice(-2, -1)[0];
|
|
28
|
+
const isNameDuplicate = files.some(
|
|
29
|
+
(_file) => file.basename === _file.basename && file.path !== _file.path,
|
|
30
|
+
);
|
|
31
|
+
const inRootDir = file.path === `${removeLeadingSlash(rootDir)}/${file.basename}`;
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
...file,
|
|
35
|
+
FileIcon,
|
|
36
|
+
inRootDir,
|
|
37
|
+
parentFolder,
|
|
38
|
+
isNameDuplicate,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return renderableFiles;
|
|
43
|
+
},
|
|
44
|
+
[files],
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getFileTypeIcon(basename: string) {
|
|
49
|
+
const extension = basename.split('.').pop()?.toLowerCase() || '';
|
|
50
|
+
return getFileIconByExt(extension);
|
|
51
|
+
}
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -32,3 +32,4 @@ export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough';
|
|
|
32
32
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough-steps';
|
|
33
33
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-walkthrough-controls';
|
|
34
34
|
export * from '@redocly/theme/core/hooks/code-walkthrough/use-code-panel';
|
|
35
|
+
export * from '@redocly/theme/core/hooks/code-walkthrough/use-renderable-files';
|
package/src/core/types/l10n.ts
CHANGED
|
@@ -85,8 +85,9 @@ export type TranslationKey =
|
|
|
85
85
|
| 'search.filter.field.reset'
|
|
86
86
|
| 'search.ai.thinkingText'
|
|
87
87
|
| 'search.ai.resourcesFound'
|
|
88
|
-
| 'search.
|
|
88
|
+
| 'search.ai.button'
|
|
89
89
|
| 'search.ai.label'
|
|
90
|
+
| 'search.ai.disclaimer'
|
|
90
91
|
| 'toc.header'
|
|
91
92
|
| 'footer.copyrightText'
|
|
92
93
|
| 'page.homeButton'
|
|
@@ -196,6 +197,7 @@ export type TranslationKey =
|
|
|
196
197
|
| 'openapi.expandAll'
|
|
197
198
|
| 'openapi.collapseAll'
|
|
198
199
|
| 'openapi.noResponseExample'
|
|
200
|
+
| 'openapi.noResponseContent'
|
|
199
201
|
| 'openapi.noRequestPayload'
|
|
200
202
|
| 'openapi.hidePattern'
|
|
201
203
|
| 'openapi.showPattern'
|
|
@@ -3,7 +3,11 @@ import JSZip from 'jszip';
|
|
|
3
3
|
|
|
4
4
|
import type { CodeWalkthroughFile, InputsState } from '@redocly/config';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
findClosestCommonDirectory,
|
|
8
|
+
getCodeWalkthroughFileText,
|
|
9
|
+
removeLeadingSlash,
|
|
10
|
+
} from '@redocly/theme/core/utils';
|
|
7
11
|
|
|
8
12
|
// https://github.com/Stuk/jszip/issues/196#issuecomment-69503828
|
|
9
13
|
JSZip.support.nodebuffer = false;
|
|
@@ -15,9 +19,17 @@ export async function downloadCodeWalkthrough(
|
|
|
15
19
|
) {
|
|
16
20
|
const zip = new JSZip();
|
|
17
21
|
|
|
22
|
+
const filePaths = files.map(({ path }) => path);
|
|
23
|
+
const commonClosestDirectory = findClosestCommonDirectory(filePaths);
|
|
24
|
+
|
|
18
25
|
for (const file of files) {
|
|
19
26
|
const fileContent = getCodeWalkthroughFileText(file, state, inputsState);
|
|
20
|
-
|
|
27
|
+
if (commonClosestDirectory === '/') {
|
|
28
|
+
zip.file(file.path, fileContent);
|
|
29
|
+
} else {
|
|
30
|
+
const filePath = file.path.replace(removeLeadingSlash(`${commonClosestDirectory}/`), '');
|
|
31
|
+
zip.file(filePath, fileContent);
|
|
32
|
+
}
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
const zipContent = await zip.generateAsync({ type: 'blob' });
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits a file path into its directory components, removing the file name.
|
|
3
|
+
* @param path - The full file path.
|
|
4
|
+
* @returns An array of directories.
|
|
5
|
+
*/
|
|
6
|
+
function splitPath(path: string): string[] {
|
|
7
|
+
const parts = path.split('/').filter(Boolean);
|
|
8
|
+
parts.pop();
|
|
9
|
+
return parts;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Finds the longest common prefix between two paths.
|
|
14
|
+
* @param path1 - The first path as an array of directories.
|
|
15
|
+
* @param path2 - The second path as an array of directories.
|
|
16
|
+
* @returns An array representing the common prefix.
|
|
17
|
+
*/
|
|
18
|
+
function findCommonPrefix(path1: string[], path2: string[]): string[] {
|
|
19
|
+
const common: string[] = [];
|
|
20
|
+
for (let i = 0; i < Math.min(path1.length, path2.length); i++) {
|
|
21
|
+
if (path1[i] === path2[i]) {
|
|
22
|
+
common.push(path1[i]);
|
|
23
|
+
} else {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return common;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Finds the closest common directory for a set of file paths.
|
|
32
|
+
* @param paths - An array of absolute file paths.
|
|
33
|
+
* @returns The closest common directory as a string.
|
|
34
|
+
*/
|
|
35
|
+
export function findClosestCommonDirectory(paths: string[]): string {
|
|
36
|
+
if (paths.length === 0) {
|
|
37
|
+
return '/';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const splitPaths = paths.map(splitPath);
|
|
41
|
+
let commonPrefix = splitPaths[0];
|
|
42
|
+
|
|
43
|
+
for (let i = 1; i < splitPaths.length; i++) {
|
|
44
|
+
commonPrefix = findCommonPrefix(commonPrefix, splitPaths[i]);
|
|
45
|
+
if (commonPrefix.length === 0) {
|
|
46
|
+
return '/';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return '/' + commonPrefix.join('/');
|
|
51
|
+
}
|
|
@@ -10,6 +10,9 @@ import { DocumentHtmlIcon } from '@redocly/theme/icons/DocumentHtmlIcon/Document
|
|
|
10
10
|
import { DocumentReactIcon } from '@redocly/theme/icons/DocumentReactIcon/DocumentReactIcon';
|
|
11
11
|
import { DocumentMarkdownIcon } from '@redocly/theme/icons/DocumentMarkdownIcon/DocumentMarkdownIcon';
|
|
12
12
|
import { DocumentGraphqlIcon } from '@redocly/theme/icons/DocumentGraphqlIcon/DocumentGraphqlIcon';
|
|
13
|
+
import { DocumentPythonIcon } from '@redocly/theme/icons/DocumentPythonIcon/DocumentPythonIcon';
|
|
14
|
+
import { DocumentShellIcon } from '@redocly/theme/icons/DocumentShellIcon/DocumentShellIcon';
|
|
15
|
+
import { DocumentJavaIcon } from '@redocly/theme/icons/DocumentJavaIcon/DocumentJavaIcon';
|
|
13
16
|
|
|
14
17
|
const fileIconMap: Record<string, React.FunctionComponent<IconProps>> = {
|
|
15
18
|
yaml: DocumentYamlIcon,
|
|
@@ -28,6 +31,10 @@ const fileIconMap: Record<string, React.FunctionComponent<IconProps>> = {
|
|
|
28
31
|
|
|
29
32
|
graphql: DocumentGraphqlIcon,
|
|
30
33
|
gql: DocumentGraphqlIcon,
|
|
34
|
+
|
|
35
|
+
py: DocumentPythonIcon,
|
|
36
|
+
sh: DocumentShellIcon,
|
|
37
|
+
java: DocumentJavaIcon,
|
|
31
38
|
};
|
|
32
39
|
|
|
33
40
|
export function getFileIconByExt(ext: string) {
|
package/src/core/utils/index.ts
CHANGED
|
@@ -30,3 +30,4 @@ export * from '@redocly/theme/core/utils/download-code-walkthrough';
|
|
|
30
30
|
export * from '@redocly/theme/core/utils/get-file-icon';
|
|
31
31
|
export * from '@redocly/theme/core/utils/match-code-walkthrough-conditions';
|
|
32
32
|
export * from '@redocly/theme/core/utils/replace-inputs-with-value';
|
|
33
|
+
export * from '@redocly/theme/core/utils/find-closest-common-directory';
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
export function replaceInputsWithValue(node: string, inputs: Record<string, { value?: string }>) {
|
|
2
|
+
const validKeys = Object.entries(inputs)
|
|
3
|
+
.filter(([_, input]) => input.value)
|
|
4
|
+
.map(([key]) => key)
|
|
5
|
+
.join('|');
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
});
|
|
7
|
+
if (!validKeys) {
|
|
8
|
+
return node;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const inputRegex = new RegExp(`\\{\\{\\s*(${validKeys})\\s*\\}\\}`, 'g');
|
|
12
|
+
|
|
13
|
+
return node.replace(inputRegex, (_, inputKey) => inputs[inputKey]?.value as string);
|
|
11
14
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { IconProps } from '@redocly/theme/icons/types';
|
|
5
|
+
|
|
6
|
+
import { getCssColorVariable } from '@redocly/theme/core/utils';
|
|
7
|
+
|
|
8
|
+
const Icon = (props: IconProps) => (
|
|
9
|
+
<svg
|
|
10
|
+
width="16"
|
|
11
|
+
height="16"
|
|
12
|
+
viewBox="0 0 16 16"
|
|
13
|
+
fill="none"
|
|
14
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path
|
|
18
|
+
d="M2 13H14V14H2V13ZM14 2H3.5C3.36739 2 3.24021 2.05268 3.14645 2.14645C3.05268 2.24021 3 2.36739 3 2.5V9C3 9.53043 3.21071 10.0391 3.58579 10.4142C3.96086 10.7893 4.46957 11 5 11H10C10.5304 11 11.0391 10.7893 11.4142 10.4142C11.7893 10.0391 12 9.53043 12 9V7H14C14.2652 7 14.5196 6.89464 14.7071 6.70711C14.8946 6.51957 15 6.26522 15 6V3C15 2.73478 14.8946 2.48043 14.7071 2.29289C14.5196 2.10536 14.2652 2 14 2ZM14 6H12V3H14V6Z"
|
|
19
|
+
fill="#3B3C45"
|
|
20
|
+
/>
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export const DocumentJavaIcon = styled(Icon).attrs(() => ({
|
|
25
|
+
'data-component-name': 'icons/DocumentJavaIcon/DocumentJavaIcon',
|
|
26
|
+
}))<IconProps>`
|
|
27
|
+
path {
|
|
28
|
+
fill: ${({ color }) => getCssColorVariable(color)};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
height: ${({ size }) => size || '16px'};
|
|
32
|
+
width: ${({ size }) => size || '16px'};
|
|
33
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DocumentJavaIcon } from '@redocly/theme/icons/DocumentJavaIcon/DocumentJavaIcon';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { IconProps } from '@redocly/theme/icons/types';
|
|
5
|
+
|
|
6
|
+
import { getCssColorVariable } from '@redocly/theme/core/utils';
|
|
7
|
+
|
|
8
|
+
const Icon = (props: IconProps) => (
|
|
9
|
+
<svg
|
|
10
|
+
width="16"
|
|
11
|
+
height="16"
|
|
12
|
+
viewBox="0 0 16 16"
|
|
13
|
+
fill="none"
|
|
14
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<path
|
|
18
|
+
d="M6.57325 1.33301C6.06757 1.33301 5.5826 1.53389 5.22504 1.89146C4.86747 2.24903 4.66659 2.734 4.66659 3.23967V4.35967H7.52659C7.78659 4.35967 7.99992 4.73967 7.99992 4.99967H3.23992C2.73424 4.99967 2.24927 5.20055 1.8917 5.55812C1.53413 5.91569 1.33325 6.40066 1.33325 6.90634V9.42701C1.33325 9.93269 1.53413 10.4177 1.8917 10.7752C2.24927 11.1328 2.73424 11.3337 3.23992 11.3337H4.02659V9.54701C4.02571 9.29694 4.07421 9.04915 4.1693 8.81786C4.26439 8.58657 4.4042 8.37633 4.58072 8.19919C4.75724 8.02206 4.96699 7.88151 5.19794 7.78561C5.4289 7.6897 5.67651 7.64034 5.92659 7.64034H9.42659C10.4799 7.64034 11.3333 6.79301 11.3333 5.73967V3.23967C11.3333 2.734 11.1324 2.24903 10.7748 1.89146C10.4172 1.53389 9.93227 1.33301 9.42659 1.33301H6.57325ZM6.09325 2.40634C6.35992 2.40634 6.57325 2.48634 6.57325 2.87967C6.57325 3.27301 6.35992 3.47367 6.09325 3.47367C5.83325 3.47367 5.61992 3.27367 5.61992 2.88034C5.61992 2.48701 5.83325 2.40634 6.09325 2.40634Z"
|
|
19
|
+
fill="#3B3C45"
|
|
20
|
+
/>
|
|
21
|
+
<path
|
|
22
|
+
d="M11.9727 4.66699V6.45366C11.9735 6.70367 11.925 6.95139 11.8298 7.1826C11.7347 7.41381 11.5949 7.62397 11.4184 7.80104C11.2419 7.97811 11.0321 8.1186 10.8012 8.21446C10.5703 8.31032 10.3228 8.35966 10.0727 8.35966H6.57341C6.32334 8.35878 6.07556 8.40728 5.84427 8.50237C5.61298 8.59746 5.40274 8.73727 5.2256 8.91379C5.04846 9.09031 4.90791 9.30006 4.81201 9.53102C4.71611 9.76197 4.66675 10.0096 4.66675 10.2597V12.7597C4.66675 13.2653 4.86763 13.7503 5.2252 14.1079C5.58277 14.4654 6.06774 14.6663 6.57341 14.6663H9.42675C9.93231 14.6663 10.4172 14.4655 10.7747 14.1081C11.1323 13.7507 11.3332 13.2659 11.3334 12.7603V11.6403H8.47275C8.21275 11.6403 8.00008 11.2603 8.00008 11.0003H12.7601C13.2658 11.0003 13.7507 10.7994 14.1083 10.4419C14.4659 10.0843 14.6667 9.59934 14.6667 9.09366V6.57366C14.6667 6.06798 14.4659 5.58301 14.1083 5.22544C13.7507 4.86787 13.2658 4.66699 12.7601 4.66699H11.9727ZM5.54675 7.67566L5.54408 7.67833L5.56941 7.67566H5.54675ZM9.90675 12.5263C10.1667 12.5263 10.3801 12.7263 10.3801 13.1197C10.3801 13.1818 10.3678 13.2434 10.3441 13.3008C10.3203 13.3582 10.2854 13.4104 10.2414 13.4544C10.1975 13.4983 10.1453 13.5332 10.0879 13.557C10.0305 13.5807 9.96891 13.593 9.90675 13.593C9.64008 13.593 9.42675 13.513 9.42675 13.1197C9.42675 12.7263 9.64008 12.5263 9.90675 12.5263Z"
|
|
23
|
+
fill="#3B3C45"
|
|
24
|
+
/>
|
|
25
|
+
</svg>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export const DocumentPythonIcon = styled(Icon).attrs(() => ({
|
|
29
|
+
'data-component-name': 'icons/DocumentPythonIcon/DocumentPythonIcon',
|
|
30
|
+
}))<IconProps>`
|
|
31
|
+
path {
|
|
32
|
+
fill: ${({ color }) => getCssColorVariable(color)};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
height: ${({ size }) => size || '16px'};
|
|
36
|
+
width: ${({ size }) => size || '16px'};
|
|
37
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DocumentPythonIcon } from '@redocly/theme/icons/DocumentPythonIcon/DocumentPythonIcon';
|