mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
chore: tweak
This commit is contained in:
parent
0142fe9fa3
commit
ab1c30eac3
@ -11,12 +11,7 @@
|
||||
|
||||
在 `plugins` 目录中:
|
||||
|
||||
- `plugin-auto-frontmatter` : 为 md 文件自动添加 frontmatter。
|
||||
- `plugin-blog-data`: 生成 blog 文章列表数据
|
||||
- `plugin-notes-data`: 生成 notes 数据,管理不同 note 的 `sidebar` 的数据
|
||||
- ~~`plugin-caniuse`: 添加 `caniuse` 内容容器,已弃用,不再维护~~
|
||||
- `plugin-content-update`: 重写 `Content` 组件,提供 `onContentUpdated` 钩子
|
||||
- ~~`plugin-copy-code`: 为 代码块添加 复制 按钮,并适配 `shikiji`,已弃用,不再维护~~
|
||||
- `plugin-search`: 为主题提供 全文模糊搜索 功能
|
||||
- `plugin-shikiji`: 代码高亮插件,支持 highlight、diff、focus、error level
|
||||
- `plugin-iconify`: 添加全局组件 `Iconify`
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
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.
|
||||
@ -1,126 +0,0 @@
|
||||
# `@vuepress-plume/plugin-auto-frontmatter`
|
||||
|
||||
自动生成 `*.md` 文件的 `frontmatter` 配置。
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install @vuepress-plume/plugin-auto-frontmatter
|
||||
# or
|
||||
pnpm add @vuepress-plume/plugin-auto-frontmatter
|
||||
# or
|
||||
yarn add @vuepress-plume/plugin-auto-frontmatter
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
``` js
|
||||
// .vuepress/config.[jt]s
|
||||
import { autoFrontmatterPlugin } from '@vuepress-plume/plugin-auto-frontmatter'
|
||||
|
||||
export default {
|
||||
// ...
|
||||
plugins: [
|
||||
autoFrontmatterPlugin({
|
||||
formatter: {
|
||||
createTime(formatTime, file, matter) {
|
||||
if (formatTime)
|
||||
return formatTime
|
||||
return file.createTime
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## `autoFrontmatterPlugin([options])`
|
||||
|
||||
### options
|
||||
|
||||
`{ include?: string | string[]; exclude?: string | string[]; formatter: Formatter }`
|
||||
|
||||
- `include`
|
||||
include 匹配字符串或数组,匹配需要自动生成 `frontmatter` 的 md文件。
|
||||
默认预设为 `['**/*.md']`。
|
||||
|
||||
- `exclude`
|
||||
exclude 排除不需要的文件
|
||||
默认预设为: `['!.vuepress/', '!node_modules/']`
|
||||
|
||||
- `formatter`
|
||||
配置`frontmatter`每个字段的生成规则。
|
||||
|
||||
```ts
|
||||
interface MarkdownFile {
|
||||
filepath: string
|
||||
relativePath: string
|
||||
content: string
|
||||
createTime: Date
|
||||
stats: fs.Stats
|
||||
}
|
||||
|
||||
interface FormatterFn<T = any, K = object> {
|
||||
(value: T, file: MarkdownFile, data: K): T
|
||||
}
|
||||
|
||||
type FormatterObject<K = object, T = any> = Record<
|
||||
string,
|
||||
FormatterFn<T, K>
|
||||
>
|
||||
|
||||
type FormatterArray = {
|
||||
include: string | string[]
|
||||
formatter: FormatterObject
|
||||
}[]
|
||||
|
||||
type Formatter = FormatterObject | FormatterArray
|
||||
|
||||
/**
|
||||
* formatterObj 对象中的 key 即为 frontmatter 配置中的key
|
||||
* 其方法返回的值将作为 frontmatter[key] 的值
|
||||
* .md
|
||||
* ---
|
||||
* createTime: 2022-03-26T11:46:50.000Z
|
||||
* ---
|
||||
*/
|
||||
const formatterObj: Formatter = {
|
||||
createTime(formatTime, file, matter) {
|
||||
if (formatTime)
|
||||
return formatTime
|
||||
return file.createTime
|
||||
}
|
||||
}
|
||||
|
||||
const formatterArr: Formatter = [
|
||||
{
|
||||
// 更精细化的匹配某个 md文件,支持glob 匹配字符串
|
||||
include: '**/{README,index}.md',
|
||||
// formatter 仅对 glob命中的文件有效
|
||||
formatter: {
|
||||
home(value, file, matter) {
|
||||
return value
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
// 通配,如果文件没有被其他精细glob命中,
|
||||
// 则使用 通配 formatter
|
||||
// 如果是数组,必须有且用一个 include 为 * 的 项
|
||||
include: '*',
|
||||
formatter: {
|
||||
title(title) {
|
||||
return title || '默认标题'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Why ?
|
||||
|
||||
- **为什么需要这个插件?**
|
||||
|
||||
有时候在开发一些主题时,期望使用户更专注于内容的编写,尽可能减少配置性的工作,可以将一些重复性的必要的配置
|
||||
直接通过本插件自动生成。
|
||||
@ -1,56 +0,0 @@
|
||||
{
|
||||
"name": "@vuepress-plume/plugin-auto-frontmatter",
|
||||
"type": "module",
|
||||
"version": "1.0.0-rc.75",
|
||||
"private": true,
|
||||
"description": "The Plugin for VuePress 2 - auto frontmatter",
|
||||
"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",
|
||||
"directory": "plugins/plugin-auto-frontmatter"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/node/index.d.ts",
|
||||
"import": "./lib/node/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "lib/node/index.js",
|
||||
"types": "./lib/node/index.d.ts",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pnpm run copy && pnpm run ts",
|
||||
"clean": "rimraf --glob ./lib ./*.tsbuildinfo",
|
||||
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
|
||||
"ts": "tsc -b tsconfig.build.json"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vuepress": "2.0.0-rc.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pengzhanbo/utils": "^1.1.2",
|
||||
"chokidar": "^3.6.0",
|
||||
"create-filter": "^1.1.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"gray-matter": "^4.0.3",
|
||||
"json2yaml": "^1.1.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keyword": [
|
||||
"VuePress",
|
||||
"vuepress plugin",
|
||||
"autoFrontmatter",
|
||||
"vuepress-plugin-plugin-auto-frontmatter"
|
||||
]
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
declare module 'json2yaml' {
|
||||
const result: any
|
||||
|
||||
export default result
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
import type {
|
||||
AutoFrontmatterOptions,
|
||||
FrontmatterArray,
|
||||
FrontmatterObject,
|
||||
} from '../shared/index.js'
|
||||
import { autoFrontmatterPlugin } from './plugin.js'
|
||||
|
||||
export * from './plugin.js'
|
||||
|
||||
export type { AutoFrontmatterOptions, FrontmatterArray, FrontmatterObject }
|
||||
|
||||
export default autoFrontmatterPlugin
|
||||
@ -1,103 +0,0 @@
|
||||
import { colors, fs, logger } from 'vuepress/utils'
|
||||
import type { Plugin } from 'vuepress/core'
|
||||
import chokidar from 'chokidar'
|
||||
import { createFilter } from 'create-filter'
|
||||
import grayMatter from 'gray-matter'
|
||||
import jsonToYaml from 'json2yaml'
|
||||
import { promiseParallel } from '@pengzhanbo/utils'
|
||||
import type {
|
||||
AutoFrontmatterOptions,
|
||||
FrontmatterArray,
|
||||
FrontmatterObject,
|
||||
MarkdownFile,
|
||||
} from '../shared/index.js'
|
||||
import { readMarkdown, readMarkdownList } from './readFiles.js'
|
||||
import { ensureArray, isEmptyObject } from './utils.js'
|
||||
|
||||
const PLUGIN_NAME = '@vuepress-plume/plugin-auto-frontmatter'
|
||||
|
||||
export function autoFrontmatterPlugin({
|
||||
include = ['**/*.md'],
|
||||
exclude = ['.vuepress/**/*', 'node_modules'],
|
||||
frontmatter = {},
|
||||
}: AutoFrontmatterOptions = {}): Plugin {
|
||||
include = ensureArray(include)
|
||||
exclude = ensureArray(exclude)
|
||||
|
||||
const globFilter = createFilter(include, exclude, { resolve: false })
|
||||
|
||||
const matterFrontmatter: FrontmatterArray = Array.isArray(frontmatter)
|
||||
? frontmatter
|
||||
: [{ include: '*', frontmatter }]
|
||||
|
||||
const globFormatter: FrontmatterObject
|
||||
= matterFrontmatter.find(({ include }) => include === '*')?.frontmatter || {}
|
||||
|
||||
const otherFormatters = matterFrontmatter
|
||||
.filter(({ include }) => include !== '*')
|
||||
.map(({ include, frontmatter }) => {
|
||||
return {
|
||||
include,
|
||||
filter: createFilter(ensureArray(include), [], { resolve: false }),
|
||||
frontmatter,
|
||||
}
|
||||
})
|
||||
|
||||
async function formatMarkdown(file: MarkdownFile): Promise<void> {
|
||||
const { filepath, relativePath } = file
|
||||
|
||||
const current = otherFormatters.find(({ filter }) => filter(relativePath))
|
||||
const formatter = current?.frontmatter || globFormatter
|
||||
const { data, content } = grayMatter(file.content)
|
||||
|
||||
for (const key in formatter) {
|
||||
const value = await formatter[key](data[key], file, data)
|
||||
data[key] = value ?? data[key]
|
||||
}
|
||||
|
||||
try {
|
||||
const yaml = isEmptyObject(data)
|
||||
? ''
|
||||
: jsonToYaml
|
||||
.stringify(data)
|
||||
.replace(/\n\s{2}/g, '\n')
|
||||
.replace(/"/g, '')
|
||||
.replace(/\s+\n/g, '\n')
|
||||
const newContent = yaml ? `${yaml}---\n${content}` : content
|
||||
|
||||
fs.writeFileSync(filepath, newContent, 'utf-8')
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: PLUGIN_NAME,
|
||||
onInitialized: async (app) => {
|
||||
const start = performance.now()
|
||||
const markdownList = await readMarkdownList(app.dir.source(), globFilter)
|
||||
await promiseParallel(
|
||||
markdownList.map(file => () => formatMarkdown(file)),
|
||||
64,
|
||||
)
|
||||
if (app.env.isDebug)
|
||||
logger.info(`\n[${colors.green(PLUGIN_NAME)}] Init time spent: ${(performance.now() - start).toFixed(2)}ms`)
|
||||
},
|
||||
onWatched: async (app, watchers) => {
|
||||
const watcher = chokidar.watch('**/*.md', {
|
||||
cwd: app.dir.source(),
|
||||
ignoreInitial: true,
|
||||
ignored: /(node_modules|\.vuepress)\//,
|
||||
})
|
||||
|
||||
watcher.on('add', async (relativePath) => {
|
||||
if (!globFilter(relativePath))
|
||||
return
|
||||
await formatMarkdown(readMarkdown(app.dir.source(), relativePath))
|
||||
})
|
||||
|
||||
watchers.push(watcher)
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import { fs, path } from 'vuepress/utils'
|
||||
import fg from 'fast-glob'
|
||||
import type { MarkdownFile } from '../shared/index.js'
|
||||
|
||||
type MarkdownFileList = MarkdownFile[]
|
||||
|
||||
export async function readMarkdownList(sourceDir: string, filter: (id: string) => boolean): Promise<MarkdownFileList> {
|
||||
const files: string[] = await fg(['**/*.md'], {
|
||||
cwd: sourceDir,
|
||||
ignore: ['node_modules', '.vuepress'],
|
||||
})
|
||||
|
||||
return files
|
||||
.filter(file => filter(file))
|
||||
.map(file => readMarkdown(sourceDir, file))
|
||||
}
|
||||
|
||||
export function readMarkdown(sourceDir: string, relativePath: string): MarkdownFile {
|
||||
const filepath = path.join(sourceDir, relativePath)
|
||||
const stats = fs.statSync(filepath)
|
||||
return {
|
||||
filepath,
|
||||
relativePath,
|
||||
content: fs.readFileSync(filepath, 'utf-8'),
|
||||
createTime: getFileCreateTime(stats),
|
||||
stats,
|
||||
}
|
||||
}
|
||||
|
||||
export function getFileCreateTime(stats: fs.Stats): Date {
|
||||
return stats.birthtime.getFullYear() !== 1970 ? stats.birthtime : stats.atime
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
export function ensureArray<T>(thing: T | T[] | null | undefined): T[] {
|
||||
if (Array.isArray(thing))
|
||||
return thing
|
||||
if (thing === null || thing === undefined)
|
||||
return []
|
||||
return [thing]
|
||||
}
|
||||
|
||||
export function isEmptyObject(obj: object) {
|
||||
return Object.keys(obj).length === 0
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import type fs from 'node:fs'
|
||||
|
||||
export interface MarkdownFile {
|
||||
filepath: string
|
||||
relativePath: string
|
||||
content: string
|
||||
createTime: Date
|
||||
stats: fs.Stats
|
||||
}
|
||||
|
||||
export type FrontmatterFn<T = any, K = object> = (
|
||||
value: T,
|
||||
file: MarkdownFile,
|
||||
data: K
|
||||
) => T | PromiseLike<T>
|
||||
|
||||
export type FrontmatterObject<K = object, T = any> = Record<string, FrontmatterFn<T, K>>
|
||||
|
||||
export type FrontmatterArray = {
|
||||
include: string | string[]
|
||||
frontmatter: FrontmatterObject
|
||||
}[]
|
||||
|
||||
export interface AutoFrontmatterOptions {
|
||||
/**
|
||||
* FilterPattern
|
||||
*/
|
||||
include?: string | string[]
|
||||
|
||||
exclude?: string | string[]
|
||||
|
||||
/**
|
||||
* {
|
||||
* key(value, file, data) {
|
||||
* return value
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
frontmatter?: FrontmatterArray | FrontmatterObject
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib"
|
||||
},
|
||||
"files": [],
|
||||
"include": ["./src"]
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
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.
|
||||
@ -1,38 +0,0 @@
|
||||
# `@vuepress-plume/plugin-blog-data`
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install @vuepress-plume/plugin-blog-data
|
||||
# or
|
||||
pnpm add @vuepress-plume/plugin-blog-data
|
||||
# or
|
||||
yarn add @vuepress-plume/plugin-blog-data
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
``` js
|
||||
// .vuepress/config.[jt]s
|
||||
import { blogDataPlugin } from '@vuepress-plume/plugin-blog-data'
|
||||
|
||||
export default {
|
||||
// ...
|
||||
plugins: [
|
||||
blogDataPlugin()
|
||||
]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```ts
|
||||
interface BlogDataPluginOptions {
|
||||
include?: string | string[]
|
||||
exclude?: string | string[]
|
||||
sortBy?: 'createTime' | false | (<T>(prev: T, next: T) => boolean)
|
||||
excerpt?: boolean
|
||||
extendBlogData?: <T = any>(page: T) => Record<string, any>
|
||||
}
|
||||
```
|
||||
@ -1,58 +0,0 @@
|
||||
{
|
||||
"name": "@vuepress-plume/plugin-blog-data",
|
||||
"type": "module",
|
||||
"version": "1.0.0-rc.75",
|
||||
"private": "true",
|
||||
"description": "The Plugin for VuePress 2 - blog data",
|
||||
"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",
|
||||
"directory": "plugins/plugin-blog-data"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/node/index.d.ts",
|
||||
"import": "./lib/node/index.js"
|
||||
},
|
||||
"./client": {
|
||||
"types": "./lib/client/index.d.ts",
|
||||
"import": "./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 copy && pnpm run ts",
|
||||
"clean": "rimraf --glob ./lib ./*.tsbuildinfo",
|
||||
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
|
||||
"ts": "tsc -b tsconfig.build.json"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vuepress": "2.0.0-rc.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "6.6.3",
|
||||
"chokidar": "^3.6.0",
|
||||
"create-filter": "^1.1.0",
|
||||
"vue": "^3.4.31"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keyword": [
|
||||
"VuePress",
|
||||
"vuepress plugin",
|
||||
"blogData",
|
||||
"vuepress-plugin-plugin-blog-data"
|
||||
]
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import type { BlogPostData } from '../shared/index.js'
|
||||
|
||||
declare module '@internal/blogData' {
|
||||
const blogPostData: BlogPostData
|
||||
|
||||
export { blogPostData }
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './useBlogPostData.js'
|
||||
@ -1,24 +0,0 @@
|
||||
import {
|
||||
blogPostData as blogPostDataRaw,
|
||||
} from '@internal/blogData'
|
||||
import { ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import type { BlogPostData } from '../../shared/index.js'
|
||||
|
||||
declare const __VUE_HMR_RUNTIME__: Record<string, any>
|
||||
|
||||
export type BlogDataRef<T extends BlogPostData = BlogPostData> = Ref<T>
|
||||
|
||||
export const blogPostData: BlogDataRef = ref(blogPostDataRaw)
|
||||
|
||||
export function useBlogPostData<
|
||||
T extends BlogPostData = BlogPostData,
|
||||
>(): BlogDataRef<T> {
|
||||
return blogPostData as BlogDataRef<T>
|
||||
}
|
||||
|
||||
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
|
||||
__VUE_HMR_RUNTIME__.updateBlogData = (data: BlogPostData) => {
|
||||
blogPostData.value = data
|
||||
}
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
import { setupDevtoolsPlugin } from '@vue/devtools-api'
|
||||
import { defineClientConfig } from 'vuepress/client'
|
||||
import type { ClientConfig } from 'vuepress/client'
|
||||
import { useBlogPostData } from './composables/index.js'
|
||||
|
||||
declare const __VUE_PROD_DEVTOOLS__: boolean
|
||||
|
||||
export default defineClientConfig({
|
||||
enhance({ app }) {
|
||||
const blogPostData = useBlogPostData()
|
||||
|
||||
Object.defineProperties(app.config.globalProperties, {
|
||||
$blogPostData: {
|
||||
get() {
|
||||
return blogPostData.value
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// setup devtools in dev mode
|
||||
if (__VUEPRESS_DEV__ || __VUE_PROD_DEVTOOLS__) {
|
||||
const PLUGIN_ID = 'org.vuejs.vuepress'
|
||||
const PLUGIN_LABEL = 'VuePress'
|
||||
const INSPECTOR_ID = PLUGIN_ID
|
||||
|
||||
setupDevtoolsPlugin(
|
||||
{
|
||||
// fix recursive reference
|
||||
app: app as any,
|
||||
id: PLUGIN_ID,
|
||||
label: PLUGIN_LABEL,
|
||||
packageName: '@vuepress-plume/plugin-blog-data',
|
||||
homepage: 'https://pengzhanbo.cn',
|
||||
logo: 'https://v2.vuepress.vuejs.org/images/hero.png',
|
||||
componentStateTypes: ['VuePress'],
|
||||
},
|
||||
(api) => {
|
||||
api.on.inspectComponent((payload) => {
|
||||
payload.instanceData.state.push({
|
||||
type: 'VuePress',
|
||||
key: 'blogPostData',
|
||||
editable: false,
|
||||
value: blogPostData.value,
|
||||
})
|
||||
})
|
||||
api.on.getInspectorTree((payload) => {
|
||||
if (payload.inspectorId !== INSPECTOR_ID)
|
||||
return
|
||||
payload.rootNodes.push({
|
||||
id: 'blog_post_data',
|
||||
label: 'Blog Post Data',
|
||||
})
|
||||
})
|
||||
api.on.getInspectorState((payload) => {
|
||||
if (payload.inspectorId !== INSPECTOR_ID)
|
||||
return
|
||||
if (payload.nodeId === 'blog_post_data') {
|
||||
payload.state = {
|
||||
BlogPostData: [{
|
||||
key: 'blogPostData',
|
||||
value: blogPostData.value,
|
||||
}],
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
}) as ClientConfig
|
||||
@ -1,5 +0,0 @@
|
||||
import type { BlogPostData, BlogPostDataItem } from '../shared/index.js'
|
||||
|
||||
export * from './composables/index.js'
|
||||
|
||||
export type { BlogPostData, BlogPostDataItem }
|
||||
@ -1,6 +0,0 @@
|
||||
import { blogDataPlugin } from './plugin.js'
|
||||
|
||||
export * from '../shared/index.js'
|
||||
export { blogDataPlugin }
|
||||
|
||||
export default blogDataPlugin
|
||||
@ -1,52 +0,0 @@
|
||||
import type { Plugin } from 'vuepress/core'
|
||||
import { getDirname, path } from 'vuepress/utils'
|
||||
import chokidar from 'chokidar'
|
||||
import { createFilter } from 'create-filter'
|
||||
import { preparedBlogData } from './prepareBlogData.js'
|
||||
import type { BlogDataPluginOptions } from './index.js'
|
||||
|
||||
const __dirname = getDirname(import.meta.url)
|
||||
|
||||
export type PluginOption = Omit<BlogDataPluginOptions, 'include' | 'exclude'>
|
||||
|
||||
export function blogDataPlugin({
|
||||
include,
|
||||
exclude,
|
||||
...pluginOptions
|
||||
}: BlogDataPluginOptions = {}): Plugin {
|
||||
const pageFilter = createFilter(toArray(include), toArray(exclude), {
|
||||
resolve: false,
|
||||
})
|
||||
|
||||
return {
|
||||
name: '@vuepress-plume/plugin-blog-data',
|
||||
clientConfigFile: path.resolve(__dirname, '../client/config.js'),
|
||||
extendsPage(page) {
|
||||
if (page.filePathRelative && pageFilter(page.filePathRelative)) {
|
||||
;(page.data as any).isBlogPost = true
|
||||
}
|
||||
},
|
||||
onPrepared: async app =>
|
||||
await preparedBlogData(app, pageFilter, pluginOptions),
|
||||
onWatched(app, watchers) {
|
||||
const watcher = chokidar.watch('pages/**/*', {
|
||||
cwd: app.dir.temp(),
|
||||
ignoreInitial: true,
|
||||
})
|
||||
|
||||
const handler = () => preparedBlogData(app, pageFilter, pluginOptions)
|
||||
|
||||
watcher.on('add', handler)
|
||||
watcher.on('change', handler)
|
||||
watcher.on('unlink', handler)
|
||||
|
||||
watchers.push(watcher)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function toArray(likeArr: string | string[] | undefined): string[] {
|
||||
if (Array.isArray(likeArr))
|
||||
return likeArr
|
||||
return likeArr ? [likeArr] : []
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import type { App, Page } from 'vuepress/core'
|
||||
import { colors, logger } from 'vuepress/utils'
|
||||
import type { BlogPostData, BlogPostDataItem } from '../shared/index.js'
|
||||
import type { PluginOption } from './plugin.js'
|
||||
|
||||
const HMR_CODE = `
|
||||
if (import.meta.webpackHot) {
|
||||
import.meta.webpackHot.accept()
|
||||
if (__VUE_HMR_RUNTIME__.updateBlogData) {
|
||||
__VUE_HMR_RUNTIME__.updateBlogData(blogPostData)
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(({ blogPostData }) => {
|
||||
__VUE_HMR_RUNTIME__.updateBlogData(blogPostData)
|
||||
})
|
||||
}
|
||||
`
|
||||
|
||||
const headingRe = /<h(\d)[^>]*>.*?<\/h\1>/gi
|
||||
|
||||
const EXCERPT_SPLIT = '<!-- more -->'
|
||||
let contentHash: string | undefined
|
||||
|
||||
export async function preparedBlogData(app: App, pageFilter: (id: string) => boolean, options: PluginOption): Promise<void> {
|
||||
const start = performance.now()
|
||||
|
||||
let pages = app.pages.filter((page) => {
|
||||
return page.filePathRelative && pageFilter(page.filePathRelative)
|
||||
})
|
||||
if (options.pageFilter)
|
||||
pages = pages.filter(options.pageFilter)
|
||||
|
||||
if (options.sortBy) {
|
||||
pages = pages.sort((prev, next) => {
|
||||
if (options.sortBy === 'createTime') {
|
||||
return getTimestamp(prev.frontmatter.createTime as Date)
|
||||
< getTimestamp(next.frontmatter.createTime as Date)
|
||||
? 1
|
||||
: -1
|
||||
}
|
||||
else {
|
||||
return typeof options.sortBy === 'function'
|
||||
&& options.sortBy(prev, next)
|
||||
? 1
|
||||
: -1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const blogData: BlogPostData = pages.map((page: Page) => {
|
||||
let extended: Partial<BlogPostDataItem> = {}
|
||||
if (typeof options.extendBlogData === 'function')
|
||||
extended = options.extendBlogData(page)
|
||||
|
||||
const data = {
|
||||
path: page.path,
|
||||
title: page.title,
|
||||
...extended,
|
||||
}
|
||||
|
||||
if (options.excerpt && page.contentRendered.includes(EXCERPT_SPLIT)) {
|
||||
const contents = page.contentRendered.split(EXCERPT_SPLIT)
|
||||
let excerpt = contents[0]
|
||||
// 删除摘要中的标题
|
||||
excerpt = excerpt.replace(headingRe, '')
|
||||
data.excerpt = excerpt
|
||||
}
|
||||
|
||||
return data as BlogPostDataItem
|
||||
})
|
||||
|
||||
let content = `\
|
||||
export const blogPostData = ${JSON.stringify(blogData)};
|
||||
`
|
||||
|
||||
// inject HMR code
|
||||
if (app.env.isDev)
|
||||
content += HMR_CODE
|
||||
|
||||
const currentHash = hash(content)
|
||||
if (!contentHash || contentHash !== currentHash) {
|
||||
contentHash = currentHash
|
||||
await app.writeTemp('internal/blogData.js', content)
|
||||
}
|
||||
|
||||
if (app.env.isDebug)
|
||||
logger.info(`\n[${colors.green('@vuepress-plume/plugin-blog-data')}] prepare blog data time spent: ${(performance.now() - start).toFixed(2)}ms`)
|
||||
}
|
||||
|
||||
function getTimestamp(time: Date): number {
|
||||
return new Date(time).getTime()
|
||||
}
|
||||
|
||||
function hash(content: string): string {
|
||||
return createHash('md5').update(content).digest('hex')
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
import type { Page } from 'vuepress/core'
|
||||
|
||||
export interface BlogDataPluginOptions {
|
||||
include?: string | string[]
|
||||
exclude?: string | string[]
|
||||
sortBy?: 'createTime' | false | (<T>(prev: T, next: T) => boolean)
|
||||
excerpt?: boolean
|
||||
extendBlogData?: <T = any>(page: T) => Record<string, any>
|
||||
pageFilter?: (page: Page) => boolean
|
||||
}
|
||||
|
||||
export type BlogPostData<T extends object = object> = BlogPostDataItem<T>[]
|
||||
|
||||
export type BlogPostDataItem<T extends object = object> = {
|
||||
path: string
|
||||
title: string
|
||||
excerpt: string
|
||||
[x: string]: any
|
||||
} & T
|
||||
@ -1,13 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"rootDir": "./src",
|
||||
"paths": {
|
||||
"@internal/blogData": ["./src/client/blogPostData.d.ts"]
|
||||
},
|
||||
"types": ["vuepress/client-types", "vite/client", "webpack-env"],
|
||||
"outDir": "./lib"
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
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.
|
||||
@ -1,71 +0,0 @@
|
||||
# vuepress-plugin-caniuse
|
||||
|
||||
VuePress 2 Plugin
|
||||
|
||||
VuePress 2 插件
|
||||
|
||||
在Markdown中添加 [can-i-use](https://caniuse.com/) 支持,这对于你在写前端技术博客时,说明某个feature的兼容性时特别有用。
|
||||
|
||||
## Install
|
||||
|
||||
``` sh
|
||||
npm install @vuepress-plume/plugin-caniuse
|
||||
# or
|
||||
pnpm add @vuepress-plume/plugin-caniuse
|
||||
# or
|
||||
yarn add @vuepress-plume/plugin-caniuse
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### 在VuePress 配置文件中添加插件
|
||||
|
||||
``` js
|
||||
// .vuepress/config.[jt]s
|
||||
import { caniusePlugin } from '@vuepress-plume/plugin-caniuse'
|
||||
|
||||
export default {
|
||||
// ...
|
||||
plugins: [
|
||||
caniusePlugin({ mode: 'image' }),
|
||||
]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 在markdown中编写
|
||||
|
||||
``` md
|
||||
::: caniuse <feature> {{browser_versions}}
|
||||
:::
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `options.mode`: can-i-use插入文档的模式, 支持 `embed` 和`image`, 默认值是 `image`。
|
||||
- `image`: 插入图片
|
||||
- `embed`: 使用iframe嵌入 can-i-use
|
||||
|
||||
### \<feature>
|
||||
|
||||
正确取值请参考 [https://caniuse.bitsofco.de/](https://caniuse.bitsofco.de/)
|
||||
|
||||
### \{browser_versions\}`
|
||||
|
||||
可选。当前特性在多个版本中的支持情况。
|
||||
|
||||
格式: `{number,number,...}` 取值范围为 `-5 ~ 3`
|
||||
|
||||
- 小于`0` 表示低于当前浏览器版本的支持情况
|
||||
- `0` 表示当前浏览器版本的支持情况
|
||||
- 大于`0` 表示高于当前浏览器版本的支持情况
|
||||
|
||||
## Example
|
||||
|
||||
``` md
|
||||
::: caniuse css-matches-pseudo {-2,-1,1}
|
||||
:::
|
||||
```
|
||||
|
||||
效果:
|
||||

|
||||
@ -1,58 +0,0 @@
|
||||
{
|
||||
"name": "@vuepress-plume/plugin-caniuse",
|
||||
"type": "module",
|
||||
"version": "1.0.0-rc.75",
|
||||
"private": "true",
|
||||
"description": "The Plugin for VuePress 2, Support Can-I-Use feature",
|
||||
"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",
|
||||
"directory": "plugins/plugin-caniuse"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"VuePress",
|
||||
"plugin",
|
||||
"vuepress-plugin",
|
||||
"can-i-use",
|
||||
"caniuse"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/node/index.d.ts",
|
||||
"import": "./lib/node/index.js"
|
||||
},
|
||||
"./client": {
|
||||
"types": "./lib/client/index.d.ts",
|
||||
"import": "./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 ts",
|
||||
"clean": "rimraf --glob ./lib ./*.tsbuildinfo",
|
||||
"ts": "tsc -b tsconfig.build.json"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vuepress": "2.0.0-rc.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"markdown-it-container": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^14.1.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import { defineClientConfig } from 'vuepress/client'
|
||||
import type { ClientConfig } from 'vuepress/client'
|
||||
import type { CanIUseMode } from '../shared/index.js'
|
||||
import { resolveCanIUse } from './resolveCanIUse.js'
|
||||
|
||||
declare const __CAN_I_USE_INJECT_MODE__: CanIUseMode
|
||||
declare const __VUEPRESS_SSR__: boolean
|
||||
|
||||
const mode = __CAN_I_USE_INJECT_MODE__
|
||||
|
||||
export default defineClientConfig({
|
||||
enhance({ router }) {
|
||||
if (__VUEPRESS_SSR__)
|
||||
return
|
||||
|
||||
router.afterEach(() => {
|
||||
if (mode === 'embed')
|
||||
resolveCanIUse()
|
||||
})
|
||||
},
|
||||
}) as ClientConfig
|
||||
@ -1 +0,0 @@
|
||||
export * from '../shared/index.js'
|
||||
@ -1,20 +0,0 @@
|
||||
let isBind = false
|
||||
export function resolveCanIUse(): void {
|
||||
if (isBind)
|
||||
return
|
||||
isBind = true
|
||||
|
||||
window.addEventListener('message', (message) => {
|
||||
const data = message.data
|
||||
|
||||
if (typeof data === 'string' && data.includes('ciu_embed')) {
|
||||
const [, feature, height] = data.split(':')
|
||||
const el = document.querySelector(`.ciu_embed[data-feature="${feature}"]:not([data-skip])`)
|
||||
if (el) {
|
||||
const h = Number.parseInt(height) + 30
|
||||
;(el.childNodes[0] as any).height = `${h}px`
|
||||
el.setAttribute('data-skip', 'true')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
import { caniusePlugin } from './plugin.js'
|
||||
|
||||
export * from './plugin.js'
|
||||
export * from '../shared/index.js'
|
||||
|
||||
export default caniusePlugin
|
||||
@ -1,6 +0,0 @@
|
||||
declare module 'markdown-it-container' {
|
||||
import type { PluginWithParams } from 'markdown-it'
|
||||
|
||||
const container: PluginWithParams
|
||||
export = container
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
import type { Plugin, PluginObject } from 'vuepress/core'
|
||||
import { getDirname, path } from 'vuepress/utils'
|
||||
import type Token from 'markdown-it/lib/token.mjs'
|
||||
import container from 'markdown-it-container'
|
||||
import type { CanIUseMode, CanIUsePluginOptions } from '../shared/index.js'
|
||||
import { resolveCanIUse } from './resolveCanIUse.js'
|
||||
|
||||
const __dirname = getDirname(import.meta.url)
|
||||
const modeMap: CanIUseMode[] = ['image', 'embed']
|
||||
const isMode = (mode: CanIUseMode): boolean => modeMap.includes(mode)
|
||||
|
||||
export function caniusePlugin({
|
||||
mode = modeMap[0],
|
||||
}: CanIUsePluginOptions): Plugin {
|
||||
mode = isMode(mode) ? mode : modeMap[0]
|
||||
const type = 'caniuse'
|
||||
const validateReg = new RegExp(`^${type}(?:$|\s)`)
|
||||
const pluginObj: PluginObject = {
|
||||
name: '@vuepress-plume/plugin-caniuse',
|
||||
clientConfigFile: path.resolve(__dirname, '../client/clientConfig.js'),
|
||||
define: {
|
||||
__CAN_I_USE_INJECT_MODE__: mode,
|
||||
},
|
||||
}
|
||||
|
||||
const validate = (info: string): boolean => {
|
||||
return validateReg.test(info.trim())
|
||||
}
|
||||
|
||||
const render = (tokens: Token[], index: number): string => {
|
||||
const token = tokens[index]
|
||||
if (token.nesting === 1) {
|
||||
const info = token.info.trim().slice(type.length).trim() || ''
|
||||
const feature = info.split(/\s+/)[0]
|
||||
const versions = info.match(/\{(.*)\}/)?.[1] || ''
|
||||
return feature ? resolveCanIUse(feature, mode, versions) : ''
|
||||
}
|
||||
else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
pluginObj.extendsMarkdown = (md) => {
|
||||
md.use(container as any, type, { validate, render })
|
||||
}
|
||||
|
||||
return pluginObj
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
import type { CanIUseMode } from '../shared/index.js'
|
||||
|
||||
export function resolveCanIUse(feature: string, mode: CanIUseMode, versions: string): string {
|
||||
if (!feature)
|
||||
return ''
|
||||
|
||||
if (mode === 'image') {
|
||||
return `<picture>
|
||||
<source type="image/webp" srcset="https://caniuse.bitsofco.de/image/${feature}.webp">
|
||||
<source type="image/png" srcset="https://caniuse.bitsofco.de/image/${feature}.png">
|
||||
<img src="https://caniuse.bitsofco.de/image/${feature}.jpg" alt="Data on support for the ${feature} feature across the major browsers from caniuse.com">
|
||||
</picture>`
|
||||
}
|
||||
|
||||
const periods = resolveVersions(versions)
|
||||
const accessible = 'false'
|
||||
const image = 'none'
|
||||
const url = 'https://caniuse.bitsofco.de/embed/index.html'
|
||||
const src = `${url}?feat=${feature}&periods=${periods}&accessible-colours=${accessible}&image-base=${image}`
|
||||
|
||||
return `<div class="ciu_embed" style="margin:16px 0" data-feature="${feature}"><iframe src="${src}" frameborder="0" width="100%" height="400px"></iframe></div>`
|
||||
}
|
||||
|
||||
function resolveVersions(versions: string): string {
|
||||
if (!versions)
|
||||
return 'future_1,current,past_1,past_2'
|
||||
|
||||
const list = versions
|
||||
.split(',')
|
||||
.map(v => Number(v.trim()))
|
||||
.filter(v => !Number.isNaN(v) && v >= -5 && v <= 3)
|
||||
|
||||
list.push(0)
|
||||
|
||||
const uniq = [...new Set(list)].sort((a, b) => b - a)
|
||||
const result: string[] = []
|
||||
uniq.forEach((v) => {
|
||||
if (v < 0)
|
||||
result.push(`past_${Math.abs(v)}`)
|
||||
if (v === 0)
|
||||
result.push('current')
|
||||
if (v > 0)
|
||||
result.push(`future_${v}`)
|
||||
})
|
||||
return result.join(',')
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
export type CanIUseMode = 'embed' | 'image'
|
||||
|
||||
/**
|
||||
* can-i-use plugin options
|
||||
*/
|
||||
export interface CanIUsePluginOptions {
|
||||
/**
|
||||
* 嵌入模式
|
||||
*
|
||||
* embed 通过iframe嵌入,提供可交互视图
|
||||
*
|
||||
* image 通过图片嵌入,静态
|
||||
*/
|
||||
mode: CanIUseMode
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './caniuse.js'
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib"
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
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.
|
||||
@ -1,26 +0,0 @@
|
||||
# `@vuepress-plume/plugin-copy-code`
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install @vuepress-plume/plugin-copy-code
|
||||
# or
|
||||
pnpm add @vuepress-plume/plugin-copy-code
|
||||
# or
|
||||
yarn add @vuepress-plume/plugin-copy-code
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
``` js
|
||||
// .vuepress/config.js
|
||||
import { copyCodePlugin } from '@vuepress-plume/plugin-copy-code'
|
||||
|
||||
export default {
|
||||
// ...
|
||||
plugins: [
|
||||
copyCodePlugin()
|
||||
]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
@ -1,56 +0,0 @@
|
||||
{
|
||||
"name": "@vuepress-plume/plugin-copy-code",
|
||||
"type": "module",
|
||||
"version": "1.0.0-rc.75",
|
||||
"private": "true",
|
||||
"description": "The Plugin for VuePress 2 - copy code",
|
||||
"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",
|
||||
"directory": "plugins/plugin-copy-code"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/node/index.d.ts",
|
||||
"import": "./lib/node/index.js"
|
||||
},
|
||||
"./client": {
|
||||
"types": "./lib/client/index.d.ts",
|
||||
"import": "./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 copy && pnpm run ts",
|
||||
"clean": "rimraf --glob ./lib ./*.tsbuildinfo",
|
||||
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
|
||||
"ts": "tsc -b tsconfig.build.json"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vuepress": "2.0.0-rc.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vuepress-plume/plugin-content-update": "workspace:~",
|
||||
"vue": "^3.4.31"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keyword": [
|
||||
"VuePress",
|
||||
"vuepress plugin",
|
||||
"copyCode",
|
||||
"vuepress-plugin-plugin-copy-code"
|
||||
]
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
import { defineClientConfig } from 'vuepress/client'
|
||||
import type { ClientConfig } from 'vuepress/client'
|
||||
import { setupCopyCode } from './setupCopyCode.js'
|
||||
|
||||
import './styles/button.css'
|
||||
|
||||
export default defineClientConfig({
|
||||
setup() {
|
||||
setupCopyCode()
|
||||
},
|
||||
}) as ClientConfig
|
||||
@ -1 +0,0 @@
|
||||
export * from '../shared/index.js'
|
||||
@ -1,142 +0,0 @@
|
||||
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', '.vp-copy-ignore']
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
:root {
|
||||
--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");
|
||||
--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
:root {
|
||||
--vp-code-copy-code-border-color: #e2e2e3;
|
||||
--vp-code-copy-code-bg: #f6f6f7;
|
||||
--vp-code-copy-code-hover-border-color: #e2e2e3;
|
||||
--vp-code-copy-code-hover-bg: #fff;
|
||||
--vp-code-copy-code-active-text: rgba(60, 60, 67, 0.78);
|
||||
--vp-code-copy-copied-text-content: "Copied";
|
||||
}
|
||||
|
||||
html[lang="zh-CN"] {
|
||||
--vp-code-copy-copied-text-content: "已复制";
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-code-copy-code-border-color: #2e2e32;
|
||||
--vp-code-copy-code-bg: #202127;
|
||||
--vp-code-copy-code-hover-bg: #1b1b1f;
|
||||
--vp-code-copy-code-hover-border-color: #2e2e32;
|
||||
--vp-code-copy-code-active-text: rgba(235, 235, 245, 0.6);
|
||||
}
|
||||
|
||||
.copy-code-button {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
|
||||
/* rtl:ignore */
|
||||
right: 12px;
|
||||
z-index: 3;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
background-color: var(--vp-code-copy-code-bg);
|
||||
background-image: var(--vp-icon-copy);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50%;
|
||||
background-size: 20px;
|
||||
border: 1px solid var(--vp-code-copy-code-border-color);
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
transition:
|
||||
border-color 0.25s,
|
||||
background-color 0.25s,
|
||||
opacity 0.25s;
|
||||
|
||||
/* rtl:ignore */
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
[class*="language-"]:hover > .copy-code-button,
|
||||
[class*="language-"] > .copy-code-button:focus,
|
||||
[class*="language-"] > .copy-code-button.copied {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[class*="language-"] > .copy-code-button:hover,
|
||||
[class*="language-"] > .copy-code-button.copied {
|
||||
background-color: var(--vp-code-copy-code-hover-bg);
|
||||
border-color: var(--vp-code-copy-code-hover-border-color);
|
||||
}
|
||||
|
||||
[class*="language-"] > .copy-code-button.copied,
|
||||
[class*="language-"] > .copy-code-button:hover.copied {
|
||||
background-color: var(--vp-code-copy-code-hover-bg);
|
||||
background-image: var(--vp-icon-copied);
|
||||
|
||||
/* rtl:ignore */
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
[class*="language-"] > .copy-code-button.copied::before,
|
||||
[class*="language-"] > .copy-code-button:hover.copied::before {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-code-copy-code-active-text);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
content: var(--vp-code-copy-copied-text-content);
|
||||
background-color: var(--vp-code-copy-code-hover-bg);
|
||||
border: 1px solid var(--vp-code-copy-code-hover-border-color);
|
||||
|
||||
/* rtl:ignore */
|
||||
border-right: 0;
|
||||
border-radius: 4px 0 0 4px;
|
||||
|
||||
/* rtl:ignore */
|
||||
transform: translateX(calc(-100% - 1px));
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
import { copyCodePlugin } from './plugin.js'
|
||||
|
||||
export * from './plugin.js'
|
||||
export * from '../shared/index.js'
|
||||
|
||||
export default copyCodePlugin
|
||||
@ -1,26 +0,0 @@
|
||||
import type { Plugin } from 'vuepress/core'
|
||||
import { getDirname, path } from 'vuepress/utils'
|
||||
import type { CopyCodeOptions } from '../shared/index.js'
|
||||
|
||||
const __dirname = getDirname(import.meta.url)
|
||||
|
||||
const defaultOptions: CopyCodeOptions = {
|
||||
selector: '.theme-default-content div[class*="language-"] pre',
|
||||
duration: 1500,
|
||||
delay: 500,
|
||||
showInMobile: false,
|
||||
}
|
||||
|
||||
export function copyCodePlugin(options: CopyCodeOptions): Plugin {
|
||||
options = Object.assign({}, defaultOptions, options)
|
||||
|
||||
return {
|
||||
name: '@vuepress-plume/plugin-copy-code',
|
||||
|
||||
define: (): Record<string, unknown> => ({
|
||||
__COPY_CODE_OPTIONS__: options,
|
||||
}),
|
||||
|
||||
clientConfigFile: path.resolve(__dirname, '../client/clientConfig.js'),
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
export interface CopyCodeOptions {
|
||||
/**
|
||||
* 代码块选择器
|
||||
*
|
||||
* @default '.theme-default-content dev[class*="language-"] pre'
|
||||
*/
|
||||
selector?: string | string[]
|
||||
|
||||
/**
|
||||
* 提示消息显示时间
|
||||
*
|
||||
* @description 设置为 `0` 将会禁用提示
|
||||
*
|
||||
* @default 1500
|
||||
*/
|
||||
duration?: number
|
||||
|
||||
/**
|
||||
* 是否展示在移动端
|
||||
*/
|
||||
showInMobile?: boolean
|
||||
|
||||
/**
|
||||
* 注册复制按钮的延时,单位 ms
|
||||
*
|
||||
* @default 500
|
||||
*/
|
||||
delay?: number
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib"
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
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.
|
||||
@ -1,55 +0,0 @@
|
||||
# `@vuepress-plume/plugin-notes-data`
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install @vuepress-plume/plugin-notes-data
|
||||
# or
|
||||
pnpm add @vuepress-plume/plugin-notes-data
|
||||
# or
|
||||
yarn add @vuepress-plume/plugin-notes-data
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
``` js
|
||||
// .vuepress/config.[jt]s
|
||||
import { notesDataPlugin } from '@vuepress-plume/plugin-notes-data'
|
||||
|
||||
export default {
|
||||
// ...
|
||||
plugins: [
|
||||
notesDataPlugin()
|
||||
]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
``` ts
|
||||
interface NotesDataOptions {
|
||||
dir: string
|
||||
link: string
|
||||
include?: string | string[]
|
||||
exclude?: string | string[]
|
||||
notes: NotesItem[]
|
||||
}
|
||||
|
||||
interface NotesItem {
|
||||
dir: string
|
||||
link: string
|
||||
text: string
|
||||
sidebar?: NotesSidebar | 'auto'
|
||||
}
|
||||
|
||||
type NotesSidebar = (NotesSidebarItem | string)[]
|
||||
|
||||
interface NotesSidebarItem {
|
||||
text?: string
|
||||
link?: string
|
||||
dir?: string
|
||||
collapsed?: boolean
|
||||
items?: NotesSidebar
|
||||
}
|
||||
```
|
||||
@ -1,58 +0,0 @@
|
||||
{
|
||||
"name": "@vuepress-plume/plugin-notes-data",
|
||||
"type": "module",
|
||||
"version": "1.0.0-rc.75",
|
||||
"private": "true",
|
||||
"description": "The Plugin for VuePress 2 - notes data",
|
||||
"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",
|
||||
"directory": "plugins/plugin-notes-data"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/node/index.d.ts",
|
||||
"import": "./lib/node/index.js"
|
||||
},
|
||||
"./client": {
|
||||
"types": "./lib/client/index.d.ts",
|
||||
"import": "./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 copy && pnpm run ts",
|
||||
"clean": "rimraf --glob ./lib ./*.tsbuildinfo",
|
||||
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
|
||||
"ts": "tsc -b tsconfig.build.json"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vuepress": "2.0.0-rc.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "6.6.3",
|
||||
"chokidar": "^3.6.0",
|
||||
"create-filter": "^1.1.0",
|
||||
"vue": "^3.4.31"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keyword": [
|
||||
"VuePress",
|
||||
"vuepress plugin",
|
||||
"notesData",
|
||||
"vuepress-plugin-plugin-notes-data"
|
||||
]
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
import { setupDevtoolsPlugin } from '@vue/devtools-api'
|
||||
import { defineClientConfig } from 'vuepress/client'
|
||||
import type { ClientConfig } from 'vuepress/client'
|
||||
import { useNotesData } from './composables/index.js'
|
||||
|
||||
declare const __VUE_PROD_DEVTOOLS__: boolean
|
||||
|
||||
export default defineClientConfig({
|
||||
enhance({ app }) {
|
||||
const notesData = useNotesData()
|
||||
|
||||
Object.defineProperties(app.config.globalProperties, {
|
||||
$notesData: {
|
||||
get() {
|
||||
return notesData.value
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// setup devtools in dev mode
|
||||
if (__VUEPRESS_DEV__ || __VUE_PROD_DEVTOOLS__) {
|
||||
const PLUGIN_ID = 'org.vuejs.vuepress'
|
||||
const PLUGIN_LABEL = 'VuePress'
|
||||
const INSPECTOR_ID = PLUGIN_ID
|
||||
|
||||
setupDevtoolsPlugin(
|
||||
{
|
||||
// fix recursive reference
|
||||
app: app as any,
|
||||
id: PLUGIN_ID,
|
||||
label: PLUGIN_LABEL,
|
||||
packageName: '@vuepress-plume/plugin-notes-data',
|
||||
homepage: 'https://theme-plume.vuejs.press/',
|
||||
logo: 'https://v2.vuepress.vuejs.org/images/hero.png',
|
||||
componentStateTypes: ['VuePress'],
|
||||
},
|
||||
(api) => {
|
||||
api.on.inspectComponent((payload) => {
|
||||
payload.instanceData.state.push({
|
||||
type: 'VuePress',
|
||||
key: 'notesData',
|
||||
editable: false,
|
||||
value: notesData.value,
|
||||
})
|
||||
})
|
||||
api.on.getInspectorTree((payload) => {
|
||||
if (payload.inspectorId !== INSPECTOR_ID)
|
||||
return
|
||||
payload.rootNodes.push({
|
||||
id: 'notes_data',
|
||||
label: 'Notes Data',
|
||||
})
|
||||
})
|
||||
api.on.getInspectorState((payload) => {
|
||||
if (payload.inspectorId !== INSPECTOR_ID)
|
||||
return
|
||||
if (payload.nodeId === 'notes_data') {
|
||||
payload.state = {
|
||||
NotesData: [{
|
||||
key: 'notesData',
|
||||
value: notesData.value,
|
||||
}],
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
}) as ClientConfig
|
||||
@ -1 +0,0 @@
|
||||
export * from './notesDate.js'
|
||||
@ -1,22 +0,0 @@
|
||||
import { notesData as notesDataRaw } from '@internal/notesData'
|
||||
import { ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import type { NotesData } from '../../shared/index.js'
|
||||
|
||||
declare const __VUE_HMR_RUNTIME__: Record<string, any>
|
||||
|
||||
export type NotesDataRef<T extends NotesData = NotesData> = Ref<T>
|
||||
|
||||
export const notesData: NotesDataRef = ref(notesDataRaw)
|
||||
|
||||
export function useNotesData<
|
||||
T extends NotesData = NotesData,
|
||||
>(): NotesDataRef<T> {
|
||||
return notesData as NotesDataRef<T>
|
||||
}
|
||||
|
||||
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
|
||||
__VUE_HMR_RUNTIME__.updateNotesData = (data: NotesData) => {
|
||||
notesData.value = data
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export type { NotesData, NotesSidebarItem } from '../shared/index.js'
|
||||
export * from './composables/index.js'
|
||||
@ -1,7 +0,0 @@
|
||||
import type { NotesData } from '../shared/index.js'
|
||||
|
||||
declare module '@internal/notesData' {
|
||||
const notesData: NotesData
|
||||
|
||||
export { notesData }
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
import { notesDataPlugin } from './plugin.js'
|
||||
|
||||
export * from './plugin.js'
|
||||
export * from '../shared/index.js'
|
||||
|
||||
export default notesDataPlugin
|
||||
@ -1,22 +0,0 @@
|
||||
import type { Plugin } from 'vuepress/core'
|
||||
import { getDirname, path } from 'vuepress/utils'
|
||||
import type { NotesDataOptions } from '../shared/index.js'
|
||||
import { prepareNotesData, watchNotesData } from './prepareNotesData.js'
|
||||
import { wait } from './utils.js'
|
||||
|
||||
export function notesDataPlugin(options: NotesDataOptions | NotesDataOptions[]): Plugin {
|
||||
return {
|
||||
name: '@vuepress-plume/plugin-notes-data',
|
||||
|
||||
clientConfigFile: path.join(
|
||||
getDirname(import.meta.url),
|
||||
'../client/clientConfig.js',
|
||||
),
|
||||
|
||||
onPrepared: async (app) => {
|
||||
await wait(50)
|
||||
await prepareNotesData(app, options)
|
||||
},
|
||||
onWatched: (app, watchers) => watchNotesData(app, watchers, options),
|
||||
}
|
||||
}
|
||||
@ -1,246 +0,0 @@
|
||||
import { colors, logger, path } from 'vuepress/utils'
|
||||
import type { App } from 'vuepress/core'
|
||||
import * as chokidar from 'chokidar'
|
||||
import { createFilter } from 'create-filter'
|
||||
import type {
|
||||
NotesData,
|
||||
NotesDataOptions,
|
||||
NotesItemOptions,
|
||||
NotesSidebar,
|
||||
NotesSidebarItem,
|
||||
} from '../shared/index.js'
|
||||
import { ensureArray, hash, normalizePath } from './utils.js'
|
||||
|
||||
const HMR_CODE = `
|
||||
if (import.meta.webpackHot) {
|
||||
import.meta.webpackHot.accept()
|
||||
if (__VUE_HMR_RUNTIME__.updateNotesData) {
|
||||
__VUE_HMR_RUNTIME__.updateNotesData(notesData)
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(({ notesData }) => {
|
||||
__VUE_HMR_RUNTIME__.updateNotesData(notesData)
|
||||
})
|
||||
}
|
||||
`
|
||||
|
||||
interface NotePage {
|
||||
relativePath: string
|
||||
title: string
|
||||
link: string
|
||||
frontmatter: Record<string, any>
|
||||
}
|
||||
|
||||
function resolvedNotesData(app: App, options: NotesDataOptions, result: NotesData) {
|
||||
const { include, exclude, notes, dir: _dir, link } = options
|
||||
if (!notes || notes.length === 0)
|
||||
return
|
||||
const dir = normalizePath(_dir).replace(/^\//, '')
|
||||
const filter = createFilter(ensureArray(include), ensureArray(exclude), {
|
||||
resolve: false,
|
||||
})
|
||||
const DIR_PATTERN = new RegExp(`^${normalizePath(path.join(dir, '/'))}`)
|
||||
const notesPageList: NotePage[] = app.pages
|
||||
.filter(
|
||||
page =>
|
||||
page.filePathRelative
|
||||
&& page.filePathRelative.startsWith(dir)
|
||||
&& filter(page.filePathRelative),
|
||||
)
|
||||
.map(page => ({
|
||||
relativePath: page.filePathRelative?.replace(DIR_PATTERN, '') || '',
|
||||
title: page.title,
|
||||
link: page.path,
|
||||
frontmatter: page.frontmatter,
|
||||
}))
|
||||
notes.forEach((note) => {
|
||||
result[normalizePath(path.join('/', link, note.link))] = initSidebar(
|
||||
note,
|
||||
notesPageList.filter(page =>
|
||||
page.relativePath.startsWith(note.dir.trim().replace(/^\/|\/$/g, '')),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let contentHash: string | undefined
|
||||
export async function prepareNotesData(app: App, options: NotesDataOptions | NotesDataOptions[]) {
|
||||
const start = performance.now()
|
||||
const notesData: NotesData = {}
|
||||
const allOptions = ensureArray<NotesDataOptions>(options)
|
||||
|
||||
allOptions.forEach(option => resolvedNotesData(app, option, notesData))
|
||||
|
||||
let content = `
|
||||
export const notesData = ${JSON.stringify(notesData, null, 2)}
|
||||
`
|
||||
if (app.env.isDev)
|
||||
content += HMR_CODE
|
||||
|
||||
const currentHash = hash(content)
|
||||
if (!contentHash || contentHash !== currentHash) {
|
||||
contentHash = currentHash
|
||||
await app.writeTemp('internal/notesData.js', content)
|
||||
}
|
||||
|
||||
if (app.env.isDebug) {
|
||||
logger.info(
|
||||
`\n[${colors.green('@vuepress-plume/plugin-notes-data')}] prepare notes data time spent: ${(performance.now() - start).toFixed(2)}ms`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function watchNotesData(app: App, watchers: any[], options: NotesDataOptions | NotesDataOptions[]): void {
|
||||
const allOptions = ensureArray<NotesDataOptions>(options)
|
||||
if (!allOptions.length)
|
||||
return
|
||||
|
||||
const [firstLink, ...links] = allOptions.map(option => option.link).filter(Boolean)
|
||||
|
||||
if (!firstLink)
|
||||
return
|
||||
|
||||
const dir = path.join('pages', firstLink, '**/*')
|
||||
const watcher = chokidar.watch(dir, {
|
||||
cwd: app.dir.temp(),
|
||||
ignoreInitial: true,
|
||||
})
|
||||
|
||||
links.length && watcher.add(links.map(link => path.join('pages', link, '**/*')))
|
||||
|
||||
watcher.on('add', () => prepareNotesData(app, options))
|
||||
watcher.on('change', () => prepareNotesData(app, options))
|
||||
watcher.on('unlink', () => prepareNotesData(app, options))
|
||||
watchers.push(watcher)
|
||||
}
|
||||
|
||||
function initSidebar(note: NotesItemOptions, pages: NotePage[]): NotesSidebarItem[] {
|
||||
if (!note.sidebar)
|
||||
return []
|
||||
if (note.sidebar === 'auto')
|
||||
return initSidebarByAuto(note, pages)
|
||||
return initSidebarByConfig(note, pages)
|
||||
}
|
||||
|
||||
function initSidebarByAuto(
|
||||
note: NotesItemOptions,
|
||||
pages: NotePage[],
|
||||
): NotesSidebarItem[] {
|
||||
let tempPages = pages.map((page) => {
|
||||
return { ...page, splitPath: page.relativePath.split('/') }
|
||||
})
|
||||
|
||||
const maxIndex = Math.max(...tempPages.map(page => page.splitPath.length))
|
||||
let nowIndex = 0
|
||||
|
||||
while (nowIndex < maxIndex) {
|
||||
tempPages = tempPages.sort((prev, next) => {
|
||||
const pi = prev.splitPath?.[nowIndex]?.match(/(\d+)\.(?=[^/]+$)/)?.[1]
|
||||
const ni = next.splitPath?.[nowIndex]?.match(/(\d+)\.(?=[^/]+$)/)?.[1]
|
||||
if (!pi || !ni)
|
||||
return 0
|
||||
return Number.parseFloat(pi) < Number.parseFloat(ni) ? -1 : 1
|
||||
})
|
||||
|
||||
nowIndex++
|
||||
}
|
||||
|
||||
pages = tempPages.map((page) => {
|
||||
delete (page as any).splitPath
|
||||
return page
|
||||
})
|
||||
|
||||
const RE_INDEX = ['index.md', 'README.md', 'readme.md']
|
||||
const result: NotesSidebarItem[] = []
|
||||
for (const page of pages) {
|
||||
const { relativePath, title, link, frontmatter } = page
|
||||
const paths = relativePath
|
||||
.slice(note.dir.replace(/^\/|\/$/g, '').length + 1)
|
||||
.split('/')
|
||||
let index = 0
|
||||
let dir: string
|
||||
let items = result
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while ((dir = paths[index])) {
|
||||
const text = dir.replace(/\.md$/, '').replace(/^\d+\./, '')
|
||||
let current = items.find(item => item.text === text)
|
||||
if (!current) {
|
||||
current = { text, link: undefined, items: [] }
|
||||
!RE_INDEX.includes(dir) ? items.push(current) : items.unshift(current)
|
||||
}
|
||||
if (dir.endsWith('.md')) {
|
||||
current.link = link
|
||||
current.text = title
|
||||
}
|
||||
if (frontmatter.icon)
|
||||
current.icon = frontmatter.icon
|
||||
|
||||
items = current.items as NotesSidebarItem[]
|
||||
index++
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function initSidebarByConfig(
|
||||
{ text, dir, sidebar }: NotesItemOptions,
|
||||
pages: NotePage[],
|
||||
): NotesSidebarItem[] {
|
||||
return (sidebar as NotesSidebar).map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
const current = findNotePage(item, dir, pages)
|
||||
return {
|
||||
text: current?.title || text,
|
||||
link: current?.link,
|
||||
icon: current?.frontmatter.icon,
|
||||
// items: [],
|
||||
}
|
||||
}
|
||||
else {
|
||||
const current = findNotePage(item.link || '', dir, pages)
|
||||
return {
|
||||
text: item.text || item.dir || current?.title,
|
||||
collapsed: item.collapsed,
|
||||
icon: item.icon || current?.frontmatter.icon,
|
||||
link: item.link,
|
||||
items: initSidebarByConfig(
|
||||
{
|
||||
link: item.link || '',
|
||||
text: item.text || '',
|
||||
sidebar: item.items,
|
||||
dir: normalizePath(path.join(dir, item.dir || '')),
|
||||
},
|
||||
pages,
|
||||
),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function findNotePage(
|
||||
sidebar: string,
|
||||
dir: string,
|
||||
notePageList: NotePage[],
|
||||
): NotePage | undefined {
|
||||
if (sidebar === '' || sidebar === 'README.md' || sidebar === 'index.md') {
|
||||
return notePageList.find((page) => {
|
||||
const relative = page.relativePath
|
||||
return (
|
||||
relative === normalizePath(path.join(dir, 'README.md'))
|
||||
|| relative === normalizePath(path.join(dir, 'index.md'))
|
||||
)
|
||||
})
|
||||
}
|
||||
else {
|
||||
return notePageList.find((page) => {
|
||||
const relative = page.relativePath
|
||||
return (
|
||||
relative === normalizePath(path.join(dir, sidebar))
|
||||
|| relative === normalizePath(path.join(dir, `${sidebar}.md`))
|
||||
|| page.link === sidebar
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
|
||||
export function ensureArray<T>(thing: T | T[] | null | undefined): T[] {
|
||||
if (Array.isArray(thing))
|
||||
return thing
|
||||
if (thing === null || thing === undefined)
|
||||
return []
|
||||
return [thing]
|
||||
}
|
||||
|
||||
export function normalizePath(str: string) {
|
||||
return str.replace(/\\+/g, '/')
|
||||
}
|
||||
|
||||
export function wait(time: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, time))
|
||||
}
|
||||
|
||||
export function hash(content: string): string {
|
||||
return createHash('md5').update(content).digest('hex')
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
export interface NotesDataOptions {
|
||||
/**
|
||||
* 保存所有笔记的目录
|
||||
* @default '/notes/'
|
||||
*/
|
||||
dir: string
|
||||
/**
|
||||
* 所有笔记的默认链接前缀
|
||||
* @default '/'
|
||||
*/
|
||||
link: string
|
||||
/**
|
||||
* global include,只加载需要加载到笔记中的文件
|
||||
*/
|
||||
include?: string | string[]
|
||||
/**
|
||||
* global exclude,排除不需要加载到笔记中的文件
|
||||
*/
|
||||
exclude?: string | string[]
|
||||
/**
|
||||
* 笔记配置
|
||||
*/
|
||||
notes: NotesItemOptions[]
|
||||
}
|
||||
|
||||
export type NotesItemOptions = (Omit<NotesItem, 'text'> & { text?: string })
|
||||
|
||||
export interface NotesItem {
|
||||
/**
|
||||
* 保存笔记的目录
|
||||
*/
|
||||
dir: string
|
||||
/**
|
||||
* 当前笔记的链接前缀,将会与 `notes.link` 合并
|
||||
*/
|
||||
link: string
|
||||
/**
|
||||
* 当前笔记名称
|
||||
*/
|
||||
text: string
|
||||
/**
|
||||
* 当前笔记的侧边栏配置
|
||||
*/
|
||||
sidebar?: NotesSidebar | 'auto'
|
||||
}
|
||||
|
||||
export type NotesSidebar = (NotesSidebarItem | string)[]
|
||||
|
||||
export interface NotesSidebarItem {
|
||||
/**
|
||||
* 侧边栏文本,如果为空,则使用 `dir`
|
||||
*/
|
||||
text?: string
|
||||
/**
|
||||
* 侧边栏链接
|
||||
*/
|
||||
link?: string
|
||||
/**
|
||||
* 次级侧边栏所在目录
|
||||
*/
|
||||
dir?: string
|
||||
/**
|
||||
* 是否折叠, 未定义时不可折叠
|
||||
* @default undefined
|
||||
*/
|
||||
collapsed?: boolean
|
||||
/**
|
||||
* 次级侧边栏
|
||||
*/
|
||||
items?: NotesSidebar
|
||||
/**
|
||||
* 侧边栏图标
|
||||
*/
|
||||
icon?: string | { svg: string }
|
||||
}
|
||||
|
||||
export type NotesData = Record<string, NotesSidebarItem[]>
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"paths": {
|
||||
"@internal/notesData": ["./src/client/notesData.d.ts"]
|
||||
},
|
||||
"types": ["vuepress/client-types", "vite/client", "webpack-env"],
|
||||
"outDir": "./lib"
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
@ -4,13 +4,9 @@
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{ "path": "./plugin-auto-frontmatter/tsconfig.build.json" },
|
||||
{ "path": "./plugin-baidu-tongji/tsconfig.build.json" },
|
||||
{ "path": "./plugin-blog-data/tsconfig.build.json" },
|
||||
{ "path": "./plugin-caniuse/tsconfig.build.json" },
|
||||
{ "path": "./plugin-copy-code/tsconfig.build.json" },
|
||||
{ "path": "./plugin-iconify/tsconfig.build.json" },
|
||||
{ "path": "./plugin-notes-data/tsconfig.build.json" },
|
||||
{ "path": "./plugin-fonts/tsconfig.build.json" },
|
||||
{ "path": "./plugin-shikiji/tsconfig.build.json" },
|
||||
{ "path": "./plugin-content-update/tsconfig.build.json" },
|
||||
{ "path": "./plugin-search/tsconfig.build.json" },
|
||||
|
||||
85
pnpm-lock.yaml
generated
85
pnpm-lock.yaml
generated
@ -106,67 +106,12 @@ importers:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
|
||||
plugins/plugin-auto-frontmatter:
|
||||
dependencies:
|
||||
'@pengzhanbo/utils':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2
|
||||
chokidar:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
create-filter:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
fast-glob:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
gray-matter:
|
||||
specifier: ^4.0.3
|
||||
version: 4.0.3
|
||||
json2yaml:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
vuepress:
|
||||
specifier: 2.0.0-rc.14
|
||||
version: 2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(typescript@5.5.3))(typescript@5.5.3)(vue@3.4.31(typescript@5.5.3))
|
||||
|
||||
plugins/plugin-baidu-tongji:
|
||||
dependencies:
|
||||
vuepress:
|
||||
specifier: 2.0.0-rc.14
|
||||
version: 2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(typescript@5.5.3))(typescript@5.5.3)(vue@3.4.31(typescript@5.5.3))
|
||||
|
||||
plugins/plugin-blog-data:
|
||||
dependencies:
|
||||
'@vue/devtools-api':
|
||||
specifier: 6.6.3
|
||||
version: 6.6.3
|
||||
chokidar:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
create-filter:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
vue:
|
||||
specifier: ^3.4.31
|
||||
version: 3.4.31(typescript@5.5.3)
|
||||
vuepress:
|
||||
specifier: 2.0.0-rc.14
|
||||
version: 2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(typescript@5.5.3))(typescript@5.5.3)(vue@3.4.31(typescript@5.5.3))
|
||||
|
||||
plugins/plugin-caniuse:
|
||||
dependencies:
|
||||
markdown-it-container:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
vuepress:
|
||||
specifier: 2.0.0-rc.14
|
||||
version: 2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(typescript@5.5.3))(typescript@5.5.3)(vue@3.4.31(typescript@5.5.3))
|
||||
devDependencies:
|
||||
'@types/markdown-it':
|
||||
specifier: ^14.1.1
|
||||
version: 14.1.1
|
||||
|
||||
plugins/plugin-content-update:
|
||||
dependencies:
|
||||
vue:
|
||||
@ -176,18 +121,6 @@ importers:
|
||||
specifier: 2.0.0-rc.14
|
||||
version: 2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(typescript@5.5.3))(typescript@5.5.3)(vue@3.4.31(typescript@5.5.3))
|
||||
|
||||
plugins/plugin-copy-code:
|
||||
dependencies:
|
||||
'@vuepress-plume/plugin-content-update':
|
||||
specifier: workspace:~
|
||||
version: link:../plugin-content-update
|
||||
vue:
|
||||
specifier: ^3.4.31
|
||||
version: 3.4.31(typescript@5.5.3)
|
||||
vuepress:
|
||||
specifier: 2.0.0-rc.14
|
||||
version: 2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(typescript@5.5.3))(typescript@5.5.3)(vue@3.4.31(typescript@5.5.3))
|
||||
|
||||
plugins/plugin-fonts:
|
||||
dependencies:
|
||||
vuepress:
|
||||
@ -249,24 +182,6 @@ importers:
|
||||
specifier: ^14.1.1
|
||||
version: 14.1.1
|
||||
|
||||
plugins/plugin-notes-data:
|
||||
dependencies:
|
||||
'@vue/devtools-api':
|
||||
specifier: 6.6.3
|
||||
version: 6.6.3
|
||||
chokidar:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
create-filter:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
vue:
|
||||
specifier: ^3.4.31
|
||||
version: 3.4.31(typescript@5.5.3)
|
||||
vuepress:
|
||||
specifier: 2.0.0-rc.14
|
||||
version: 2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(typescript@5.5.3))(typescript@5.5.3)(vue@3.4.31(typescript@5.5.3))
|
||||
|
||||
plugins/plugin-search:
|
||||
dependencies:
|
||||
'@vuepress/helper':
|
||||
|
||||
@ -4,12 +4,6 @@
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@internal/blogData": [
|
||||
"./plugins/plugin-blog-data/src/client/blogPostData.d.ts"
|
||||
],
|
||||
"@internal/notesData": [
|
||||
"./plugins/plugin-notes-data/src/client/notesData.d.ts"
|
||||
],
|
||||
"@internal/md-power/replEditorData": [
|
||||
"./plugins/plugin-md-power/src/client/shim.d.ts"
|
||||
],
|
||||
@ -22,9 +16,7 @@
|
||||
"vuepress-plugin-md-power": [
|
||||
"./plugins/plugin-md-power/src/node/index.ts"
|
||||
],
|
||||
"@theme/*": [
|
||||
"./theme/src/client/components/*"
|
||||
]
|
||||
"@theme/*": ["./theme/src/client/components/*"]
|
||||
},
|
||||
"types": ["webpack-env", "vite/client", "vuepress/client-types"]
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user