@movk/nuxt-docs 1.1.0

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.
Files changed (49) hide show
  1. package/README.md +265 -0
  2. package/app/app.config.ts +53 -0
  3. package/app/app.vue +73 -0
  4. package/app/components/AdsCarbon.vue +3 -0
  5. package/app/components/Footer.vue +32 -0
  6. package/app/components/PageHeaderLinks.vue +73 -0
  7. package/app/components/StarsBg.vue +122 -0
  8. package/app/components/content/ComponentEmits.vue +43 -0
  9. package/app/components/content/ComponentExample.vue +247 -0
  10. package/app/components/content/ComponentProps.vue +105 -0
  11. package/app/components/content/ComponentPropsLinks.vue +20 -0
  12. package/app/components/content/ComponentPropsSchema.vue +55 -0
  13. package/app/components/content/ComponentSlots.vue +50 -0
  14. package/app/components/content/HeroBackground.vue +65 -0
  15. package/app/components/content/HighlightInlineType.vue +39 -0
  16. package/app/components/content/Motion.vue +21 -0
  17. package/app/components/header/Header.vue +60 -0
  18. package/app/components/header/HeaderBody.vue +19 -0
  19. package/app/components/header/HeaderBottom.vue +26 -0
  20. package/app/components/header/HeaderLogo.vue +57 -0
  21. package/app/components/theme-picker/ThemePicker.vue +152 -0
  22. package/app/components/theme-picker/ThemePickerButton.vue +37 -0
  23. package/app/composables/fetchComponentExample.ts +32 -0
  24. package/app/composables/fetchComponentMeta.ts +34 -0
  25. package/app/composables/useCategory.ts +5 -0
  26. package/app/composables/useHeader.ts +6 -0
  27. package/app/composables/useNavigation.ts +89 -0
  28. package/app/error.vue +59 -0
  29. package/app/layouts/default.vue +3 -0
  30. package/app/layouts/docs.vue +31 -0
  31. package/app/pages/docs/[...slug].vue +158 -0
  32. package/app/pages/index.vue +30 -0
  33. package/app/pages/releases.vue +92 -0
  34. package/app/plugins/prettier.ts +67 -0
  35. package/app/plugins/theme.ts +82 -0
  36. package/app/types/index.d.ts +37 -0
  37. package/app/workers/prettier.js +36 -0
  38. package/content.config.ts +68 -0
  39. package/modules/component-example.ts +128 -0
  40. package/modules/component-meta.ts +22 -0
  41. package/modules/config.ts +79 -0
  42. package/modules/css.ts +33 -0
  43. package/nuxt.config.ts +80 -0
  44. package/package.json +55 -0
  45. package/server/api/component-example.get.ts +19 -0
  46. package/server/plugins/llms.ts +24 -0
  47. package/server/routes/raw/[...slug].md.get.ts +27 -0
  48. package/utils/git.ts +108 -0
  49. package/utils/meta.ts +29 -0
