@theia/getting-started 1.75.0-next.21 → 1.75.0-next.28

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 (86) hide show
  1. package/lib/browser/getting-started-contribution.d.ts +30 -2
  2. package/lib/browser/getting-started-contribution.d.ts.map +1 -1
  3. package/lib/browser/getting-started-contribution.js +120 -0
  4. package/lib/browser/getting-started-contribution.js.map +1 -1
  5. package/lib/browser/getting-started-contribution.spec.d.ts +2 -0
  6. package/lib/browser/getting-started-contribution.spec.d.ts.map +1 -0
  7. package/lib/browser/getting-started-contribution.spec.js +152 -0
  8. package/lib/browser/getting-started-contribution.spec.js.map +1 -0
  9. package/lib/browser/getting-started-frontend-module.d.ts.map +1 -1
  10. package/lib/browser/getting-started-frontend-module.js +8 -0
  11. package/lib/browser/getting-started-frontend-module.js.map +1 -1
  12. package/lib/browser/getting-started-widget.d.ts +19 -0
  13. package/lib/browser/getting-started-widget.d.ts.map +1 -1
  14. package/lib/browser/getting-started-widget.js +57 -2
  15. package/lib/browser/getting-started-widget.js.map +1 -1
  16. package/lib/browser/getting-started-widget.spec.d.ts +2 -0
  17. package/lib/browser/getting-started-widget.spec.d.ts.map +1 -0
  18. package/lib/browser/getting-started-widget.spec.js +71 -0
  19. package/lib/browser/getting-started-widget.spec.js.map +1 -0
  20. package/lib/browser/walkthrough-card.d.ts +8 -0
  21. package/lib/browser/walkthrough-card.d.ts.map +1 -0
  22. package/lib/browser/walkthrough-card.js +40 -0
  23. package/lib/browser/walkthrough-card.js.map +1 -0
  24. package/lib/browser/walkthrough-card.spec.d.ts +2 -0
  25. package/lib/browser/walkthrough-card.spec.d.ts.map +1 -0
  26. package/lib/browser/walkthrough-card.spec.js +188 -0
  27. package/lib/browser/walkthrough-card.spec.js.map +1 -0
  28. package/lib/browser/walkthrough-detail.d.ts +19 -0
  29. package/lib/browser/walkthrough-detail.d.ts.map +1 -0
  30. package/lib/browser/walkthrough-detail.js +210 -0
  31. package/lib/browser/walkthrough-detail.js.map +1 -0
  32. package/lib/browser/walkthrough-detail.spec.d.ts +2 -0
  33. package/lib/browser/walkthrough-detail.spec.d.ts.map +1 -0
  34. package/lib/browser/walkthrough-detail.spec.js +409 -0
  35. package/lib/browser/walkthrough-detail.spec.js.map +1 -0
  36. package/lib/browser/walkthrough-icon.d.ts +10 -0
  37. package/lib/browser/walkthrough-icon.d.ts.map +1 -0
  38. package/lib/browser/walkthrough-icon.js +37 -0
  39. package/lib/browser/walkthrough-icon.js.map +1 -0
  40. package/lib/browser/walkthrough-section.d.ts +23 -0
  41. package/lib/browser/walkthrough-section.d.ts.map +1 -0
  42. package/lib/browser/walkthrough-section.js +65 -0
  43. package/lib/browser/walkthrough-section.js.map +1 -0
  44. package/lib/browser/walkthrough-section.spec.d.ts +2 -0
  45. package/lib/browser/walkthrough-section.spec.d.ts.map +1 -0
  46. package/lib/browser/walkthrough-section.spec.js +328 -0
  47. package/lib/browser/walkthrough-section.spec.js.map +1 -0
  48. package/lib/browser/walkthrough-service.d.ts +166 -0
  49. package/lib/browser/walkthrough-service.d.ts.map +1 -0
  50. package/lib/browser/walkthrough-service.js +505 -0
  51. package/lib/browser/walkthrough-service.js.map +1 -0
  52. package/lib/browser/walkthrough-service.spec.d.ts +2 -0
  53. package/lib/browser/walkthrough-service.spec.d.ts.map +1 -0
  54. package/lib/browser/walkthrough-service.spec.js +1026 -0
  55. package/lib/browser/walkthrough-service.spec.js.map +1 -0
  56. package/lib/common/getting-started-preferences.d.ts +1 -0
  57. package/lib/common/getting-started-preferences.d.ts.map +1 -1
  58. package/lib/common/getting-started-preferences.js +5 -0
  59. package/lib/common/getting-started-preferences.js.map +1 -1
  60. package/lib/common/walkthrough-commands.d.ts +11 -0
  61. package/lib/common/walkthrough-commands.d.ts.map +1 -0
  62. package/lib/common/walkthrough-commands.js +41 -0
  63. package/lib/common/walkthrough-commands.js.map +1 -0
  64. package/lib/common/walkthrough-types.d.ts +22 -0
  65. package/lib/common/walkthrough-types.d.ts.map +1 -0
  66. package/lib/common/walkthrough-types.js +18 -0
  67. package/lib/common/walkthrough-types.js.map +1 -0
  68. package/package.json +8 -7
  69. package/src/browser/getting-started-contribution.spec.ts +188 -0
  70. package/src/browser/getting-started-contribution.ts +139 -3
  71. package/src/browser/getting-started-frontend-module.ts +8 -0
  72. package/src/browser/getting-started-widget.spec.tsx +88 -0
  73. package/src/browser/getting-started-widget.tsx +70 -3
  74. package/src/browser/style/index.css +279 -0
  75. package/src/browser/walkthrough-card.spec.tsx +247 -0
  76. package/src/browser/walkthrough-card.tsx +62 -0
  77. package/src/browser/walkthrough-detail.spec.tsx +675 -0
  78. package/src/browser/walkthrough-detail.tsx +347 -0
  79. package/src/browser/walkthrough-icon.tsx +42 -0
  80. package/src/browser/walkthrough-section.spec.tsx +484 -0
  81. package/src/browser/walkthrough-section.tsx +119 -0
  82. package/src/browser/walkthrough-service.spec.ts +1279 -0
  83. package/src/browser/walkthrough-service.ts +565 -0
  84. package/src/common/getting-started-preferences.ts +6 -0
  85. package/src/common/walkthrough-commands.ts +37 -0
  86. package/src/common/walkthrough-types.ts +39 -0
