@redocly/theme 0.48.0 → 0.48.2
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/DatePicker/variables.js +1 -1
- 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/contexts/CodeWalkthrough/CodeWalkthroughControlsContext.js +2 -6
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.d.ts +7 -10
- package/lib/core/hooks/code-walkthrough/use-code-walkthrough-controls.js +63 -97
- 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/styles/global.js +18 -0
- package/lib/core/types/l10n.d.ts +1 -1
- package/lib/core/utils/download-code-walkthrough.d.ts +4 -2
- 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-code-walkthrough-file-text.d.ts +4 -2
- 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 +25 -4
- package/lib/markdoc/components/CodeWalkthrough/CodeFilters.d.ts +5 -4
- 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 +5 -2
- package/lib/markdoc/components/CodeWalkthrough/CodeToggle.js +5 -5
- package/lib/markdoc/components/CodeWalkthrough/CodeWalkthrough.js +3 -3
- package/lib/markdoc/components/CodeWalkthrough/Input.js +7 -5
- package/lib/markdoc/tags/code-walkthrough.js +5 -0
- package/package.json +3 -3
- package/src/components/DatePicker/variables.ts +1 -1
- 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/contexts/CodeWalkthrough/CodeWalkthroughControlsContext.tsx +2 -8
- package/src/core/hooks/code-walkthrough/use-code-walkthrough-controls.ts +90 -142
- 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/styles/global.ts +18 -0
- package/src/core/types/l10n.ts +3 -1
- package/src/core/utils/download-code-walkthrough.ts +16 -4
- package/src/core/utils/find-closest-common-directory.ts +51 -0
- package/src/core/utils/get-code-walkthrough-file-text.ts +3 -3
- 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 +28 -3
- package/src/markdoc/components/CodeWalkthrough/CodeFilters.tsx +21 -4
- 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 +5 -1
- package/src/markdoc/components/CodeWalkthrough/CodeToggle.tsx +5 -5
- package/src/markdoc/components/CodeWalkthrough/CodeWalkthrough.tsx +11 -5
- package/src/markdoc/components/CodeWalkthrough/Input.tsx +8 -6
- package/src/markdoc/tags/code-walkthrough.ts +5 -0
|
@@ -7,14 +7,9 @@ import type {
|
|
|
7
7
|
InputsMarkdocAttr,
|
|
8
8
|
TogglesMarkdocAttr,
|
|
9
9
|
CodeWalkthroughControls,
|
|
10
|
-
TogglesState,
|
|
11
|
-
InputsState,
|
|
12
|
-
FiltersState,
|
|
13
10
|
CodeWalkthroughFilterItem,
|
|
14
|
-
ControlState,
|
|
15
|
-
ControlTypeValue,
|
|
16
|
-
ControlType,
|
|
17
11
|
CodeWalkthroughConditionsObject,
|
|
12
|
+
CodeWalkthroughControlsState,
|
|
18
13
|
} from '@redocly/config';
|
|
19
14
|
|
|
20
15
|
import {
|
|
@@ -24,8 +19,6 @@ import {
|
|
|
24
19
|
replaceInputsWithValue,
|
|
25
20
|
} from '@redocly/theme/core/utils';
|
|
26
21
|
|
|
27
|
-
export type getState<T extends ControlType> = (id: string) => Omit<ControlState<T>, 'type'> | null;
|
|
28
|
-
export type changeState<T extends ControlType> = (id: string, value: ControlTypeValue<T>) => void;
|
|
29
22
|
export type ActiveFilter = {
|
|
30
23
|
id: string;
|
|
31
24
|
label?: string;
|
|
@@ -34,15 +27,8 @@ export type ActiveFilter = {
|
|
|
34
27
|
|
|
35
28
|
export type WalkthroughControlsState = {
|
|
36
29
|
activeFilters: ActiveFilter[];
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
changeToggleState: changeState<'toggle'>;
|
|
40
|
-
/* Input State Controls */
|
|
41
|
-
getInputState: getState<'input'>;
|
|
42
|
-
changeInputState: changeState<'input'>;
|
|
43
|
-
/* Filter State Controls */
|
|
44
|
-
getFilterState: getState<'filter'>;
|
|
45
|
-
changeFilterState: changeState<'filter'>;
|
|
30
|
+
getControlState: (id: string) => { value: string | boolean; render: boolean } | null;
|
|
31
|
+
changeControlState: (id: string, value: string | boolean) => void;
|
|
46
32
|
/* Utility */
|
|
47
33
|
areConditionsMet: (conditions: CodeWalkthroughConditionsObject) => boolean;
|
|
48
34
|
handleDownloadCode: (files: CodeWalkthroughFile[]) => Promise<void>;
|
|
@@ -60,163 +46,126 @@ export function useCodeWalkthroughControls(
|
|
|
60
46
|
filters: Record<string, CodeWalkthroughFilter>,
|
|
61
47
|
inputs: InputsMarkdocAttr,
|
|
62
48
|
toggles: TogglesMarkdocAttr,
|
|
49
|
+
enableDeepLink: boolean,
|
|
63
50
|
): WalkthroughControlsState {
|
|
64
51
|
const location = useLocation();
|
|
65
52
|
const navigate = useNavigate();
|
|
66
53
|
const searchParams = useMemo(() => new URLSearchParams(location.search), [location.search]);
|
|
67
54
|
|
|
68
|
-
const [
|
|
69
|
-
const initialState:
|
|
55
|
+
const [controlsState, setControlsState] = useState(() => {
|
|
56
|
+
const initialState: CodeWalkthroughControlsState = {};
|
|
57
|
+
|
|
70
58
|
for (const [id, toggle] of Object.entries(toggles)) {
|
|
71
59
|
initialState[id] = {
|
|
72
60
|
...toggle,
|
|
73
61
|
render: true,
|
|
74
62
|
type: 'toggle',
|
|
75
|
-
value: searchParams.get(id) === 'true',
|
|
63
|
+
value: enableDeepLink ? searchParams.get(id) === 'true' : false,
|
|
76
64
|
};
|
|
77
65
|
}
|
|
78
|
-
return initialState;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const changeToggleState = (toggleId: string, checked: boolean) => {
|
|
82
|
-
setTogglesState((prev) => {
|
|
83
|
-
const toggle = prev[toggleId];
|
|
84
|
-
if (toggle) {
|
|
85
|
-
return {
|
|
86
|
-
...prev,
|
|
87
|
-
[toggleId]: {
|
|
88
|
-
...toggle,
|
|
89
|
-
value: checked,
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
} else {
|
|
93
|
-
return prev;
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const getToggleState = (toggleId: string) => {
|
|
99
|
-
const toggleState = togglesState[toggleId];
|
|
100
|
-
|
|
101
|
-
if (toggleState) {
|
|
102
|
-
return {
|
|
103
|
-
render: toggleState.render,
|
|
104
|
-
value: toggleState.value,
|
|
105
|
-
};
|
|
106
|
-
} else {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const [inputsState, setInputsState] = useState(() => {
|
|
112
|
-
const initialState: InputsState = {};
|
|
113
66
|
|
|
114
67
|
for (const [id, input] of Object.entries(inputs)) {
|
|
115
68
|
initialState[id] = {
|
|
116
69
|
...input,
|
|
117
70
|
render: true,
|
|
118
71
|
type: 'input',
|
|
119
|
-
value: searchParams.get(id)
|
|
72
|
+
value: enableDeepLink ? (searchParams.get(id) ?? input.value) : input.value,
|
|
120
73
|
};
|
|
121
74
|
}
|
|
122
|
-
return initialState;
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const changeInputState = (inputId: string, value: string) => {
|
|
126
|
-
setInputsState((prev) => {
|
|
127
|
-
const input = prev[inputId];
|
|
128
|
-
if (input) {
|
|
129
|
-
return {
|
|
130
|
-
...prev,
|
|
131
|
-
[inputId]: {
|
|
132
|
-
...input,
|
|
133
|
-
value,
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
} else {
|
|
137
|
-
return prev;
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const getInputState = (inputId: string) => {
|
|
143
|
-
const inputState = inputsState[inputId];
|
|
144
|
-
|
|
145
|
-
if (inputState) {
|
|
146
|
-
return {
|
|
147
|
-
render: inputState.render,
|
|
148
|
-
value: inputState.value,
|
|
149
|
-
};
|
|
150
|
-
} else {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const [filtersState, setFiltersState] = useState(() => {
|
|
156
|
-
const initialState: FiltersState = {};
|
|
157
75
|
|
|
158
76
|
for (const [id, filter] of Object.entries(filters)) {
|
|
77
|
+
const defaultValue = filter?.items?.[0]?.value || '';
|
|
159
78
|
initialState[id] = {
|
|
160
79
|
...filter,
|
|
161
80
|
render: true,
|
|
162
81
|
type: 'filter',
|
|
163
|
-
value: searchParams.get(id)
|
|
82
|
+
value: enableDeepLink ? (searchParams.get(id) ?? defaultValue) : defaultValue,
|
|
164
83
|
};
|
|
165
84
|
}
|
|
166
85
|
|
|
167
86
|
return initialState;
|
|
168
87
|
});
|
|
169
88
|
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
...prev,
|
|
176
|
-
[filterId]: {
|
|
177
|
-
...filter,
|
|
178
|
-
value,
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
} else {
|
|
89
|
+
const changeControlState = (id: string, value: string | boolean) => {
|
|
90
|
+
setControlsState((prev) => {
|
|
91
|
+
const control = prev[id];
|
|
92
|
+
if (!control) {
|
|
93
|
+
console.error(`Control with id "${id}" not found.`);
|
|
182
94
|
return prev;
|
|
183
95
|
}
|
|
96
|
+
|
|
97
|
+
switch (control.type) {
|
|
98
|
+
case 'input':
|
|
99
|
+
if (typeof value !== 'string') {
|
|
100
|
+
console.error(
|
|
101
|
+
`Invalid value type for input "${id}". Input control type requires a string value.`,
|
|
102
|
+
);
|
|
103
|
+
return prev;
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case 'toggle':
|
|
108
|
+
if (typeof value !== 'boolean') {
|
|
109
|
+
console.error(
|
|
110
|
+
`Invalid value type for toggle "${id}". Toggle control type requires a boolean value.`,
|
|
111
|
+
);
|
|
112
|
+
return prev;
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case 'filter':
|
|
117
|
+
if (typeof value !== 'string') {
|
|
118
|
+
console.error(
|
|
119
|
+
`Invalid value type for filter "${id}". Filter control type requires a string value.`,
|
|
120
|
+
);
|
|
121
|
+
return prev;
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
default:
|
|
126
|
+
console.error(
|
|
127
|
+
`Invalid control type "${(control as { type: string })?.type}" for control "${id}". Allowed types are "toggle", "input", or "filter".`,
|
|
128
|
+
);
|
|
129
|
+
return prev;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
...prev,
|
|
134
|
+
[id]: {
|
|
135
|
+
...control,
|
|
136
|
+
value,
|
|
137
|
+
},
|
|
138
|
+
} as CodeWalkthroughControlsState;
|
|
184
139
|
});
|
|
185
140
|
};
|
|
186
141
|
|
|
187
|
-
const
|
|
188
|
-
const
|
|
142
|
+
const getControlState = (id: string) => {
|
|
143
|
+
const controlState = controlsState[id];
|
|
189
144
|
|
|
190
|
-
if (
|
|
145
|
+
if (controlState) {
|
|
191
146
|
return {
|
|
192
|
-
render:
|
|
193
|
-
value:
|
|
147
|
+
render: controlState.render,
|
|
148
|
+
value: controlState.value,
|
|
194
149
|
};
|
|
195
150
|
} else {
|
|
196
151
|
return null;
|
|
197
152
|
}
|
|
198
153
|
};
|
|
199
154
|
|
|
200
|
-
const state = {
|
|
201
|
-
...filtersState,
|
|
202
|
-
...togglesState,
|
|
203
|
-
...inputsState,
|
|
204
|
-
};
|
|
205
|
-
|
|
206
155
|
const walkthroughContext = useMemo(() => {
|
|
207
156
|
const areConditionsMet = (conditions: CodeWalkthroughConditionsObject) =>
|
|
208
|
-
matchCodeWalkthroughConditions(conditions,
|
|
157
|
+
matchCodeWalkthroughConditions(conditions, controlsState);
|
|
209
158
|
|
|
210
|
-
for (const [id,
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
|
|
159
|
+
for (const [id, control] of Object.entries(controlsState)) {
|
|
160
|
+
if (control && !areConditionsMet(control)) {
|
|
161
|
+
controlsState[id].render = false;
|
|
162
|
+
controlsState[id].value = defaultControlsValues[control.type];
|
|
214
163
|
}
|
|
215
164
|
}
|
|
216
165
|
|
|
217
166
|
const activeFilters = [];
|
|
218
167
|
for (const [id, filter] of Object.entries(filters)) {
|
|
219
|
-
if (!
|
|
168
|
+
if (!controlsState[id].render) {
|
|
220
169
|
continue;
|
|
221
170
|
}
|
|
222
171
|
|
|
@@ -226,18 +175,18 @@ export function useCodeWalkthroughControls(
|
|
|
226
175
|
const activeItems = items.filter((item) => areConditionsMet(item));
|
|
227
176
|
|
|
228
177
|
if (activeItems.length === 0) {
|
|
229
|
-
|
|
230
|
-
|
|
178
|
+
controlsState[id].render = false;
|
|
179
|
+
controlsState[id].value = defaultControlsValues['filter'];
|
|
231
180
|
continue;
|
|
232
181
|
}
|
|
233
182
|
|
|
234
|
-
const currentValue =
|
|
183
|
+
const currentValue = controlsState[id].value;
|
|
235
184
|
if (currentValue) {
|
|
236
185
|
const isValueInActiveItems =
|
|
237
186
|
activeItems.findIndex(({ value }) => value === currentValue) !== -1;
|
|
238
|
-
|
|
187
|
+
controlsState[id].value = isValueInActiveItems ? currentValue : activeItems[0].value;
|
|
239
188
|
} else {
|
|
240
|
-
|
|
189
|
+
controlsState[id].value = activeItems[0].value;
|
|
241
190
|
}
|
|
242
191
|
|
|
243
192
|
activeFilters.push({
|
|
@@ -247,11 +196,15 @@ export function useCodeWalkthroughControls(
|
|
|
247
196
|
});
|
|
248
197
|
}
|
|
249
198
|
|
|
199
|
+
const inputsState = Object.fromEntries(
|
|
200
|
+
Object.entries(controlsState).filter(([_, controlState]) => controlState.type === 'input'),
|
|
201
|
+
) as Record<string, { value: string }>;
|
|
202
|
+
|
|
250
203
|
const handleDownloadCode = (files: CodeWalkthroughFile[]) =>
|
|
251
|
-
downloadCodeWalkthrough(files,
|
|
204
|
+
downloadCodeWalkthrough(files, controlsState, inputsState);
|
|
252
205
|
|
|
253
206
|
const getFileText = (file: CodeWalkthroughFile) =>
|
|
254
|
-
getCodeWalkthroughFileText(file,
|
|
207
|
+
getCodeWalkthroughFileText(file, controlsState, inputsState);
|
|
255
208
|
|
|
256
209
|
const populateInputsWithValue = (node: string) => replaceInputsWithValue(node, inputsState);
|
|
257
210
|
|
|
@@ -262,17 +215,19 @@ export function useCodeWalkthroughControls(
|
|
|
262
215
|
getFileText,
|
|
263
216
|
populateInputsWithValue,
|
|
264
217
|
};
|
|
265
|
-
|
|
266
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
267
|
-
}, [filters, filtersState, togglesState, inputsState]);
|
|
218
|
+
}, [filters, controlsState]);
|
|
268
219
|
|
|
269
220
|
/**
|
|
270
221
|
* Update the URL search params with the current state of the filters and inputs
|
|
271
222
|
*/
|
|
272
223
|
useEffect(() => {
|
|
224
|
+
if (!enableDeepLink) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
273
228
|
const newSearchParams = new URLSearchParams(Array.from(searchParams.entries()));
|
|
274
229
|
|
|
275
|
-
for (const [id, { value }] of Object.entries(
|
|
230
|
+
for (const [id, { value }] of Object.entries(controlsState)) {
|
|
276
231
|
if (value) {
|
|
277
232
|
newSearchParams.set(id, value.toString());
|
|
278
233
|
} else {
|
|
@@ -285,18 +240,11 @@ export function useCodeWalkthroughControls(
|
|
|
285
240
|
navigate({ search: newSearch });
|
|
286
241
|
// Ignore searchParams in dependency array to avoid infinite re-renders
|
|
287
242
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
288
|
-
}, [filters,
|
|
243
|
+
}, [filters, controlsState, navigate, location]);
|
|
289
244
|
|
|
290
245
|
return {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
getToggleState,
|
|
295
|
-
changeToggleState,
|
|
296
|
-
|
|
297
|
-
getFilterState,
|
|
298
|
-
changeFilterState,
|
|
299
|
-
|
|
246
|
+
changeControlState,
|
|
247
|
+
getControlState,
|
|
300
248
|
...walkthroughContext,
|
|
301
249
|
};
|
|
302
250
|
}
|
|
@@ -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';
|
|
@@ -1019,6 +1019,24 @@ const error = css`
|
|
|
1019
1019
|
--detailed-error-message-width: 100%;
|
|
1020
1020
|
--detailed-error-message-font-size: var(--font-size-base);
|
|
1021
1021
|
--detailed-error-message-font-family: var(--code-block-controls-font-family);
|
|
1022
|
+
|
|
1023
|
+
--compilation-error-description-padding: 0 0 var(--spacing-base);
|
|
1024
|
+
--compilation-error-codeframe-margin: var(--spacing-xs) 0 0 0;
|
|
1025
|
+
--compilation-error-codeframe-padding: var(--spacing-xs);
|
|
1026
|
+
--compilation-error-block-padding: var(--spacing-base);
|
|
1027
|
+
--compilation-error-block-margin: var(--spacing-xl) 0;
|
|
1028
|
+
--compilation-error-title-font-size: var(--font-size-xl);
|
|
1029
|
+
--compilation-error-header-font-size: var(--font-size-lg);
|
|
1030
|
+
--compilation-error-text-font-size: var(--font-size-base);
|
|
1031
|
+
--compilation-error-header-padding: var(--spacing-sm);
|
|
1032
|
+
--compilation-error-container-margin: var(--spacing-xxl) auto;
|
|
1033
|
+
--compilation-error-container-padding: var(--spacing-lg);
|
|
1034
|
+
--compilation-error-page-max-width: 800px;
|
|
1035
|
+
--compilation-error-font-family: var(--font-family-base);
|
|
1036
|
+
--compilation-error-fix-instruction-margin: var(--spacing-sm) 0 0 0;
|
|
1037
|
+
--compilation-error-highlighted-text-padding: 0 var(--spacing-xxs);
|
|
1038
|
+
--compilation-error-divider-margin: var(--spacing-base) 0;
|
|
1039
|
+
--compilation-error-file-header-margin: 0 0 var(--spacing-xs) 0;
|
|
1022
1040
|
`;
|
|
1023
1041
|
|
|
1024
1042
|
const modal = css`
|
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'
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { saveAs } from 'file-saver';
|
|
2
2
|
import JSZip from 'jszip';
|
|
3
3
|
|
|
4
|
-
import type { CodeWalkthroughFile
|
|
4
|
+
import type { CodeWalkthroughFile } 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;
|
|
@@ -11,13 +15,21 @@ JSZip.support.nodebuffer = false;
|
|
|
11
15
|
export async function downloadCodeWalkthrough(
|
|
12
16
|
files: CodeWalkthroughFile[],
|
|
13
17
|
state: Record<string, { value: string | boolean }>,
|
|
14
|
-
inputsState:
|
|
18
|
+
inputsState: Record<string, { value: string }>,
|
|
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' });
|