Skip to content

Commit c9472c3

Browse files
authored
Merge branch 'nuxt:main' into main
2 parents 6a3cb4d + 9862b79 commit c9472c3

File tree

66 files changed

+6598
-6916
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+6598
-6916
lines changed

.github/workflows/nuxthub.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Deploy to NuxtHub
2+
on: push
3+
4+
jobs:
5+
deploy:
6+
name: Deploy to NuxtHub
7+
runs-on: ubuntu-latest
8+
environment:
9+
name: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }}
10+
url: ${{ steps.deploy.outputs.deployment-url }}
11+
permissions:
12+
contents: read
13+
id-token: write
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Install pnpm
19+
uses: pnpm/action-setup@v4
20+
21+
- name: Install Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: 22
25+
cache: pnpm
26+
27+
- name: Install dependencies
28+
run: pnpm install
29+
30+
- name: Ensure NuxtHub module is installed
31+
run: pnpx nuxthub@latest ensure
32+
33+
- name: Build application
34+
run: pnpm build
35+
36+
- name: Deploy to NuxtHub
37+
uses: nuxt-hub/action@v1
38+
id: deploy
39+
with:
40+
project-key: learn-nuxt-com-5jju

.vscode/extensions.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"recommendations": [
3+
"antfu.pnpm-catalog-lens",
4+
"dbaeumer.vscode-eslint",
5+
"vue.volar"
6+
]
7+
}