@@ -0,0 +1,347 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { codicon } from '@theia/core/lib/browser';
18
+ import { MarkdownRenderer, MarkdownRenderOptions, MarkdownRenderResult } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
19
+ import { ThemeService } from '@theia/core/lib/browser/theming';
20
+ import { MarkdownString } from '@theia/core/lib/common/markdown-rendering/markdown-string';
21
+ import { DisposableCollection } from '@theia/core/lib/common/disposable';
22
+ import { nls } from '@theia/core/lib/common/nls';
23
+ import { ILogger } from '@theia/core/lib/common/logger';
24
+ import { ThemeType } from '@theia/core/lib/common/theme';
25
+ import * as React from '@theia/core/shared/react';
26
+ import { Walkthrough, WalkthroughStep } from '../common/walkthrough-types';
27
+ import { PluginSharedStyle } from '@theia/plugin-ext/lib/main/browser/plugin-shared-style';
28
+ import { WalkthroughIcon } from './walkthrough-icon';
29
+
30
+ export interface WalkthroughDetailProps {
31
+ walkthrough: Walkthrough;
32
+ onStepSelect: (step: WalkthroughStep) => void;
33
+ onBack: () => void;
34
+ selectedStep?: WalkthroughStep;
35
+ markdownRenderer: MarkdownRenderer;
36
+ onLinkClick?: (url: string) => void;
37
+ onToggleStepDone?: (step: WalkthroughStep) => void;
38
+ onMarkAllStepsDone?: () => void;
39
+ themeService: ThemeService;
40
+ logger: ILogger;
41
+ }
42
+
43
+ export function WalkthroughDetail(props: WalkthroughDetailProps): React.ReactElement {
44
+ const { walkthrough, onStepSelect, onBack, selectedStep, markdownRenderer, themeService } = props;
45
+
46
+ return (
47
+ <div className='gs-walkthrough-detail'>
48
+ <div className='gs-walkthrough-detail-header'>
49
+ <a
50
+ role='button'
51
+ tabIndex={0}
52
+ className='gs-walkthrough-back-link'
53
+ onClick={onBack}
54
+ onKeyDown={(e: React.KeyboardEvent) => {
55
+ if (e.key === 'Enter') {
56
+ onBack();
57
+ }
58
+ }}
59
+ >
60
+ <i className={codicon('arrow-left')}></i>
61
+ <span className='gs-walkthrough-back-label'>{nls.localizeByDefault('Back')}</span>
62
+ </a>
63
+ <h2 className='gs-walkthrough-detail-title'>
64
+ <WalkthroughIcon walkthrough={walkthrough} />
65
+ {walkthrough.title}
66
+ </h2>
67
+ <p className='gs-walkthrough-detail-description'>{walkthrough.description}</p>
68
+ </div>
69
+ <div className='gs-walkthrough-detail-body'>
70
+ <div className='gs-walkthrough-steps'>
71
+ {walkthrough.steps.map(step => (
72
+ <WalkthroughStepItem
73
+ key={step.id}
74
+ step={step}
75
+ isSelected={selectedStep?.id === step.id}
76
+ onSelect={onStepSelect}
77
+ onToggleDone={props.onToggleStepDone}
78
+ />
79
+ ))}
80
+ {props.onMarkAllStepsDone && (
81
+ <button
82
+ className='theia-button secondary gs-walkthrough-mark-all-done'
83
+ disabled={walkthrough.steps.every(step => step.isComplete)}
84
+ onClick={props.onMarkAllStepsDone}
85
+ >
86
+ {nls.localizeByDefault('Mark Done')}
87
+ </button>
88
+ )}
89
+ </div>
90
+ {selectedStep && (
91
+ <div className='gs-walkthrough-step-content'>
92
+ <WalkthroughStepContent
93
+ step={selectedStep}
94
+ markdownRenderer={markdownRenderer}
95
+ onLinkClick={props.onLinkClick}
96
+ themeService={themeService}
97
+ logger={props.logger}
98
+ />
99
+ </div>
100
+ )}
101
+ </div>
102
+ </div>
103
+ );
104
+ }
105
+
106
+ interface WalkthroughStepItemProps {
107
+ step: WalkthroughStep;
108
+ isSelected: boolean;
109
+ onSelect: (step: WalkthroughStep) => void;
110
+ onToggleDone?: (step: WalkthroughStep) => void;
111
+ }
112
+
113
+ function WalkthroughStepItem(props: WalkthroughStepItemProps): React.ReactElement {
114
+ const { step, isSelected, onSelect, onToggleDone } = props;
115
+ const iconClass = step.isComplete ? codicon('pass-filled') : codicon('circle-large-outline');
116
+ const toggleDone = (event: React.SyntheticEvent) => {
117
+ // Completing a step is a separate action from opening it.
118
+ event.stopPropagation();
119
+ onToggleDone?.(step);
120
+ };
121
+
122
+ return (
123
+ <div
124
+ className={`gs-walkthrough-step-item ${isSelected ? 'selected' : ''} ${step.isComplete ? 'completed' : ''}`}
125
+ role='button'
126
+ tabIndex={0}
127
+ onClick={() => onSelect(step)}
128
+ onKeyDown={(e: React.KeyboardEvent) => {
129
+ if (e.key === 'Enter') {
130
+ onSelect(step);
131
+ }
132
+ }}
133
+ >
134
+ <span
135
+ className={`gs-walkthrough-step-icon ${iconClass}`}
136
+ role={onToggleDone ? 'checkbox' : undefined}
137
+ aria-checked={onToggleDone ? step.isComplete : undefined}
138
+ tabIndex={onToggleDone ? 0 : undefined}
139
+ title={onToggleDone
140
+ ? (step.isComplete
141
+ ? nls.localize('theia/getting-started/markStepUndone', 'Mark Undone')
142
+ : nls.localizeByDefault('Mark Done'))
143
+ : undefined}
144
+ onClick={onToggleDone && toggleDone}
145
+ onKeyDown={onToggleDone && ((e: React.KeyboardEvent) => {
146
+ if (e.key === 'Enter' || e.key === ' ') {
147
+ toggleDone(e);
148
+ }
149
+ })}
150
+ ></span>
151
+ <span className='gs-walkthrough-step-title'>{step.title}</span>
152
+ </div>
153
+ );
154
+ }
155
+
156
+ interface WalkthroughStepContentProps {
157
+ step: WalkthroughStep;
158
+ markdownRenderer: MarkdownRenderer;
159
+ onLinkClick?: (url: string) => void;
160
+ themeService: ThemeService;
161
+ logger: ILogger;
162
+ }
163
+
164
+ function WalkthroughStepContent(props: WalkthroughStepContentProps): React.ReactElement {
165
+ const { step, markdownRenderer, themeService } = props;
166
+
167
+ return (
168
+ <div className='gs-walkthrough-step-detail'>
169
+ <h3>{step.title}</h3>
170
+ <WalkthroughDescriptionContent
171
+ description={step.description}
172
+ markdownRenderer={markdownRenderer}
173
+ onLinkClick={props.onLinkClick}
174
+ />
175
+ {step.media && renderMedia(step.media, markdownRenderer, themeService, props.logger, props.onLinkClick)}
176
+ </div>
177
+ );
178
+ }
179
+
180
+ interface WalkthroughDescriptionContentProps {
181
+ description: string;
182
+ markdownRenderer: MarkdownRenderer;
183
+ onLinkClick?: (url: string) => void;
184
+ }
185
+
186
+ function WalkthroughDescriptionContent(props: WalkthroughDescriptionContentProps): React.ReactElement {
187
+ // eslint-disable-next-line no-null/no-null
188
+ const containerRef = React.useRef<HTMLDivElement>(null);
189
+ const renderResultRef = React.useRef<MarkdownRenderResult | undefined>();
190
+ const disposablesRef = React.useRef<DisposableCollection | undefined>();
191
+ const onLinkClickRef = React.useRef(props.onLinkClick);
192
+ onLinkClickRef.current = props.onLinkClick;
193
+
194
+ React.useEffect(() => {
195
+ if (!containerRef.current) { return; }
196
+ renderResultRef.current?.dispose();
197
+ disposablesRef.current?.dispose();
198
+
199
+ const options = createMarkdownOptions(onLinkClickRef, disposablesRef);
200
+ const result = props.markdownRenderer.render(toTrustedMarkdown(toMarkdownWithLineBreaks(props.description)), options);
201
+ renderResultRef.current = result;
202
+ containerRef.current.innerHTML = '';
203
+ containerRef.current.appendChild(result.element);
204
+
205
+ return () => {
206
+ renderResultRef.current?.dispose();
207
+ disposablesRef.current?.dispose();
208
+ };
209
+ }, [props.description, props.markdownRenderer]);
210
+
211
+ return <div className='gs-walkthrough-step-description' ref={containerRef} />;
212
+ }
213
+
214
+ /**
215
+ * Walkthrough content is rendered as trusted Markdown so that the `command:` links it relies on survive
216
+ * rendering. It comes from an installed plugin, which can execute commands through its own API anyway.
217
+ */
218
+ function toTrustedMarkdown(value: string): MarkdownString {
219
+ return { value, isTrusted: true };
220
+ }
221
+
222
+ /**
223
+ * Routes link activation to `onLinkClick`. Without an action handler the Monaco based renderer clears every
224
+ * `href` and installs no click handling, which leaves the links of the rendered content inert.
225
+ */
226
+ function createMarkdownOptions(
227
+ onLinkClickRef: React.MutableRefObject<((url: string) => void) | undefined>,
228
+ disposablesRef: React.MutableRefObject<DisposableCollection | undefined>
229
+ ): MarkdownRenderOptions | undefined {
230
+ if (!onLinkClickRef.current) {
231
+ return undefined;
232
+ }
233
+ const disposables = new DisposableCollection();
234
+ disposablesRef.current = disposables;
235
+ return {
236
+ actionHandler: {
237
+ callback: (content: string) => onLinkClickRef.current?.(content),
238
+ disposables
239
+ }
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Turn the single newlines that walkthrough step descriptions use for line breaks into Markdown hard breaks.
245
+ *
246
+ * VS Code renders every line of a step description as its own block, while Markdown collapses single
247
+ * newlines into spaces. Blank lines are left alone so that explicit paragraphs still work.
248
+ */
249
+ function toMarkdownWithLineBreaks(description: string): string {
250
+ return description.replace(/(?<!\n)\n(?!\n)/g, ' \n');
251
+ }
252
+
253
+ function renderMedia(
254
+ media: WalkthroughStep['media'],
255
+ markdownRenderer: MarkdownRenderer,
256
+ themeService: ThemeService,
257
+ logger: ILogger,
258
+ onLinkClick?: (url: string) => void
259
+ ): React.ReactNode {
260
+ if (!media) {
261
+ return undefined;
262
+ }
263
+ if ('markdown' in media) {
264
+ return <WalkthroughMedia src={media.markdown} markdownRenderer={markdownRenderer} logger={logger} onLinkClick={onLinkClick} />;
265
+ }
266
+ if ('svg' in media) {
267
+ return <WalkthroughMediaImage src={media.svg} altText={media.altText || ''} themeService={themeService} />;
268
+ }
269
+ if ('image' in media) {
270
+ return <WalkthroughMediaImage src={media.image} altText={media.altText || ''} themeService={themeService} />;
271
+ }
272
+ return undefined;
273
+ }
274
+
275
+ type WalkthroughMediaImageSource = string | { dark: string; light: string; hc: string; hcLight: string };
276
+
277
+ function pickThemeVariant(source: WalkthroughMediaImageSource, themeType: ThemeType): string {
278
+ if (typeof source === 'string') {
279
+ return source;
280
+ }
281
+ switch (themeType) {
282
+ case 'light': return source.light;
283
+ case 'hc': return source.hc;
284
+ case 'hcLight': return source.hcLight;
285
+ default: return source.dark;
286
+ }
287
+ }
288
+
289
+ function WalkthroughMediaImage(props: { src: WalkthroughMediaImageSource, altText: string, themeService: ThemeService }): React.ReactElement | undefined {
290
+ const { themeService } = props;
291
+ const [themeType, setThemeType] = React.useState<ThemeType>(() => themeService.getCurrentTheme().type);
292
+ const [failed, setFailed] = React.useState(false);
293
+
294
+ React.useEffect(() => {
295
+ const disposable = themeService.onDidColorThemeChange(event => setThemeType(event.newTheme.type));
296
+ return () => disposable.dispose();
297
+ }, [themeService]);
298
+
299
+ const src = PluginSharedStyle.toExternalIconUrl(pickThemeVariant(props.src, themeType));
300
+ React.useEffect(() => setFailed(false), [src]);
301
+
302
+ if (failed) {
303
+ // A missing image must not leave a broken image placeholder behind.
304
+ return undefined;
305
+ }
306
+ return <img className='gs-walkthrough-media-image' src={src} alt={props.altText} onError={() => setFailed(true)} />;
307
+ }
308
+
309
+ function WalkthroughMedia(props: {
310
+ src: string;
311
+ markdownRenderer: MarkdownRenderer;
312
+ logger: ILogger;
313
+ onLinkClick?: (url: string) => void;
314
+ }): React.ReactElement {
315
+ // eslint-disable-next-line no-null/no-null
316
+ const containerRef = React.useRef<HTMLDivElement>(null);
317
+ const renderResultRef = React.useRef<MarkdownRenderResult | undefined>();
318
+ const disposablesRef = React.useRef<DisposableCollection | undefined>();
319
+ const onLinkClickRef = React.useRef(props.onLinkClick);
320
+ onLinkClickRef.current = props.onLinkClick;
321
+
322
+ React.useEffect(() => {
323
+ let cancelled = false;
324
+ fetch(PluginSharedStyle.toExternalIconUrl(props.src))
325
+ .then(response => !cancelled && response.ok ? response.text() : '')
326
+ .then(text => {
327
+ if (!cancelled && containerRef.current && text) {
328
+ renderResultRef.current?.dispose();
329
+ disposablesRef.current?.dispose();
330
+ const options = createMarkdownOptions(onLinkClickRef, disposablesRef);
331
+ const result = props.markdownRenderer.render(toTrustedMarkdown(text), options);
332
+ renderResultRef.current = result;
333
+ containerRef.current.innerHTML = '';
334
+ containerRef.current.appendChild(result.element);
335
+ }
336
+ })
337
+ .catch(error => props.logger.warn(`Could not load the walkthrough media '${props.src}'.`, error));
338
+ return () => {
339
+ cancelled = true;
340
+ renderResultRef.current?.dispose();
341
+ renderResultRef.current = undefined;
342
+ disposablesRef.current?.dispose();
343
+ };
344
+ }, [props.src, props.markdownRenderer]);
345
+
346
+ return <div className='gs-walkthrough-media-markdown' ref={containerRef} />;
347
+ }
@@ -0,0 +1,42 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 EclipseSource and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { codicon } from '@theia/core/lib/browser';
18
+ import * as React from '@theia/core/shared/react';
19
+ import { PluginSharedStyle } from '@theia/plugin-ext/lib/main/browser/plugin-shared-style';
20
+ import { Walkthrough } from '../common/walkthrough-types';
21
+
22
+ /**
23
+ * Renders the icon of a walkthrough: the codicon it contributes itself if there is one, otherwise the icon
24
+ * of the extension it comes from. Renders nothing when neither is available.
25
+ */
26
+ export function WalkthroughIcon(props: { walkthrough: Walkthrough }): React.ReactElement | undefined {
27
+ const { walkthrough } = props;
28
+ const [failed, setFailed] = React.useState(false);
29
+
30
+ if (walkthrough.icon) {
31
+ return <span className={`gs-walkthrough-icon ${codicon(walkthrough.icon)}`}></span>;
32
+ }
33
+ if (walkthrough.pluginIcon && !failed) {
34
+ return <img
35
+ className='gs-walkthrough-icon gs-walkthrough-plugin-icon'
36
+ src={PluginSharedStyle.toExternalIconUrl(walkthrough.pluginIcon)}
37
+ alt=''
38
+ onError={() => setFailed(true)}
39
+ />;
40
+ }
41
+ return undefined;
42
+ }