diff --git a/package.json b/package.json index 82fb7f61..72840540 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/plugins/plugin-content-update/LICENSE b/plugins/plugin-content-update/LICENSE new file mode 100644 index 00000000..9f677c90 --- /dev/null +++ b/plugins/plugin-content-update/LICENSE @@ -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. diff --git a/plugins/plugin-content-update/README.md b/plugins/plugin-content-update/README.md new file mode 100644 index 00000000..22fdd80a --- /dev/null +++ b/plugins/plugin-content-update/README.md @@ -0,0 +1,21 @@ +# `@vuepress-plume/plugin-content-update` + +替换 `@vuepress/client` 的 `` 组件,注入 `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() + ] + // ... +} +``` diff --git a/plugins/plugin-content-update/package.json b/plugins/plugin-content-update/package.json new file mode 100644 index 00000000..ab711f61 --- /dev/null +++ b/plugins/plugin-content-update/package.json @@ -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 ", + "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" + ] +} diff --git a/plugins/plugin-content-update/src/client/clientConfig.ts b/plugins/plugin-content-update/src/client/clientConfig.ts new file mode 100644 index 00000000..db419180 --- /dev/null +++ b/plugins/plugin-content-update/src/client/clientConfig.ts @@ -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) + }, +}) diff --git a/plugins/plugin-content-update/src/client/components/Content.ts b/plugins/plugin-content-update/src/client/components/Content.ts new file mode 100644 index 00000000..7430a828 --- /dev/null +++ b/plugins/plugin-content-update/src/client/components/Content.ts @@ -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', + ) + }, +}) diff --git a/plugins/plugin-content-update/src/client/composables/index.ts b/plugins/plugin-content-update/src/client/composables/index.ts new file mode 100644 index 00000000..c61b6972 --- /dev/null +++ b/plugins/plugin-content-update/src/client/composables/index.ts @@ -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()) +} diff --git a/plugins/plugin-content-update/src/client/index.ts b/plugins/plugin-content-update/src/client/index.ts new file mode 100644 index 00000000..5a4bbf87 --- /dev/null +++ b/plugins/plugin-content-update/src/client/index.ts @@ -0,0 +1,2 @@ +export * from './components/Content.js' +export { onContentUpdated } from './composables/index.js' diff --git a/plugins/plugin-content-update/src/node/index.ts b/plugins/plugin-content-update/src/node/index.ts new file mode 100644 index 00000000..429a98f1 --- /dev/null +++ b/plugins/plugin-content-update/src/node/index.ts @@ -0,0 +1,5 @@ +import { contentUpdatePlugin } from './plugin.js' + +export { contentUpdatePlugin } + +export default contentUpdatePlugin diff --git a/plugins/plugin-content-update/src/node/plugin.ts b/plugins/plugin-content-update/src/node/plugin.ts new file mode 100644 index 00000000..02c61177 --- /dev/null +++ b/plugins/plugin-content-update/src/node/plugin.ts @@ -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'), + } +} diff --git a/plugins/plugin-content-update/src/shim.d.ts b/plugins/plugin-content-update/src/shim.d.ts new file mode 100644 index 00000000..603f69cb --- /dev/null +++ b/plugins/plugin-content-update/src/shim.d.ts @@ -0,0 +1,4 @@ +declare module '@internal/pagesComponents' { + const pagesComponents: Record + export { pagesComponents } +} diff --git a/plugins/plugin-content-update/tsconfig.build.json b/plugins/plugin-content-update/tsconfig.build.json new file mode 100644 index 00000000..6bf67375 --- /dev/null +++ b/plugins/plugin-content-update/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.build.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib" + }, + "include": ["./src"] +} diff --git a/plugins/plugin-copy-code/package.json b/plugins/plugin-copy-code/package.json index 6ac38e16..733ee396 100644 --- a/plugins/plugin-copy-code/package.json +++ b/plugins/plugin-copy-code/package.json @@ -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" diff --git a/plugins/plugin-copy-code/src/client/clientConfig.ts b/plugins/plugin-copy-code/src/client/clientConfig.ts index 78240577..06ba938d 100644 --- a/plugins/plugin-copy-code/src/client/clientConfig.ts +++ b/plugins/plugin-copy-code/src/client/clientConfig.ts @@ -1,5 +1,5 @@ import { defineClientConfig } from '@vuepress/client' -import { setupCopyCode } from './composables/index.js' +import { setupCopyCode } from './setupCopyCode.js' import './styles/button.scss' diff --git a/plugins/plugin-copy-code/src/client/composables/copyToClipboard.ts b/plugins/plugin-copy-code/src/client/composables/copyToClipboard.ts deleted file mode 100644 index 8cb0eed1..00000000 --- a/plugins/plugin-copy-code/src/client/composables/copyToClipboard.ts +++ /dev/null @@ -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) - } -} diff --git a/plugins/plugin-copy-code/src/client/composables/index.ts b/plugins/plugin-copy-code/src/client/composables/index.ts deleted file mode 100644 index 94060776..00000000 --- a/plugins/plugin-copy-code/src/client/composables/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './setup.js' diff --git a/plugins/plugin-copy-code/src/client/composables/setup.ts b/plugins/plugin-copy-code/src/client/composables/setup.ts deleted file mode 100644 index 076d25d2..00000000 --- a/plugins/plugin-copy-code/src/client/composables/setup.ts +++ /dev/null @@ -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(selector).forEach(insertBtn) - } - else if (Array.isArray(selector)) { - selector.forEach((item) => { - document.querySelectorAll(item).forEach(insertBtn) - }) - } - }, delay) - } - - onMounted(() => { - if (!isMobile() || options.showInMobile) - generateButton() - }) - watch( - () => route.path, - () => { - if (!isMobile() || options.showInMobile) - generateButton() - }, - ) -} diff --git a/plugins/plugin-copy-code/src/client/setupCopyCode.ts b/plugins/plugin-copy-code/src/client/setupCopyCode.ts new file mode 100644 index 00000000..18e5360c --- /dev/null +++ b/plugins/plugin-copy-code/src/client/setupCopyCode.ts @@ -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 { + 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(item).forEach(insertBtn) + }) + } + + onMounted(async () => { + if (!isMobile() || options.showInMobile) { + await generateButton() + + const timeoutIdMap: WeakMap = 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() + } + } +} diff --git a/plugins/plugin-copy-code/src/client/styles/button.scss b/plugins/plugin-copy-code/src/client/styles/button.scss index 4b6c58a8..8606b9a1 100644 --- a/plugins/plugin-copy-code/src/client/styles/button.scss +++ b/plugins/plugin-copy-code/src/client/styles/button.scss @@ -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; } diff --git a/plugins/plugin-iconify/src/client/components/Iconify.vue b/plugins/plugin-iconify/src/client/components/Iconify.vue index 34df1bfb..3a347240 100644 --- a/plugins/plugin-iconify/src/client/components/Iconify.vue +++ b/plugins/plugin-iconify/src/client/components/Iconify.vue @@ -53,9 +53,6 @@ declare const __VUEPRESS_PLUGIN_ICONIFY_DEFAULT_COLOR__: string class="vp-iconify" :style="iconStyle" /> - - {{ props.name }} - diff --git a/plugins/plugin-iconify/src/client/composables/iconify.ts b/plugins/plugin-iconify/src/client/composables/iconify.ts index 1d324894..352a750f 100644 --- a/plugins/plugin-iconify/src/client/composables/iconify.ts +++ b/plugins/plugin-iconify/src/client/composables/iconify.ts @@ -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> = ref({}) - -export function useIconify(name: ComputedRef | Ref) { - const icon = computed(() => iconCache.value[name.value]) - const loaded = ref(true) +export function useIconify(name: Ref) { + const icon = ref(null) + const loaded = ref(false) async function loadIconComponent() { if (icon.value) @@ -16,7 +14,8 @@ export function useIconify(name: ComputedRef | Ref) { 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 diff --git a/plugins/tsconfig.build.json b/plugins/tsconfig.build.json index c2cf0900..1f591418 100644 --- a/plugins/tsconfig.build.json +++ b/plugins/tsconfig.build.json @@ -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": [] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92f7f03b..e8bb3e07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/scripts/README.md b/scripts/README.md index 28d22d0d..d6e4bb60 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,11 +1,3 @@ -## scripts/autoInstall - -检查各个 workspace package 中的 vuepress 相关依赖,并更新到最新版本。 - -``` sh -pnpm autoUpdate -``` - ## scripts/create 在 `packages/` 目录下生成一个新的 插件包 diff --git a/scripts/autoInstall.js b/scripts/autoInstall.js deleted file mode 100644 index c0f678fb..00000000 --- a/scripts/autoInstall.js +++ /dev/null @@ -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() diff --git a/theme/package.json b/theme/package.json index c48593ce..297b3f5c 100644 --- a/theme/package.json +++ b/theme/package.json @@ -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" diff --git a/theme/src/client/components/BlogExtract.vue b/theme/src/client/components/BlogExtract.vue index f9150a89..a07870b7 100644 --- a/theme/src/client/components/BlogExtract.vue +++ b/theme/src/client/components/BlogExtract.vue @@ -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(() => { diff --git a/theme/src/client/components/ShortPostList.vue b/theme/src/client/components/ShortPostList.vue index 70370001..51bb5568 100644 --- a/theme/src/client/components/ShortPostList.vue +++ b/theme/src/client/components/ShortPostList.vue @@ -11,14 +11,16 @@ defineProps<{ diff --git a/theme/src/client/components/Tags.vue b/theme/src/client/components/Tags.vue index 18300f41..ae64dc71 100644 --- a/theme/src/client/components/Tags.vue +++ b/theme/src/client/components/Tags.vue @@ -8,7 +8,7 @@ const { tags: tagsLink } = useBlogExtract() diff --git a/theme/src/client/components/Toc.ts b/theme/src/client/components/Toc.ts deleted file mode 100644 index e71fcaa6..00000000 --- a/theme/src/client/components/Toc.ts +++ /dev/null @@ -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, - required: false, - default: null, - }, - options: { - type: Object as PropType, - 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(() => { - const headerToUse = propsHeaders.value || page.value.headers - - return headerToUse[0]?.level === 1 ? headerToUse[0].children : headerToUse - }) - const options = computed(() => ({ - ...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 diff --git a/theme/src/client/components/VFooter.vue b/theme/src/client/components/VFooter.vue index 44a5ce31..4c6009ff 100644 --- a/theme/src/client/components/VFooter.vue +++ b/theme/src/client/components/VFooter.vue @@ -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) { diff --git a/theme/src/client/composables/blog.ts b/theme/src/client/composables/blog.ts index ae0e1309..1d6789a6 100644 --- a/theme/src/client/composables/blog.ts +++ b/theme/src/client/composables/blog.ts @@ -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, '-'), }) }) diff --git a/theme/src/client/config.ts b/theme/src/client/config.ts index e08aa266..c4549fa2 100644 --- a/theme/src/client/config.ts +++ b/theme/src/client/config.ts @@ -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', () => { diff --git a/theme/src/node/autoFrontmatter.ts b/theme/src/node/autoFrontmatter.ts index a7411f69..b042c525 100644 --- a/theme/src/node/autoFrontmatter.ts +++ b/theme/src/node/autoFrontmatter.ts @@ -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(), '/') }, }, }, diff --git a/theme/src/node/plugins.ts b/theme/src/node/plugins.ts index 99dc5aa1..e30d7311 100644 --- a/theme/src/node/plugins.ts +++ b/theme/src/node/plugins.ts @@ -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, [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, [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 } diff --git a/theme/src/node/setupPages.ts b/theme/src/node/setupPages.ts index fd4d2437..566cbb9a 100644 --- a/theme/src/node/setupPages.ts +++ b/theme/src/node/setupPages.ts @@ -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, diff --git a/theme/src/node/theme.ts b/theme/src/node/theme.ts index a4b05478..27a215ae 100644 --- a/theme/src/node/theme.ts +++ b/theme/src/node/theme.ts @@ -23,8 +23,7 @@ export function plumeTheme({ plugins: setupPlugins(app, themePlugins, localeOptions), onInitialized: async app => await setupPage(app, localeOptions), extendsPage: (page: Page) => - extendsPageData(app, page, localeOptions) - , + extendsPageData(app, page, localeOptions), } } } diff --git a/theme/src/node/utils.ts b/theme/src/node/utils.ts new file mode 100644 index 00000000..dd9abbf9 --- /dev/null +++ b/theme/src/node/utils.ts @@ -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] : '' +} diff --git a/theme/src/shared/options/plugins.ts b/theme/src/shared/options/plugins.ts index cdd53aee..85d97f5d 100644 --- a/theme/src/shared/options/plugins.ts +++ b/theme/src/shared/options/plugins.ts @@ -50,7 +50,7 @@ export interface PlumeThemePluginOptions { baiduTongji?: false | BaiduTongjiOptions - frontmatter?: AutoFrontmatterOptions + frontmatter?: Omit readingTime?: false | ReadingTimeOptions } diff --git a/tsconfig.json b/tsconfig.json index e5590835..4f8e6589 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"] }