Merge pull request #30 from pengzhanbo/RC-7

RC-7
This commit is contained in:
pengzhanbo 2023-12-29 03:40:55 +08:00 committed by GitHub
commit 4f914d8eee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 790 additions and 668 deletions

View File

@ -3,7 +3,7 @@
"type": "module",
"version": "1.0.0-rc.6",
"private": true,
"packageManager": "pnpm@8.12.1",
"packageManager": "pnpm@8.13.1",
"author": "pengzhanbo",
"license": "MIT",
"keywords": [
@ -18,7 +18,6 @@
"pnpm": ">=7"
},
"scripts": {
"autoUpdate": "node scripts/autoInstall.js",
"build": "pnpm run build:package",
"build:package": "pnpm --filter=!vuepress-theme-plume-monorepo --filter=!docs run -r --stream build",
"commit": "cz",
@ -36,8 +35,7 @@
"release:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"release:check": "pnpm lint && pnpm build",
"release:publish": "pnpm -r publish",
"release:version": "bumpp package.json plugins/*/package.json theme/package.json --execute=\"pnpm release:changelog\" --commit \"build: publish v%s\" --all --tag --push",
"up": "taze -r major"
"release:version": "bumpp package.json plugins/*/package.json theme/package.json --execute=\"pnpm release:changelog\" --commit \"build: publish v%s\" --all --tag --push"
},
"devDependencies": {
"@commitlint/cli": "^18.4.3",
@ -62,10 +60,10 @@
"lint-staged": "^15.2.0",
"minimist": "^1.2.8",
"ora": "^8.0.1",
"pnpm": "^8.12.1",
"pnpm": "^8.13.1",
"rimraf": "^5.0.5",
"sort-package-json": "^2.6.0",
"taze": "^0.13.0",
"taze": "^0.13.1",
"tsconfig-vuepress": "^4.5.0",
"typescript": "^5.3.3",
"vite": "^5.0.10"

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (C) 2021 - PRESENT by pengzhanbo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,21 @@
# `@vuepress-plume/plugin-content-update`
替换 `@vuepress/client``<Content />` 组件,注入 `onContentUpdated` 生命周期。
实现当页面内容发生更新时,触发 `onContentUpdated` 事件。
## Install
```
yarn add @vuepress-plume/plugin-content-update
```
## Usage
``` js
// .vuepress/config.js
const { contentUpdatePlugin } = require('@vuepress-plume/plugin-content-update')
module.exports = {
// ...
plugins: [
contentUpdatePlugin()
]
// ...
}
```

View File

@ -0,0 +1,49 @@
{
"name": "@vuepress-plume/plugin-content-update",
"type": "module",
"version": "1.0.0-rc.6",
"description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
},
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"exports": {
".": "./lib/node/index.js",
"./client": "./lib/client/index.js",
"./package.json": "./package.json"
},
"main": "lib/node/index.js",
"types": "./lib/node/index.d.ts",
"files": [
"lib"
],
"scripts": {
"build": "pnpm run clean && pnpm run copy && pnpm run ts",
"clean": "rimraf lib *.tsbuildinfo",
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
"ts": "tsc -b tsconfig.build.json"
},
"dependencies": {
"@vuepress/client": "2.0.0-rc.0",
"@vuepress/core": "2.0.0-rc.0",
"@vuepress/shared": "2.0.0-rc.0",
"@vuepress/utils": "2.0.0-rc.0",
"vue": "^3.3.13",
"vue-router": "4.2.5"
},
"publishConfig": {
"access": "public"
},
"keyword": [
"VuePress",
"vuepress plugin",
"content-update",
"vuepress-plugin-plugin-content-update"
]
}

View File

@ -0,0 +1,11 @@
import { defineClientConfig } from '@vuepress/client'
import { Content } from './components/Content.js'
export default defineClientConfig({
enhance({ app }) {
if (app._context.components.Content)
delete app._context.components.Content
app.component('Content', Content)
},
})

View File

@ -0,0 +1,42 @@
import { pagesComponents } from '@internal/pagesComponents'
import { computed, defineComponent, h } from 'vue'
import { usePageData } from '@vuepress/client'
import { runCallbacks } from '../composables/index.js'
declare const __VUEPRESS_DEV__: boolean
/**
* Markdown rendered content
*/
export const Content = defineComponent({
name: 'Content',
props: {
pageKey: {
type: String,
required: false,
default: '',
},
},
setup(props) {
const page = usePageData()
const pageComponent = computed(
() => pagesComponents[props.pageKey || page.value.key],
)
return () =>
pageComponent.value
? h(pageComponent.value, {
onVnodeMounted: runCallbacks,
onVnodeUpdated: runCallbacks,
onVnodeBeforeUnmount: runCallbacks,
})
: h(
'div',
__VUEPRESS_DEV__
? 'Page does not exist. This is a fallback content.'
: '404 Not Found',
)
},
})

View File

@ -0,0 +1,19 @@
import { onUnmounted } from 'vue'
// eslint-disable-next-line import/no-mutable-exports
export let contentUpdatedCallbacks: (() => any)[] = []
/**
* Register callback that is called every time the markdown content is updated
* in the DOM.
*/
export function onContentUpdated(fn: () => any) {
contentUpdatedCallbacks.push(fn)
onUnmounted(() => {
contentUpdatedCallbacks = contentUpdatedCallbacks.filter(f => f !== fn)
})
}
export function runCallbacks() {
contentUpdatedCallbacks.forEach(fn => fn())
}

View File

@ -0,0 +1,2 @@
export * from './components/Content.js'
export { onContentUpdated } from './composables/index.js'

View File

@ -0,0 +1,5 @@
import { contentUpdatePlugin } from './plugin.js'
export { contentUpdatePlugin }
export default contentUpdatePlugin

View File

@ -0,0 +1,11 @@
import type { Plugin } from '@vuepress/core'
import { getDirname, path } from '@vuepress/utils'
const __dirname = getDirname(import.meta.url)
export function contentUpdatePlugin(): Plugin {
return {
name: '@vuepress-plume/plugin-content-update',
clientConfigFile: path.resolve(__dirname, '../client/clientConfig.js'),
}
}

View File

@ -0,0 +1,4 @@
declare module '@internal/pagesComponents' {
const pagesComponents: Record<string, any>
export { pagesComponents }
}

View File

@ -0,0 +1,8 @@
{
"extends": "../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
},
"include": ["./src"]
}

View File

