-
-
Notifications
You must be signed in to change notification settings - Fork 778
docs: add examples #3984
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
docs: add examples #3984
Changes from all commits
e9228a8
35aca57
ff143c8
c0ad8ac
54de472
3d0502a
c83bfb2
a128cc3
4173bd7
2d61ed2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| <script setup lang="ts"> | ||
| const props = defineProps<{ | ||
| src?: string | ||
| repo?: string | ||
| branch?: string | ||
| dir?: string | ||
| file?: string | ||
| }>() | ||
|
|
||
| const colorMode = useColorMode() | ||
|
|
||
| const url = computed(() => { | ||
| if (props.src) { | ||
| return props.src | ||
| } | ||
| const base = `https://stackblitz.com/github/${props.repo}/tree/${props.branch || 'main'}/${props.dir || ''}` | ||
| const params = new URLSearchParams({ | ||
| embed: '1', | ||
| file: props.file || 'README.md', | ||
| theme: colorMode.value, | ||
| }) | ||
| return `${base}?${params.toString()}` | ||
| }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <div class="w-full min-h-[500px] mx-auto overflow-hidden rounded-md mt-4 border border-default"> | ||
| <iframe | ||
| v-if="url" | ||
| :src="url" | ||
| title="StackBlitz Sandbox" | ||
| sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" | ||
| class="w-full h-full min-h-[600px] overflow-hidden bg-gray-100 dark:bg-gray-800" | ||
| /> | ||
| <div v-else class="flex items-center justify-center h-[600px] text-muted"> | ||
| Loading Sandbox... | ||
| </div> | ||
| </div> | ||
| </template> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { defineContentConfig, defineCollection, z } from '@nuxt/content' | ||
| import { resolve } from 'pathe' | ||
|
|
||
| export default defineContentConfig({ | ||
| collections: { | ||
| examples: defineCollection({ | ||
| type: 'page', | ||
| source: { | ||
| cwd: resolve(__dirname, '../../examples'), | ||
| include: '**/README.md', | ||
| prefix: '/examples', | ||
| exclude: ['**/.**/**', '**/node_modules/**', '**/dist/**', '**/.docs/**'], | ||
| }, | ||
| schema: z.object({ | ||
| category: z.string().optional(), | ||
| icon: z.string().optional(), | ||
| }), | ||
| }), | ||
| }, | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| <script setup lang="ts"> | ||
| import type { ContentNavigationItem } from '@nuxt/content' | ||
| import { categoryOrder } from '~/utils/examples' | ||
|
|
||
| // Fetch all examples and group by category | ||
| const { data: examples } = await useAsyncData('examples-nav', () => | ||
| queryCollection('examples') | ||
| .select('title', 'description', 'category', 'path') | ||
| .all(), | ||
| ) | ||
|
|
||
| // Group examples by category | ||
| const groupedExamples = computed(() => { | ||
| if (!examples.value) return [] | ||
|
|
||
| const groups: Record<string, ContentNavigationItem[]> = {} | ||
|
|
||
| for (const example of examples.value) { | ||
| const category = example.category || 'Other' | ||
| if (!groups[category]) { | ||
| groups[category] = [] | ||
| } | ||
| groups[category].push({ | ||
| title: example.title, | ||
| path: example.path.replace(/\/readme$/i, ''), | ||
| }) | ||
| } | ||
|
|
||
| // Convert to navigation items with children, sorted by categoryOrder | ||
| const sortedEntries = Object.entries(groups).sort(([a], [b]) => { | ||
| const aIndex = categoryOrder.indexOf(a.toLowerCase()) | ||
| const bIndex = categoryOrder.indexOf(b.toLowerCase()) | ||
| if (aIndex === -1 && bIndex === -1) return a.localeCompare(b) | ||
| if (aIndex === -1) return 1 | ||
| if (bIndex === -1) return -1 | ||
| return aIndex - bIndex | ||
| }) | ||
|
|
||
| return sortedEntries.map(([category, items]) => ({ | ||
| title: category.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '), | ||
| path: '', | ||
| children: items, | ||
| })) | ||
| }) | ||
|
|
||
| // Flat list for navigation (no groups) | ||
| const flatExamples = computed(() => { | ||
| if (!examples.value) return [] | ||
| return examples.value.map((example) => ({ | ||
| title: example.title, | ||
| path: example.path.replace(/\/readme$/i, ''), | ||
| })) | ||
| }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <UContainer> | ||
| <UPage :ui="{ left: 'lg:col-span-2 pr-2 border-r border-default' }"> | ||
| <template #left> | ||
| <UPageAside> | ||
| <UPageAnchors | ||
| :links="[ | ||
| { label: 'Docs', icon: 'i-lucide-book-open', to: '/docs' }, | ||
| { label: 'Deploy', icon: 'ri:upload-cloud-2-line', to: '/deploy' }, | ||
| { label: 'Config', icon: 'ri:settings-3-line', to: '/config' }, | ||
| { label: 'Examples', icon: 'i-lucide-folder-code', to: '/examples', active: true }, | ||
| ]" | ||
| /> | ||
| <USeparator type="dashed" class="py-6" /> | ||
| <UContentNavigation | ||
| v-if="groupedExamples.length" | ||
| :navigation="groupedExamples" | ||
| :collapsible="false" | ||
| /> | ||
| <UContentNavigation | ||
| v-else-if="flatExamples.length" | ||
| :navigation="flatExamples" | ||
| :collapsible="false" | ||
| /> | ||
| </UPageAside> | ||
| </template> | ||
| <slot /> | ||
| </UPage> | ||
| </UContainer> | ||
| </template> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| <script setup lang="ts"> | ||
| import { joinURL } from 'ufo' | ||
| import { kebabCase } from 'scule' | ||
|
|
||
| definePageMeta({ | ||
| layout: 'examples', | ||
| }) | ||
|
|
||
| const appConfig = useAppConfig() | ||
| const route = useRoute() | ||
|
|
||
| const { data: page } = await useAsyncData(kebabCase(route.path), () => | ||
| queryCollection('examples').path(`${route.path}/readme`).first(), | ||
| ) | ||
| if (!page.value) { | ||
| throw createError({ | ||
| statusCode: 404, | ||
| statusMessage: 'Example not found', | ||
| message: `${route.path} does not exist`, | ||
| fatal: true, | ||
| }) | ||
| } | ||
|
|
||
| const { data: surround } = await useAsyncData(`${kebabCase(route.path)}-surround`, () => { | ||
| return queryCollectionItemSurroundings('examples', `${route.path}/readme`, { | ||
| fields: ['description'], | ||
| }) | ||
| }) | ||
|
|
||
| // Extract example name from route (e.g., "/examples/vite-ssr-html" -> "vite-ssr-html") | ||
| const exampleName = computed(() => { | ||
| return route.path.replace(/^\/examples\//, '') | ||
| }) | ||
|
|
||
| const breadcrumb = computed(() => [ | ||
| { label: 'Examples', icon: 'i-lucide-folder-code', to: '/examples' }, | ||
| { label: page.value?.title || exampleName.value }, | ||
| ]) | ||
|
|
||
| usePageSEO({ | ||
| title: `${page.value?.title} - ${appConfig.site.name}`, | ||
| ogTitle: page.value?.title, | ||
| description: page.value?.description, | ||
| }) | ||
|
|
||
| const path = computed(() => route.path.replace(/\/$/, '')) | ||
| prerenderRoutes([joinURL('/raw', `${path.value}.md`)]) | ||
| useHead({ | ||
| link: [ | ||
| { | ||
| rel: 'alternate', | ||
| href: joinURL(appConfig.site.url, 'raw', `${path.value}.md`), | ||
| type: 'text/markdown', | ||
| }, | ||
| ], | ||
| }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <UPage v-if="page"> | ||
| <UPageHeader | ||
| :title="page.title" | ||
| :description="page.description" | ||
| :ui="{ | ||
| wrapper: 'flex-row items-center flex-wrap justify-between', | ||
| }" | ||
| > | ||
| <template #headline> | ||
| <UBreadcrumb :items="breadcrumb" /> | ||
| </template> | ||
| <template #links> | ||
| <UButton | ||
| icon="i-simple-icons-stackblitz" | ||
| label="Open in Playground" | ||
| color="neutral" | ||
| variant="soft" | ||
| size="sm" | ||
| :to="`https://stackblitz.com/fork/github/${appConfig.docs.github}/tree/${appConfig.docs.branch || 'main'}/examples/${exampleName}`" | ||
| target="_blank" | ||
| /> | ||
|
|
||
| <UButton | ||
| icon="i-simple-icons-github" | ||
| label="Source" | ||
| color="neutral" | ||
| variant="soft" | ||
| size="sm" | ||
| :to="`https://github.com/${appConfig.docs.github}/tree/${appConfig.docs.branch || 'main'}/examples/${exampleName}`" | ||
| target="_blank" | ||
| /> | ||
|
|
||
| <PageHeaderLinks /> | ||
| </template> | ||
| </UPageHeader> | ||
|
|
||
| <template v-if="page.body?.toc?.links?.length" #right> | ||
| <UContentToc title="On this page" :links="page.body?.toc?.links || []" highlight /> | ||
| </template> | ||
|
|
||
| <UPageBody prose class="break-words"> | ||
| <ContentRenderer v-if="page.body" :value="page" /> | ||
|
|
||
| <div class="space-y-6"> | ||
| <USeparator type="dashed" /> | ||
| <div class="mb-4"> | ||
| <UPageLinks | ||
| class="inline-block" | ||
| :links="[ | ||
| { | ||
| icon: 'i-lucide-pencil', | ||
| label: 'Edit this page', | ||
| to: `https://github.com/${appConfig.docs.github}/edit/${appConfig.docs.branch || 'main'}/examples/${exampleName}/README.md`, | ||
| target: '_blank', | ||
| }, | ||
| ]" | ||
| /> | ||
| </div> | ||
| <UContentSurround v-if="surround?.length" class="mb-4" :surround="surround" /> | ||
| </div> | ||
| </UPageBody> | ||
| </UPage> | ||
| </template> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| <script setup lang="ts"> | ||
| import { categoryOrder, categoryIcons } from '~/utils/examples' | ||
|
|
||
| definePageMeta({ | ||
| layout: 'examples', | ||
| }) | ||
|
|
||
| const appConfig = useAppConfig() | ||
|
|
||
| // Fetch all examples | ||
| const { data: examples } = await useAsyncData('examples-list', () => | ||
| queryCollection('examples') | ||
| .select('title', 'description', 'category', 'path', 'icon') | ||
| .all(), | ||
| ) | ||
|
|
||
| // Group examples by category | ||
| const groupedExamples = computed(() => { | ||
| if (!examples.value) return {} | ||
|
|
||
| const groups: Record<string, typeof examples.value> = {} | ||
|
|
||
| for (const example of examples.value) { | ||
| const category = example.category || 'Other' | ||
| if (!groups[category]) { | ||
| groups[category] = [] | ||
| } | ||
| groups[category].push(example) | ||
| } | ||
|
|
||
| // Sort groups by categoryOrder | ||
| const sortedEntries = Object.entries(groups).sort(([a], [b]) => { | ||
| const aIndex = categoryOrder.indexOf(a.toLowerCase()) | ||
| const bIndex = categoryOrder.indexOf(b.toLowerCase()) | ||
| if (aIndex === -1 && bIndex === -1) return a.localeCompare(b) | ||
| if (aIndex === -1) return 1 | ||
| if (bIndex === -1) return -1 | ||
| return aIndex - bIndex | ||
| }) | ||
|
|
||
| return Object.fromEntries(sortedEntries) | ||
| }) | ||
|
|
||
| usePageSEO({ | ||
| title: `Examples - ${appConfig.site.name}`, | ||
| ogTitle: 'Examples', | ||
| description: 'Explore Nitro examples to learn how to build full-stack applications', | ||
| }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <UPage> | ||
| <UPageHeader | ||
| title="Examples" | ||
| description="Explore Nitro examples to learn how to build full-stack applications with different frameworks and features." | ||
| > | ||
| <template #headline> | ||
| <UBreadcrumb :items="[{ label: 'Examples', icon: 'i-lucide-code' }]" /> | ||
| </template> | ||
| </UPageHeader> | ||
|
|
||
| <UPageBody> | ||
| <UAlert | ||
| color="warning" | ||
| variant="subtle" | ||
| icon="i-lucide-triangle-alert" | ||
| title="Work in Progress" | ||
| description="Nitro v3 Alpha docs and examples are a work in progress β expect updates, rough edges, and occasional inaccuracies." | ||
| class="mb-8" | ||
| /> | ||
|
|
||
| <div v-for="(categoryExamples, category) in groupedExamples" :key="category" class="mb-12"> | ||
| <h2 class="text-xl font-semibold mb-4 flex items-center gap-2"> | ||
| <UIcon :name="categoryIcons[String(category).toLowerCase()] || categoryIcons.other" class="size-5" /> | ||
| {{ String(category).charAt(0).toUpperCase() + String(category).slice(1) }} | ||
| </h2> | ||
|
|
||
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> | ||
| <UPageCard | ||
| v-for="example in categoryExamples" | ||
| :key="example.path" | ||
| :to="example.path.replace(/\/readme$/i, '')" | ||
| :title="example.title" | ||
| :description="example.description" | ||
| :icon="example.icon" | ||
| /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div v-if="!examples?.length" class="text-center py-12"> | ||
| <UIcon name="i-lucide-book-dashed" class="size-12 text-muted mx-auto mb-4" /> | ||
| <p class="text-muted">No examples</p> | ||
| </div> | ||
| </UPageBody> | ||
| </UPage> | ||
| </template> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||||||||||||||||||||||||||||||||||
| import { queryCollection } from '@nuxt/content/server' | ||||||||||||||||||||||||||||||||||||
| import { stringify } from 'minimark/stringify' | ||||||||||||||||||||||||||||||||||||
| import { withLeadingSlash } from 'ufo' | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export default eventHandler(async (event) => { | ||||||||||||||||||||||||||||||||||||
| const slug = getRouterParams(event)['slug.md'] | ||||||||||||||||||||||||||||||||||||
| if (!slug?.endsWith('.md')) { | ||||||||||||||||||||||||||||||||||||
| throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true }) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Convert /raw/examples/hello-world.md -> /examples/hello-world/readme | ||||||||||||||||||||||||||||||||||||
| const exampleName = slug.replace('.md', '') | ||||||||||||||||||||||||||||||||||||
| const path = withLeadingSlash(`examples/${exampleName}/readme`) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const page = await queryCollection(event, 'examples').path(path).first() | ||||||||||||||||||||||||||||||||||||
| if (!page) { | ||||||||||||||||||||||||||||||||||||
| throw createError({ statusCode: 404, statusMessage: 'Example not found', fatal: true }) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Add title and description to the top of the page if missing | ||||||||||||||||||||||||||||||||||||
| if (page.body.value[0]?.[0] !== 'h1') { | ||||||||||||||||||||||||||||||||||||
| page.body.value.unshift(['blockquote', {}, page.description]) | ||||||||||||||||||||||||||||||||||||
| page.body.value.unshift(['h1', {}, page.title]) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8') | ||||||||||||||||||||||||||||||||||||
| return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' }) | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: find . -name "[...slug].md.get.ts" -o -name "*.md.get.ts" | head -20Repository: nitrojs/nitro Length of output: 116 π Script executed: git ls-files | grep -E "(routes|examples)" | head -30Repository: nitrojs/nitro Length of output: 1149 π Script executed: cat -n "docs/.docs/server/routes/raw/examples/[...slug].md.get.ts"Repository: nitrojs/nitro Length of output: 1374 π Script executed: # Search for how queryCollection is used elsewhere
rg "queryCollection" --type ts --type js -A 3 -B 1 | head -60Repository: nitrojs/nitro Length of output: 39 π Script executed: # Check if there are other examples of handling page.body
rg "page\.body" --type ts --type js -B 2 -A 2Repository: nitrojs/nitro Length of output: 39 π Script executed: # Search for any Nuxt Content related documentation or usage
rg "page\.body|page\.title|page\.description" -A 2 -B 2Repository: nitrojs/nitro Length of output: 39 π Script executed: # Check for any TypeScript types or interfaces that define the page structure
rg "body\s*:\s*" --type ts | head -20Repository: nitrojs/nitro Length of output: 1457 π Script executed: # Look at the imports and see what queryCollection returns
rg "queryCollection" --type ts --type js -B 5 -A 10Repository: nitrojs/nitro Length of output: 39 π Script executed: # Check if there's any TypeScript definition or documentation about the page object
rg "@nuxt/content" --type ts --type jsonRepository: nitrojs/nitro Length of output: 39 π Script executed: # Look for any validation or checks on page properties in the codebase
rg "page\?" --type ts -B 2 -A 2Repository: nitrojs/nitro Length of output: 39 π Script executed: # Check if page.body.value is accessed elsewhere and if there are null checks
rg "\.body\.value" --type ts -B 1 -A 1Repository: nitrojs/nitro Length of output: 39 π Script executed: # Check if there are other similar route handlers in the docs
fd ".get.ts" docs/.docs/server/routes/ | head -20Repository: nitrojs/nitro Length of output: 114 π Script executed: # Look at the package.json to understand the `@nuxt/content` version being used
cat docs/package.json 2>/dev/null || cat package.json | grep -A 5 -B 5 "@nuxt/content"Repository: nitrojs/nitro Length of output: 292 π Script executed: # Check if there are defensive patterns used in other docs routes
fd ".get.ts" docs/ -x cat {} \; | head -100Repository: nitrojs/nitro Length of output: 39 π Script executed: # Check root package.json for dependencies
cat package.json | grep -A 100 "dependencies" | head -50Repository: nitrojs/nitro Length of output: 1599 π Script executed: # Look for any tests or examples related to this specific route
fd "slug" docs/ --type fRepository: nitrojs/nitro Length of output: 39 π Script executed: # Check if there's documentation about the page structure
find docs -name "*.md" -type f | xargs grep -l "body\|title\|description" 2>/dev/null | head -5Repository: nitrojs/nitro Length of output: 191 π Script executed: # Check if there are any TypeScript type definitions in the project
fd "*.d.ts" docs/ | head -10Repository: nitrojs/nitro Length of output: 342 π Script executed: # Look for any validation or type checking of the page object
rg "page\." docs/.docs/server/ -B 2 -A 2Repository: nitrojs/nitro Length of output: 1021 π Script executed: # Check the Nuxt Content documentation online or look for imports
rg "from.*content" docs/.docs/server/ -B 1 -A 1Repository: nitrojs/nitro Length of output: 274 π Script executed: # Search for any error handling or try-catch around queryCollection calls
rg "queryCollection|\.first\(\)" docs/.docs/server/ -B 5 -A 5Repository: nitrojs/nitro Length of output: 1723 π Script executed: # Look for any tests or example data to understand page structure
find docs -name "*.json" -o -name "*.yaml" | xargs grep -l "title\|description" 2>/dev/null | head -5Repository: nitrojs/nitro Length of output: 99 π Script executed: # Check if there's a nuxt-content configuration file
fd "content.config\|nuxt.config" docs/Repository: nitrojs/nitro Length of output: 39 π Script executed: # Check the docs directory structure to see what examples exist
ls -la docs/.docs/content/examples/ 2>/dev/null || ls -la examples/ | head -20Repository: nitrojs/nitro Length of output: 1183 π Script executed: # Look for any README files in examples that might show the expected structure
find examples -name "README.md" | head -5 | xargs cat | head -50Repository: nitrojs/nitro Length of output: 1123 π Script executed: # Check if there's any validation code or error handling in similar files
fd "\[.*\]\..*\.ts" docs/.docs/server/routes/ | xargs cat 2>/dev/nullRepository: nitrojs/nitro Length of output: 1178 π Script executed: # Check if there are any tests for this route
fd "slug.*test\|test.*slug" docs/Repository: nitrojs/nitro Length of output: 39 π Script executed: # Look for any other examples of defensive body handling in the codebase
rg "body\.value" -B 3 -A 3 --type tsRepository: nitrojs/nitro Length of output: 39 π Script executed: # Check if Nuxt Content is listed as a dependency anywhere
rg "@nuxt/content" --type jsonRepository: nitrojs/nitro Length of output: 39 π Script executed: # Check if there's any documentation about the page structure from Nuxt Content
find docs -name "*.md" | xargs grep -l "queryCollection\|page\.body\|page\.title" 2>/dev/null | head -5Repository: nitrojs/nitro Length of output: 39 π Script executed: # Look for any examples or tests in the repository that show how page is used
rg "stringify.*page" -B 5 -A 2Repository: nitrojs/nitro Length of output: 39 π Script executed: # Search for how unshift is used in the codebase
rg "unshift" --type ts -B 2 -A 2Repository: nitrojs/nitro Length of output: 4504 π Script executed: # Check if there are integration tests or fixtures that might show what a page object looks like
find docs -name "*.json" -o -name "*.yaml" -o -name "*.yml" | xargs grep -l "body" 2>/dev/null | head -5Repository: nitrojs/nitro Length of output: 76 π Script executed: # Check the Undocs source to understand if it wraps Nuxt Content
rg "undocs" package.json docs/package.jsonRepository: nitrojs/nitro Length of output: 205 π Script executed: # Look for any README or documentation files in the docs directory
find docs -maxdepth 2 -name "README*" -o -name "*.md" | head -10 | xargs cat 2>/dev/null | head -100Repository: nitrojs/nitro Length of output: 4698 π Script executed: # Let me check if there are any actual README files that might be parsed to understand the expected structure
find examples -name "README.md" -exec head -15 {} \; | head -50Repository: nitrojs/nitro Length of output: 2223 π Script executed: # Check if there's any type definition or interface that defines the page structure from Nuxt Content
rg "interface.*page|type.*page" docs/.docs/ --type tsRepository: nitrojs/nitro Length of output: 105 π Script executed: cat -n docs/.docs/content.config.tsRepository: nitrojs/nitro Length of output: 715 π Script executed: # Let's also check if there's any actual data in the content directory that shows what a real page looks like
find docs -path "*content*" -name "*.md" -o -name "*.json" 2>/dev/null | head -10Repository: nitrojs/nitro Length of output: 74 π Script executed: # Check if there's a lockfile or version info for `@nuxt/content`
grep -r "@nuxt/content" docs/pnpm-lock.yaml 2>/dev/null | head -5Repository: nitrojs/nitro Length of output: 742 π Script executed: # Look for any other routes that use page.title or page.description
rg "page\.(title|description|body)" docs/.docs/server/ --type tsRepository: nitrojs/nitro Length of output: 532 π Script executed: # Check if there are any Vue components that might show how the page object is used
rg "page\." docs/.docs/pages/ --type vue --type ts | head -20Repository: nitrojs/nitro Length of output: 84 Add guards for optional page properties before injection. The code assumes Safer implementation // Add title and description to the top of the page if missing
- if (page.body.value[0]?.[0] !== 'h1') {
- page.body.value.unshift(['blockquote', {}, page.description])
- page.body.value.unshift(['h1', {}, page.title])
+ const body = Array.isArray(page.body?.value) ? [...page.body.value] : []
+ if (body[0]?.[0] !== 'h1') {
+ if (page.description) body.unshift(['blockquote', {}, page.description])
+ if (page.title) body.unshift(['h1', {}, page.title])
}
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
- return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
+ return stringify({ ...page.body, value: body, type: 'minimark' }, { format: 'markdown/html' })π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§© Analysis chain
π Script executed:
fd -t f "\[\.\.\.slug\]\.vue" docs/Repository: nitrojs/nitro
Length of output: 39
π Script executed:
Repository: nitrojs/nitro
Length of output: 206
π Script executed:
Repository: nitrojs/nitro
Length of output: 4173
π Script executed:
Repository: nitrojs/nitro
Length of output: 39
π Script executed:
Repository: nitrojs/nitro
Length of output: 39
π Script executed:
Repository: nitrojs/nitro
Length of output: 39
π Script executed:
ls -la docs/.docs/ | head -30Repository: nitrojs/nitro
Length of output: 510
π Script executed:
Repository: nitrojs/nitro
Length of output: 582
π Script executed:
Repository: nitrojs/nitro
Length of output: 39
π Script executed:
rg "route\.path" docs/ -A 2 -B 2Repository: nitrojs/nitro
Length of output: 39
π Script executed:
rg "trailing" docs/ -iRepository: nitrojs/nitro
Length of output: 715
π Script executed:
rg "useAsyncData" docs/.docs/pages/ -A 3 -B 1Repository: nitrojs/nitro
Length of output: 1571
π Script executed:
rg "queryCollection.*path.*route\.path" docs/Repository: nitrojs/nitro
Length of output: 39
π Script executed:
Repository: nitrojs/nitro
Length of output: 582
Consolidate trailing slash normalization to avoid double slashes in content queries.
Using
route.pathdirectly in content queries (lines 12-14, 24-28) can produce//readmeif a trailing slash is present. Line 46 already normalizes for the raw markdown URLβapply the same normalization consistently across all queries and path computations by normalizing once at the top.Consolidate normalizations
π€ Prompt for AI Agents