undocs 0.4.11 → 0.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/PageHeaderLinks.vue +70 -0
- package/app/modules/content/hooks.ts +2 -2
- package/app/modules/md-rewrite.ts +32 -0
- package/app/nuxt.config.ts +1 -1
- package/app/pages/[...slug].vue +25 -1
- package/app/server/routes/raw/[...slug].md.get.ts +26 -0
- package/cli/cli.mjs +8 -4
- package/cli/setup.mjs +16 -0
- package/package.json +28 -28
- package/schema/config.d.ts +6 -0
- package/schema/config.json +34 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useClipboard } from '@vueuse/core'
|
|
3
|
+
import { useRuntimeConfig } from '#imports'
|
|
4
|
+
|
|
5
|
+
const route = useRoute()
|
|
6
|
+
const appBaseURL = useRuntimeConfig().app?.baseURL || '/'
|
|
7
|
+
|
|
8
|
+
const { copy, copied } = useClipboard()
|
|
9
|
+
|
|
10
|
+
const markdownLink = computed(() => `${window?.location?.origin}${appBaseURL}raw${route.path}.md`)
|
|
11
|
+
const items = [
|
|
12
|
+
{
|
|
13
|
+
label: 'Copy Markdown Link',
|
|
14
|
+
icon: 'i-lucide-link',
|
|
15
|
+
onSelect() {
|
|
16
|
+
copy(markdownLink.value)
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
label: 'View as Markdown',
|
|
21
|
+
icon: 'i-simple-icons:markdown',
|
|
22
|
+
target: '_blank',
|
|
23
|
+
to: markdownLink.value,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: 'Open in ChatGPT',
|
|
27
|
+
icon: 'i-simple-icons:openai',
|
|
28
|
+
target: '_blank',
|
|
29
|
+
to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: 'Open in Claude',
|
|
33
|
+
icon: 'i-simple-icons:anthropic',
|
|
34
|
+
target: '_blank',
|
|
35
|
+
to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
async function copyPage() {
|
|
40
|
+
const page = await $fetch<string>(`/raw${route.path}.md`)
|
|
41
|
+
copy(page)
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<UFieldGroup size="sm">
|
|
47
|
+
<UButton
|
|
48
|
+
label="Copy Page"
|
|
49
|
+
:icon="copied ? 'i-lucide-check' : 'i-lucide-copy'"
|
|
50
|
+
color="neutral"
|
|
51
|
+
variant="soft"
|
|
52
|
+
:ui="{
|
|
53
|
+
leadingIcon: 'text-neutral size-3.5',
|
|
54
|
+
}"
|
|
55
|
+
@click="copyPage"
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
<UDropdownMenu
|
|
59
|
+
size="sm"
|
|
60
|
+
:items="items"
|
|
61
|
+
:content="{
|
|
62
|
+
align: 'end',
|
|
63
|
+
side: 'bottom',
|
|
64
|
+
sideOffset: 8,
|
|
65
|
+
}"
|
|
66
|
+
>
|
|
67
|
+
<UButton icon="i-lucide-chevron-down" color="neutral" variant="soft" class="border-l border-muted" />
|
|
68
|
+
</UDropdownMenu>
|
|
69
|
+
</UFieldGroup>
|
|
70
|
+
</template>
|
|
@@ -116,8 +116,8 @@ function transformMermaid(node: MinimarkNode) {
|
|
|
116
116
|
node[0] = 'mermaid'
|
|
117
117
|
// @ts-expect-error
|
|
118
118
|
node[1] = { code: node[1].code || '' }
|
|
119
|
-
//
|
|
120
|
-
node
|
|
119
|
+
// Remove all children (splice instead of node[2] = [] which would create an invalid empty array child)
|
|
120
|
+
node.splice(2)
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { NitroModule } from 'nitropack'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
4
|
+
|
|
5
|
+
import { defineNuxtModule } from 'nuxt/kit'
|
|
6
|
+
|
|
7
|
+
export default defineNuxtModule((_options, nuxt) => {
|
|
8
|
+
nuxt.options.nitro.modules ??= []
|
|
9
|
+
nuxt.options.nitro.modules.push(mdRewrite())
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
function mdRewrite(): NitroModule {
|
|
13
|
+
return {
|
|
14
|
+
name: 'markdown-rewrite',
|
|
15
|
+
setup(nitro) {
|
|
16
|
+
if (nitro.options.dev || !nitro.options.preset.includes('vercel')) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
nitro.hooks.hook('compiled', async () => {
|
|
20
|
+
const vcJSON = resolve(nitro.options.output.dir, 'config.json')
|
|
21
|
+
const vcConfig = JSON.parse(await readFile(vcJSON, 'utf8'))
|
|
22
|
+
vcConfig.routes.unshift({
|
|
23
|
+
src: '^/(.*)$',
|
|
24
|
+
dest: '/raw/$1.md',
|
|
25
|
+
has: [{ type: 'header', key: 'accept', value: '(.*)text/markdown(.*)' }],
|
|
26
|
+
check: true,
|
|
27
|
+
})
|
|
28
|
+
await writeFile(vcJSON, JSON.stringify(vcConfig, null, 2), 'utf8')
|
|
29
|
+
})
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
}
|
package/app/nuxt.config.ts
CHANGED
|
@@ -16,7 +16,7 @@ export default defineNuxtConfig({
|
|
|
16
16
|
name: 'undocs',
|
|
17
17
|
},
|
|
18
18
|
ssr,
|
|
19
|
-
modules: ['@nuxt/ui', '@nuxt/content', isProd && '@nuxtjs/plausible'],
|
|
19
|
+
modules: ['@nuxt/ui', '@nuxt/content', isProd && '@nuxtjs/plausible', 'nuxt-llms'],
|
|
20
20
|
css: [resolve('./assets/main.css')],
|
|
21
21
|
ui: {
|
|
22
22
|
theme: {
|
package/app/pages/[...slug].vue
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { joinURL } from 'ufo'
|
|
2
3
|
import { kebabCase } from 'scule'
|
|
3
4
|
import type { ContentNavigationItem } from '@nuxt/content'
|
|
4
5
|
|
|
@@ -56,14 +57,37 @@ usePageSEO({
|
|
|
56
57
|
ogTitle: page.value?.title,
|
|
57
58
|
description: page.value?.description,
|
|
58
59
|
})
|
|
60
|
+
|
|
61
|
+
const path = computed(() => route.path.replace(/\/$/, ''))
|
|
62
|
+
prerenderRoutes([joinURL('/raw', `${path.value}.md`)])
|
|
63
|
+
useHead({
|
|
64
|
+
link: [
|
|
65
|
+
{
|
|
66
|
+
rel: 'alternate',
|
|
67
|
+
href: joinURL(appConfig.site.url, 'raw', `${path.value}.md`),
|
|
68
|
+
type: 'text/markdown',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
})
|
|
59
72
|
</script>
|
|
60
73
|
|
|
61
74
|
<template>
|
|
62
75
|
<UPage v-if="page">
|
|
63
|
-
<UPageHeader
|
|
76
|
+
<UPageHeader
|
|
77
|
+
:title="page.title"
|
|
78
|
+
:description="page.description"
|
|
79
|
+
:ui="{
|
|
80
|
+
wrapper: 'flex-row items-center flex-wrap justify-between',
|
|
81
|
+
}"
|
|
82
|
+
>
|
|
64
83
|
<template #headline>
|
|
65
84
|
<UBreadcrumb :items="breadcrumb" />
|
|
66
85
|
</template>
|
|
86
|
+
<template #links>
|
|
87
|
+
<UButton v-for="(link, index) in page.links" :key="index" size="sm" v-bind="link" />
|
|
88
|
+
|
|
89
|
+
<PageHeaderLinks />
|
|
90
|
+
</template>
|
|
67
91
|
</UPageHeader>
|
|
68
92
|
|
|
69
93
|
<template v-if="page.body?.toc?.links?.length" #right>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { queryCollection } from '@nuxt/content/server'
|
|
2
|
+
import { stringify } from 'minimark/stringify'
|
|
3
|
+
import { withLeadingSlash } from 'ufo'
|
|
4
|
+
|
|
5
|
+
export default eventHandler(async (event) => {
|
|
6
|
+
const slug = getRouterParams(event)['slug.md']
|
|
7
|
+
if (!slug?.endsWith('.md')) {
|
|
8
|
+
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const path = withLeadingSlash(slug.replace('.md', ''))
|
|
12
|
+
|
|
13
|
+
const page = await queryCollection(event, 'content').path(path).first()
|
|
14
|
+
if (!page) {
|
|
15
|
+
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Add title and description to the top of the page if missing
|
|
19
|
+
if (page.body.value[0]?.[0] !== 'h1') {
|
|
20
|
+
page.body.value.unshift(['blockquote', {}, page.description])
|
|
21
|
+
page.body.value.unshift(['h1', {}, page.title])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
|
|
25
|
+
return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
|
|
26
|
+
})
|
package/cli/cli.mjs
CHANGED
|
@@ -58,8 +58,11 @@ export function createCLI(opts) {
|
|
|
58
58
|
process.chdir(appDir)
|
|
59
59
|
process.on('exit', () => unwatch())
|
|
60
60
|
|
|
61
|
-
const { runCommand } = await import('nuxi')
|
|
62
|
-
|
|
61
|
+
const { runCommand, main } = await import('nuxi')
|
|
62
|
+
const cmd = await main.subCommands.dev()
|
|
63
|
+
await runCommand(cmd, [appDir, '--no-fork', '--port', process.env.PORT || '4000'], {
|
|
64
|
+
overrides: nuxtConfig,
|
|
65
|
+
})
|
|
63
66
|
},
|
|
64
67
|
})
|
|
65
68
|
|
|
@@ -74,8 +77,9 @@ export function createCLI(opts) {
|
|
|
74
77
|
|
|
75
78
|
process.chdir(appDir)
|
|
76
79
|
|
|
77
|
-
const { runCommand } = await import('nuxi')
|
|
78
|
-
|
|
80
|
+
const { runCommand, main } = await import('nuxi')
|
|
81
|
+
const cmd = await main.subCommands.generate()
|
|
82
|
+
await runCommand(cmd, [appDir], { overrides: nuxtConfig })
|
|
79
83
|
},
|
|
80
84
|
})
|
|
81
85
|
|
package/cli/setup.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { resolve } from 'node:path'
|
|
|
3
3
|
import { existsSync } from 'node:fs'
|
|
4
4
|
import { execSync } from 'node:child_process'
|
|
5
5
|
import { loadConfig, watchConfig } from 'c12'
|
|
6
|
+
import { defu } from 'defu'
|
|
6
7
|
|
|
7
8
|
const appDir = fileURLToPath(new URL('../app', import.meta.url))
|
|
8
9
|
|
|
@@ -80,6 +81,16 @@ export async function setupDocs(docsDir, opts = {}) {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
// Prepare loadNuxt overrides
|
|
84
|
+
const llmsConfig = defu(docsconfig.llms, {
|
|
85
|
+
domain: docsconfig.url,
|
|
86
|
+
title: docsconfig.name || '',
|
|
87
|
+
description: docsconfig.description || '',
|
|
88
|
+
full: {
|
|
89
|
+
title: docsconfig.name || '',
|
|
90
|
+
description: docsconfig.description || '',
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
|
|
83
94
|
const nuxtConfig = {
|
|
84
95
|
compatibilityDate: 'latest',
|
|
85
96
|
rootDir: docsSrcDir,
|
|
@@ -95,6 +106,8 @@ export async function setupDocs(docsDir, opts = {}) {
|
|
|
95
106
|
description: docsconfig.description || '',
|
|
96
107
|
url: docsconfig.url,
|
|
97
108
|
},
|
|
109
|
+
// @ts-ignore
|
|
110
|
+
llms: llmsConfig,
|
|
98
111
|
appConfig: {
|
|
99
112
|
site: {
|
|
100
113
|
name: docsconfig.name || '',
|
|
@@ -120,6 +133,9 @@ export async function setupDocs(docsDir, opts = {}) {
|
|
|
120
133
|
dir: resolve(docsDir, '.docs/public'),
|
|
121
134
|
},
|
|
122
135
|
],
|
|
136
|
+
prerender: {
|
|
137
|
+
routes: ['/llms.txt', '/llms-full.txt'],
|
|
138
|
+
},
|
|
123
139
|
},
|
|
124
140
|
routeRules: {
|
|
125
141
|
...Object.fromEntries(Object.entries(docsconfig.redirects || {}).map(([from, to]) => [from, { redirect: to }])),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undocs",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.12",
|
|
4
4
|
"repository": "unjs/undocs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,47 +31,47 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@headlessui/vue": "^1.7.23",
|
|
34
|
-
"@iconify-json/logos": "^1.2.
|
|
35
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
36
|
-
"@nuxt/content": "^3.
|
|
37
|
-
"@nuxt/fonts": "^0.
|
|
38
|
-
"@nuxt/ui": "4.
|
|
34
|
+
"@iconify-json/logos": "^1.2.10",
|
|
35
|
+
"@iconify-json/simple-icons": "^1.2.66",
|
|
36
|
+
"@nuxt/content": "^3.10.0",
|
|
37
|
+
"@nuxt/fonts": "^0.12.1",
|
|
38
|
+
"@nuxt/ui": "4.2.0",
|
|
39
39
|
"@nuxtjs/plausible": "^2.0.1",
|
|
40
40
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
41
41
|
"automd": "^0.4.2",
|
|
42
|
-
"c12": "^3.3.
|
|
42
|
+
"c12": "^3.3.3",
|
|
43
43
|
"citty": "^0.1.6",
|
|
44
44
|
"consola": "^3.4.2",
|
|
45
45
|
"defu": "^6.1.4",
|
|
46
46
|
"is-buffer": "^2.0.5",
|
|
47
47
|
"md4w": "^0.2.7",
|
|
48
|
-
"mermaid": "^11.12.
|
|
49
|
-
"motion-v": "^1.
|
|
50
|
-
"nitropack": "^2.
|
|
51
|
-
"nuxi": "^3.
|
|
52
|
-
"nuxt": "^4.
|
|
48
|
+
"mermaid": "^11.12.2",
|
|
49
|
+
"motion-v": "^1.9.0",
|
|
50
|
+
"nitropack": "^2.13.1",
|
|
51
|
+
"nuxi": "^3.32.0",
|
|
52
|
+
"nuxt": "^4.2.2",
|
|
53
53
|
"nuxt-build-cache": "^0.1.1",
|
|
54
54
|
"nuxt-llms": "^0.1.3",
|
|
55
55
|
"pkg-types": "^2.3.0",
|
|
56
56
|
"scule": "^1.3.0",
|
|
57
|
-
"shiki": "^3.
|
|
58
|
-
"tailwindcss": "^4.1.
|
|
59
|
-
"unctx": "^2.
|
|
60
|
-
"unstorage": "^1.17.
|
|
61
|
-
"vue": "^3.5.
|
|
62
|
-
"vue-router": "^4.
|
|
57
|
+
"shiki": "^3.21.0",
|
|
58
|
+
"tailwindcss": "^4.1.18",
|
|
59
|
+
"unctx": "^2.5.0",
|
|
60
|
+
"unstorage": "^1.17.4",
|
|
61
|
+
"vue": "^3.5.26",
|
|
62
|
+
"vue-router": "^4.6.4"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@nuxt/eslint-config": "^1.
|
|
66
|
-
"@nuxt/image": "^
|
|
67
|
-
"@types/node": "^
|
|
65
|
+
"@nuxt/eslint-config": "^1.12.1",
|
|
66
|
+
"@nuxt/image": "^2.0.0",
|
|
67
|
+
"@types/node": "^25.0.8",
|
|
68
68
|
"changelogen": "^0.6.2",
|
|
69
|
-
"eslint": "^9.
|
|
70
|
-
"eslint-config-unjs": "^0.
|
|
71
|
-
"jiti": "^2.
|
|
72
|
-
"prettier": "^3.
|
|
73
|
-
"typescript": "^5.9.
|
|
74
|
-
"vue-tsc": "^3.
|
|
69
|
+
"eslint": "^9.39.2",
|
|
70
|
+
"eslint-config-unjs": "^0.6.2",
|
|
71
|
+
"jiti": "^2.6.1",
|
|
72
|
+
"prettier": "^3.8.0",
|
|
73
|
+
"typescript": "^5.9.3",
|
|
74
|
+
"vue-tsc": "^3.2.2"
|
|
75
75
|
},
|
|
76
|
-
"packageManager": "pnpm@10.
|
|
76
|
+
"packageManager": "pnpm@10.28.0"
|
|
77
77
|
}
|
package/schema/config.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ export interface DocsConfig {
|
|
|
8
8
|
url?: string
|
|
9
9
|
github?: string
|
|
10
10
|
socials?: Record<string, string>
|
|
11
|
+
llms?: {
|
|
12
|
+
full?: {
|
|
13
|
+
title?: string
|
|
14
|
+
description?: string
|
|
15
|
+
}
|
|
16
|
+
}
|
|
11
17
|
branch?: string
|
|
12
18
|
banner?: BannerProps
|
|
13
19
|
versions?: { label: string; to: string; active?: boolean }[]
|
package/schema/config.json
CHANGED
|
@@ -35,6 +35,40 @@
|
|
|
35
35
|
"type": "string",
|
|
36
36
|
"description": "The GitHub repository for the documentation site."
|
|
37
37
|
},
|
|
38
|
+
"llms": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"description": "Configuration for `nuxt-llms` generation (`/llms.txt` and `/llms-full.txt`).",
|
|
41
|
+
"additionalProperties": false,
|
|
42
|
+
"properties": {
|
|
43
|
+
"domain": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "Public site domain (e.g. https://example.com)."
|
|
46
|
+
},
|
|
47
|
+
"title": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": "Title used in `/llms.txt`."
|
|
50
|
+
},
|
|
51
|
+
"description": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Description used in `/llms.txt`."
|
|
54
|
+
},
|
|
55
|
+
"full": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"description": "Metadata for `/llms-full.txt`.",
|
|
58
|
+
"additionalProperties": false,
|
|
59
|
+
"properties": {
|
|
60
|
+
"title": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Title used in `/llms-full.txt`."
|
|
63
|
+
},
|
|
64
|
+
"description": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"description": "Description used in `/llms-full.txt`."
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
38
72
|
"socials": {
|
|
39
73
|
"type": "object",
|
|
40
74
|
"description": "Social media links for the documentation site.",
|