Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 0 additions & 3 deletions docs/app/pages/docs/[...slug].vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script setup lang="ts">
import { joinURL } from 'ufo'
import { kebabCase } from 'scule'
import type { ContentNavigationItem } from '@nuxt/content'

Expand Down Expand Up @@ -77,8 +76,6 @@ useSeoMeta({
const path = computed(() => route.path.replace(/\/$/, ''))

if (import.meta.server) {
prerenderRoutes([joinURL('/raw', `${path.value}.md`)])

if (route.path.startsWith('/docs/components/')) {
defineOgImage('Component.takumi', {
title: page.value.title,
Expand Down
2 changes: 0 additions & 2 deletions docs/app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ const { url } = useSiteConfig()
const appConfig = useAppConfig()

if (import.meta.server) {
prerenderRoutes(['/raw/index.md'])

useSchemaOrg([
defineSoftwareApp({
name: 'Nuxt UI',
Expand Down
13 changes: 8 additions & 5 deletions docs/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,6 @@ export default defineNuxtConfig({
'/',
'/docs/getting-started',
'/openapi.json',
// Also prerendered through `prerenderRoutes()` in `app/pages/index.vue`;
// listed here so the guarantee does not hang off a page component.
'/raw/index.md',
'/api/countries.json',
'/api/phone-codes.json',
'/api/locales.json',
Expand Down Expand Up @@ -448,13 +445,19 @@ export default defineNuxtConfig({
title: 'Components',
contentCollection: 'docs',
contentFilters: [
{ field: 'path', operator: 'LIKE', value: '/docs/components/%' }
{ field: 'path', operator: 'LIKE', value: '/docs/components%' }
]
}, {
title: 'Composables',
contentCollection: 'docs',
contentFilters: [
{ field: 'path', operator: 'LIKE', value: '/docs/composables/%' }
{ field: 'path', operator: 'LIKE', value: '/docs/composables%' }
]
}, {
title: 'Typography',
contentCollection: 'docs',
contentFilters: [
{ field: 'path', operator: 'LIKE', value: '/docs/typography%' }
]
}],
notes: [
Expand Down
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"minimark": "^0.2.0",
"motion-v": "^2.4.0",
"nuxt": "^4.5.2",
"nuxt-agent-discovery": "^0.1.1",
"nuxt-agent-discovery": "^0.4.0",
"nuxt-component-meta": "^0.18.0",
"nuxt-llms": "^0.2.0",
"nuxt-og-image": "^6.7.8",
Expand Down
90 changes: 90 additions & 0 deletions docs/server/utils/markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { textContent } from 'minimark'

/**
* Markdown builders for what `minimark/stringify` cannot express in its
* `markdown/html` format: it has no pipe-table handler, so a `table` node
* comes out as HTML, and its `pre` handler always opens a three-backtick
* fence, which code carrying fences of its own breaks out of. Both return a
* string to drop into the tree as a paragraph, which the stringifier passes
* through untouched. Links stay as written: `nuxt-agent-discovery` absolutizes
* the stringified document afterwards, table rows included.
*/

/**
* A backtick run that cannot be closed early by the text: one longer than
* any run of `minimum` or more backticks inside it, and at least `minimum`.
*/
function backticks(text: string, minimum: number): string {
const runs = Array.from(text.matchAll(new RegExp(`\`{${minimum},}`, 'g')), match => match[0].length)
return '`'.repeat(Math.max(minimum - 1, ...runs) + 1)
}

// GFM reads `\|` as a pipe anywhere in a cell, code spans included, while
// every other backslash inside a code span stays literal. So plain text gets
// both characters escaped and a code span only its pipes, joined rather than
// replaced so a literal backslash is never doubled there.
function escapeText(text: string): string {
return text.replace(/[\\|]/g, '\\$&')
}

function escapePipes(text: string): string {
return text.split('|').join('\\|')
}

/** Renders the inline nodes a table cell can hold to markdown. */
function inlineMarkdown(node: any): string {
if (typeof node === 'string') return escapeText(node)
const [tag, attrs = {}, ...children] = node
const inner = () => children.map(inlineMarkdown).join('')

switch (tag) {
case 'code': {
const text = escapePipes(textContent(node))
const fence = backticks(text, 1)
// A space keeps a leading or trailing backtick apart from the fence.
return /^`|`$/.test(text) ? `${fence} ${text} ${fence}` : `${fence}${text}${fence}`
}
case 'a': return `[${inner()}](${escapePipes(String(attrs.href ?? ''))})`
case 'strong':
case 'b': return `**${inner()}**`
case 'em':
case 'i': return `*${inner()}*`
case 'del': return `~~${inner()}~~`
case 'img': return `![${escapeText(String(attrs.alt ?? ''))}](${escapePipes(String(attrs.src ?? ''))})`
// The one line break a pipe cell can hold.
case 'br': return '<br>'
default: return inner()
}
}

function cell(node: any): string {
return inlineMarkdown(node).replace(/\s+/g, ' ').trim()
}

/** A `table` node as a GFM pipe table, the first row serving as header. */
export function pipeTable(table: any[]): string {
const rows: string[][] = []
for (const section of table.slice(2)) {
if (!Array.isArray(section)) continue
// Rows sit under `thead` and `tbody`; a bare `tr` is tolerated too.
for (const row of section[0] === 'tr' ? [section] : section.slice(2)) {
if (Array.isArray(row) && row[0] === 'tr') {
rows.push(row.slice(2).filter(child => Array.isArray(child)).map(cell))
}
}
}

const [header, ...body] = rows
if (!header?.length) return ''

const width = header.length
const line = (cells: string[]) => `| ${Array.from({ length: width }, (_, i) => cells[i] ?? '').join(' | ')} |`

return [line(header), line(header.map(() => '---')), ...body.map(line)].join('\n')
}

/** A fenced code block whose fence is longer than any fence-like run inside. */
export function fencedBlock(code: string, language = '', filename?: string, meta?: string): string {
const fence = backticks(code, 3)
return `${fence}${language}${filename ? ` [${filename}]` : ''}${meta || ''}\n${code.trim()}\n${fence}`
Comment thread
benjamincanac marked this conversation as resolved.
}
Loading
Loading