diff --git a/packages/docs/build/api-plugin.ts b/packages/docs/build/api-plugin.ts index 60433356134..536ec1d50de 100644 --- a/packages/docs/build/api-plugin.ts +++ b/packages/docs/build/api-plugin.ts @@ -1,163 +1,28 @@ // Imports -import fs from 'fs' -import path, { resolve } from 'path' -import { createRequire } from 'module' -import { startCase } from 'lodash-es' -import locales from '../src/i18n/locales.json' -import pageToApi from '../src/data/page-to-api.json' +import fs from 'node:fs/promises' +import path from 'node:path' import type { Plugin } from 'vite' -import { rimraf } from 'rimraf' -import { mkdirp } from 'mkdirp' -const API_ROOT = resolve('../api-generator/dist/api') -const API_PAGES_ROOT = resolve('./node_modules/.cache/api-pages') - -const require = createRequire(import.meta.url) - -const sections = ['props', 'events', 'slots', 'exposed', 'sass', 'argument', 'modifiers', 'value'] as const -// This can't be imported from the api-generator because it mixes the type definitions up -type Data = { - displayName: string // user visible name used in page titles - fileName: string // file name for translation strings and generated types - pathName: string // kebab-case name for use in urls -} & Record> - -const localeList = locales - .filter(item => item.enabled) - .map(item => item.alternate || item.locale) - -function genApiLinks (componentName: string, header: string) { - const section = ['', ''] - const links = (Object.keys(pageToApi) as (keyof typeof pageToApi)[]) - .filter(page => pageToApi[page].includes(componentName)) - .reduce((acc, href) => { - const name = href.split('/')[1] - acc.push(`- [${startCase(name)}](/${href})`) - return acc - }, []) - - if (links.length && header) { - section.unshift(...[links.join('\n'), `## ${header} {#links}`]) - } - - return `${section.join('\n\n')}\n\n` -} - -function genFrontMatter (component: string) { - const fm = [ - `title: ${component} API`, - `description: API for the ${component} component.`, - `keywords: ${component}, api, vuetify`, - ] - - return `---\nmeta:\n${fm.map(s => ' ' + s).join('\n')}\n---` -} - -function genHeader (componentName: string) { - const header = [ - genFrontMatter(componentName), - `# ${componentName} API`, - '', - ] - - return `${header.join('\n\n')}\n\n` -} - -const sanitize = (str: string) => str.replace(/\$/g, '') - -async function loadMessages (locale: string) { - const prefix = path.resolve('./src/i18n/messages/') - const fallback = require(path.join(prefix, 'en.json')) - - try { - const messages = require(path.join(prefix, `${locale}.json`)) - - return { - ...fallback['api-headers'], - ...(messages['api-headers'] || {}), - } - } catch (err) { - return fallback['api-headers'] - } -} - -async function createMdFile (component: Data, locale: string) { - const messages = await loadMessages(locale) - let str = '' - - str += genHeader(component.displayName) - str += genApiLinks(component.displayName, messages.links) - - for (const section of sections) { - if (Object.keys(component[section] ?? {}).length) { - str += `## ${messages[section]} {#${section}}\n\n` - str += `\n\n` - } - } - - return str -} - -async function writeFile (componentApi: Data, locale: string) { - if (!componentApi?.fileName) return - - const folder = resolve(API_PAGES_ROOT, locale, 'api') - - if (!fs.existsSync(folder)) { - fs.mkdirSync(folder, { recursive: true }) - } - - fs.writeFileSync(resolve(folder, `${sanitize(componentApi.pathName)}.md`), await createMdFile(componentApi, locale)) -} - -function getApiData () { - const files = fs.readdirSync(API_ROOT) - const data: Data[] = [] - - for (const file of files) { - const obj = JSON.parse(fs.readFileSync(resolve(API_ROOT, file), 'utf-8')) - - data.push(obj) - } - - return data -} - -async function generateFiles () { - // const api: Record[] = getCompleteApi(localeList) - const api = getApiData() - - for (const locale of localeList) { - // const pages = {} as Record - - for (const item of api) { - await writeFile(item, locale) - - // pages[`/${locale}/api/${sanitize(kebabCase(item.name))}/`] = item.name - } - - // fs.writeFileSync(resolve(API_PAGES_ROOT, `${locale}/pages.json`), JSON.stringify(pages, null, 2)) - fs.writeFileSync(resolve(API_PAGES_ROOT, `${locale}.js`), `export default require.context('./${locale}/api', true, /\\.md$/)`) - } - - // for (const item of api) { - // writeData(item.name, item) - // } - - // fs.writeFileSync(resolve(API_PAGES_ROOT, 'sass.json'), JSON.stringify([ - // ...api.filter(item => item && item.sass && item.sass.length > 0).map(item => item.name), - // ])) -} +const API_ROOT = path.resolve('../api-generator/dist/api') export default function Api (): Plugin { return { name: 'vuetify:api', enforce: 'pre', - async config () { - await rimraf(API_PAGES_ROOT) - await mkdirp(API_PAGES_ROOT) + resolveId (id) { + return id === 'virtual:api-list' ? '\0' + id : undefined + }, + async load (id) { + if (id === '\0virtual:api-list') { + const files = await fs.readdir(API_ROOT) + + const names = files + .sort((a, b) => a.localeCompare(b)) + .map(file => `'${file.split('.')[0]}'`) + .join(', ') - await generateFiles() + return `export default [${names}]` + } }, } } diff --git a/packages/docs/components.d.ts b/packages/docs/components.d.ts index 07b399955a6..34b2633b0c7 100644 --- a/packages/docs/components.d.ts +++ b/packages/docs/components.d.ts @@ -11,6 +11,7 @@ declare module 'vue' { AboutTeamMembers: typeof import('./src/components/about/TeamMembers.vue')['default'] Alert: typeof import('./src/components/Alert.vue')['default'] ApiApiTable: typeof import('./src/components/api/ApiTable.vue')['default'] + ApiBacklinks: typeof import('./src/components/api/Backlinks.vue')['default'] ApiDirectiveTable: typeof import('./src/components/api/DirectiveTable.vue')['default'] ApiEventsTable: typeof import('./src/components/api/EventsTable.vue')['default'] ApiExposedTable: typeof import('./src/components/api/ExposedTable.vue')['default'] @@ -23,6 +24,7 @@ declare module 'vue' { ApiSearch: typeof import('./src/components/api/Search.vue')['default'] ApiSection: typeof import('./src/components/api/Section.vue')['default'] ApiSlotsTable: typeof import('./src/components/api/SlotsTable.vue')['default'] + ApiView: typeof import('./src/components/api/View.vue')['default'] AppBackToTop: typeof import('./src/components/app/BackToTop.vue')['default'] AppBarBar: typeof import('./src/components/app/bar/Bar.vue')['default'] AppBarEcosystemMenu: typeof import('./src/components/app/bar/EcosystemMenu.vue')['default'] diff --git a/packages/docs/package.json b/packages/docs/package.json index 11f50447db4..bb0b8c90e6b 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -42,10 +42,13 @@ "vuetify": "workspace:*" }, "devDependencies": { + "@babel/generator": "^7.25.0", + "@babel/types": "^7.25.2", "@emailjs/browser": "^4.3.3", "@intlify/unplugin-vue-i18n": "^4.0.0", "@mdi/js": "7.4.47", "@mdi/svg": "7.4.47", + "@types/babel__generator": "^7.6.8", "@types/lodash-es": "^4.17.12", "@types/markdown-it": "^14.0.0", "@types/markdown-it-container": "^2.0.10", diff --git a/packages/docs/src/components/api/Backlinks.vue b/packages/docs/src/components/api/Backlinks.vue new file mode 100644 index 00000000000..7a3a006eba2 --- /dev/null +++ b/packages/docs/src/components/api/Backlinks.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/docs/src/components/api/Inline.vue b/packages/docs/src/components/api/Inline.vue index 745aaf091ea..ad19a97b229 100644 --- a/packages/docs/src/components/api/Inline.vue +++ b/packages/docs/src/components/api/Inline.vue @@ -29,7 +29,6 @@ diff --git a/packages/docs/src/components/api/Section.vue b/packages/docs/src/components/api/Section.vue index f586345d7ac..688da67b67e 100644 --- a/packages/docs/src/components/api/Section.vue +++ b/packages/docs/src/components/api/Section.vue @@ -8,7 +8,7 @@ @input="filter = $event" /> --> - + @@ -45,7 +45,6 @@ type: String as PropType, required: true, }, - showHeadline: Boolean, }) const store = useLocaleStore() diff --git a/packages/docs/src/components/api/View.vue b/packages/docs/src/components/api/View.vue new file mode 100644 index 00000000000..c71bd344f4e --- /dev/null +++ b/packages/docs/src/components/api/View.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/docs/src/components/app/list/List.vue b/packages/docs/src/components/app/list/List.vue index 7f4f8cf1c8e..3601092b320 100644 --- a/packages/docs/src/components/app/list/List.vue +++ b/packages/docs/src/components/app/list/List.vue @@ -58,8 +58,9 @@ + +# {{ name }} API + + + + + + + + + + + diff --git a/packages/docs/src/shims.d.ts b/packages/docs/src/shims.d.ts index 74d0a1ee34e..c4de6c0982f 100644 --- a/packages/docs/src/shims.d.ts +++ b/packages/docs/src/shims.d.ts @@ -19,6 +19,11 @@ declare module 'virtual:mdi-js-icons' { export const icons: IconEntry[] } +declare module 'virtual:api-list' { + const list: string[] + export default list +} + declare module 'markdown-it-header-sections' { import type MarkdownIt from 'markdown-it' diff --git a/packages/docs/vite.config.mts b/packages/docs/vite.config.mts index bd2f35fbf4d..0d777aa0d4d 100644 --- a/packages/docs/vite.config.mts +++ b/packages/docs/vite.config.mts @@ -86,6 +86,7 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { plugins: [ // https://github.com/unplugin/unplugin-auto-import AutoImport({ + include: [/\.[tj]sx?$/, /\.vue$/, /\.vue\?vue/, /\.md$/], dirs: [ './src/composables/**', './src/stores/**', @@ -158,7 +159,6 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { extensions: ['vue', 'md'], dirs: [ { dir: 'src/pages', baseRoute: '' }, - { dir: 'node_modules/.cache/api-pages', baseRoute: '' }, ], extendRoute (route) { let [locale, category, ...rest] = route.path.split('/').slice(1) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c725442c7b1..48a46d86bed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -293,6 +293,12 @@ importers: specifier: workspace:* version: link:../vuetify devDependencies: + '@babel/generator': + specifier: ^7.25.0 + version: 7.25.0 + '@babel/types': + specifier: ^7.25.2 + version: 7.25.2 '@emailjs/browser': specifier: ^4.3.3 version: 4.3.3 @@ -305,6 +311,9 @@ importers: '@mdi/svg': specifier: 7.4.47 version: 7.4.47 + '@types/babel__generator': + specifier: ^7.6.8 + version: 7.6.8 '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 @@ -2666,6 +2675,9 @@ packages: '@types/babel__generator@7.6.1': resolution: {integrity: sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==} + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@types/babel__template@7.0.2': resolution: {integrity: sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==} @@ -11243,6 +11255,10 @@ snapshots: dependencies: '@babel/types': 7.25.2 + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.25.2 + '@types/babel__template@7.0.2': dependencies: '@babel/parser': 7.25.3