@@ -0,0 +1,22 @@
1
+ import { defineNuxtModule } from '@nuxt/kit'
2
+ import type { NuxtComponentMeta } from 'nuxt-component-meta'
3
+
4
+ export default defineNuxtModule({
5
+ meta: {
6
+ name: 'component-meta'
7
+ },
8
+ async setup(_options, nuxt) {
9
+ // @ts-expect-error - Hook is not typed correctly
10
+ nuxt.hook('component-meta:schema', (schema: NuxtComponentMeta) => {
11
+ for (const componentName in schema) {
12
+ const component = schema[componentName]
13
+ // Delete schema from slots to reduce metadata file size
14
+ if (component?.meta?.slots) {
15
+ for (const slot of component.meta.slots) {
16
+ delete (slot as any).schema
17
+ }
18
+ }
19
+ }
20
+ })
21
+ }
22
+ })
@@ -0,0 +1,79 @@
1
+ import { createResolver, defineNuxtModule } from '@nuxt/kit'
2
+ import { defu } from 'defu'
3
+ import { getGitBranch, getGitEnv, getLocalGitInfo } from '../utils/git'
4
+ import { getPackageJsonMetadata, inferSiteURL } from '../utils/meta'
5
+
6
+ export default defineNuxtModule({
7
+ meta: {
8
+ name: 'config'
9
+ },
10
+ async setup(_options, nuxt) {
11
+ const { resolve } = createResolver(import.meta.url)
12
+ const dir = nuxt.options.rootDir
13
+ const url = inferSiteURL()
14
+ const meta = await getPackageJsonMetadata(dir)
15
+ const gitInfo = await getLocalGitInfo(dir) || getGitEnv()
16
+ const siteName = nuxt.options?.site?.name || meta.name || gitInfo?.name || ''
17
+
18
+ nuxt.options.llms = defu(nuxt.options.llms, {
19
+ domain: url || 'https://example.com',
20
+ title: siteName,
21
+ description: meta.description || '',
22
+ full: {
23
+ title: siteName,
24
+ description: meta.description || ''
25
+ }
26
+ })
27
+
28
+ nuxt.options.site = defu(nuxt.options.site, {
29
+ url,
30
+ name: siteName,
31
+ debug: false
32
+ })
33
+
34
+ nuxt.options.robots = defu(nuxt.options.robots, {
35
+ sitemap: url ? `${url.replace(/\/$/, '')}/sitemap.xml` : undefined
36
+ })
37
+
38
+ nuxt.options.appConfig.header = defu(nuxt.options.appConfig.header, {
39
+ title: siteName
40
+ })
41
+
42
+ nuxt.options.appConfig.seo = defu(nuxt.options.appConfig.seo, {
43
+ titleTemplate: `%s - ${siteName}`,
44
+ title: siteName,
45
+ description: meta.description || ''
46
+ })
47
+
48
+ nuxt.options.appConfig.github = defu(nuxt.options.appConfig.github, {
49
+ owner: gitInfo?.owner,
50
+ name: gitInfo?.name,
51
+ url: gitInfo?.url,
52
+ branch: getGitBranch()
53
+ })
54
+
55
+ const componentsPath = resolve('../app/components')
56
+
57
+ nuxt.options.componentMeta = defu(nuxt.options.componentMeta, {
58
+ exclude: [
59
+ '@nuxt/content',
60
+ '@nuxt/icon',
61
+ '@nuxt/image',
62
+ '@nuxtjs/color-mode',
63
+ '@nuxtjs/mdc',
64
+ 'nuxt/dist',
65
+ 'nuxt-og-image',
66
+ '@nuxtjs/plausible',
67
+ '@nuxt/ui',
68
+ new RegExp(`${componentsPath.replace(/[/\\]/g, '[/\\\\]')}/(?!content/(ComponentEmits|ComponentProps|ComponentSlots|ComponentExample)\\.vue$)`)
69
+ ],
70
+ metaFields: {
71
+ type: false,
72
+ props: true,
73
+ slots: true,
74
+ events: true,
75
+ exposed: false
76
+ }
77
+ })
78
+ }
79
+ })
package/modules/css.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { addTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
2
+ import { joinURL } from 'ufo'
3
+ import { resolveModulePath } from 'exsolve'
4
+
5
+ export default defineNuxtModule({
6
+ meta: {
7
+ name: 'css'
8
+ },
9
+ async setup(_options, nuxt) {
10
+ const dir = nuxt.options.rootDir
11
+ const { resolve } = createResolver(import.meta.url)
12
+
13
+ const layerDir = resolve('../app')
14
+ const contentDir = joinURL(dir, 'content')
15
+ const uiPath = resolveModulePath('@nuxt/ui', { from: import.meta.url, conditions: ['style'] })
16
+ const tailwindPath = resolveModulePath('tailwindcss', { from: import.meta.url, conditions: ['style'] })
17
+
18
+ const cssTemplate = addTemplate({
19
+ filename: 'movk-nuxt-docs.css',
20
+ getContents: () => {
21
+ return `@import ${JSON.stringify(tailwindPath)};
22
+ @import ${JSON.stringify(tailwindPath)}/theme.css theme(static);
23
+ @import ${JSON.stringify(uiPath)};
24
+
25
+ @source "${contentDir.replace(/\\/g, '/')}/**/*";
26
+ @source "${layerDir.replace(/\\/g, '/')}/**/*";
27
+ @source "../../app.config.ts";`
28
+ }
29
+ })
30
+
31
+ nuxt.options.css.unshift(cssTemplate.dst)
32
+ }
33
+ })
package/nuxt.config.ts ADDED
@@ -0,0 +1,80 @@
1
+ import { createResolver } from '@nuxt/kit'
2
+
3
+ const { resolve } = createResolver(import.meta.url)
4
+
5
+ export default defineNuxtConfig({
6
+ modules: [
7
+ resolve('./modules/config'),
8
+ resolve('./modules/css'),
9
+ resolve('./modules/component-example'),
10
+ resolve('./modules/component-meta'),
11
+ '@nuxt/ui',
12
+ '@nuxt/content',
13
+ '@nuxt/image',
14
+ '@vueuse/nuxt',
15
+ '@nuxtjs/seo',
16
+ 'nuxt-component-meta',
17
+ 'motion-v/nuxt',
18
+ 'nuxt-llms'
19
+ ],
20
+ content: {
21
+ build: {
22
+ markdown: {
23
+ highlight: {
24
+ langs: ['bash', 'ts', 'typescript', 'diff', 'vue', 'json', 'yml', 'css', 'mdc', 'blade', 'edge']
25
+ }
26
+ }
27
+ }
28
+ },
29
+ mdc: {
30
+ highlight: {
31
+ noApiRoute: false
32
+ }
33
+ },
34
+ compatibilityDate: 'latest',
35
+ nitro: {
36
+ prerender: {
37
+ routes: ['/', '/sitemap.xml', '/robots.txt', '/404.html'],
38
+ crawlLinks: true,
39
+ autoSubfolderIndex: false
40
+ }
41
+ },
42
+ vite: {
43
+ optimizeDeps: {
44
+ include: [
45
+ '@nuxt/content > slugify',
46
+ 'colortranslator',
47
+ 'tailwindcss/colors',
48
+ 'tailwind-variants',
49
+ 'ufo',
50
+ 'zod',
51
+ 'scule',
52
+ 'motion-v',
53
+ 'ohash'
54
+ ]
55
+ }
56
+ },
57
+ icon: {
58
+ provider: 'iconify'
59
+ },
60
+ linkChecker: {
61
+ report: {
62
+ publish: true,
63
+ html: true,
64
+ markdown: true,
65
+ json: true
66
+ }
67
+ },
68
+ ogImage: {
69
+ googleFontMirror: 'fonts.loli.net',
70
+ fonts: [
71
+ // 思源黑体 - 支持中文
72
+ 'Noto+Sans+SC:400',
73
+ 'Noto+Sans+SC:500',
74
+ 'Noto+Sans+SC:700',
75
+ // 如果需要英文字体
76
+ 'Inter:400',
77
+ 'Inter:700'
78
+ ]
79
+ }
80
+ })
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@movk/nuxt-docs",
3
+ "type": "module",
4
+ "version": "1.1.0",
5
+ "private": false,
6
+ "description": "An elegant documentation theme for Nuxt, powered by Nuxt UI and Nuxt Content.",
7
+ "author": "YiXuan <mhaibaraai@gmail.com>",
8
+ "license": "MIT",
9
+ "homepage": "https://docs.mhaibaraai.cn",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/mhaibaraai/movk-nuxt-docs.git"
13
+ },
14
+ "main": "./nuxt.config.ts",
15
+ "peerDependencies": {
16
+ "better-sqlite3": "12.x",
17
+ "nuxt": "4.x"
18
+ },
19
+ "files": [
20
+ "app",
21
+ "content.config.ts",
22
+ "modules",
23
+ "nuxt.config.ts",
24
+ "server",
25
+ "utils",
26
+ "pnpm-workspace.yaml",
27
+ "README.md"
28
+ ],
29
+ "dependencies": {
30
+ "@iconify-json/lucide": "^1.2.69",
31
+ "@iconify-json/simple-icons": "^1.2.54",
32
+ "@iconify-json/vscode-icons": "^1.2.32",
33
+ "@movk/core": "^0.0.5",
34
+ "@nuxt/content": "^3.7.1",
35
+ "@nuxt/image": "^1.11.0",
36
+ "@nuxt/kit": "^4.1.3",
37
+ "@nuxt/ui": "^4.0.1",
38
+ "@nuxtjs/seo": "^3.2.2",
39
+ "@vueuse/core": "^13.9.0",
40
+ "@vueuse/nuxt": "^13.9.0",
41
+ "defu": "^6.1.4",
42
+ "pathe": "^2.0.3",
43
+ "exsolve": "^1.0.7",
44
+ "git-url-parse": "^16.1.0",
45
+ "motion-v": "^1.7.2",
46
+ "nuxt-component-meta": "^0.14.1",
47
+ "prettier": "^3.6.2",
48
+ "nuxt-llms": "^0.1.3",
49
+ "pkg-types": "^2.3.0",
50
+ "scule": "^1.3.0",
51
+ "tailwindcss": "^4.1.14",
52
+ "ohash": "^2.0.11",
53
+ "ufo": "^1.6.1"
54
+ }
55
+ }
@@ -0,0 +1,19 @@
1
+ import { defineEventHandler, createError, appendHeader } from 'h3'
2
+ import { pascalCase } from 'scule'
3
+ // @ts-expect-error - no types available
4
+ import components from '#component-example/nitro'
5
+
6
+ export default defineEventHandler((event) => {
7
+ appendHeader(event, 'Access-Control-Allow-Origin', '*')
8
+ const componentName = (event.context.params?.['component?'] || '').replace(/\.json$/, '')
9
+ if (componentName) {
10
+ const component = components[pascalCase(componentName)]
11
+ if (!component) {
12
+ throw createError({
13
+ statusMessage: 'Example not found!',
14
+ statusCode: 404
15
+ })
16
+ }
17
+ return component
18
+ }
19
+ })
@@ -0,0 +1,24 @@
1
+ export default defineNitroPlugin((nitroApp) => {
2
+ /**
3
+ * @see
4
+ * https://github.com/nuxt-content/nuxt-llms?tab=readme-ov-file#readme
5
+ */
6
+ nitroApp.hooks.hook('llms:generate', (_, { sections, domain }) => {
7
+ // Transform links except for "Documentation Sets"
8
+ sections.forEach((section) => {
9
+ if (section.title !== 'Documentation Sets') {
10
+ section.links = section.links?.map(link => ({
11
+ ...link,
12
+ href: `${link.href.replace(new RegExp(`^${domain}`), `${domain}/raw`)}.md`
13
+ }))
14
+ }
15
+ })
16
+
17
+ // Move "Documentation Sets" to the end
18
+ const docSetIdx = sections.findIndex(s => s.title === 'Documentation Sets')
19
+ if (docSetIdx !== -1) {
20
+ const [docSet] = sections.splice(docSetIdx, 1)
21
+ sections.push(docSet)
22
+ }
23
+ })
24
+ })
@@ -0,0 +1,27 @@
1
+ import type { Collections } from '@nuxt/content'
2
+ import { queryCollection } from '@nuxt/content/server'
3
+ import { stringify } from 'minimark/stringify'
4
+ import { withLeadingSlash } from 'ufo'
5
+
6
+ export default eventHandler(async (event) => {
7
+ const slug = getRouterParams(event)['slug.md']
8
+ if (!slug?.endsWith('.md')) {
9
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
10
+ }
11
+
12
+ const path = withLeadingSlash(slug.replace('.md', ''))
13
+
14
+ const page = await queryCollection(event, 'docs' as keyof Collections).path(path).first()
15
+ if (!page) {
16
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
17
+ }
18
+
19
+ // Add title and description to the top of the page if missing
20
+ if (page.body.value[0]?.[0] !== 'h1') {
21
+ page.body.value.unshift(['blockquote', {}, page.description])
22
+ page.body.value.unshift(['h1', {}, page.title])
23
+ }
24
+
25
+ setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
26
+ return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
27
+ })
package/utils/git.ts ADDED
@@ -0,0 +1,108 @@
1
+ // copy from https://github.com/nuxt-content/docus/tree/main/layer/utils
2
+ import { execSync } from 'node:child_process'
3
+ import gitUrlParse from 'git-url-parse'
4
+ import { readGitConfig } from 'pkg-types'
5
+
6
+ export interface GitInfo {
7
+ // Repository name
8
+ name: string
9
+ // Repository owner/organization
10
+ owner: string
11
+ // Repository URL
12
+ url: string
13
+ }
14
+
15
+ export function getGitBranch() {
16
+ const envName
17
+ = process.env.CF_PAGES_BRANCH
18
+ || process.env.CI_COMMIT_BRANCH
19
+ || process.env.VERCEL_GIT_COMMIT_REF
20
+ || process.env.BRANCH
21
+ || process.env.GITHUB_REF_NAME
22
+
23
+ if (envName && envName !== 'HEAD') {
24
+ return envName
25
+ }
26
+ try {
27
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
28
+ if (branch && branch !== 'HEAD') {
29
+ return branch
30
+ }
31
+ } catch {
32
+ // Ignore error
33
+ }
34
+
35
+ return 'main'
36
+ }
37
+
38
+ export async function getLocalGitInfo(rootDir: string): Promise<GitInfo | undefined> {
39
+ const remote = await getLocalGitRemote(rootDir)
40
+ if (!remote) {
41
+ return
42
+ }
43
+
44
+ // https://www.npmjs.com/package/git-url-parse#clipboard-example
45
+ const { name, owner, source } = gitUrlParse(remote)
46
+ const url = `https://${source}/${owner}/${name}`
47
+
48
+ return {
49
+ name,
50
+ owner,
51
+ url
52
+ }
53
+ }
54
+
55
+ async function getLocalGitRemote(dir: string): Promise<string | undefined> {
56
+ try {
57
+ const parsed = await readGitConfig(dir)
58
+ if (!parsed) {
59
+ return
60
+ }
61
+ return parsed.remote?.origin?.url
62
+ } catch {
63
+ // Ignore error
64
+ }
65
+ }
66
+
67
+ export function getGitEnv(): GitInfo {
68
+ // https://github.com/unjs/std-env/issues/59
69
+ const envInfo = {
70
+ // Provider
71
+ provider: process.env.VERCEL_GIT_PROVIDER // vercel
72
+ || (process.env.GITHUB_SERVER_URL ? 'github' : undefined) // github
73
+ || '',
74
+ // Owner
75
+ owner: process.env.VERCEL_GIT_REPO_OWNER // vercel
76
+ || process.env.GITHUB_REPOSITORY_OWNER // github
77
+ || process.env.CI_PROJECT_PATH?.split('/').shift() // gitlab
78
+ || '',
79
+ // Name
80
+ name: process.env.VERCEL_GIT_REPO_SLUG
81
+ || process.env.GITHUB_REPOSITORY?.split('/').pop() // github
82
+ || process.env.CI_PROJECT_PATH?.split('/').splice(1).join('/') // gitlab
83
+ || '',
84
+ // Url
85
+ url: process.env.REPOSITORY_URL || '' // netlify
86
+ }
87
+
88
+ if (!envInfo.url && envInfo.provider && envInfo.owner && envInfo.name) {
89
+ envInfo.url = `https://${envInfo.provider}.com/${envInfo.owner}/${envInfo.name}`
90
+ }
91
+
92
+ // If only url available (ex: Netlify)
93
+ if (!envInfo.name && !envInfo.owner && envInfo.url) {
94
+ try {
95
+ const { name, owner } = gitUrlParse(envInfo.url)
96
+ envInfo.name = name
97
+ envInfo.owner = owner
98
+ } catch {
99
+ // Ignore error
100
+ }
101
+ }
102
+
103
+ return {
104
+ name: envInfo.name,
105
+ owner: envInfo.owner,
106
+ url: envInfo.url
107
+ }
108
+ }
package/utils/meta.ts ADDED
@@ -0,0 +1,29 @@
1
+ // copy from https://github.com/nuxt-content/docus/tree/main/layer/utils
2
+ import { readFile } from 'node:fs/promises'
3
+ import { resolve } from 'node:path'
4
+
5
+ export function inferSiteURL() {
6
+ // https://github.com/unjs/std-env/issues/59
7
+ return (
8
+ process.env.NUXT_SITE_URL
9
+ || (process.env.NEXT_PUBLIC_VERCEL_URL && `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`) // Vercel
10
+ || process.env.URL // Netlify
11
+ || process.env.CI_PAGES_URL // Gitlab Pages
12
+ || process.env.CF_PAGES_URL // Cloudflare Pages
13
+ )
14
+ }
15
+
16
+ export async function getPackageJsonMetadata(dir: string) {
17
+ try {
18
+ const packageJson = await readFile(resolve(dir, 'package.json'), 'utf-8')
19
+ const parsed = JSON.parse(packageJson)
20
+ return {
21
+ name: parsed.name,
22
+ description: parsed.description
23
+ }
24
+ } catch {
25
+ return {
26
+ name: 'docs'
27
+ }
28
+ }
29
+ }