Skip to content

Commit

Permalink
feat(breadcrumbs): add vitepress breadcrumbs plugin (#327)
Browse files Browse the repository at this point in the history
* add breadcrumbs plugin
* use rollup instead of mkdist
* fix naming issues
* add spaces to different markdown blocks
* refactor: use a single function instead create a class
* add spaces between different markdown blocks
* add  missing css class prefix
* fix broken lockfile
  • Loading branch information
LemonNekoGH authored Nov 13, 2024
1 parent 0145f42 commit 99d7970
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 0 deletions.
99 changes: 99 additions & 0 deletions packages/vitepress-plugin-breadcrumbs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# @nolebase/vitepress-plugin-breadcrumbs

A VitePress plugin that adds breadcrumbs to your documentation.

## Get started

Install:

```shell
pnpm install @nolebase/vitepress-plugin-breadcrumbs
# or use bun
bun install @nolebase/vitepress-plugin-breadcrumbs
```

Generate breadcrumbs data when build your pages in `.vitepress/config.ts`:

```typescript
import { generateBreadcrumbsData } from '@nolebase/vitepress-plugin-breadcrumbs'
import { defineConfig } from 'vitepress'

export default defineConfig({
// other config...
transformPageData(pageData, context) {
generateBreadcrumbsData(pageData, context)
// other transforming...
},
// other config...
})

```

Add default breadcrumb vue component to each page in `.vitepress/theme/index.ts`:

```typescript
import type { Theme as ThemeConfig } from 'vitepress'
import { NolebaseBreadcrumbs } from '@nolebase/vitepress-plugin-breadcrumbs/client'
import DefaultTheme from 'vitepress/theme'
import { h } from 'vue'

export const Theme: ThemeConfig = {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
// add breadcrumb above document
'doc-before': () => h(NolebaseBreadcrumbs),
})
},
enhanceApp({ app }) {
app.provide<Options>(InjectionKey, {
spotlight: {
defaultToggle: true
}
})
}
}

export default Theme
```

Add this plugin to `noExternal` and `exclude` properties when building:

```typescript
export default defineConfig({
vite: {
optimizeDeps: {
exclude: [
'@nolebase/vitepress-plugin-breadcrumbs/client'
]
},
ssr: {
noExternal: [
'@nolebase/vitepress-plugin-breadcrumbs'
]
}
},
// other config...
})
```

## Use custom breadcrumb component

If you don't like the style or other something of default breadcrumb component, you can create your own component, this plugin will inject breadcrumb data into frontmatter of the page, so you can use breadcrumb data like this:

```vue
<script setup lang="ts">
import { useData } from 'vitepress'
const { frontmatter } = useData()
console.log(frontmatter.breadcrumbs)
// and do something other...
</script>
<template>
<div>
<!-- ui of your own component -->
</div>
</template>
```
16 changes: 16 additions & 0 deletions packages/vitepress-plugin-breadcrumbs/build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
entries: [
{ builder: 'mkdist', input: './src/client', outDir: './dist/client', pattern: ['**/*.vue'], loaders: ['vue'] },
{ builder: 'mkdist', input: './src/client', outDir: './dist/client', pattern: ['**/*.ts'], format: 'cjs', loaders: ['js'] },
{ builder: 'mkdist', input: './src/client', outDir: './dist/client', pattern: ['**/*.ts'], format: 'esm', loaders: ['js'] },
{ builder: 'rollup', input: './src/vitepress/index', outDir: 'dist/vitepress/' },
],
rollup: {
emitCJS: true,
},
clean: true,
sourcemap: true,
declaration: true,
})
58 changes: 58 additions & 0 deletions packages/vitepress-plugin-breadcrumbs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "@nolebase/vitepress-plugin-breadcrumbs",
"type": "module",
"version": "2.8.1",
"description": "A VitePress plugin that adds breadcrumbs to your documentation.",
"author": {
"name": "Nólëbase",
"email": "[email protected]",
"url": "https://github.com/nolebase"
},
"license": "MIT",
"homepage": "https://nolebase-integrations.ayaka.io/pages/en/integrations/vitepress-plugin-breadcrumbs/",
"repository": {
"type": "git",
"url": "https://github.com/nolebase/integrations.git",
"directory": "packages/vitepress-plugin-breadcrumbs"
},
"keywords": [
"vitepress",
"nolebase",
"vitepress-plugin",
"nolebase-integration"
],
"exports": {
".": {
"types": "./dist/vitepress/index.d.ts",
"import": "./dist/vitepress/index.mjs",
"require": "./dist/vitepress/index.cjs"
},
"./vitepress": {
"types": "./dist/vitepress/index.d.ts",
"import": "./dist/vitepress/index.mjs",
"require": "./dist/vitepress/index.cjs"
},
"./client": {
"types": "./dist/client/index.d.ts",
"import": "./dist/client/index.mjs",
"require": "./dist/client/index.js"
}
},
"main": "./dist/vitepress/index.cjs",
"module": "./dist/vitepress/index.mjs",
"types": "./dist/vitepress/index.d.ts",
"files": [
"README.md",
"dist",
"package.json"
],
"scripts": {
"dev": "unbuild --stub",
"stub": "unbuild --stub",
"build": "unbuild",
"package:publish": "pnpm build && pnpm publish --access public --no-git-checks"
},
"dependencies": {
"vitepress": "^1.5.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">
import { useData } from 'vitepress'
const { frontmatter } = useData()
</script>

<template>
<div class="vp-nolebase-breadcrumbs">
<span v-for="item in frontmatter.breadcrumbs" :key="item">
<a v-if="item.link" :href="item.link">{{ item.title }}</a>
<span v-else>{{ item.title }}</span>
</span>
</div>
</template>

<style scoped>
.vp-nolebase-breadcrumbs {
display: flex;
gap: 8px;
font-size: 14px;
margin-bottom: 2rem;
}
.vp-nolebase-breadcrumbs span {
transition: color 0.25s, opacity 0.25s;
color: var(--vp-c-text-2);
}
.vp-nolebase-breadcrumbs span:hover {
color: var(--vp-c-brand-1);
}
.vp-nolebase-breadcrumbs span:last-child {
color: var(--vp-c-text-1);
}
.vp-nolebase-breadcrumbs span:last-child:hover {
color: var(--vp-c-brand-1);
}
.vp-nolebase-breadcrumbs span:not(:first-child)::before {
content: '/';
padding-right: 8px;
color: var(--vp-c-text-3);
}
</style>
3 changes: 3 additions & 0 deletions packages/vitepress-plugin-breadcrumbs/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import NolebaseBreadcrumbs from './components/NolebaseBreadcrumbs.vue'

export { NolebaseBreadcrumbs }
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { PageData, TransformPageContext } from 'vitepress'
import { expect, it } from 'vitest'
import { generateBreadcrumbsData } from '../vitepress'

it('page is not index', () => {
const pageData = {
relativePath: 'a/b/c/d.md',
filePath: 'a/b/c/d.md',
title: 'd',
frontmatter: {},
} as PageData

const context = {
siteConfig: {
site: {
title: 'Home',
},
pages: ['a', 'a/b', 'a/b/index.md', 'a/b/c/d.md'],
},
} as TransformPageContext

generateBreadcrumbsData(pageData, context)

expect(pageData.frontmatter.breadcrumbs).toEqual([
{ title: 'Home', link: '/a' },
{ title: 'b', link: '/a/b' },
{ title: 'c', link: '' },
{ title: 'd', link: '/a/b/c/d' },
])
})

it('page is index', () => {
const pageData = {
relativePath: 'a/b/c/d/index.md',
filePath: 'a/b/c/d/index.md',
title: 'd',
frontmatter: {},
} as PageData

const context = {
siteConfig: {
site: {
title: 'Home',
},
pages: ['a', 'a/b', 'a/b/index.md', 'a/b/c/d/index.md'],
},
} as TransformPageContext

generateBreadcrumbsData(pageData, context)

expect(pageData.frontmatter.breadcrumbs).toEqual([
{ title: 'Home', link: '/a' },
{ title: 'b', link: '/a/b' },
{ title: 'c', link: '' },
{ title: 'd', link: '/a/b/c/d' },
])
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { PageData, TransformPageContext } from 'vitepress'

export function generateBreadcrumbsData(pageData: PageData, context: TransformPageContext) {
const splitPath = pageData.filePath.split('/')
const rootDirectory = splitPath[0]
const pages = context.siteConfig.pages
const breadcrumbs: {
title: string
link: string
}[] = [{
title: context.siteConfig.site.title,
link: `/${rootDirectory}`,
}]

for (let i = 1; i < splitPath.length; i++) {
let link = ''
let encodedLink = ''
let title = splitPath[i]
if (i === splitPath.length - 1) {
title = pageData.title
}

for (let j = 0; j <= i; j++) {
link += `${splitPath[j]}`
encodedLink += `${encodeURIComponent(splitPath[j])}`

if (j !== i) {
link += `/`
encodedLink += `/`
}
}

if (!pages.includes(link) && !pages.includes(`${link}/index.md`)) {
breadcrumbs.push({ title: splitPath[i], link: '' })
continue
}

if (link.endsWith('index.md')) {
continue
}

if (link.endsWith('.md')) {
encodedLink = encodedLink.slice(0, -3)
}

breadcrumbs.push({ title, link: `/${encodedLink}` })
}

if (splitPath.length)
pageData.frontmatter.breadcrumbs = breadcrumbs
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { generateBreadcrumbsData } from './breadcrumbs-data-generator'
35 changes: 35 additions & 0 deletions packages/vitepress-plugin-breadcrumbs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": false,
"baseUrl": ".",
"strict": true,
"strictNullChecks": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"declaration": true,
"noEmit": false,
"outDir": "./dist",
"removeComments": false,
"sourceMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipDefaultLibCheck": true,
"skipLibCheck": true
},
"include": [
"src/**/*.ts",
"src/**/*.mts",
"src/**/*.d.ts",
"src/**/*.vue",
"src/**/*.tsx"
],
"exclude": [
"**/dist/**",
"node_modules"
]
}
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 99d7970

Please sign in to comment.