.vscode/settings.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
{
2-
// Enable the ESlint flat config support
3-
"eslint.experimental.useFlatConfig": true,
4-
52
// Disable the default formatter, use eslint instead
63
"prettier.enable": false,
74
"editor.formatOnSave": false,

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@
55
66
An interactive tutorial and playground for learning Nuxt. Powered by [Nuxt](https://nuxt.com/docs) and [WebContainers](https://webcontainers.io/).
77

8-
Inspired by [learn.svelte.dev](https://learn.svelte.dev).
8+
[📖 learn.nuxt.com](https://learn.nuxt.com).
99

10-
`learn.nuxt.com` is not yet deployed, but you can preview the latest branch at [learn-dev.nuxt.com](https://learn-dev.nuxt.com).
10+
> Inspired by [learn.svelte.dev](https://learn.svelte.dev).
1111
12-
## Live Streaming
12+
## Project Development Process
1313

14-
Anthony Fu built this project from scratch on Live Streaming. You can watch the recordings of the full process on [YouTube](https://www.youtube.com/playlist?list=PL4ETc_mXFfxUGiY852jH3ctljnI2e9Rax).
14+
Anthony Fu is building this project from scratch on Live Streaming.
15+
You can watch the recordings of the full process on [YouTube](https://www.youtube.com/playlist?list=PL4ETc_mXFfxUGiY852jH3ctljnI2e9Rax).
1516

1617
## Contributing
1718

1819
### Development
1920

20-
To run this project locally, you need to have [Node.js v20.0+](https://nodejs.org/en/) and [pnpm](https://pnpm.io/) installed.
21+
To run this project locally, you need to have [Node.js v22.0+](https://nodejs.org/en/) and [pnpm](https://pnpm.io/) installed.
2122

2223
After cloning the repo, run the following commands to install dependencies:
2324

components/ContentNavCard.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defineProps<{
44
icon?: string
55
title?: string
66
subheader: string
7-
description: string
7+
description?: string
88
}>()
99
</script>
1010

@@ -19,7 +19,7 @@ defineProps<{
1919
<div class="my-0 text-lg font-semibold">
2020
{{ title }}
2121
</div>
22-
<div class="line-clamp line-clamp-2 mb-0 mt-1 text-[14px] op50">
22+
<div v-if="description" class="line-clamp line-clamp-2 mb-0 mt-1 text-[14px] op50">
2323
{{ description }}
2424
<slot />
2525
</div>

components/ContentNavItem.vue

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
<script setup lang="ts">
2-
import type { NavItem } from '@nuxt/content'
2+
import type { ContentNavigationItem } from '@nuxt/content'
33
44
const props = withDefaults(
55
defineProps<{
6-
item: NavItem
6+
item: ContentNavigationItem
77
level?: number
88
}>(),
99
{
1010
level: 0,
1111
},
1212
)
1313
14+
const route = useRoute()
1415
const ui = useUiState()
1516
1617
const resolved = computed(() => {
@@ -25,7 +26,7 @@ const paddingLeft = computed(() => `${0.5 + props.level * 0.8}rem`)
2526
<template>
2627
<div class="content-nav-item">
2728
<template v-if="resolved.children?.length">
28-
<details :open="$route.path.includes(resolved._path)">
29+
<details :open="route.path.includes(resolved.path)">
2930
<summary>
3031
<div
3132
flex="~ gap-1 items-center" cursor-pointer select-none px1 py0.5
@@ -41,18 +42,20 @@ const paddingLeft = computed(() => `${0.5 + props.level * 0.8}rem`)
4142
</summary>
4243
<div v-if="resolved.children?.length">
4344
<ContentNavItem
44-
v-for="child in resolved.children"
45-
:key="child.url" :item="child" :level="props.level + 1"
45+
v-for="child of resolved.children"
46+
:key="child.path"
47+
:item="child"
48+
:level="props.level + 1"
4649
/>
4750
</div>
4851
</details>
4952
</template>
5053
<NuxtLink
5154
v-else
52-
:to="resolved._path"
55+
:to="resolved.path"
5356
px1 py0.5
5457
:style="{ paddingLeft }"
55-
:class="{ 'text-primary bg-active': resolved._path === $route.path }"
58+
:class="{ 'text-primary bg-active': resolved.path === route.path }"
5659
flex="~ gap-1 items-center"
5760
hover="text-primary bg-active "
5861
@click="ui.isContentDropdownShown = false"

components/MainPlayground.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const terminalPaneProps = computed(() => {
4343
}
4444
})
4545

46+
const route = useRoute()
47+
4648
// For panes size initialization on SSR
4749
const isMounted = useMounted()
4850
const panelInitDocs = computed(() => isMounted.value || {
@@ -73,7 +75,7 @@ const panelInitTerminal = computed(() => isMounted.value || {
7375
:size="ui.panelDocs" min-size="10"
7476
:style="panelInitDocs"
7577
>
76-
<PanelDocs />
78+
<PanelDocs :key="route.path" />
7779
</Pane>
7880
<PaneSplitter />
7981
<Pane

components/PanelDocs.vue

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,46 @@
11
<script setup lang="ts">
2-
import type { NavItem, ParsedContent } from '@nuxt/content'
2+
import type { ContentNavigationItem } from '@nuxt/content'
33
44
const runtime = useRuntimeConfig()
5-
const {
6-
navigation,
7-
page,
8-
next,
9-
prev,
10-
} = useContent() as {
11-
navigation: Ref<NavItem[]>
12-
page: Ref<ParsedContent>
13-
next: Ref<ParsedContent | undefined>
14-
prev: Ref<ParsedContent | undefined>
15-
}
5+
const route = useRoute()
6+
const { data: page } = useAsyncData(route.path, () => {
7+
return queryCollection('tutorials').path(route.path).first()
8+
})
9+
const { data: navigation } = useAsyncData(`navigation`, () => {
10+
return queryCollectionNavigation('tutorials')
11+
})
12+
const { data: surroundings } = useAsyncData(`${route.path}-surroundings`, () => {
13+
return queryCollectionItemSurroundings('tutorials', route.path, {
14+
fields: ['title', 'description'],
15+
})
16+
})
17+
18+
const prev = computed(() => surroundings.value?.[0])
19+
const next = computed(() => surroundings.value?.[1])
1620
1721
interface BreadcrumbItem {
1822
title: string
1923
path?: string
2024
}
2125
22-
function findNavItemFromPath(path: string, items = navigation.value): NavItem | undefined {
23-
const item = items.find(i => i._path === path)
26+
function findNavItemFromPath(
27+
path: string,
28+
items: ContentNavigationItem[] | null = navigation.value,
29+
): ContentNavigationItem | undefined {
30+
const item = items?.find(i => i.path === path)
2431
if (item)
2532
return item
2633
2734
const parts = path.split('/').filter(Boolean)
2835
for (let i = parts.length - 1; i > 0; i--) {
2936
const parentPath = `/${parts.slice(0, i).join('/')}`
30-
const parent = items.find(i => i._path === parentPath)
37+
const parent = items?.find(i => i.path === parentPath)
3138
if (parent)
3239
return findNavItemFromPath(path, parent.children || [])
3340
}
3441
}
3542
36-
const contentPath = computed(() => page.value?._path as string | undefined)
43+
const contentPath = computed(() => page.value?.path as string | undefined)
3744
const breadcrumbs = computed(() => {
3845
const parts = contentPath.value?.split('/').filter(Boolean) || []
3946
const breadcrumbs = parts
@@ -58,8 +65,8 @@ const breadcrumbs = computed(() => {
5865
const ui = useUiState()
5966
6067
const sourceUrl = computed(() =>
61-
page.value?._file
62-
? `${runtime.public.repoUrl}/edit/main/content/${page.value._file}`
68+
page.value?.id
69+
? `${runtime.public.repoUrl}/edit/main/content/${page.value.id}`
6370
: undefined,
6471
)
6572
@@ -94,24 +101,24 @@ router.beforeEach(() => {
94101
</div>
95102
<div relative h-full of-hidden>
96103
<article ref="docsEl" class="max-w-none prose" h-full of-auto p6>
97-
<ContentDoc />
104+
<ContentRenderer v-if="page" :key="page.id" :value="page" />
98105
<div mt8 py2 grid="~ cols-[1fr_1fr] gap-4">
99106
<div>
100107
<ContentNavCard
101108
v-if="prev"
102-
:to="prev._path"
109+
:to="prev.path"
103110
:title="prev.title"
104-
:description="prev.description"
111+
:description="prev.description as string"
105112
subheader="Previous section"
106113
icon="i-ph-arrow-left"
107114
/>
108115
</div>
109116
<div>
110117
<ContentNavCard
111118
v-if="next"
112-
:to="next._path"
119+
:to="next.path"
113120
:title="next.title"
114-
:description="next.description"
121+
:description="next.description as string"
115122
subheader="Next section"
116123
icon="i-ph-arrow-right"
117124
items-end text-right
@@ -137,10 +144,10 @@ router.beforeEach(() => {
137144
v-if="ui.isContentDropdownShown"
138145
flex="~ col"
139146
border="b base"
140-
absolute left-0 right-0 top-0 max-h-60vh py2
141-
backdrop-blur-10 bg-base important-bg-opacity-80
147+
148+
absolute left-0 right-0 top-0 max-h-60vh overflow-y-auto py2 backdrop-blur-10 bg-base important-bg-opacity-80
142149
>
143-
<ContentNavItem v-for="item of navigation" :key="item.url" :item="item" />
150+
<ContentNavItem v-for="item of navigation" :key="item.path" :item="item" />
144151
</div>
145152
</Transition>
146153
</div>

components/PanelPreview.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function navigate() {
7777
<span text-sm>Preview</span>
7878
</div>
7979
<button
80-
v-if="preview.url && guide.features.navigation"
80+
8181
rounded p1
8282
hover="bg-active"
8383
title="Refresh Preview"

content.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { defineCollection, defineContentConfig } from '@nuxt/content'
2+
3+
export default defineContentConfig({
4+
collections: {
5+
tutorials: defineCollection({
6+
type: 'page',
7+
source: {
8+
include: '**',
9+
exclude: ['**/.template/**'],
10+
},
11+
}),
12+
},
13+
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script setup lang="ts">
2+
const { data: todos, refresh } = useFetch('/api/todos')
3+
</script>
4+
5+
<template>
6+
<button type="button" @click="refresh()">
7+
Refresh
8+
</button>
9+
10+
<ul>
11+
<li v-for="todo in todos" :key="todo.id">
12+
{{ todo.title }}
13+
</li>
14+
</ul>
15+
</template>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default defineEventHandler(() => {
2+
return [
3+
{ id: 1, title: 'Todo 1' },
4+
{ id: 2, title: 'Todo 2' },
5+
{ id: 3, title: 'Todo 3' },
6+
]
7+
})
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { GuideMeta } from '~/types/guides'
2+
3+
export const meta: GuideMeta = {
4+
startingFile: 'app.vue',
5+
features: {
6+
fileTree: true,
7+
},
8+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
const { data: todos, refresh } = useFetch('/api/todos')
3+
</script>
4+
5+
<template>
6+
<button type="button" @click="refresh()">
7+
Refresh
8+
</button>
9+
10+
<ul>
11+
<li v-for="todo in todos" :key="todo.id">
12+
<span :class="{ completed: todo.completed }">{{ todo.title }}</span>
13+
</li>
14+
</ul>
15+
</template>
16+
17+
<style scoped>
18+
.completed {
19+
text-decoration: line-through;
20+
}
21+
</style>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default defineEventHandler(() => {
2+
return [
3+
{ id: 1, title: 'Todo 1', completed: false },
4+
{ id: 2, title: 'Todo 2', completed: false },
5+
{ id: 3, title: 'Todo 3', completed: true },
6+
{ id: 4, title: 'Todo 4', completed: false },
7+
]
8+
})

0 commit comments

Comments
 (0)