Skip to content

Commit dd9c52c

Browse files
authored
feat: add "Edit this page" link (#130)
1 parent f85e8eb commit dd9c52c

File tree

16 files changed

+128
-9
lines changed

16 files changed

+128
-9
lines changed

docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ type I18nText = {
4848
*/
4949
partTemplate?: string,
5050

51+
/**
52+
* Text of the edit page link.
53+
*
54+
* @default 'Edit this page'
55+
*/
56+
editPageText?: string
57+
5158
/**
5259
* Text shown when there are no previews or steps to show in the prepare environment section.
5360
*
@@ -170,3 +177,30 @@ Navigating to a lesson that specifies `autoReload` will always reload the previe
170177
Specified which folder from the `src/templates/` directory should be used as the basis for the code. See the "[Code templates](/guides/creating-content/#code-templates)" guide for a detailed explainer.
171178
<PropertyTable inherited type="string" />
172179

180+
#### `editPageLink`
181+
Display a link in lesson for editing the page content.
182+
The value is a URL pattern where `${path}` is replaced with the lesson's location relative to the `src/content/tutorial`.
183+
184+
<PropertyTable inherited type="string|false" />
185+
186+
```yaml
187+
editPageLink: https://github.com/stackblitz/tutorialkit/blob/main/packages/template/src/content/tutorial/${path}
188+
```
189+
190+
The inherited value can be disabled in specific parts using `false`.
191+
192+
```yaml
193+
editPageLink: false
194+
```
195+
196+
:::tip
197+
Note that Github will try to automatically render the `.md` files when linked to.
198+
You can instruct Github to show the source code instead by adding `plain=1` query parameter.
199+
200+
```diff
201+
-editPageLink: https://github.com/stackblitz/tutorialkit/blob/main/packages/template/src/content/tutorial/${path}
202+
+editPageLink: https://github.com/stackblitz/tutorialkit/blob/main/packages/template/src/content/tutorial/${path}?plain=1
203+
```
204+
205+
:::
206+
Loading

docs/tutorialkit.dev/src/content/docs/reference/theming.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,20 @@ The navigation cards are the cards at the bottom of a lesson to navigate to the
316316
| `--tk-elements-navCard-iconColor` | The icon color of the navigation card. |
317317
| `--tk-elements-navCard-iconColorHover` | The icon color of the navigation card when hovering. |
318318

319+
### Edit Page Link
320+
321+
The edit page link is shown above the navigation cards when configured by [`editPageLink` option](/reference/configuration/#editpagelink).
322+
323+
![Edit Page Link](./images/theming-editpagelink.png)
324+
325+
| Token | Description |
326+
| ------------------------------------------- | --------------------------------------------------- |
327+
| `--tk-elements-editPageLink-textColor` | The text color of the edit page link |
328+
| `--tk-elements-editPageLink-textColorHover` | The text color of the edit page link when hovering. |
329+
| `--tk-elements-editPageLink-iconColor` | The icon color of the edit page link |
330+
| `--tk-elements-editPageLink-iconColorHover` | The icon color of the edit page link when hovering |
331+
| `--tk-elements-editPageLink-borderColor` | The border color of the edit page link |
332+
319333
### Breadcrumbs
320334

321335
The breadcrumbs are the navigation elements that show the path of the current lesson. The breadcrumbs are divided into multiple parts.

packages/astro/src/default/components/TutorialContent.astro

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,28 @@ interface Props {
88
}
99
1010
const { lesson } = Astro.props;
11-
const { Markdown, prev, next } = lesson;
11+
const { Markdown, editPageLink, prev, next } = lesson;
1212
---
1313

1414
<div class="h-full overflow-auto scrollbar-transparent p-6 sm:p-8">
1515
<div class="markdown-content text-tk-elements-content-textColor">
1616
<Markdown />
1717
</div>
18+
19+
{
20+
editPageLink && (
21+
<div class="pb-4 mt-8 border-b border-tk-elements-editPageLink-borderColor">
22+
<a
23+
href={editPageLink}
24+
class="inline-flex flex-items-center text-tk-elements-editPageLink-textColor hover:text-tk-elements-editPageLink-textColorHover hover:underline"
25+
>
26+
<span class="icon i-ph-note-pencil pointer-events-none h-5 w-5 mr-2 text-tk-elements-editPageLink-iconColor group-hover:text-tk-elements-editPageLink-iconColorHover" />
27+
<span>{lesson.data.i18n!.editPageText}</span>
28+
</a>
29+
</div>
30+
)
31+
}
32+
1833
<div class="grid grid-cols-[1fr_1fr] gap-4 mt-8">
1934
<div class="flex">
2035
{prev && <NavCard lesson={prev} type="prev" />}

packages/astro/src/default/styles/variables.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,13 @@
245245
--tk-elements-navCard-iconColor: var(--tk-elements-app-textColor);
246246
--tk-elements-navCard-iconColorHover: var(--tk-text-accent);
247247

248+
/* Edit Page Link */
249+
--tk-elements-editPageLink-textColor: var(--tk-elements-app-textColor);
250+
--tk-elements-editPageLink-textColorHover: var(--tk-text-active);
251+
--tk-elements-editPageLink-iconColor: var(--tk-elements-app-textColor);
252+
--tk-elements-editPageLink-iconColorHover: var(--tk-text-accent);
253+
--tk-elements-editPageLink-borderColor: var(--tk-border-secondary);
254+
248255
/* Breadcrumb > Nav Button */
249256
--tk-elements-breadcrumbs-navButton-iconColor: var(--tk-text-secondary);
250257
--tk-elements-breadcrumbs-navButton-iconColorHover: var(--tk-text-active);

packages/astro/src/default/utils/content.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
Tutorial,
88
TutorialSchema,
99
} from '@tutorialkit/types';
10-
import { folderPathToFilesRef } from '@tutorialkit/types';
10+
import { folderPathToFilesRef, interpolateString } from '@tutorialkit/types';
1111
import { getCollection } from 'astro:content';
1212
import glob from 'fast-glob';
1313
import path from 'node:path';
@@ -44,6 +44,7 @@ export async function getTutorial(): Promise<Tutorial> {
4444
partTemplate: 'Part ${index}: ${title}',
4545
noPreviewNorStepsText: 'No preview to run nor steps to show',
4646
startWebContainerText: 'Run this tutorial',
47+
editPageText: 'Edit this page',
4748
} satisfies Lesson['data']['i18n'],
4849
tutorialMetaData.i18n,
4950
);
@@ -90,6 +91,7 @@ export async function getTutorial(): Promise<Tutorial> {
9091
const lesson: Lesson = {
9192
data,
9293
id: lessonId,
94+
filepath: id,
9395
order: -1,
9496
part: {
9597
id: partId,
@@ -251,7 +253,18 @@ export async function getTutorial(): Promise<Tutorial> {
251253
...lesson.data,
252254
...squash(
253255
[lesson.data, chapterMetadata, partMetadata, tutorialMetaData],
254-
['mainCommand', 'prepareCommands', 'previews', 'autoReload', 'template', 'terminal', 'editor', 'focus', 'i18n'],
256+
[
257+
'mainCommand',
258+
'prepareCommands',
259+
'previews',
260+
'autoReload',
261+
'template',
262+
'terminal',
263+
'editor',
264+
'focus',
265+
'i18n',
266+
'editPageLink',
267+
],
255268
),
256269
};
257270

@@ -274,9 +287,11 @@ export async function getTutorial(): Promise<Tutorial> {
274287
href: joinPaths(baseURL, `/${partSlug}/${chapterSlug}/${nextLesson.slug}`),
275288
};
276289
}
277-
}
278290

279-
// console.log(inspect(_tutorial, undefined, Infinity, true));
291+
if (lesson.data.editPageLink && typeof lesson.data.editPageLink === 'string') {
292+
lesson.editPageLink = interpolateString(lesson.data.editPageLink, { path: lesson.filepath });
293+
}
294+
}
280295

