Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,15 @@ You can instruct Github to show the source code instead by adding `plain=1` quer

:::

### `openInStackBlitzLink`
### `openInStackBlitz`
Display a link for opening current lesson in StackBlitz.
<PropertyTable inherited type="boolean" />
<PropertyTable inherited type="OpenInStackBlitz" />

The `OpenInStackBlitz` type has the following shape:

```ts
type OpenInStackBlitz = string
| boolean
| { projectTitle?: string, projectDescription?: string }

```
10 changes: 6 additions & 4 deletions packages/astro/src/default/components/OpenInStackblitzLink.astro
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import StackBlitzSDK from '@stackblitz/sdk';
import { tutorialStore } from '../components/webcontainer.js';

// initialize handlers on each page load as it's possible some pages disable openInStackBlitzLink
// initialize handlers on each page load as it's possible some pages disable openInStackBlitz
document.addEventListener('astro:page-load', onInit);

function onInit() {
Expand All @@ -23,15 +23,17 @@

function onClick() {
const lesson = tutorialStore.lesson;
const snapshot = tutorialStore.takeSnapshot();

if (!lesson) {
throw new Error('Missing lesson');
}

const snapshot = tutorialStore.takeSnapshot();
const options = typeof lesson.data.openInStackBlitz === 'object' ? lesson.data.openInStackBlitz : {};

StackBlitzSDK.openProject({
title: lesson.data.title || 'Lesson',
description: `${lesson.part.title} / ${lesson.chapter.title}`,
title: options.projectTitle || 'Project generated by TutorialKit',
description: options.projectDescription,
template: 'node',
files: snapshot.files,
});
Expand Down
7 changes: 4 additions & 3 deletions packages/astro/src/default/components/TopBarWrapper.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import { TopBar } from 'tutorialkit:override-components';
import type { Lesson } from '@tutorialkit/types';
import { ThemeSwitch } from './ThemeSwitch';
import { LoginButton } from './LoginButton';
import OpenInStackblitzLink from './OpenInStackblitzLink.astro';
Expand All @@ -8,16 +9,16 @@ import { useAuth } from './setup';

interface Props {
logoLink: string;
openInStackBlitzLink: boolean | undefined;
openInStackBlitz: Lesson['data']['openInStackBlitz'];
}

const { logoLink, openInStackBlitzLink: showOpenInStackBlitzLink } = Astro.props;
const { logoLink, openInStackBlitz } = Astro.props;
---

<TopBar>
<Logo slot="logo" logoLink={logoLink ?? '/'} />

{showOpenInStackBlitzLink && <OpenInStackblitzLink slot="open-in-stackblitz-link" />}
{openInStackBlitz && <OpenInStackblitzLink slot="open-in-stackblitz-link" />}

<ThemeSwitch client:load transition:persist slot="theme-switch" />

Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/default/pages/[...slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const { lesson, logoLink, navList, title } = Astro.props as Props;
<PageLoadingIndicator />
<div id="previews-container"></div>
<main class="max-w-full flex flex-col h-full overflow-hidden" data-swap-root>
<TopBarWrapper logoLink={logoLink ?? '/'} openInStackBlitzLink={lesson.data.openInStackBlitzLink} />
<TopBarWrapper logoLink={logoLink ?? '/'} openInStackBlitz={lesson.data.openInStackBlitz} />
<MainContainer lesson={lesson} navList={navList} />
</main>
</Layout>
4 changes: 2 additions & 2 deletions packages/astro/src/default/utils/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export async function getTutorial(): Promise<Tutorial> {
// default template if not specified
tutorialMetaData.template ??= 'default';
tutorialMetaData.i18n = Object.assign({ ...DEFAULT_LOCALIZATION }, tutorialMetaData.i18n);
tutorialMetaData.openInStackBlitzLink ??= true;
tutorialMetaData.openInStackBlitz ??= true;

_tutorial.logoLink = data.logoLink;
} else if (type === 'part') {
Expand Down Expand Up @@ -258,7 +258,7 @@ export async function getTutorial(): Promise<Tutorial> {
'focus',
'i18n',
'editPageLink',
'openInStackBlitzLink',
'openInStackBlitz',
],
),
};
Expand Down
14 changes: 10 additions & 4 deletions packages/runtime/src/tutorial-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class TutorialRunner {

// this strongly assumes that there's a single package json which might not be true
private _packageJsonContent = '';
private _packageJsonPath = '';

constructor(
private _webcontainer: Promise<WebContainer>,
Expand Down Expand Up @@ -188,7 +189,7 @@ export class TutorialRunner {
this._currentTemplate = { ...template };
this._currentFiles = { ...files };

this._updateDirtyState(files);
this._updateDirtyState({ ...template, ...files });
},
{ ignoreCancel: true, signal },
);
Expand Down Expand Up @@ -325,11 +326,11 @@ export class TutorialRunner {
}
}

if (files['package.json']) {
if (this._packageJsonContent) {
let packageJson;

try {
packageJson = JSON.parse(files['package.json']);
packageJson = JSON.parse(this._packageJsonContent);
} catch {}

// add start commands when missing
Expand All @@ -338,7 +339,11 @@ export class TutorialRunner {
const prepareCommands = (this._currentRunCommands?.prepareCommands || []).map((c) => c.shellCommand);
const startCommand = [...prepareCommands, mainCommand].filter(Boolean).join(' && ');

files['package.json'] = JSON.stringify({ ...packageJson, stackblitz: { startCommand } }, null, 2);
files[this._packageJsonPath.slice(1)] = JSON.stringify(
{ ...packageJson, stackblitz: { startCommand } },
null,
2,
);
}
}

Expand Down Expand Up @@ -460,6 +465,7 @@ export class TutorialRunner {
for (const filePath in files) {
if (filePath.endsWith('/package.json') && files[filePath] != this._packageJsonContent) {
this._packageJsonContent = files[filePath] as string;
this._packageJsonPath = filePath;
this._packageJsonDirty = true;

return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
type: chapter
title: The second chapter in part 1
openInStackBlitz: true
---
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
type: chapter
title: The first chatper in part 2
openInStackBlitzLink: false
openInStackBlitz: false
---
3 changes: 3 additions & 0 deletions packages/template/src/content/tutorial/meta.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ prepareCommands:
- ['npm install', 'Installing dependencies']
i18n:
partTemplate: ${title}
openInStackBlitz:
projectTitle: Example Title
projectDescription: Example Description
---
13 changes: 12 additions & 1 deletion packages/types/src/schemas/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,18 @@ export const webcontainerSchema = commandsSchema.extend({
.describe(
'Display a link in lesson for editing the page content. The value is a URL pattern where `${path}` is replaced with the lesson’s location relative to `src/content/tutorial`.',
),
openInStackBlitzLink: z.boolean().optional().describe('Display a link for opening current lesson in StackBlitz.'),
openInStackBlitz: z
.union([
// `false` for disabling the link
z.boolean(),

z.strictObject({
projectTitle: z.string().optional(),
projectDescription: z.string().optional(),
}),
])
.optional()
.describe('Display a link for opening current lesson in StackBlitz.'),
});

export const baseSchema = webcontainerSchema.extend({
Expand Down