@ -30,12 +30,12 @@
"ts": "tsc -b tsconfig.build.json"
},
"dependencies": {
"@vuepress-plume/plugin-content-update": "workspace:*",
"@vuepress/client": "2.0.0-rc.0",
"@vuepress/core": "2.0.0-rc.0",
"@vuepress/shared": "2.0.0-rc.0",
"@vuepress/utils": "2.0.0-rc.0",
"vue": "^3.3.13",
"vue-router": "4.2.5"
"vue": "^3.3.13"
},
"publishConfig": {
"access": "public"

View File

@ -1,5 +1,5 @@
import { defineClientConfig } from '@vuepress/client'
import { setupCopyCode } from './composables/index.js'
import { setupCopyCode } from './setupCopyCode.js'
import './styles/button.scss'

View File

@ -1,23 +0,0 @@
export function copyToClipboard(str: string): void {
const selection = document.getSelection()
const selectedRange
= selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : false
const textEl = document.createElement('textarea')
textEl.value = str
textEl.setAttribute('readonly', '')
textEl.style.position = 'absolute'
textEl.style.top = '-9999px'
document.body.appendChild(textEl)
textEl.select()
document.execCommand('copy')
document.body.removeChild(textEl)
if (selectedRange && selection) {
selection.removeAllRanges()
selection.addRange(selectedRange)
}
}

View File

@ -1 +0,0 @@
export * from './setup.js'

View File

@ -1,67 +0,0 @@
import { onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import type { CopyCodeOptions } from '../../shared/index.js'
import { copyToClipboard } from './copyToClipboard.js'
declare const __COPY_CODE_OPTIONS__: CopyCodeOptions
const options = __COPY_CODE_OPTIONS__
function isMobile(): boolean {
return navigator
? /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/iu.test(
navigator.userAgent,
)
: false
}
export function setupCopyCode(): void {
const route = useRoute()
const insertBtn = (codeBlockEl: HTMLElement): void => {
if (codeBlockEl.hasAttribute('has-copy-code'))
return
const button = document.createElement('button')
button.className = 'copy-code-button'
button.addEventListener('click', () => {
copyToClipboard(codeBlockEl.textContent || '')
button.classList.add('copied')
options.duration
&& setTimeout(() => {
button.classList.remove('copied')
}, options.duration)
})
if (codeBlockEl.parentElement)
codeBlockEl.parentElement.insertBefore(button, codeBlockEl)
codeBlockEl.setAttribute('has-copy-code', '')
}
const generateButton = (): void => {
const { selector, delay } = options
setTimeout(() => {
if (typeof selector === 'string') {
document.querySelectorAll<HTMLElement>(selector).forEach(insertBtn)
}
else if (Array.isArray(selector)) {
selector.forEach((item) => {
document.querySelectorAll<HTMLElement>(item).forEach(insertBtn)
})
}
}, delay)
}
onMounted(() => {
if (!isMobile() || options.showInMobile)
generateButton()
})
watch(
() => route.path,
() => {
if (!isMobile() || options.showInMobile)
generateButton()
},
)
}

View File

@ -0,0 +1,142 @@
import { nextTick, onMounted } from 'vue'
import { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
import type { CopyCodeOptions } from '../shared/index.js'
declare const __COPY_CODE_OPTIONS__: CopyCodeOptions
const options = __COPY_CODE_OPTIONS__
const RE_LANGUAGE = /language-([\w]+)/
const RE_START_CODE = /^ *(\$|>)/gm
const shells = ['shellscript', 'shell', 'bash', 'sh', 'zsh']
const ignoredNodes = ['.diff.remove']
function isMobile(): boolean {
return navigator
? /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/iu.test(
navigator.userAgent,
)
: false
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
export function setupCopyCode(): void {
const insertBtn = (codeBlockEl: HTMLElement): void => {
if (codeBlockEl.hasAttribute('has-copy-code'))
return
const button = document.createElement('button')
button.className = 'copy-code-button'
const parent = codeBlockEl.parentElement
if (parent) {
parent.insertBefore(button, codeBlockEl)
const classes = parent.className
const match = classes.match(RE_LANGUAGE) || []
if (match[1])
button.setAttribute('data-lang', match[1])
}
codeBlockEl.setAttribute('has-copy-code', '')
}
const generateButton = async () => {
const { selector, delay } = options
await nextTick()
await sleep(delay || 0)
const selectors = Array.isArray(selector) ? selector : [selector!]
selectors.forEach((item) => {
document.querySelectorAll<HTMLElement>(item).forEach(insertBtn)
})
}
onMounted(async () => {
if (!isMobile() || options.showInMobile) {
await generateButton()
const timeoutIdMap: WeakMap<HTMLElement, NodeJS.Timeout> = new WeakMap()
window.addEventListener('click', (e) => {
const el = e.target as HTMLElement
if (el.matches('div[class*="language-"] > button.copy-code-button')) {
const parent = el.parentElement
const sibling = el.nextElementSibling
if (!parent || !sibling)
return
// Clone the node and remove the ignored nodes
const clone = sibling.cloneNode(true) as HTMLElement
clone
.querySelectorAll(ignoredNodes.join(','))
.forEach(node => node.remove())
let text = clone.textContent || ''
const lang = el.getAttribute('data-lang') || ''
if (lang && shells.includes(lang))
text = text.replace(RE_START_CODE, '').trim()
copyToClipboard(text).then(() => {
el.classList.add('copied')
clearTimeout(timeoutIdMap.get(el))
const timeoutId = setTimeout(() => {
el.classList.remove('copied')
el.blur()
timeoutIdMap.delete(el)
}, options.duration)
timeoutIdMap.set(el, timeoutId)
})
}
})
}
})
onContentUpdated(() => {
if (!isMobile() || options.showInMobile)
generateButton()
})
}
async function copyToClipboard(text: string) {
try {
return navigator.clipboard.writeText(text)
}
catch {
const element = document.createElement('textarea')
const previouslyFocusedElement = document.activeElement
element.value = text
// Prevent keyboard from showing on mobile
element.setAttribute('readonly', '')
element.style.contain = 'strict'
element.style.position = 'absolute'
element.style.left = '-9999px'
element.style.fontSize = '12pt' // Prevent zooming on iOS
const selection = document.getSelection()
const originalRange = selection
? selection.rangeCount > 0 && selection.getRangeAt(0)
: null
document.body.appendChild(element)
element.select()
// Explicit selection workaround for iOS
element.selectionStart = 0
element.selectionEnd = text.length
document.execCommand('copy')
document.body.removeChild(element)
if (originalRange) {
selection!.removeAllRanges() // originalRange can't be truthy when selection is falsy
selection!.addRange(originalRange)
}
// Get the focus back on the previously focused element, if any
if (previouslyFocusedElement) {
; (previouslyFocusedElement as HTMLElement).focus()
}
}
}

View File

@ -50,7 +50,8 @@ html[lang='zh-CN'] {
}
[class*='language-']:hover > .copy-code-button,
[class*='language-'] > .copy-code-button:focus {
[class*='language-'] > .copy-code-button:focus,
[class*='language-'] > .copy-code-button.copied {
opacity: 1;
}

View File

@ -53,9 +53,6 @@ declare const __VUEPRESS_PLUGIN_ICONIFY_DEFAULT_COLOR__: string
class="vp-iconify"
:style="iconStyle"
/>
<span v-else class="vp-iconify" :style="{ ...iconStyle, fontSize: size }">
{{ props.name }}
</span>
</ClientOnly>
</template>

View File

@ -1,13 +1,11 @@
import type { IconifyIcon } from '@iconify/vue'
import { loadIcon } from '@iconify/vue'
import { computed, ref, watch } from 'vue'
import type { ComputedRef, Ref } from 'vue'
import { ref, watch } from 'vue'
import type { Ref } from 'vue'
const iconCache: Ref<Record<string, IconifyIcon>> = ref({})
export function useIconify(name: ComputedRef<string> | Ref<string>) {
const icon = computed(() => iconCache.value[name.value])
const loaded = ref(true)
export function useIconify(name: Ref<string>) {
const icon = ref<IconifyIcon | null>(null)
const loaded = ref(false)
async function loadIconComponent() {
if (icon.value)
@ -16,7 +14,8 @@ export function useIconify(name: ComputedRef<string> | Ref<string>) {
if (!__VUEPRESS_SSR__) {
try {
loaded.value = false
iconCache.value[name.value] = await loadIcon(name.value)
const cached = await loadIcon(name.value)
icon.value = cached
}
finally {
loaded.value = true

View File

@ -13,7 +13,8 @@
{ "path": "./plugin-netlify-functions/tsconfig.build.json" },
{ "path": "./plugin-notes-data/tsconfig.build.json" },
{ "path": "./plugin-page-collection/tsconfig.build.json" },
{ "path": "./plugin-shikiji/tsconfig.build.json" }
{ "path": "./plugin-shikiji/tsconfig.build.json" },
{ "path": "./plugin-content-update/tsconfig.build.json" }
],
"files": []
}

172
pnpm-lock.yaml generated
View File

@ -75,8 +75,8 @@ importers:
specifier: ^8.0.1
version: 8.0.1
pnpm:
specifier: ^8.12.1
version: 8.12.1
specifier: ^8.13.1
version: 8.13.1
rimraf:
specifier: ^5.0.5
version: 5.0.5
@ -84,8 +84,8 @@ importers:
specifier: ^2.6.0
version: 2.6.0
taze:
specifier: ^0.13.0
version: 0.13.0
specifier: ^0.13.1
version: 0.13.1
tsconfig-vuepress:
specifier: ^4.5.0
version: 4.5.0
@ -222,7 +222,7 @@ importers:
specifier: ^4.0.0
version: 4.0.0
plugins/plugin-copy-code:
plugins/plugin-content-update:
dependencies:
'@vuepress/client':
specifier: 2.0.0-rc.0
@ -243,6 +243,27 @@ importers:
specifier: 4.2.5
version: 4.2.5(vue@3.3.13)
plugins/plugin-copy-code:
dependencies:
'@vuepress-plume/plugin-content-update':
specifier: workspace:*
version: link:../plugin-content-update
'@vuepress/client':
specifier: 2.0.0-rc.0
version: 2.0.0-rc.0(typescript@5.3.3)
'@vuepress/core':
specifier: 2.0.0-rc.0
version: 2.0.0-rc.0(typescript@5.3.3)
'@vuepress/shared':
specifier: 2.0.0-rc.0
version: 2.0.0-rc.0
'@vuepress/utils':
specifier: 2.0.0-rc.0
version: 2.0.0-rc.0
vue:
specifier: ^3.3.13
version: 3.3.13(typescript@5.3.3)
plugins/plugin-iconify:
dependencies:
'@iconify/vue':
@ -390,6 +411,9 @@ importers:
theme:
dependencies:
'@pengzhanbo/utils':
specifier: ^1.1.1
version: 1.1.1
'@vuepress-plume/plugin-auto-frontmatter':
specifier: workspace:*
version: link:../plugins/plugin-auto-frontmatter
@ -402,6 +426,9 @@ importers:
'@vuepress-plume/plugin-caniuse':
specifier: workspace:*
version: link:../plugins/plugin-caniuse
'@vuepress-plume/plugin-content-update':
specifier: workspace:*
version: link:../plugins/plugin-content-update
'@vuepress-plume/plugin-copy-code':
specifier: workspace:*
version: link:../plugins/plugin-copy-code
@ -460,8 +487,8 @@ importers:
specifier: 2.0.0-rc.0
version: 2.0.0-rc.0
'@vueuse/core':
specifier: ^10.7.0
version: 10.7.0(vue@3.3.13)
specifier: ^10.7.1
version: 10.7.1(vue@3.3.13)
date-fns:
specifier: ^3.0.6
version: 3.0.6
@ -481,20 +508,20 @@ importers:
specifier: 4.2.5
version: 4.2.5(vue@3.3.13)
vuepress-plugin-comment2:
specifier: 2.0.0-rc.6
version: 2.0.0-rc.6(typescript@5.3.3)
specifier: 2.0.0-rc.7
version: 2.0.0-rc.7(typescript@5.3.3)
vuepress-plugin-md-enhance:
specifier: 2.0.0-rc.6
version: 2.0.0-rc.6(markdown-it@13.0.2)(typescript@5.3.3)
specifier: 2.0.0-rc.7
version: 2.0.0-rc.7(markdown-it@13.0.2)(typescript@5.3.3)
vuepress-plugin-reading-time2:
specifier: 2.0.0-rc.6
version: 2.0.0-rc.6(typescript@5.3.3)
specifier: 2.0.0-rc.7
version: 2.0.0-rc.7(typescript@5.3.3)
vuepress-plugin-seo2:
specifier: 2.0.0-rc.6
version: 2.0.0-rc.6(typescript@5.3.3)
specifier: 2.0.0-rc.7
version: 2.0.0-rc.7(typescript@5.3.3)
vuepress-plugin-sitemap2:
specifier: 2.0.0-rc.6
version: 2.0.0-rc.6(typescript@5.3.3)
specifier: 2.0.0-rc.7
version: 2.0.0-rc.7(typescript@5.3.3)
devDependencies:
'@types/lodash.merge':
specifier: ^4.6.9
@ -3832,6 +3859,12 @@ packages:
- vue-eslint-parser
dev: true
/@pengzhanbo/utils@1.1.1:
resolution: {integrity: sha512-gY4nPqvkOpP7aCzrxVEurkOXe9VGT0OmUBpTj2TNU/Jycvxbs5u2IrUXMgAATauab/qMHS6/EpvAqneo7EYEsw==}
dependencies:
throttle-debounce: 5.0.0
dev: false
/@pkgjs/parseargs@0.11.0:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@ -4868,7 +4901,7 @@ packages:
dependencies:
'@vue/devtools-api': 6.5.1
'@vuepress/shared': 2.0.0-rc.0
'@vueuse/core': 10.7.0(vue@3.3.13)
'@vueuse/core': 10.7.1(vue@3.3.13)
vue: 3.3.13(typescript@5.3.3)
vue-router: 4.2.5(vue@3.3.13)
transitivePeerDependencies:
@ -4954,7 +4987,7 @@ packages:
'@vuepress/core': 2.0.0-rc.0(typescript@5.3.3)
'@vuepress/shared': 2.0.0-rc.0
'@vuepress/utils': 2.0.0-rc.0
'@vueuse/core': 10.7.0(vue@3.3.13)
'@vueuse/core': 10.7.1(vue@3.3.13)
ts-debounce: 4.0.0
vue: 3.3.13(typescript@5.3.3)
vue-router: 4.2.5(vue@3.3.13)
@ -5106,24 +5139,24 @@ packages:
- supports-color
dev: false
/@vueuse/core@10.7.0(vue@3.3.13):
resolution: {integrity: sha512-4EUDESCHtwu44ZWK3Gc/hZUVhVo/ysvdtwocB5vcauSV4B7NiGY5972WnsojB3vRNdxvAt7kzJWE2h9h7C9d5w==}
/@vueuse/core@10.7.1(vue@3.3.13):
resolution: {integrity: sha512-74mWHlaesJSWGp1ihg76vAnfVq9NTv1YT0SYhAQ6zwFNdBkkP+CKKJmVOEHcdSnLXCXYiL5e7MaewblfiYLP7g==}
dependencies:
'@types/web-bluetooth': 0.0.20
'@vueuse/metadata': 10.7.0
'@vueuse/shared': 10.7.0(vue@3.3.13)
'@vueuse/metadata': 10.7.1
'@vueuse/shared': 10.7.1(vue@3.3.13)
vue-demi: 0.14.6(vue@3.3.13)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: false
/@vueuse/metadata@10.7.0:
resolution: {integrity: sha512-GlaH7tKP2iBCZ3bHNZ6b0cl9g0CJK8lttkBNUX156gWvNYhTKEtbweWLm9rxCPIiwzYcr/5xML6T8ZUEt+DkvA==}
/@vueuse/metadata@10.7.1:
resolution: {integrity: sha512-jX8MbX5UX067DYVsbtrmKn6eG6KMcXxLRLlurGkZku5ZYT3vxgBjui2zajvUZ18QLIjrgBkFRsu7CqTAg18QFw==}
dev: false
/@vueuse/shared@10.7.0(vue@3.3.13):
resolution: {integrity: sha512-kc00uV6CiaTdc3i1CDC4a3lBxzaBE9AgYNtFN87B5OOscqeWElj/uza8qVDmk7/U8JbqoONLbtqiLJ5LGRuqlw==}
/@vueuse/shared@10.7.1(vue@3.3.13):
resolution: {integrity: sha512-v0jbRR31LSgRY/C5i5X279A/WQjD6/JsMzGa+eqt658oJ75IvQXAeONmwvEMrvJQKnRElq/frzBR7fhmWY5uLw==}
dependencies:
vue-demi: 0.14.6(vue@3.3.13)
transitivePeerDependencies:
@ -6208,7 +6241,7 @@ packages:
minipass-pipeline: 1.2.4
p-map: 4.0.0
ssri: 10.0.4
tar: 6.1.13
tar: 6.2.0
unique-filename: 3.0.0
dev: true
@ -12553,7 +12586,7 @@ packages:
npmlog: 6.0.2
rimraf: 3.0.2
semver: 7.5.4
tar: 6.1.13
tar: 6.2.0
which: 2.0.2
transitivePeerDependencies:
- bluebird
@ -13188,8 +13221,8 @@ packages:
semver: 7.5.4
dev: false
/pacote@17.0.4:
resolution: {integrity: sha512-eGdLHrV/g5b5MtD5cTPyss+JxOlaOloSMG3UwPMAvL8ywaLJ6beONPF40K4KKl/UI6q5hTKCJq5rCu8tkF+7Dg==}
/pacote@17.0.5:
resolution: {integrity: sha512-TAE0m20zSDMnchPja9vtQjri19X3pZIyRpm2TJVeI+yU42leJBBDTRYhOcWFsPhaMxf+3iwQkFiKz16G9AEeeA==}
engines: {node: ^16.14.0 || >=18.0.0}
hasBin: true
dependencies:
@ -13210,7 +13243,7 @@ packages:
read-package-json-fast: 3.0.2
sigstore: 2.1.0
ssri: 10.0.4
tar: 6.1.13
tar: 6.2.0
transitivePeerDependencies:
- bluebird
- supports-color
@ -13467,8 +13500,8 @@ packages:
engines: {node: '>=4'}
dev: true
/pnpm@8.12.1:
resolution: {integrity: sha512-ZwXqGWattlhwU+bP5v3cN3+BBmIo2vc2xEkAEvRPL/G/DHE7uPMUX84OPvZpx8FfGaWawVeDiVHyNMF7gcplXQ==}
/pnpm@8.13.1:
resolution: {integrity: sha512-dYvrxpictkYsUTgSmEAZOxDCWN3NsKtD7g753Yd4S8IY1uQctb7qw2K4zjPx+LCAJbWZkzwLzoIFCpo2uEdNQw==}
engines: {node: '>=16.14'}
hasBin: true
dev: true
@ -15431,18 +15464,6 @@ packages:
streamx: 2.15.0
dev: false
/tar@6.1.13:
resolution: {integrity: sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==}
engines: {node: '>=10'}
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 4.2.7
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
dev: true
/tar@6.2.0:
resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==}
engines: {node: '>=10'}
@ -15454,8 +15475,8 @@ packages:
mkdirp: 1.0.4
yallist: 4.0.0
/taze@0.13.0:
resolution: {integrity: sha512-o17Y6miF+44sDJ7haRHTqZefTlGOaBSdDHRwRcrt+s3WeGSPBUtPSG18XNwToIXN//eh58hyYnyDwTnpYLp4sg==}
/taze@0.13.1:
resolution: {integrity: sha512-viIeTWEezE1mtQ9M9dtvIwfF7WkNRXVZP7g/NsGDFxwwzJIVJReSiZiyfsJccdJo6XUe7FaYzvrG1VD6Tnj4Jg==}
hasBin: true
dependencies:
'@antfu/ni': 0.21.12
@ -15464,7 +15485,7 @@ packages:
deepmerge: 4.3.1
detect-indent: 7.0.1
execa: 8.0.1
pacote: 17.0.4
pacote: 17.0.5
picocolors: 1.0.0
prompts: 2.4.2
semver: 7.5.4
@ -15589,6 +15610,11 @@ packages:
long: 2.4.0
dev: false
/throttle-debounce@5.0.0:
resolution: {integrity: sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==}
engines: {node: '>=12.22'}
dev: false
/through2-filter@3.0.0:
resolution: {integrity: sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==}
dependencies:
@ -16388,8 +16414,8 @@ packages:
typescript: 5.3.3
dev: false
/vuepress-plugin-comment2@2.0.0-rc.6(typescript@5.3.3):
resolution: {integrity: sha512-NOGhWS9jrmcFgkaNd5AGtuERok3mbSPBQQ5CV8Uegs0409Zm1L0kmhOPB959Z1KwLxfoNvhD97XruWcQ29bhUw==}
/vuepress-plugin-comment2@2.0.0-rc.7(typescript@5.3.3):
resolution: {integrity: sha512-bZ5BhJ/dQdc24GDc1T95nRCnkjZZo05wpSSU2pI7sRZSeX6MjbpJLMNJPKH3BIMQQQaQUZUIzKLl4U0ja9B07A==}
engines: {node: '>=18.16.0', npm: '>=8', pnpm: '>=7', yarn: '>=2'}
peerDependencies:
'@waline/client': ^2.15.8 || ^3.0.0-alpha.8
@ -16421,16 +16447,16 @@ packages:
giscus: 1.4.0
vue: 3.3.13(typescript@5.3.3)
vue-router: 4.2.5(vue@3.3.13)
vuepress-plugin-sass-palette: 2.0.0-rc.6(typescript@5.3.3)
vuepress-shared: 2.0.0-rc.6(typescript@5.3.3)
vuepress-plugin-sass-palette: 2.0.0-rc.7(typescript@5.3.3)
vuepress-shared: 2.0.0-rc.7(typescript@5.3.3)
transitivePeerDependencies:
- '@vue/composition-api'
- supports-color
- typescript
dev: false
/vuepress-plugin-md-enhance@2.0.0-rc.6(markdown-it@13.0.2)(typescript@5.3.3):
resolution: {integrity: sha512-cEsMccjqdNFq4UjnFbg9OlBwrIF9Ducr2MX2G+6p4h17yJ8yf7NctpGZwnVMh2FGmQ7oYjcIpkuUDrkLn4elzw==}
/vuepress-plugin-md-enhance@2.0.0-rc.7(markdown-it@13.0.2)(typescript@5.3.3):
resolution: {integrity: sha512-UHcsPNbbg9itmIndTKn5nObjy/eVzbNo+6A9P1JaOPfNukkC6H7LaHHgwMlPXNsmaeYZmR18ZA+NCSTGcRDTvQ==}
engines: {node: '>=18.16.0', npm: '>=8', pnpm: '>=7', yarn: '>=2'}
peerDependencies:
'@types/reveal.js': ^4.4.5
@ -16511,13 +16537,13 @@ packages:
'@vuepress/client': 2.0.0-rc.0(typescript@5.3.3)
'@vuepress/shared': 2.0.0-rc.0
'@vuepress/utils': 2.0.0-rc.0
'@vueuse/core': 10.7.0(vue@3.3.13)
'@vueuse/core': 10.7.1(vue@3.3.13)
balloon-css: 1.2.0
js-yaml: 4.1.0
vue: 3.3.13(typescript@5.3.3)
vue-router: 4.2.5(vue@3.3.13)
vuepress-plugin-sass-palette: 2.0.0-rc.6(typescript@5.3.3)
vuepress-shared: 2.0.0-rc.6(typescript@5.3.3)
vuepress-plugin-sass-palette: 2.0.0-rc.7(typescript@5.3.3)
vuepress-shared: 2.0.0-rc.7(typescript@5.3.3)
transitivePeerDependencies:
- '@vue/composition-api'
- markdown-it
@ -16525,8 +16551,8 @@ packages:
- typescript
dev: false
/vuepress-plugin-reading-time2@2.0.0-rc.6(typescript@5.3.3):
resolution: {integrity: sha512-GgyKWS66QvlrXV2zMF0qhgtEpELvN0kSOEAqgGn8mR+01TgLirdq+k9rExp1zab7UH7h8FjtRgWlcYgfPUbGEA==}
/vuepress-plugin-reading-time2@2.0.0-rc.7(typescript@5.3.3):
resolution: {integrity: sha512-OBFxXNZQGOyhzIx2ZfJ/nPBiFJYRGOCT+1GrZRjeW0cfrdvgYZo6x2jDBDtmrrzT3TlhZRpQDZB6QMbiORQhPQ==}
engines: {node: '>=18.16.0', npm: '>=8', pnpm: '>=7', yarn: '>=2'}
peerDependencies:
vuepress: 2.0.0-rc.0
@ -16542,15 +16568,15 @@ packages:
dependencies:
'@vuepress/client': 2.0.0-rc.0(typescript@5.3.3)
vue: 3.3.13(typescript@5.3.3)
vuepress-shared: 2.0.0-rc.6(typescript@5.3.3)
vuepress-shared: 2.0.0-rc.7(typescript@5.3.3)
transitivePeerDependencies:
- '@vue/composition-api'
- supports-color
- typescript
dev: false
/vuepress-plugin-sass-palette@2.0.0-rc.6(typescript@5.3.3):
resolution: {integrity: sha512-/LAcJvznI5C/cSO0IM2btGivA9IgOON+YwpEzHGhkZKwBKx/lLqOpHH4ghaKmC/hX72QISyM1pjQJ/9pjOQEOg==}
/vuepress-plugin-sass-palette@2.0.0-rc.7(typescript@5.3.3):
resolution: {integrity: sha512-xi8UDaPGS67MVzUDoaQN67dZoTjkW30KpHQJH8R/6DybOz9hC0oVqvkZM9waPPCCdcPytxVUfVDdincARZ4eHw==}
engines: {node: '>=18.16.0', npm: '>=8', pnpm: '>=7', yarn: '>=2'}
peerDependencies:
sass-loader: ^13.3.2
@ -16571,15 +16597,15 @@ packages:
'@vuepress/utils': 2.0.0-rc.0
chokidar: 3.5.3
sass: 1.69.5
vuepress-shared: 2.0.0-rc.6(typescript@5.3.3)
vuepress-shared: 2.0.0-rc.7(typescript@5.3.3)
transitivePeerDependencies:
- '@vue/composition-api'
- supports-color
- typescript
dev: false
/vuepress-plugin-seo2@2.0.0-rc.6(typescript@5.3.3):
resolution: {integrity: sha512-ZvYQNV/aZV5iCi7D7qg1Bz7rrQ00f9exPQ+m/JEsJr+uH0g1frAbCI46Ilu0Y42+XTU6id/wO23yKoS0GFQ8jw==}
/vuepress-plugin-seo2@2.0.0-rc.7(typescript@5.3.3):
resolution: {integrity: sha512-WHn4s1N2cHPQZQh+rZyiH9mDtGYFh9E2F5ZUV3LT2IddGuHluzuwVScIxix9q76AvbdCl4rhg7vr97oc8oPoUA==}
engines: {node: '>=18.16.0', npm: '>=8', pnpm: '>=7', yarn: '>=2'}
peerDependencies:
vuepress: 2.0.0-rc.0
@ -16595,15 +16621,15 @@ packages:
dependencies:
'@vuepress/shared': 2.0.0-rc.0
'@vuepress/utils': 2.0.0-rc.0
vuepress-shared: 2.0.0-rc.6(typescript@5.3.3)
vuepress-shared: 2.0.0-rc.7(typescript@5.3.3)
transitivePeerDependencies:
- '@vue/composition-api'
- supports-color
- typescript
dev: false
/vuepress-plugin-sitemap2@2.0.0-rc.6(typescript@5.3.3):
resolution: {integrity: sha512-b9gNdmaUsXRBpE1OKkTmp/WhCBKQgkHQHhEK1oK9oZiCEAP4Xhmnob5UhtS7WeBK9HBsjCCCUwAEBbNZ8WdiEw==}
/vuepress-plugin-sitemap2@2.0.0-rc.7(typescript@5.3.3):
resolution: {integrity: sha512-Vc13ucmamsXu0PD9Gu1lRm0NKOAwV/h3hZA+h2jAnavsiAqkAUF1WmvjNLPMWCVz77lez3knQNpWDoHZVBoxLQ==}
engines: {node: '>=18.16.0', npm: '>=8', pnpm: '>=7', yarn: '>=2'}
peerDependencies:
vuepress: 2.0.0-rc.0
@ -16620,15 +16646,15 @@ packages:
'@vuepress/shared': 2.0.0-rc.0
'@vuepress/utils': 2.0.0-rc.0
sitemap: 7.1.1
vuepress-shared: 2.0.0-rc.6(typescript@5.3.3)
vuepress-shared: 2.0.0-rc.7(typescript@5.3.3)
transitivePeerDependencies:
- '@vue/composition-api'
- supports-color
- typescript
dev: false
/vuepress-shared@2.0.0-rc.6(typescript@5.3.3):
resolution: {integrity: sha512-bcEBxOX0ulWtCeRCBOdIpvFC+m4HvyWGLm6CPXedPiHaSl6avp/S6akYNcj2dyBPXxC5v3WfiGJDumis0fqYbg==}
/vuepress-shared@2.0.0-rc.7(typescript@5.3.3):
resolution: {integrity: sha512-1EUgbOe8/VK0yuzau2mtL22Av2547zVJP/q2niCoCyBTzMrn0agaWSRIDhhQx+e4xPlE5Mi3FsCHDXTfkWSuIw==}
engines: {node: '>=18.16.0', npm: '>=8', pnpm: '>=7', yarn: '>=2'}
peerDependencies:
vuepress: 2.0.0-rc.0
@ -16645,7 +16671,7 @@ packages:
'@vuepress/client': 2.0.0-rc.0(typescript@5.3.3)
'@vuepress/shared': 2.0.0-rc.0
'@vuepress/utils': 2.0.0-rc.0
'@vueuse/core': 10.7.0(vue@3.3.13)
'@vueuse/core': 10.7.1(vue@3.3.13)
cheerio: 1.0.0-rc.12
dayjs: 1.11.10
execa: 8.0.1

View File

@ -1,11 +1,3 @@
## scripts/autoInstall
检查各个 workspace package 中的 vuepress 相关依赖,并更新到最新版本。
``` sh
pnpm autoUpdate
```
## scripts/create
`packages/` 目录下生成一个新的 插件包

View File

@ -1,80 +0,0 @@
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import process from 'node:process'
import { execa } from 'execa'
import ora from 'ora'
import chalk from 'chalk'
const _dirname
= typeof __dirname !== 'undefined'
? __dirname
: path.dirname(fileURLToPath(import.meta.url))
const packages = [
...fs
.readdirSync(path.join(_dirname, '../packages'))
.filter(file => file !== '.DS_Store' && file !== 'tsconfig.build.json')
.map(dir => path.join('../packages', dir)),
'../docs',
]
const dependencies = packages.map((dir) => {
const pkg = fs.readFileSync(
path.join(_dirname, dir, 'package.json'),
'utf-8',
)
const { dependencies, devDependencies } = JSON.parse(pkg)
return {
dirname: path.join(_dirname, dir),
dependencies: filterVuePress(Object.keys(dependencies || {})),
devDependencies: filterVuePress(Object.keys(devDependencies || {})),
}
})
function filterVuePress(dependencies) {
const vuepress = dependencies
.filter(
dependence =>
dependence.startsWith('@vuepress/'),
)
.map(dependence => `${dependence}@next`)
const includes = ['vue', 'vue-router']
const vue = dependencies
.filter(dependence => includes.includes(dependence))
.map(dependence => `${dependence}@latest`)
return [...vue, ...vuepress]
}
const options = []
dependencies.forEach(({ dirname, dependencies, devDependencies }) => {
if (dependencies.length)
options.push(['pnpm', ['add', ...dependencies], { cwd: dirname }])
if (devDependencies.length)
options.push(['pnpm', ['add', '-D', ...devDependencies], { cwd: dirname }])
})
async function install(index = 0) {
if (index >= options.length)
return
const spinner = ora()
const opt = options[index]
const dir = opt[2].cwd.split('/').slice(-2).join('/')
console.log('Installing ', chalk.cyan(dir))
console.log(chalk.gray(opt[0], opt[1].join(' ')))
console.log('\n')
const current = execa(opt[0], opt[1], opt[2])
current?.stdout?.pipe(process.stdout)
try {
await current
spinner.succeed('Installed.')
await install(index + 1)
}
catch (e) {
spinner.fail('Install Fail.')
console.log(e)
}
}
install()

View File

@ -43,10 +43,12 @@
"ts:watch": "tsc -b tsconfig.build.json --watch"
},
"dependencies": {
"@pengzhanbo/utils": "^1.1.1",
"@vuepress-plume/plugin-auto-frontmatter": "workspace:*",
"@vuepress-plume/plugin-baidu-tongji": "workspace:*",
"@vuepress-plume/plugin-blog-data": "workspace:*",
"@vuepress-plume/plugin-caniuse": "workspace:*",
"@vuepress-plume/plugin-content-update": "workspace:*",
"@vuepress-plume/plugin-copy-code": "workspace:*",
"@vuepress-plume/plugin-iconify": "workspace:*",
"@vuepress-plume/plugin-notes-data": "workspace:*",
@ -66,18 +68,18 @@
"@vuepress/plugin-toc": "2.0.0-rc.0",
"@vuepress/shared": "2.0.0-rc.0",
"@vuepress/utils": "2.0.0-rc.0",
"@vueuse/core": "^10.7.0",
"@vueuse/core": "^10.7.1",
"date-fns": "^3.0.6",
"lodash.merge": "^4.6.2",
"nanoid": "^5.0.4",
"ts-debounce": "^4.0.0",
"vue": "^3.3.13",
"vue-router": "4.2.5",
"vuepress-plugin-comment2": "2.0.0-rc.6",
"vuepress-plugin-md-enhance": "2.0.0-rc.6",
"vuepress-plugin-reading-time2": "2.0.0-rc.6",
"vuepress-plugin-seo2": "2.0.0-rc.6",
"vuepress-plugin-sitemap2": "2.0.0-rc.6"
"vuepress-plugin-comment2": "2.0.0-rc.7",
"vuepress-plugin-md-enhance": "2.0.0-rc.7",
"vuepress-plugin-reading-time2": "2.0.0-rc.7",
"vuepress-plugin-seo2": "2.0.0-rc.7",
"vuepress-plugin-sitemap2": "2.0.0-rc.7"
},
"devDependencies": {
"@types/lodash.merge": "^4.6.9"

View File

@ -15,6 +15,7 @@ const route = useRoute()
const avatar = computed(() => theme.value.avatar)
const { hasBlogExtract, tags, archives } = useBlogExtract()
const open = ref(false)
const lazyOpen = ref(false)
const isLocked = useScrollLock(inBrowser ? document.body : null)
@ -22,6 +23,15 @@ watch(() => route.path, () => {
open.value = false
})
watch(open, async () => {
if (open.value) {
setTimeout(() => {
lazyOpen.value = true
}, 200)
}
else { lazyOpen.value = false }
})
watch(
[() => open.value],
() => {
@ -39,34 +49,38 @@ const showBlogExtract = computed(() => {
</script>
<template>
<div v-if="showBlogExtract" class="blog-extract" @click="open = !open">
<IconBlogExt class="icon" />
</div>
<div v-if="showBlogExtract" class="blog-modal" :class="{ open }" @click.self="open = false">
<div class="blog-modal-container">
<div v-if="avatar" class="avatar-profile">
<p v-if="avatar.url" class="avatar">
<img :src="avatar.url" :alt="avatar.name">
</p>
<div>
<h3>{{ avatar.name }}</h3>
<p class="desc">
{{ avatar.description }}
</p>
<template v-if="showBlogExtract">
<div class="blog-extract" @click="open = !open">
<IconBlogExt class="icon" />
</div>
<Transition name="fade">
<div v-show="open" class="blog-modal" @click.self="open = false">
<div class="blog-modal-container" :class="{ open: lazyOpen }">
<div v-if="avatar" class="avatar-profile">
<p v-if="avatar.url" class="avatar">
<img :src="avatar.url" :alt="avatar.name">
</p>
<div>
<h3>{{ avatar.name }}</h3>
<p class="desc">
{{ avatar.description }}
</p>
</div>
</div>
<div v-if="hasBlogExtract" class="blog-nav">
<AutoLink class="nav-link" :href="tags.link">
<IconTag class="icon" />
<span>{{ tags.text }}</span>
</AutoLink>
<AutoLink class="nav-link" :href="archives.link">
<IconArchive class="icon" />
<span>{{ archives.text }}</span>
</AutoLink>
</div>
</div>
</div>
<div v-if="hasBlogExtract" class="blog-nav">
<AutoLink class="nav-link" :href="tags.link">
<IconTag class="icon" />
<span>{{ tags.text }}</span>
</AutoLink>
<AutoLink class="nav-link" :href="archives.link">
<IconArchive class="icon" />
<span>{{ archives.text }}</span>
</AutoLink>
</div>
</div>
</div>
</Transition>
</template>
</template>
<style scoped>
@ -104,19 +118,18 @@ const showBlogExtract = computed(() => {
left: 0;
z-index: var(--vp-z-index-sidebar);
width: 100%;
opacity: 0;
overflow-x: hidden;
overflow-y: auto;
transform: translateY(100%);
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
opacity: 1;
background-color: rgba(0, 0, 0, 0.3);
}
.blog-modal.open {
opacity: 1;
visibility: visible;
transform: translateY(0);
transition: opacity 0.25s, transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
.blog-modal.fade-enter-from,
.blog-modal.fade-leave-to {
opacity: 0;
}
.blog-modal.fade-leave-active,
.blog-modal.fade-enter-active {
transition: opacity 0.5s cubic-bezier(0.19, 1, 0.22, 1);
}
.blog-modal-container {
@ -125,8 +138,15 @@ const showBlogExtract = computed(() => {
width: 100%;
padding: 24px;
background-color: var(--vp-c-bg);
border-top-left-radius: 8px;
border-top-right-radius: 8px;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
box-shadow: 0 -3px 12px rgba(0, 0, 0, 0.1), 0 -1px 4px rgba(0, 0, 0, 0.1);;
transform: translateY(100%);
transition: transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
}
.blog-modal-container.open {
transform: translateY(0);
}
.avatar-profile {

View File

@ -1,6 +1,7 @@
<script lang="ts" setup>
import { usePageData } from '@vuepress/client'
import { computed, ref } from 'vue'
import { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
import { useActiveAnchor, useThemeLocaleData } from '../composables/index.js'
import IconPrint from './icons/IconPrint.vue'
import PageAsideItem from './PageAsideItem.vue'
@ -8,9 +9,13 @@ import PageAsideItem from './PageAsideItem.vue'
const page = usePageData()
const theme = useThemeLocaleData()
const headers = computed(() => page.value.headers)
const headers = ref(page.value.headers)
const hasOutline = computed(() => headers.value.length > 0)
onContentUpdated(() => {
headers.value = page.value.headers
})
const container = ref()
const marker = ref()

View File

@ -52,16 +52,20 @@ const {
}
.btn:hover {
color: var(--vp-c-brand-2);
color: var(--vp-c-bg);
background-color: var(--vp-c-brand-2);
border-color: var(--vp-c-brand-2);
}
.btn[disabled] {
.btn[disabled],
.btn[disabled]:hover {
color: var(--vp-c-gray-1);
border-color: var(--vp-c-divider);
background-color: transparent;
cursor: not-allowed;
}
.page-info {
color: var(--vp-c-brand-2);
color: var(--vp-c-text-3);
font-weight: 500;
}
</style>

View File

@ -11,14 +11,16 @@ defineProps<{
</script>
<template>
<div class="post-list">
<p v-for="post in postList" :key="post.path">
<AutoLink class="post-title" :href="post.path">
{{ post.title }}
</AutoLink>
<ul class="post-list">
<li v-for="post in postList" :key="post.path">
<p class="post-title">
<AutoLink class="post-link" :href="post.path">
{{ post.title }}
</AutoLink>
</p>
<span class="post-time">{{ post.createTime }}</span>
</p>
</div>
</li>
</ul>
</template>
<style scoped>
@ -27,7 +29,7 @@ defineProps<{
padding: 0 12px;
}
.post-list p {
.post-list li {
display: flex;
align-items: center;
justify-content: space-between;
@ -37,7 +39,7 @@ defineProps<{
.post-list .post-title {
flex: 1;
margin-right: 20px;
margin-right: 14px;
font-weight: 600;
transition: all var(--t-color);
display: -webkit-box;
@ -52,10 +54,10 @@ defineProps<{
transition: all var(--t-color);
}
.post-list p:hover .post-title {
.post-list li:hover .post-title {
color: var(--vp-c-brand-1);
}
.post-list p:hover .post-time {
.post-list li:hover .post-time {
color: var(--vp-c-text-2);
}
</style>

View File

@ -8,7 +8,7 @@ const { tags: tagsLink } = useBlogExtract()
</script>
<template>
<div class="tags-wrapper">
<div class="tags-wrapper" :class="{ 'has-list': postList.length > 0 }">
<h2 class="tags-title">
<IconTag class="icon" />
<span>{{ tagsLink.text }}</span>
@ -22,26 +22,34 @@ const { tags: tagsLink } = useBlogExtract()
@click="handleTagClick(tag.name)"
>
<span class="tag-name">{{ tag.name }}</span>
<span class="tag-count">({{ tag.count }})</span>
<span class="tag-count">{{ tag.count }}</span>
</p>
</div>
<h3 v-if="currentTag" class="tag-title">
{{ currentTag }}
</h3>
<ShortPostList v-if="postList.length" :post-list="postList" />
</div>
</template>
<style scoped>
.tags-wrapper {
padding: 32px 24px;
padding: 32px 24px 168px;
flex: 1;
}
.tags-wrapper.has-list {
padding-bottom: 64px;
}
.tags-title {
display: flex;
align-items: center;
font-size: 24px;
font-size: 20px;
font-weight: 700;
color: var(--vp-c-brand-1);
margin-bottom: 40px;
margin-bottom: 20px;
}
.tags-title .icon {
width: 1em;
@ -55,20 +63,25 @@ const { tags: tagsLink } = useBlogExtract()
align-items: center;
}
.tags .tag {
display: inline-block;
display: flex;
align-items: center;
word-wrap: break-word;
margin: 8px;
padding: 2px 10px;
padding: 6px 6px 6px 10px;
background-color: var(--vp-c-default-soft);
color: var(--vp-c-text-3);
color: var(--vp-c-text-2);
line-height: 1;
border-radius: 4px;
cursor: pointer;
transition: all var(--t-color);
}
.tags .tag:hover,
.tags .tag.active {
background-color: var(--vp-c-brand-1);
color: var(--vp-c-bg);
.tag-title {
font-size: 20px;
font-weight: 600;
border-bottom: 1px solid var(--vp-c-divider);
padding-bottom: 8px;
margin: 20px 12px -10px 12px;
}
.tag-name {
@ -76,6 +89,22 @@ const { tags: tagsLink } = useBlogExtract()
}
.tag-count {
display: inline-block;
border-left: 1px solid var(--vp-c-divider);
padding-left: 6px;
margin-left: 4px;
color: var(--vp-c-text-3);
transition: all var(--t-color);
}
.tags .tag:hover,
.tags .tag.active {
background-color: var(--vp-c-brand-1);
color: var(--vp-c-bg);
}
.tags .tag:hover .tag-count,
.tags .tag.active .tag-count {
color: var(--vp-bg);
}
</style>

View File

@ -1,139 +0,0 @@
import { usePageData } from '@vuepress/client'
import type { PageHeader } from '@vuepress/client'
import type { PropType, VNode } from 'vue'
import { computed, defineComponent, h, toRefs } from 'vue'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { useRoute } from 'vue-router'
import { scrollTo } from '../utils/index.js'
export type TocPropsHeaders = PageHeader[]
export interface TocPropsOptions {
containerTag: string
containerClass: string
listClass: string
itemClass: string
linkClass: string
linkActiveClass: string
linkChildrenActiveClass: string
}
export interface TocProps {
headers: TocPropsHeaders
options: TocPropsOptions
}
function renderLink(header: PageHeader, options: TocPropsOptions, route: RouteLocationNormalizedLoaded): VNode {
const hash = `#${header.slug}`
const linkClass = [options.linkClass]
if (options.linkActiveClass && route.hash === hash)
linkClass.push(options.linkActiveClass)
if (
options.linkChildrenActiveClass
&& header.children.some(item => `#${item.slug}` === route.hash)
)
linkClass.push(options.linkChildrenActiveClass)
const setActiveRouteHash = (): void => {
const headerAnchors: HTMLAnchorElement[] = Array.from(
document.querySelectorAll('.header-anchor'),
)
const anchor = headerAnchors.find(
anchor => decodeURI(anchor.hash) === hash,
)
if (!anchor)
return
const el = document.documentElement
const top = anchor.getBoundingClientRect().top - 80 + el.scrollTop
scrollTo(document, top)
}
return h(
'a',
{
href: hash,
class: linkClass,
ariaLabel: header.title,
onClick: (e: MouseEvent) => {
e.preventDefault()
setActiveRouteHash()
},
},
header.title,
)
}
function renderHeaders(headers: PageHeader[], options: TocPropsOptions, route: RouteLocationNormalizedLoaded): VNode[] {
if (headers.length === 0)
return []
return [
h(
'ul',
{ class: options.listClass },
headers.map(header =>
h('li', { class: options.itemClass }, [
renderLink(header, options, route),
renderHeaders(header.children, options, route),
]),
),
),
]
}
const Toc = defineComponent({
name: 'Toc',
props: {
headers: {
type: Array as PropType<TocPropsHeaders>,
required: false,
default: null,
},
options: {
type: Object as PropType<TocPropsOptions>,
required: false,
default: () => ({}),
},
},
setup(props) {
const { headers: propsHeaders, options: propsOptions } = toRefs(props)
const defaultOptions: TocPropsOptions = {
containerTag: 'nav',
containerClass: 'theme-plume-toc',
listClass: 'theme-plume-toc-list',
itemClass: 'theme-plume-toc-item',
linkClass: 'theme-plume-toc-link',
linkActiveClass: 'active',
linkChildrenActiveClass: 'active',
}
const route = useRoute()
const page = usePageData()
const headers = computed<TocPropsHeaders>(() => {
const headerToUse = propsHeaders.value || page.value.headers
return headerToUse[0]?.level === 1 ? headerToUse[0].children : headerToUse
})
const options = computed<TocPropsOptions>(() => ({
...defaultOptions,
...propsOptions.value,
}))
return () => {
const renderedHeaders = renderHeaders(headers.value, options.value, route)
if (options.value.containerTag) {
return h(
options.value.containerTag,
{ class: options.value.containerClass },
renderedHeaders,
)
}
return renderedHeaders
}
},
})
export default Toc

View File

@ -44,11 +44,12 @@ const { hasSidebar } = useSidebar()
.plume-footer :deep(a) {
text-decoration-line: underline;
text-underline-offset: 2px;
transition: color 0.25s;
transition: color, text-underline-offset 0.25s;
}
.plume-footer :deep(a:hover) {
color: var(--vp-c-text-1);
text-underline-offset: 4px;
}
@media (min-width: 768px) {

View File

@ -146,7 +146,7 @@ export function useTags() {
}).map(item => ({
title: item.title,
path: item.path,
createTime: item.createTime.split(' ')[0],
createTime: item.createTime.split(' ')[0].replace(/\//g, '-'),
}))
}
@ -178,7 +178,7 @@ export function useArchives() {
current.list.push({
title: item.title,
path: item.path,
createTime: createTime.slice(year.length + 1),
createTime: createTime.slice(year.length + 1).replace(/\//g, '-'),
})
})

View File

@ -11,8 +11,10 @@ import NotFound from './layouts/NotFound.vue'
export default defineClientConfig({
enhance({ app, router }) {
// global component
app.component('Badge', Badge)
if (app._context.components.ExternalLinkIcon)
delete app._context.components.ExternalLinkIcon
app.component('ExternalLinkIcon', ExternalLinkIcon)
app.component('DocSearch', () => {

View File

@ -1,6 +1,4 @@
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import type { App } from '@vuepress/core'
import { resolveLocalePath } from '@vuepress/shared'
import type {
@ -8,28 +6,13 @@ import type {
FrontmatterArray,
FrontmatterObject,
} from '@vuepress-plume/plugin-auto-frontmatter'
import type { NotesItem } from '@vuepress-plume/plugin-notes-data'
import { format } from 'date-fns'
import { customAlphabet } from 'nanoid'
import { uniq } from '@pengzhanbo/utils'
import type {
PlumeThemeLocaleOptions,
PlumeThemePluginOptions,
} from '../shared/index.js'
const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8)
function getPackage() {
let pkg = {} as any
try {
const content = fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8')
pkg = JSON.parse(content)
}
catch {}
return pkg
}
function normalizePath(dir: string) {
return dir.replace(/\\+/g, '/')
}
import { getCurrentDirname, getPackage, nanoid, pathJoin } from './utils.js'
export default function autoFrontmatter(
app: App,
@ -38,17 +21,17 @@ export default function autoFrontmatter(
): AutoFrontmatterOptions {
const sourceDir = app.dir.source()
const pkg = getPackage()
const articlePrefix = localeOption.article || '/article/'
const { locales = {}, avatar, article: articlePrefix = '/article/' } = localeOption
const { frontmatter } = options
const locales = (app.siteData.locales || {}) as PlumeThemeLocaleOptions
const localesNotesDirs = Object.keys(locales)
const localesNotesDirs = Object.keys(app.siteData.locales || {})
.map((locale) => {
// fixed: #15
const notes = localeOption.locales?.[locale]?.notes
const notes = locales[locale]?.notes
if (!notes)
return ''
const dir = notes.dir
return dir ? normalizePath(path.join(locale, dir)).replace(/^\//, '') : ''
return notes.dir ? pathJoin(locale, notes.dir).replace(/^\//, '') : ''
})
.filter(Boolean)
@ -58,7 +41,7 @@ export default function autoFrontmatter(
return author
if (data.friends)
return
return localeOption.avatar?.name || pkg.author || ''
return avatar?.name || pkg.author || ''
},
createTime(formatTime: string, { createTime }, data: any) {
if (formatTime)
@ -70,20 +53,20 @@ export default function autoFrontmatter(
}
const resolveLocale = (filepath: string) => {
const file = normalizePath(
path.join('/', path.relative(sourceDir, filepath)),
)
const file = pathJoin('/', path.relative(sourceDir, filepath))
return resolveLocalePath(localeOption.locales!, file)
}
const notesByLocale = (locale: string) => {
const notes = localeOption.locales![locale]?.notes || localeOption.notes
const notes = locales[locale]?.notes || localeOption.notes
if (notes === false)
return undefined
return notes
}
const findNote = (filepath: string) => {
const file = path.join('/', path.relative(sourceDir, filepath))
const locale = resolveLocalePath(localeOption.locales!, normalizePath(file))
const file = pathJoin('/', path.relative(sourceDir, filepath))
const locale = resolveLocalePath(locales, file)
const notes = notesByLocale(locale)
if (!notes)
return undefined
@ -94,22 +77,15 @@ export default function autoFrontmatter(
)
}
const getCurrentDirname = (note: NotesItem | undefined, filepath: string) => {
const dirList = normalizePath(note?.dir || path.dirname(filepath))
.replace(/^\/|\/$/g, '')
.split('/')
return dirList.length > 0 ? dirList[dirList.length - 1] : ''
}
return {
include: options.frontmatter?.include ?? ['**/*.md'],
exclude: options.frontmatter?.exclude ?? ['.vuepress/**/*', 'node_modules'],
frontmatter: options.frontmatter?.frontmatter ?? [
include: frontmatter?.include ?? ['**/*.md'],
exclude: uniq(['.vuepress/**/*', 'node_modules', ...(frontmatter?.exclude ?? [])]),
frontmatter: [
localesNotesDirs.length
? {
// note 首页链接
include: localesNotesDirs.map(dir =>
normalizePath(path.join(dir, '**/{readme,README,index}.md')),
),
include: localesNotesDirs.map(dir => pathJoin(dir, '**/{readme,README,index}.md')),
frontmatter: {
title(title: string, { filepath }) {
if (title)
@ -117,7 +93,7 @@ export default function autoFrontmatter(
const note = findNote(filepath)
if (note?.text)
return note.text
return getCurrentDirname(note, filepath) || ''
return getCurrentDirname(note?.dir, filepath) || ''
},
...baseFrontmatter,
permalink(permalink: string, { filepath }, data: any) {
@ -128,13 +104,11 @@ export default function autoFrontmatter(
const locale = resolveLocale(filepath)
const notes = notesByLocale(locale)
const note = findNote(filepath)
return normalizePath(
path.join(
locale,
notes?.link || '',
note?.link || getCurrentDirname(note, filepath),
'/',
),
return pathJoin(
locale,
notes?.link || '',
note?.link || getCurrentDirname(note?.dir, filepath),
'/',
)
},
},
@ -142,9 +116,7 @@ export default function autoFrontmatter(
: '',
localesNotesDirs.length
? {
include: localesNotesDirs.map(dir =>
normalizePath(path.join(dir, '**/**.md')),
),
include: localesNotesDirs.map(dir => pathJoin(dir, '**/**.md')),
frontmatter: {
title(title: string, { filepath }) {
if (title)
@ -161,14 +133,12 @@ export default function autoFrontmatter(
const locale = resolveLocale(filepath)
const note = findNote(filepath)
const notes = notesByLocale(locale)
return normalizePath(
path.join(
locale,
notes?.link || '',
note?.link || getCurrentDirname(note, filepath),
nanoid(),
'/',
),
return pathJoin(
locale,
notes?.link || '',
note?.link || getCurrentDirname(note?.dir, filepath),
nanoid(),
'/',
)
},
},
@ -192,9 +162,7 @@ export default function autoFrontmatter(
if (permalink)
return permalink
const locale = resolveLocale(filepath)
return normalizePath(
path.join(locale, articlePrefix, nanoid(), '/'),
)
return pathJoin(locale, articlePrefix, nanoid(), '/')
},
},
},

View File

@ -18,10 +18,11 @@ import { iconifyPlugin } from '@vuepress-plume/plugin-iconify'
import { notesDataPlugin } from '@vuepress-plume/plugin-notes-data'
import { shikijiPlugin } from '@vuepress-plume/plugin-shikiji'
import { commentPlugin } from 'vuepress-plugin-comment2'
import { mdEnhancePlugin } from 'vuepress-plugin-md-enhance'
import { useReadingTimePlugin } from 'vuepress-plugin-reading-time2'
import { type MarkdownEnhanceOptions, mdEnhancePlugin } from 'vuepress-plugin-md-enhance'
import { readingTimePlugin } from 'vuepress-plugin-reading-time2'
import { seoPlugin } from 'vuepress-plugin-seo2'
import { sitemapPlugin } from 'vuepress-plugin-sitemap2'
import { contentUpdatePlugin } from '@vuepress-plume/plugin-content-update'
import type {
PlumeThemeLocaleOptions,
PlumeThemePluginOptions,
@ -41,10 +42,7 @@ export function setupPlugins(app: App, options: PlumeThemePluginOptions, localeO
})
.filter(Boolean)
if (options.readingTime !== false)
useReadingTimePlugin(app, options.readingTime || {}, true)
return [
const plugins: PluginConfig = [
palettePlugin({ preset: 'sass' }),
themeDataPlugin({
@ -55,6 +53,7 @@ export function setupPlugins(app: App, options: PlumeThemePluginOptions, localeO
: undefined,
} as any,
}),
autoFrontmatterPlugin(autoFrontmatter(app, options, localeOptions)),
blogDataPlugin({
@ -85,113 +84,134 @@ export function setupPlugins(app: App, options: PlumeThemePluginOptions, localeO
},
}),
localeOptions.notes ? notesDataPlugin(localeOptions.notes) : [],
iconifyPlugin(),
contentUpdatePlugin(),
activeHeaderLinksPlugin({
headerLinkSelector: 'a.outline-link',
headerAnchorSelector: '.header-anchor',
delay: 200,
offset: 20,
}),
options.nprogress !== false ? nprogressPlugin() : [],
options.git !== false
? gitPlugin({
createdTime: false,
updatedTime: localeOptions.lastUpdated !== false,
contributors: localeOptions.contributors !== false,
})
: [],
options.mediumZoom !== false
? mediumZoomPlugin({
selector: '.plume-content > img, .plume-content :not(a) > img',
zoomOptions: {
background: 'var(--vp-c-bg)',
},
delay: 300,
})
: [],
options.caniuse !== false
? caniusePlugin(
options.caniuse || {
mode: 'embed',
},
)
: [],
options.externalLinkIcon !== false
? externalLinkIconPlugin({
locales: Object.entries(localeOptions.locales || {}).reduce(
(result: Record<string, any>, [key, value]) => {
result[key] = {
openInNewWindow:
value.openInNewWindow ?? localeOptions.openInNewWindow,
}
return result
},
{},
),
})
: [],
options.search !== false ? searchPlugin(options.search) : [],
options.docsearch !== false && !options.search
? docsearchPlugin(options.docsearch!)
: [],
options.shikiji !== false
? shikijiPlugin({
theme: { light: 'vitesse-light', dark: 'vitesse-dark' },
...(options.shikiji ?? {}),
})
: [],
options.copyCode !== false
? copyCodePlugin({
selector: '.plume-content div[class*="language-"] pre',
...options.copyCode,
})
: [],
options.markdownEnhance !== false
? mdEnhancePlugin(
Object.assign(
{
hint: true, // info note tip warning danger details d
codetabs: true,
tabs: true,
align: true,
mark: true,
tasklist: true,
demo: true,
attrs: true,
},
options.markdownEnhance || {},
),
)
: [],
options.comment !== false ? commentPlugin(options.comment || {}) : [],
options.baiduTongji !== false && options.baiduTongji?.key
? baiduTongjiPlugin(options.baiduTongji)
: [],
options.sitemap !== false && localeOptions.hostname && isProd
? sitemapPlugin({
hostname: localeOptions.hostname,
})
: [],
options.seo !== false && localeOptions.hostname && isProd
? seoPlugin({
hostname: localeOptions.hostname || '',
author: localeOptions.avatar?.name,
})
: [],
]
if (options.readingTime !== false)
plugins.push(readingTimePlugin(options.readingTime || {}))
if (localeOptions.notes)
plugins.push(notesDataPlugin(localeOptions.notes))
if (options.nprogress !== false)
plugins.push(nprogressPlugin())
if (options.git !== false) {
plugins.push(gitPlugin({
createdTime: false,
updatedTime: localeOptions.lastUpdated !== false,
contributors: localeOptions.contributors !== false,
}))
}
if (options.mediumZoom !== false) {
plugins.push(mediumZoomPlugin({
selector: '.plume-content > img, .plume-content :not(a) > img',
zoomOptions: {
background: 'var(--vp-c-bg)',
},
delay: 300,
}))
}
if (options.caniuse !== false) {
plugins.push(caniusePlugin(
options.caniuse || {
mode: 'embed',
},
))
}
if (options.externalLinkIcon !== false) {
plugins.push(externalLinkIconPlugin({
locales: Object.entries(localeOptions.locales || {}).reduce(
(result: Record<string, any>, [key, value]) => {
result[key] = {
openInNewWindow:
value.openInNewWindow ?? localeOptions.openInNewWindow,
}
return result
},
{},
),
}))
}
if (options.search !== false)
plugins.push(searchPlugin(options.search))
if (options.docsearch !== false && !options.search) {
if (options.docsearch?.appId && options.docsearch?.apiKey) {
plugins.push(docsearchPlugin(options.docsearch))
}
else {
console.error(
'docsearch plugin: appId and apiKey are both required',
)
}
}
if (options.shikiji !== false) {
plugins.push(shikijiPlugin({
theme: { light: 'vitesse-light', dark: 'vitesse-dark' },
...(options.shikiji ?? {}),
}))
}
if (options.copyCode !== false) {
plugins.push(copyCodePlugin({
selector: '.plume-content div[class*="language-"] pre',
...options.copyCode,
}))
}
if (options.markdownEnhance !== false) {
plugins.push(mdEnhancePlugin(
Object.assign(
{
hint: true, // info note tip warning danger details
codetabs: true,
tabs: true,
align: true,
mark: true,
tasklist: true,
demo: true,
attrs: true,
sup: true,
sub: true,
} as MarkdownEnhanceOptions,
options.markdownEnhance || {},
),
))
}
if (options.comment !== false)
plugins.push(commentPlugin(options.comment || {}))
if (options.baiduTongji !== false && options.baiduTongji?.key)
plugins.push(baiduTongjiPlugin(options.baiduTongji))
if (options.sitemap !== false && localeOptions.hostname && isProd) {
plugins.push(sitemapPlugin({
hostname: localeOptions.hostname,
}))
}
if (options.seo !== false && localeOptions.hostname && isProd) {
plugins.push(seoPlugin({
hostname: localeOptions.hostname || '',
author: localeOptions.avatar?.name,
}))
}
return plugins
}

View File

@ -6,10 +6,7 @@ import type {
PlumeThemeLocaleOptions,
PlumeThemePageData,
} from '../shared/index.js'
function normalizePath(dir: string) {
return dir.replace(/\\+/g, '/')
}
import { normalizePath } from './utils.js'
export async function setupPage(
app: App,

View File

@ -23,8 +23,7 @@ export function plumeTheme({
plugins: setupPlugins(app, themePlugins, localeOptions),
onInitialized: async app => await setupPage(app, localeOptions),
extendsPage: (page: Page<PlumeThemePageData>) =>
extendsPageData(app, page, localeOptions)
,
extendsPageData(app, page, localeOptions),
}
}
}

33
theme/src/node/utils.ts Normal file
View File

@ -0,0 +1,33 @@
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { customAlphabet } from 'nanoid'
export const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8)
export function getPackage() {
let pkg = {} as any
try {
const content = fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8')
pkg = JSON.parse(content)
}
catch { }
return pkg
}
const RE_SLASH = /\\+/g
export function normalizePath(dir: string) {
return dir.replace(RE_SLASH, '/')
}
export function pathJoin(...args: string[]) {
return normalizePath(path.join(...args))
}
const RE_START_END_SLASH = /^\/|\/$/g
export function getCurrentDirname(basePath: string | undefined, filepath: string) {
const dirList = normalizePath(basePath || path.dirname(filepath))
.replace(RE_START_END_SLASH, '')
.split('/')
return dirList.length > 0 ? dirList[dirList.length - 1] : ''
}

View File

@ -50,7 +50,7 @@ export interface PlumeThemePluginOptions {
baiduTongji?: false | BaiduTongjiOptions
frontmatter?: AutoFrontmatterOptions
frontmatter?: Omit<AutoFrontmatterOptions, 'frontmatter'>
readingTime?: false | ReadingTimeOptions
}

View File

@ -10,6 +10,7 @@
"@internal/notesData": [
"./plugins/plugin-notes-data/src/client/notesData.d.ts"
],
"@internal/pageComponents": ["./docs/.vuepress/.temp/internal/pageComponents.js"],
"@internal/*": ["./docs/.vuepress/.temp/internal/*"],
"@vuepress-plume/*": ["./plugins/*/src/node/index.ts"],
"vuepress-theme-plume": ["./theme/src/node/index.ts"],
@ -25,5 +26,5 @@
"docs/.vuepress/**/*",
"scripts/**/*"
],
"exclude": ["node_modules", ".temp", ".cache", "lib", "dist"]
"exclude": ["node_modules", ".cache", "lib", "dist"]
}