281296
return _tutorial;
282297
}

packages/components/react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
],
5151
"scripts": {
5252
"build": "node ./scripts/build.js",
53-
"test": "vitest"
53+
"test": "vitest --passWithNoTests"
5454
},
5555
"dependencies": {
5656
"@codemirror/autocomplete": "^6.16.3",

packages/components/react/src/Nav.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import type { Lesson, NavItem, NavList } from '@tutorialkit/types';
1+
import { interpolateString, type Lesson, type NavItem, type NavList } from '@tutorialkit/types';
22
import * as Accordion from '@radix-ui/react-accordion';
33
import navStyles from './styles/nav.module.css';
44
import { classNames } from './utils/classnames.js';
55
import { AnimatePresence, cubicBezier, motion } from 'framer-motion';
66
import { useCallback, useRef, useState } from 'react';
77
import { useOutsideClick } from './hooks/useOutsideClick.js';
8-
import { interpolateString } from './utils/interpolation.js';
98

109
const dropdownEasing = cubicBezier(0.4, 0, 0.2, 1);
1110

packages/components/react/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,3 @@ export * from './Panels/TerminalPanel.js';
66
export * from './Panels/WorkspacePanel.js';
77
export type * from './core/types.js';
88
export * from './utils/classnames.js';
9-
export * from './utils/interpolation.js';

packages/theme/src/theme.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,13 @@ export const theme = {
252252
iconColor: 'var(--tk-elements-navCard-iconColor)',
253253
iconColorHover: 'var(--tk-elements-navCard-iconColorHover)',
254254
},
255+
editPageLink: {
256+
textColor: 'var(--tk-elements-editPageLink-textColor)',
257+
textColorHover: 'var(--tk-elements-editPageLink-textColorHover)',
258+
iconColor: 'var(--tk-elements-editPageLink-iconColor)',
259+
iconColorHover: 'var(--tk-elements-editPageLink-iconColorHover)',
260+
borderColor: 'var(--tk-elements-editPageLink-borderColor)',
261+
},
255262
breadcrumbs: {
256263
navButton: {
257264
iconColor: 'var(--tk-elements-breadcrumbs-navButton-iconColor)',

packages/types/src/entities/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export interface Lesson<T = unknown> {
4040
part: { id: string; title: string };
4141
chapter: { id: string; title: string };
4242
slug: string;
43+
filepath: string;
44+
editPageLink?: string;
4345
files: FilesRefList;
4446
solution: FilesRefList;
4547
next?: LessonLink;

packages/types/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export type * from './entities/index.js';
22
export * from './schemas/index.js';
33
export * from './files-ref.js';
4+
export { interpolateString } from './utils/interpolation.js';

packages/types/src/schemas/common.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,15 @@ export const webcontainerSchema = commandsSchema.extend({
151151
}),
152152
]),
153153
i18n: i18nSchema.optional(),
154+
editPageLink: z
155+
.union([
156+
// pattern for creating the URL
157+
z.string(),
158+
159+
// `false` for disabling the edit link
160+
z.boolean(),
161+
])
162+
.optional(),
154163
});
155164

156165
export const baseSchema = webcontainerSchema.extend({

packages/types/src/schemas/i18n.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ export const i18nSchema = z.object({
88
*/
99
partTemplate: z.string().optional(),
1010

11+
/**
12+
* Text of the edit page link.
13+
*
14+
* @default 'Edit this page'
15+
*/
16+
editPageText: z.string().optional(),
17+
1118
/**
1219
* Text shown when there are no previews or steps to show in the prepare environment section.
1320
*

packages/components/react/src/utils/interpolation.spec.ts renamed to packages/types/src/utils/interpolation.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ describe('interpolateString', () => {
99
expect(interpolateString(template, variables)).toBe('Hello, world!');
1010
});
1111

12+
it('should interpolate variables inside URL', () => {
13+
const template =
14+
'https://github.com/stackblitz/tutorialkit/blob/main/packages/template/src/content/tutorial/${path}?plain=1';
15+
const variables = { path: '1-basics/2-foo/1-welcome/content.md' };
16+
17+
expect(interpolateString(template, variables)).toBe(
18+
'https://github.com/stackblitz/tutorialkit/blob/main/packages/template/src/content/tutorial/1-basics/2-foo/1-welcome/content.md?plain=1',
19+
);
20+
});
21+
1222
it('should interpolate multiple variables', () => {
1323
const template = 'Part ${index}: ${title}';
1424
const variables = { index: 5, title: 'Welcome to foo bar' };

0 commit comments

Comments
 (0)