chore: support eslint flat config

This commit is contained in:
pengzhanbo 2023-12-27 02:18:19 +08:00
parent 6e6c214e59
commit db5220a015
172 changed files with 1864 additions and 1972 deletions

View File

@ -1,5 +1,5 @@
const fs = require('fs') const fs = require('node:fs')
const path = require('path') const path = require('node:path')
const packages = fs.readdirSync(path.resolve(__dirname, 'plugins')) const packages = fs.readdirSync(path.resolve(__dirname, 'plugins'))

View File

@ -1,9 +0,0 @@
node_modules/
.temp/
.cache/
lib/
dist/
!.vuepress/
!.*.js
scripts/
LICENSE

View File

@ -1,57 +0,0 @@
module.exports = {
root: true,
extends: 'vuepress',
globals: {
__VUEPRESS_VERSION__: 'readonly',
__VUEPRESS_BASE__: 'readonly',
__VUEPRESS_DEV__: 'readonly',
__VUEPRESS_SSR__: 'readonly',
__VUE_HMR_RUNTIME__: 'readonly',
__VUE_OPTIONS_API__: 'readonly',
__VUE_PROD_DEVTOOLS__: 'readonly',
},
overrides: [
{
files: ['*.ts', '*.vue', '*.cts'],
extends: 'vuepress-typescript',
parserOptions: {
project: ['tsconfig.json'],
},
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'vue/multi-word-component-names': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'vue/component-tags-order': [
'error',
{
order: ['script', 'template', 'style'],
},
],
},
},
{
files: ['**/client/config.ts'],
rules: {
'vue/match-component-file-name': 'off',
},
},
{
files: ['**/__tests__/**/*.ts'],
env: {
jest: true,
},
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'vue/one-component-per-file': 'off',
'import/no-extraneous-dependencies': 'off',
},
},
{
files: ['docs/**/*.ts'],
rules: {
'import/no-extraneous-dependencies': 'off',
},
},
],
}

View File

@ -9,14 +9,6 @@
"files.trimTrailingWhitespace": false "files.trimTrailingWhitespace": false
}, },
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"eslint.experimental.useFlatConfig": false,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue"
],
"cSpell.words": [ "cSpell.words": [
"bumpp", "bumpp",
"caniuse", "caniuse",

17
eslint.config.js Normal file
View File

@ -0,0 +1,17 @@
import config from '@pengzhanbo/eslint-config-vue'
export default config({
formatters: {
css: false,
},
ignores: ['.temp', '.cache', 'lib', 'dist', 'docs', '**/*.html'],
globals: {
__VUEPRESS_VERSION__: 'readonly',
__VUEPRESS_BASE__: 'readonly',
__VUEPRESS_DEV__: 'readonly',
__VUEPRESS_SSR__: 'readonly',
__VUE_HMR_RUNTIME__: 'readonly',
__VUE_OPTIONS_API__: 'readonly',
__VUE_PROD_DEVTOOLS__: 'readonly',
},
})

View File

@ -1,7 +1,11 @@
{ {
"name": "vuepress-theme-plume-monorepo", "name": "vuepress-theme-plume-monorepo",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"private": true, "private": true,
"packageManager": "pnpm@8.12.1",
"author": "pengzhanbo",
"license": "MIT",
"keywords": [ "keywords": [
"vuepress", "vuepress",
"vuepress-next", "vuepress-next",
@ -9,9 +13,10 @@
"vuepress theme", "vuepress theme",
"vuepress-theme-plume" "vuepress-theme-plume"
], ],
"license": "MIT", "engines": {
"author": "pengzhanbo", "node": ">=16",
"type": "module", "pnpm": ">=7"
},
"scripts": { "scripts": {
"autoUpdate": "node scripts/autoInstall.js", "autoUpdate": "node scripts/autoInstall.js",
"build": "pnpm run build:package", "build": "pnpm run build:package",
@ -24,7 +29,7 @@
"docs:build": "pnpm --filter=docs docs:build", "docs:build": "pnpm --filter=docs docs:build",
"docs:clean": "pnpm --filter=docs docs:clean", "docs:clean": "pnpm --filter=docs docs:clean",
"docs:serve": "pnpm --filter=docs docs:serve", "docs:serve": "pnpm --filter=docs docs:serve",
"lint": "eslint --ext .js,.ts,.vue .", "lint": "eslint .",
"pkg": "node scripts/create/index.js", "pkg": "node scripts/create/index.js",
"prepare": "husky install", "prepare": "husky install",
"release": "pnpm release:check && pnpm release:version && pnpm release:publish", "release": "pnpm release:check && pnpm release:version && pnpm release:publish",
@ -34,20 +39,10 @@
"release:version": "bumpp package.json plugins/*/package.json theme/package.json --execute=\"pnpm release:changelog\" --commit \"build: publish v%s\" --all --tag --push", "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" "up": "taze -r major"
}, },
"lint-staged": {
"*.{js,ts,vue}": "eslint --fix",
"*.{json,yml,css,scss}": "prettier --write",
"package.json": "sort-package-json"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"prettier": "prettier-config-vuepress",
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^18.4.3", "@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3", "@commitlint/config-conventional": "^18.4.3",
"@pengzhanbo/eslint-config-vue": "^1.4.0",
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"@types/node": "20.9.1", "@types/node": "20.9.1",
"@types/webpack-env": "^1.18.4", "@types/webpack-env": "^1.18.4",
@ -60,8 +55,7 @@
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-vuepress": "^4.9.0", "eslint-plugin-format": "^0.1.0",
"eslint-config-vuepress-typescript": "^4.9.0",
"execa": "^8.0.1", "execa": "^8.0.1",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"husky": "^8.0.3", "husky": "^8.0.3",
@ -69,8 +63,6 @@
"minimist": "^1.2.8", "minimist": "^1.2.8",
"ora": "^8.0.1", "ora": "^8.0.1",
"pnpm": "^8.12.1", "pnpm": "^8.12.1",
"prettier": "^3.1.1",
"prettier-config-vuepress": "^4.4.0",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"sort-package-json": "^2.6.0", "sort-package-json": "^2.6.0",
"taze": "^0.13.0", "taze": "^0.13.0",
@ -78,11 +70,6 @@
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.0.10" "vite": "^5.0.10"
}, },
"packageManager": "pnpm@8.12.1",
"engines": {
"node": ">=16",
"pnpm": ">=7"
},
"pnpm": { "pnpm": {
"peerDependencyRules": { "peerDependencyRules": {
"ignoreMissing": [ "ignoreMissing": [
@ -99,5 +86,13 @@
"jest" "jest"
] ]
} }
},
"lint-staged": {
"*": "eslint --fix"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
} }
} }

View File

@ -9,14 +9,15 @@ yarn add @vuepress-plume/plugin-auto-frontmatter
## Usage ## Usage
``` js ``` js
// .vuepress/config.js // .vuepress/config.js
import { autoFrontmatterPlugin } from '@vuepress-plume/plugin-auto-frontmatter' import { autoFrontmatterPlugin } from '@vuepress-plume/plugin-auto-frontmatter'
export default { export default {
//... // ...
plugins: [ plugins: [
autoFrontmatterPlugin({ autoFrontmatterPlugin({
formatter: { formatter: {
createTime(formatTime, file, matter) { createTime(formatTime, file, matter) {
if (formatTime) return formatTime if (formatTime)
return formatTime
return file.createTime return file.createTime
} }
} }
@ -32,7 +33,7 @@ export default {
`{ include?: string | string[]; exclude?: string | string[]; formatter: Formatter }` `{ include?: string | string[]; exclude?: string | string[]; formatter: Formatter }`
- `include` - `include`
include 匹配字符串或数组,匹配需要自动生成 `frontmatter` 的 md文件。 include 匹配字符串或数组,匹配需要自动生成 `frontmatter` 的 md文件。
默认预设为 `['**/*.md']` 默认预设为 `['**/*.md']`
@ -70,14 +71,15 @@ export default {
/** /**
* formatterObj 对象中的 key 即为 frontmatter 配置中的key * formatterObj 对象中的 key 即为 frontmatter 配置中的key
* 其方法返回的值将作为 frontmatter[key] 的值 * 其方法返回的值将作为 frontmatter[key] 的值
* *.md * .md
* --- * ---
* createTime: 2022-03-26T11:46:50.000Z * createTime: 2022-03-26T11:46:50.000Z
* --- * ---
*/ */
const formatterObj: Formatter = { const formatterObj: Formatter = {
createTime(formatTime, file, matter) { createTime(formatTime, file, matter) {
if (formatTime) return formatTime if (formatTime)
return formatTime
return file.createTime return file.createTime
} }
} }
@ -92,25 +94,24 @@ export default {
return value return value
} }
}, },
{ },
// 通配如果文件没有被其他精细glob命中 {
// 则使用 通配 formatter // 通配如果文件没有被其他精细glob命中
// 如果是数组,必须有且用一个 include 为 * 的 项 // 则使用 通配 formatter
include: '*', // 如果是数组,必须有且用一个 include 为 * 的 项
formatter: { include: '*',
title(title) { formatter: {
return title || '默认标题' title(title) {
} return title || '默认标题'
} }
} }
} }
] ]
```
```
## Why ? ## Why ?
- **为什么需要这个插件?** - **为什么需要这个插件?**
有时候在开发一些主题时,期望使用户更专注于内容的编写,尽可能减少配置性的工作,可以将一些重复性的必要的配置 有时候在开发一些主题时,期望使用户更专注于内容的编写,尽可能减少配置性的工作,可以将一些重复性的必要的配置
直接通过本插件自动生成。 直接通过本插件自动生成。

View File

@ -1,18 +1,18 @@
{ {
"name": "@vuepress-plume/plugin-auto-frontmatter", "name": "@vuepress-plume/plugin-auto-frontmatter",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2", "description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme", "homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git" "url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
}, },
"license": "MIT", "bugs": {
"author": "pengzhanbo <volodymyr@foxmail.com>", "url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
"type": "module", },
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./package.json": "./package.json" "./package.json": "./package.json"

View File

@ -13,11 +13,11 @@ import type {
import { readMarkdown, readMarkdownList } from './readFiles.js' import { readMarkdown, readMarkdownList } from './readFiles.js'
import { ensureArray, isEmptyObject } from './utils.js' import { ensureArray, isEmptyObject } from './utils.js'
export const autoFrontmatterPlugin = ({ export function autoFrontmatterPlugin({
include = ['**/*.{md,MD}'], include = ['**/*.{md,MD}'],
exclude = ['.vuepress/**/*', 'node_modules'], exclude = ['.vuepress/**/*', 'node_modules'],
frontmatter = {}, frontmatter = {},
}: AutoFrontmatterOptions = {}): Plugin => { }: AutoFrontmatterOptions = {}): Plugin {
include = ensureArray(include) include = ensureArray(include)
exclude = ensureArray(exclude) exclude = ensureArray(exclude)
@ -27,8 +27,8 @@ export const autoFrontmatterPlugin = ({
? frontmatter ? frontmatter
: [{ include: '*', frontmatter }] : [{ include: '*', frontmatter }]
const globFormatter: FrontmatterObject = const globFormatter: FrontmatterObject
matterFrontmatter.find(({ include }) => include === '*')?.frontmatter || {} = matterFrontmatter.find(({ include }) => include === '*')?.frontmatter || {}
const otherFormatters = matterFrontmatter const otherFormatters = matterFrontmatter
.filter(({ include }) => include !== '*') .filter(({ include }) => include !== '*')
@ -56,14 +56,15 @@ export const autoFrontmatterPlugin = ({
const yaml = isEmptyObject(data) const yaml = isEmptyObject(data)
? '' ? ''
: jsonToYaml : jsonToYaml
.stringify(data) .stringify(data)
.replace(/\n\s{2}/g, '\n') .replace(/\n\s{2}/g, '\n')
.replace(/"/g, '') .replace(/"/g, '')
const newContent = yaml ? `${yaml}---\n${content}` : content const newContent = yaml ? `${yaml}---\n${content}` : content
fs.writeFileSync(filepath, newContent, 'utf-8') fs.writeFileSync(filepath, newContent, 'utf-8')
} catch (e) { }
console.log(e) catch (e) {
console.error(e)
} }
} }
@ -71,9 +72,8 @@ export const autoFrontmatterPlugin = ({
name: '@vuepress-plume/plugin-auto-frontmatter', name: '@vuepress-plume/plugin-auto-frontmatter',
onInitialized: async (app) => { onInitialized: async (app) => {
const markdownList = await readMarkdownList(app.dir.source(), globFilter) const markdownList = await readMarkdownList(app.dir.source(), globFilter)
for (const file of markdownList) { for (const file of markdownList)
await formatMarkdown(file) await formatMarkdown(file)
}
}, },
onWatched: async (app, watchers) => { onWatched: async (app, watchers) => {
const watcher = chokidar.watch('**/*.md', { const watcher = chokidar.watch('**/*.md', {
@ -83,7 +83,8 @@ export const autoFrontmatterPlugin = ({
}) })
watcher.on('add', async (relativePath) => { watcher.on('add', async (relativePath) => {
if (!globFilter(relativePath)) return if (!globFilter(relativePath))
return
await formatMarkdown(readMarkdown(app.dir.source(), relativePath)) await formatMarkdown(readMarkdown(app.dir.source(), relativePath))
}) })

View File

@ -5,24 +5,18 @@ import type { MarkdownFile } from '../shared/index.js'
type MarkdownFileList = MarkdownFile[] type MarkdownFileList = MarkdownFile[]
export const readMarkdownList = async ( export async function readMarkdownList(sourceDir: string, filter: (id: string) => boolean): Promise<MarkdownFileList> {
sourceDir: string,
filter: (id: string) => boolean
): Promise<MarkdownFileList> => {
const files: string[] = await fg(['**/*.md'], { const files: string[] = await fg(['**/*.md'], {
cwd: sourceDir, cwd: sourceDir,
ignore: ['node_modules', '.vuepress'], ignore: ['node_modules', '.vuepress'],
}) })
return files return files
.filter((file) => filter(file)) .filter(file => filter(file))
.map((file) => readMarkdown(sourceDir, file)) .map(file => readMarkdown(sourceDir, file))
} }
export const readMarkdown = ( export function readMarkdown(sourceDir: string, relativePath: string): MarkdownFile {
sourceDir: string,
relativePath: string
): MarkdownFile => {
const filepath = path.join(sourceDir, relativePath) const filepath = path.join(sourceDir, relativePath)
const stats = fs.statSync(filepath) const stats = fs.statSync(filepath)
return { return {
@ -34,6 +28,6 @@ export const readMarkdown = (
} }
} }
export const getFileCreateTime = (stats: fs.Stats): Date => { export function getFileCreateTime(stats: fs.Stats): Date {
return stats.birthtime.getFullYear() !== 1970 ? stats.birthtime : stats.atime return stats.birthtime.getFullYear() !== 1970 ? stats.birthtime : stats.atime
} }

View File

@ -1,6 +1,8 @@
export function ensureArray<T>(thing: T | T[] | null | undefined): T[] { export function ensureArray<T>(thing: T | T[] | null | undefined): T[] {
if (Array.isArray(thing)) return thing if (Array.isArray(thing))
if (thing === null || thing === undefined) return [] return thing
if (thing === null || thing === undefined)
return []
return [thing] return [thing]
} }

View File

@ -1,4 +1,5 @@
import type fs from 'node:fs' import type fs from 'node:fs'
export interface MarkdownFile { export interface MarkdownFile {
filepath: string filepath: string
relativePath: string relativePath: string
@ -13,7 +14,7 @@ export type FrontmatterFn<T = any, K = object> = (
data: K data: K
) => T | PromiseLike<T> ) => T | PromiseLike<T>
export type FrontmatterObject<K = object, T = any> = Record<string, FrontmatterFn<T, K>>; export type FrontmatterObject<K = object, T = any> = Record<string, FrontmatterFn<T, K>>
export type FrontmatterArray = { export type FrontmatterArray = {
include: string | string[] include: string | string[]

View File

@ -4,6 +4,6 @@
"rootDir": "./src", "rootDir": "./src",
"outDir": "./lib" "outDir": "./lib"
}, },
"include": ["./src"], "files": [],
"files": [] "include": ["./src"]
} }

View File

@ -11,7 +11,7 @@ yarn add @vuepress-plume/plugin-baidu-tongji
// .vuepress/config.js // .vuepress/config.js
const { baiduTongjiPlugin } = require('@vuepress-plume/plugin-baidu-tongji') const { baiduTongjiPlugin } = require('@vuepress-plume/plugin-baidu-tongji')
module.exports = { module.exports = {
//... // ...
plugins: [ plugins: [
baiduTongjiPlugin({ baiduTongjiPlugin({
key: '', // 百度统计使用的 key key: '', // 百度统计使用的 key

View File

@ -1,18 +1,18 @@
{ {
"name": "@vuepress-plume/plugin-baidu-tongji", "name": "@vuepress-plume/plugin-baidu-tongji",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2", "description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com> (https://github.com/pengzhanbo/)",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme", "homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git" "url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
}, },
"license": "MIT", "bugs": {
"author": "pengzhanbo <volodymyr@foxmail.com> (https://github.com/pengzhanbo/)", "url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
"type": "module", },
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./package.json": "./package.json" "./package.json": "./package.json"

View File

@ -4,11 +4,11 @@ export interface BaiduTongjiOptions {
key?: string key?: string
} }
export const baiduTongjiPlugin = ({ key = '' }: BaiduTongjiOptions): Plugin => { export function baiduTongjiPlugin({ key = '' }: BaiduTongjiOptions): Plugin {
return { return {
name: '@vuepress-plume/plugin-baidu-tongji', name: '@vuepress-plume/plugin-baidu-tongji',
extendsPage: (page) => { extendsPage: (page) => {
page.frontmatter.head = page.frontmatter.head || []; page.frontmatter.head = page.frontmatter.head || []
page.frontmatter.head?.push([ page.frontmatter.head?.push([
'script', 'script',
{ {

View File

@ -1,10 +1,10 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"composite": true,
"rootDir": "./src", "rootDir": "./src",
"outDir": "./lib", "outDir": "./lib"
"composite": true
}, },
"include": ["./src"], "files": [],
"files": [] "include": ["./src"]
} }

View File

@ -9,7 +9,7 @@ yarn add @vuepress-plume/plugin-blog-data
// .vuepress/config.js // .vuepress/config.js
const { blogDataPlugin } = require('@vuepress-plume/plugin-blog-data') const { blogDataPlugin } = require('@vuepress-plume/plugin-blog-data')
module.exports = { module.exports = {
//... // ...
plugins: [ plugins: [
blogDataPlugin() blogDataPlugin()
] ]

View File

@ -1,18 +1,18 @@
{ {
"name": "@vuepress-plume/plugin-blog-data", "name": "@vuepress-plume/plugin-blog-data",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2", "description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme", "homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git" "url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
}, },
"license": "MIT", "bugs": {
"author": "pengzhanbo <volodymyr@foxmail.com>", "url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
"type": "module", },
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./client": "./lib/client/index.js", "./client": "./lib/client/index.js",

View File

@ -9,9 +9,11 @@ export type BlogDataRef<T extends BlogPostData = BlogPostData> = Ref<T>
export const blogPostData: BlogDataRef = ref(blogPostDataRaw) export const blogPostData: BlogDataRef = ref(blogPostDataRaw)
export const useBlogPostData = < export function useBlogPostData<
T extends BlogPostData = BlogPostData T extends BlogPostData = BlogPostData,
>(): BlogDataRef<T> => blogPostData as BlogDataRef<T> >(): BlogDataRef<T> {
return blogPostData as BlogDataRef<T>
}
if (import.meta.webpackHot || import.meta.hot) { if (import.meta.webpackHot || import.meta.hot) {
__VUE_HMR_RUNTIME__.updateBlogData = (data: BlogPostData) => { __VUE_HMR_RUNTIME__.updateBlogData = (data: BlogPostData) => {

View File

@ -30,7 +30,7 @@ export default defineClientConfig({
value: blogPostData.value, value: blogPostData.value,
}) })
}) })
} },
) )
} }
}, },

View File

@ -1,4 +1,5 @@
import type { BlogPostData, BlogPostDataItem } from '../shared/index.js' import type { BlogPostData, BlogPostDataItem } from '../shared/index.js'
export * from './composables/index.js' export * from './composables/index.js'
export type { BlogPostData, BlogPostDataItem } export type { BlogPostData, BlogPostDataItem }

View File

@ -9,11 +9,11 @@ const __dirname = getDirname(import.meta.url)
export type PluginOption = Omit<BlogDataPluginOptions, 'include' | 'exclude'> export type PluginOption = Omit<BlogDataPluginOptions, 'include' | 'exclude'>
export const blogDataPlugin = ({ export function blogDataPlugin({
include, include,
exclude, exclude,
...pluginOptions ...pluginOptions
}: BlogDataPluginOptions = {}): Plugin => { }: BlogDataPluginOptions = {}): Plugin {
const pageFilter = createFilter(toArray(include), toArray(exclude), { const pageFilter = createFilter(toArray(include), toArray(exclude), {
resolve: false, resolve: false,
}) })
@ -26,7 +26,7 @@ export const blogDataPlugin = ({
;(page.data as any).isBlogPost = true ;(page.data as any).isBlogPost = true
} }
}, },
onPrepared: async (app) => onPrepared: async app =>
await preparedBlogData(app, pageFilter, pluginOptions), await preparedBlogData(app, pageFilter, pluginOptions),
onWatched(app, watchers) { onWatched(app, watchers) {
const watcher = chokidar.watch('pages/**/*', { const watcher = chokidar.watch('pages/**/*', {
@ -36,15 +36,15 @@ export const blogDataPlugin = ({
watcher.on( watcher.on(
'add', 'add',
async () => await preparedBlogData(app, pageFilter, pluginOptions) async () => await preparedBlogData(app, pageFilter, pluginOptions),
) )
watcher.on( watcher.on(
'change', 'change',
async () => await preparedBlogData(app, pageFilter, pluginOptions) async () => await preparedBlogData(app, pageFilter, pluginOptions),
) )
watcher.on( watcher.on(
'unlink', 'unlink',
async () => await preparedBlogData(app, pageFilter, pluginOptions) async () => await preparedBlogData(app, pageFilter, pluginOptions),
) )
watchers.push(watcher) watchers.push(watcher)
@ -53,6 +53,7 @@ export const blogDataPlugin = ({
} }
function toArray(likeArr: string | string[] | undefined): string[] { function toArray(likeArr: string | string[] | undefined): string[] {
if (Array.isArray(likeArr)) return likeArr if (Array.isArray(likeArr))
return likeArr
return likeArr ? [likeArr] : [] return likeArr ? [likeArr] : []
} }

View File

@ -17,33 +17,30 @@ if (import.meta.hot) {
} }
` `
const getTimestamp = (time: Date): number => { function getTimestamp(time: Date): number {
return new Date(time).getTime() return new Date(time).getTime()
} }
const EXCERPT_SPLIT = '<!-- more -->' const EXCERPT_SPLIT = '<!-- more -->'
export const preparedBlogData = async ( export async function preparedBlogData(app: App, pageFilter: (id: string) => boolean, options: PluginOption): Promise<void> {
app: App,
pageFilter: (id: string) => boolean,
options: PluginOption
): Promise<void> => {
let pages = app.pages.filter((page) => { let pages = app.pages.filter((page) => {
return page.filePathRelative && pageFilter(page.filePathRelative) return page.filePathRelative && pageFilter(page.filePathRelative)
}) })
if (options.pageFilter) { if (options.pageFilter)
pages = pages.filter(options.pageFilter) pages = pages.filter(options.pageFilter)
}
if (options.sortBy) { if (options.sortBy) {
pages = pages.sort((prev, next) => { pages = pages.sort((prev, next) => {
if (options.sortBy === 'createTime') { if (options.sortBy === 'createTime') {
return getTimestamp(prev.frontmatter.createTime as Date) < return getTimestamp(prev.frontmatter.createTime as Date)
getTimestamp(next.frontmatter.createTime as Date) < getTimestamp(next.frontmatter.createTime as Date)
? 1 ? 1
: -1 : -1
} else { }
return typeof options.sortBy === 'function' && else {
options.sortBy(prev, next) return typeof options.sortBy === 'function'
&& options.sortBy(prev, next)
? 1 ? 1
: -1 : -1
} }
@ -52,9 +49,9 @@ export const preparedBlogData = async (
const blogData: BlogPostData = pages.map((page: Page) => { const blogData: BlogPostData = pages.map((page: Page) => {
let extended: Partial<BlogPostDataItem> = {} let extended: Partial<BlogPostDataItem> = {}
if (typeof options.extendBlogData === 'function') { if (typeof options.extendBlogData === 'function')
extended = options.extendBlogData(page) extended = options.extendBlogData(page)
}
const data = { const data = {
path: page.path, path: page.path,
title: page.title, title: page.title,
@ -71,14 +68,13 @@ export const preparedBlogData = async (
let content = `\ let content = `\
export const blogPostData = JSON.parse(${JSON.stringify( export const blogPostData = JSON.parse(${JSON.stringify(
JSON.stringify(blogData) JSON.stringify(blogData),
)}) )})
` `
// inject HMR code // inject HMR code
if (app.env.isDev) { if (app.env.isDev)
content += HMR_CODE content += HMR_CODE
}
await app.writeTemp('internal/blogData.js', content) await app.writeTemp('internal/blogData.js', content)
} }

View File

@ -1,13 +1,13 @@
{ {
"extends": "../tsconfig.build.json", "extends": "../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"baseUrl": ".", "baseUrl": ".",
"rootDir": "./src",
"paths": { "paths": {
"@internal/blogData": ["./src/client/blogPostData.d.ts"] "@internal/blogData": ["./src/client/blogPostData.d.ts"]
}, },
"types": ["@vuepress/client/types", "vite/client", "webpack-env"] "types": ["@vuepress/client/types", "vite/client", "webpack-env"],
"outDir": "./lib"
}, },
"include": ["./src"] "include": ["./src"]
} }

View File

@ -10,7 +10,7 @@ yarn add @vuepress-plume/plugin-caniuse
// .vuepress/config.js // .vuepress/config.js
const { caniusePlugin } = require('@vuepress-plume/plugin-caniuse') const { caniusePlugin } = require('@vuepress-plume/plugin-caniuse')
module.exports = { module.exports = {
//... // ...
plugins: [ plugins: [
caniusePlugin({ mode: 'embed' }) caniusePlugin({ mode: 'embed' })
] ]

View File

@ -1,7 +1,18 @@
{ {
"name": "@vuepress-plume/plugin-caniuse", "name": "@vuepress-plume/plugin-caniuse",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2, Support Can-I-Use feature", "description": "The Plugin for VuePres 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"
},
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"keywords": [ "keywords": [
"VuePress", "VuePress",
"plugin", "plugin",
@ -9,17 +20,6 @@
"can-i-use", "can-i-use",
"caniuse" "caniuse"
], ],
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
},
"license": "MIT",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"type": "module",
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./client": "./lib/client/index.js", "./client": "./lib/client/index.js",

View File

@ -9,12 +9,12 @@ const mode = __CAN_I_USE_INJECT_MODE__
export default defineClientConfig({ export default defineClientConfig({
enhance({ router }) { enhance({ router }) {
if (__VUEPRESS_SSR__) return if (__VUEPRESS_SSR__)
return
router.afterEach((to, from) => { router.afterEach(() => {
if (mode === 'embed') { if (mode === 'embed')
resolveCanIUse() resolveCanIUse()
}
}) })
}, },
}) })

View File

@ -1,17 +1,18 @@
let isBind = false let isBind = false
export const resolveCanIUse = (): void => { export function resolveCanIUse(): void {
if (isBind) return if (isBind)
return
isBind = true isBind = true
window.addEventListener('message', (message) => { window.addEventListener('message', (message) => {
const data = message.data const data = message.data
if (typeof data === 'string' && data.indexOf('ciu_embed') > -1) { if (typeof data === 'string' && data.includes('ciu_embed')) {
const [, feature, height] = data.split(':') const [, feature, height] = data.split(':')
const el = document.querySelector(`.ciu_embed[data-feature="${feature}"]`) const el = document.querySelector(`.ciu_embed[data-feature="${feature}"]`)
if (el) { if (el) {
const h = parseInt(height) + 30 const h = Number.parseInt(height) + 30
;(el.childNodes[0] as any).height = h + 'px' ;(el.childNodes[0] as any).height = `${h}px`
} }
} }
}) })

View File

@ -1,5 +1,6 @@
declare module 'markdown-it-container' { declare module 'markdown-it-container' {
import type { PluginWithParams } from 'markdown-it' import type { PluginWithParams } from 'markdown-it'
const container: PluginWithParams const container: PluginWithParams
export = container export = container
} }

View File

@ -9,9 +9,9 @@ const __dirname = getDirname(import.meta.url)
const modeMap: CanIUseMode[] = ['image', 'embed'] const modeMap: CanIUseMode[] = ['image', 'embed']
const isMode = (mode: CanIUseMode): boolean => modeMap.includes(mode) const isMode = (mode: CanIUseMode): boolean => modeMap.includes(mode)
export const caniusePlugin = ({ export function caniusePlugin({
mode = modeMap[0], mode = modeMap[0],
}: CanIUsePluginOptions): Plugin => { }: CanIUsePluginOptions): Plugin {
mode = isMode(mode) ? mode : modeMap[0] mode = isMode(mode) ? mode : modeMap[0]
const type = 'caniuse' const type = 'caniuse'
const validateReg = new RegExp(`^${type}\\s+(.*)$`) const validateReg = new RegExp(`^${type}\\s+(.*)$`)
@ -32,7 +32,8 @@ export const caniusePlugin = ({
if (token.nesting === 1) { if (token.nesting === 1) {
const feature = token.info.trim().slice(type.length).trim() || '' const feature = token.info.trim().slice(type.length).trim() || ''
return feature ? resolveCanIUse(feature, mode) : '' return feature ? resolveCanIUse(feature, mode) : ''
} else { }
else {
return '' return ''
} }
} }

View File

@ -1,6 +1,8 @@
import type { CanIUseMode } from '../shared/index.js' import type { CanIUseMode } from '../shared/index.js'
export const resolveCanIUse = (feature: string, mode: CanIUseMode): string => {
if (!feature) return '' export function resolveCanIUse(feature: string, mode: CanIUseMode): string {
if (!feature)
return ''
if (mode === 'image') { if (mode === 'image') {
return `<picture> return `<picture>

View File

@ -9,7 +9,7 @@ yarn add @vuepress-plume/plugin-copy-code
// .vuepress/config.js // .vuepress/config.js
const { copyCodePlugin } = require('@vuepress-plume/plugin-copy-code') const { copyCodePlugin } = require('@vuepress-plume/plugin-copy-code')
module.exports = { module.exports = {
//... // ...
plugins: [ plugins: [
copyCodePlugin() copyCodePlugin()
] ]

View File

@ -1,18 +1,18 @@
{ {
"name": "@vuepress-plume/plugin-copy-code", "name": "@vuepress-plume/plugin-copy-code",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2", "description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme", "homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git" "url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
}, },
"license": "MIT", "bugs": {
"author": "pengzhanbo <volodymyr@foxmail.com>", "url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
"type": "module", },
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./client": "./lib/client/index.js", "./client": "./lib/client/index.js",

View File

@ -1,7 +1,7 @@
export const copyToClipboard = (str: string): void => { export function copyToClipboard(str: string): void {
const selection = document.getSelection() const selection = document.getSelection()
const selectedRange = const selectedRange
selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : false = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : false
const textEl = document.createElement('textarea') const textEl = document.createElement('textarea')

View File

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

View File

@ -11,9 +11,7 @@ const defaultOptions: CopyCodeOptions = {
showInMobile: false, showInMobile: false,
} }
export function copyCodePlugin(options: CopyCodeOptions): Plugin {
export const copyCodePlugin = (options: CopyCodeOptions): Plugin => {
options = Object.assign({}, defaultOptions, options) options = Object.assign({}, defaultOptions, options)
return { return {

View File

@ -11,7 +11,7 @@ yarn add @vuepress-plume/plugin-iconify
// .vuepress/config.js // .vuepress/config.js
const iconifyPlugin = require('@vuepress-plume/plugin-iconify') const iconifyPlugin = require('@vuepress-plume/plugin-iconify')
module.exports = { module.exports = {
//... // ...
plugins: [ plugins: [
iconifyPlugin() iconifyPlugin()
] ]
@ -30,7 +30,6 @@ interface IconifyOptions {
color?: string color?: string
size?: string | number size?: string | number
} }
``` ```
## Component ## Component

View File

@ -1,18 +1,18 @@
{ {
"name": "@vuepress-plume/plugin-iconify", "name": "@vuepress-plume/plugin-iconify",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2", "description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme", "homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git" "url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
}, },
"license": "MIT", "bugs": {
"author": "pengzhanbo <volodymyr@foxmail.com>", "url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
"type": "module", },
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./client": "./lib/client/index.js", "./client": "./lib/client/index.js",

View File

@ -15,7 +15,7 @@ const props = withDefaults(
name: '', name: '',
size: '', size: '',
color: '', color: '',
} },
) )
const { name } = toRefs(props) const { name } = toRefs(props)
@ -24,9 +24,9 @@ const { icon, loaded } = useIconify(name)
const size = computed(() => { const size = computed(() => {
const size = props.size || __VUEPRESS_PLUGIN_ICONIFY_DEFAULT_SIZE__ const size = props.size || __VUEPRESS_PLUGIN_ICONIFY_DEFAULT_SIZE__
if (String(Number(size)) === size) { if (String(Number(size)) === size)
return `${size}px` return `${size}px`
}
return size return size
}) })
const iconStyle = computed(() => { const iconStyle = computed(() => {
@ -46,7 +46,7 @@ declare const __VUEPRESS_PLUGIN_ICONIFY_DEFAULT_COLOR__: string
<template> <template>
<ClientOnly> <ClientOnly>
<span v-if="!loaded" class="vp-iconify" :style="iconStyle"></span> <span v-if="!loaded" class="vp-iconify" :style="iconStyle" />
<OfflineIcon <OfflineIcon
v-else-if="icon" v-else-if="icon"
:icon="icon" :icon="icon"
@ -58,6 +58,7 @@ declare const __VUEPRESS_PLUGIN_ICONIFY_DEFAULT_COLOR__: string
</span> </span>
</ClientOnly> </ClientOnly>
</template> </template>
<style scoped> <style scoped>
.vp-iconify { .vp-iconify {
display: inline-block; display: inline-block;

View File

@ -5,22 +5,24 @@ import type { ComputedRef, Ref } from 'vue'
const iconCache: Ref<Record<string, IconifyIcon>> = ref({}) const iconCache: Ref<Record<string, IconifyIcon>> = ref({})
export const useIconify = (name: ComputedRef<string> | Ref<string>) => { export function useIconify(name: ComputedRef<string> | Ref<string>) {
const icon = computed(() => iconCache.value[name.value]) const icon = computed(() => iconCache.value[name.value])
const loaded = ref(true) const loaded = ref(true)
async function loadIconComponent() { async function loadIconComponent() {
if (icon.value) { if (icon.value)
return return
}
if (!__VUEPRESS_SSR__) { if (!__VUEPRESS_SSR__) {
try { try {
loaded.value = false loaded.value = false
iconCache.value[name.value] = await loadIcon(name.value) iconCache.value[name.value] = await loadIcon(name.value)
} finally { }
finally {
loaded.value = true loaded.value = true
} }
} else { }
else {
loaded.value = true loaded.value = true
} }
} }

View File

@ -1,13 +1,13 @@
import type { App, Plugin } from '@vuepress/core' import type { Plugin } from '@vuepress/core'
import { getDirname, path } from '@vuepress/utils' import { getDirname, path } from '@vuepress/utils'
import type { IconifyOptions } from '../shared/index.js' import type { IconifyOptions } from '../shared/index.js'
export const iconifyPlugin = ({ export function iconifyPlugin({
componentName = 'Iconify', componentName = 'Iconify',
size = '1em', size = '1em',
color = 'currentColor', color = 'currentColor',
}: IconifyOptions = {}): Plugin => { }: IconifyOptions = {}): Plugin {
return (app: App) => { return () => {
return { return {
name: '@vuepress-plume/plugin-iconify', name: '@vuepress-plume/plugin-iconify',
define: { define: {
@ -17,7 +17,7 @@ export const iconifyPlugin = ({
}, },
clientConfigFile: path.resolve( clientConfigFile: path.resolve(
getDirname(import.meta.url), getDirname(import.meta.url),
'../client/clientConfig.js' '../client/clientConfig.js',
), ),
} }
} }

View File

@ -1,6 +1,5 @@
# `vuepress-plugin-netlify-functions` # `vuepress-plugin-netlify-functions`
If your vuepress site is deployed on `netlify` and you want to be able to use `netlify functions` for ` serverless`. If your vuepress site is deployed on `netlify` and you want to be able to use `netlify functions` for ` serverless`.
You may need this plugin to provide support. You may need this plugin to provide support.
@ -29,27 +28,27 @@ yarn add vuepress-plugin-netlify-functions
## Usage ## Usage
1. In a Vuepress project, or in a Vuepress theme 1. In a Vuepress project, or in a Vuepress theme
在 vuepress 项目中,或者在一个 vuepress 主题中 在 vuepress 项目中,或者在一个 vuepress 主题中
``` js ``` js
// .vuepress/config.js // .vuepress/config.js
import { netlifyFunctionsPlugin } from 'vuepress-plugin-netlify-functions' import { netlifyFunctionsPlugin } from 'vuepress-plugin-netlify-functions'
module.exports = { module.exports = {
//... // ...
plugins: [ plugins: [
netlifyFunctionsPlugin() netlifyFunctionsPlugin()
] ]
// ... // ...
} }
``` ```
2. In a vuepress plugin: 2. In a vuepress plugin:
在 vuepress plugin 中: 在 vuepress plugin 中:
``` js ``` js
import { useNetlifyFunctionsPlugin } from 'vuepress-plugin-netlify-functions' import { useNetlifyFunctionsPlugin } from 'vuepress-plugin-netlify-functions'
const myPlugin = (): Plugin => { function myPlugin(): Plugin {
return (app: App) => { return (app: App) => {
const { const {
// proxy prefix, default: /api // proxy prefix, default: /api
@ -57,14 +56,14 @@ yarn add vuepress-plugin-netlify-functions
proxyPrefix, proxyPrefix,
preparePluginFunctions, preparePluginFunctions,
generatePluginFunctions generatePluginFunctions
} = useNetlifyFunctionsPlugin(app, { } = useNetlifyFunctionsPlugin(app, {
// Specifies the functions directory for the plugin where the relevant scripts are developed // Specifies the functions directory for the plugin where the relevant scripts are developed
// 指定插件的functions目录相关脚本在此目录中开发 // 指定插件的functions目录相关脚本在此目录中开发
directory: path.resolve(__dirname, 'functions') directory: path.resolve(__dirname, 'functions')
}) })
return { return {
name: 'vuepress-plugin-myPlugin', name: 'vuepress-plugin-myPlugin',
onPrepared:() => preparePluginFunctions(), onPrepared: () => preparePluginFunctions(),
onGenerated: () => generatePluginFunctions(), onGenerated: () => generatePluginFunctions(),
} }
} }
@ -86,33 +85,33 @@ In the Vuepress configuration, or in the Vuepress topic configuration.
__options__ __options__
- `options.sourceDirectory` functions source directory。 - `options.sourceDirectory` functions source directory。
@default `app.dir.source('.vuepress/functions')。 @default `app.dir.source('.vuepress/functions')。
- `options.destDirectory` functions output directory - `options.destDirectory` functions output directory
@default `app.dir.dest('function') @default `app.dir.dest('function')
- `options.proxyPrefix` server proxy prefix - `options.proxyPrefix` server proxy prefix
@default `/api` @default `/api`
functions request to proxy `^/api/*` functions request to proxy `^/api/*`
__options__ __options__
- `options.sourceDirectory` functions 源文件夹。 - `options.sourceDirectory` functions 源文件夹。
默认 `app.dir.source('.vuepress/functions') 目录。 默认 `app.dir.source('.vuepress/functions') 目录。
- `options.destDirectory` functions 输出文件夹。 - `options.destDirectory` functions 输出文件夹。
默认 `app.dir.dest('function') 目录 默认 `app.dir.dest('function') 目录
- `options.proxyPrefix` proxy代理前缀。 - `options.proxyPrefix` proxy代理前缀。
默认 `/api` 默认 `/api`
functions下的请求通过 `/api` 转发 functions下的请求通过 `/api` 转发
### `useNetlifyFunctionsPlugin(app, options)` ### `useNetlifyFunctionsPlugin(app, options)`
@ -125,8 +124,8 @@ __app__: `App`
__options__ __options__
- `options.directory` - `options.directory`
Functions development directory in plugin Functions development directory in plugin
插件中的 functions 开发目录 插件中的 functions 开发目录

View File

@ -1,7 +1,18 @@
{ {
"name": "vuepress-plugin-netlify-functions", "name": "vuepress-plugin-netlify-functions",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2, Support Netlify Functions", "description": "The Plugin for VuePres 2, Support Netlify Functions",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
},
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"keywords": [ "keywords": [
"VuePress", "VuePress",
"vuepress plugin", "vuepress plugin",
@ -10,17 +21,6 @@
"netlifyFunctions", "netlifyFunctions",
"vuepress-plugin-netlify-functions" "vuepress-plugin-netlify-functions"
], ],
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
},
"license": "MIT",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"type": "module",
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./package.json": "./package.json" "./package.json": "./package.json"

View File

@ -1,11 +1,7 @@
import type { App } from '@vuepress/core' import type { App } from '@vuepress/core'
import type { NetlifyFunctionsPluginOptions } from '../shared/index.js' import type { NetlifyFunctionsPluginOptions } from '../shared/index.js'
export const extendsBundlerOptions = (
bundlerOption: any, export function extendsBundlerOptions(bundlerOption: any, app: App, options: NetlifyFunctionsPluginOptions, server: string): void {
app: App,
options: NetlifyFunctionsPluginOptions,
server: string
): void => {
// 在 netlify-cli 的 function:serve 中, // 在 netlify-cli 的 function:serve 中,
// 默认就是 指向 /.netlify/functions // 默认就是 指向 /.netlify/functions
// 而配置的 --functions 仅作为源文件入口 // 而配置的 --functions 仅作为源文件入口
@ -27,7 +23,8 @@ export const extendsBundlerOptions = (
const rewritePath = `^${options.proxyPrefix}` const rewritePath = `^${options.proxyPrefix}`
bundlerOption.configureWebpack( bundlerOption.configureWebpack(
(config: any, isServer: boolean, isBuild: boolean) => { (config: any, isServer: boolean, isBuild: boolean) => {
if (isBuild) return if (isBuild)
return
config.devServer = config.devServer || {} config.devServer = config.devServer || {}
config.devServer.proxy = Object.assign(config.devServer.proxy || {}, { config.devServer.proxy = Object.assign(config.devServer.proxy || {}, {
[options.proxyPrefix as string]: { [options.proxyPrefix as string]: {
@ -36,7 +33,7 @@ export const extendsBundlerOptions = (
pathRewrite: { [rewritePath]: targetPath }, pathRewrite: { [rewritePath]: targetPath },
}, },
}) })
} },
) )
} }
} }

View File

@ -5,10 +5,7 @@ import esbuild from 'esbuild'
import type { NetlifyFunctionsPluginOptions } from '../../shared/index.js' import type { NetlifyFunctionsPluginOptions } from '../../shared/index.js'
import { readFileList } from '../utils/index.js' import { readFileList } from '../utils/index.js'
export const generateFunctions = async ( export async function generateFunctions(app: App, options: NetlifyFunctionsPluginOptions): Promise<void> {
app: App,
options: NetlifyFunctionsPluginOptions
): Promise<void> => {
const { directory } = options const { directory } = options
const { source, dest } = directory const { source, dest } = directory
const userSource = source[0] const userSource = source[0]
@ -25,11 +22,9 @@ export const generateFunctions = async (
} }
} }
export const initialFunctions = async ( export async function initialFunctions(app: App, options: NetlifyFunctionsPluginOptions): Promise<void> {
app: App, if (!app.env.isDev)
options: NetlifyFunctionsPluginOptions return
): Promise<void> => {
if (!app.env.isDev) return
const { directory } = options const { directory } = options
const { source, temp } = directory const { source, temp } = directory
const userSource = source[0] const userSource = source[0]
@ -47,10 +42,7 @@ export const initialFunctions = async (
watchFunctions(app, options) watchFunctions(app, options)
} }
export const watchFunctions = ( export function watchFunctions(app: App, { directory }: NetlifyFunctionsPluginOptions): void {
app: App,
{ directory }: NetlifyFunctionsPluginOptions
): void => {
const { source, temp } = directory const { source, temp } = directory
const userSource = source[0] const userSource = source[0]
const watcher = chokidar.watch('**/*.ts', { const watcher = chokidar.watch('**/*.ts', {

View File

@ -1,3 +1,4 @@
import process from 'node:process'
import type { JsonMap } from '@iarna/toml' import type { JsonMap } from '@iarna/toml'
import { parse, stringify } from '@iarna/toml' import { parse, stringify } from '@iarna/toml'
import type { App } from '@vuepress/core' import type { App } from '@vuepress/core'
@ -11,39 +12,32 @@ export interface NetlifyConfig {
const configName = 'netlify.toml' const configName = 'netlify.toml'
const readConfig = (filepath: string): NetlifyConfig => { function readConfig(filepath: string): NetlifyConfig {
let netlifyConfig = '' let netlifyConfig = ''
if (fs.existsSync(filepath)) { if (fs.existsSync(filepath))
netlifyConfig = fs.readFileSync(filepath, 'utf-8') || '' netlifyConfig = fs.readFileSync(filepath, 'utf-8') || ''
}
return (parse(netlifyConfig) as unknown as NetlifyConfig) || {} return (parse(netlifyConfig) as unknown as NetlifyConfig) || {}
} }
const writeConfig = (filepath: string, netlifyConfig: NetlifyConfig): void => { function writeConfig(filepath: string, netlifyConfig: NetlifyConfig): void {
fs.writeFileSync( fs.writeFileSync(
filepath, filepath,
stringify(netlifyConfig as unknown as JsonMap), stringify(netlifyConfig as unknown as JsonMap),
'utf-8' 'utf-8',
) )
} }
const resolveFunctions = ( function resolveFunctions(config: NetlifyConfig, { directory }: NetlifyFunctionsPluginOptions, app: App): void {
config: NetlifyConfig,
{ directory }: NetlifyFunctionsPluginOptions,
app: App
): void => {
const functions = (config.functions = config.functions || {}) const functions = (config.functions = config.functions || {})
functions.directory = functions.directory
functions.directory || path.relative(app.dir.dest('../'), directory.dest) = functions.directory || path.relative(app.dir.dest('../'), directory.dest)
} }
const resolveRedirects = ( function resolveRedirects(config: NetlifyConfig, { proxyPrefix }: NetlifyFunctionsPluginOptions): void {
config: NetlifyConfig, const funcDir = `/${(config.functions.directory || '').replace(/^\//, '')}`
{ proxyPrefix }: NetlifyFunctionsPluginOptions
): void => {
const funcDir = '/' + (config.functions.directory || '').replace(/^\//, '')
const redirects = (config.redirects = config.redirects || []) const redirects = (config.redirects = config.redirects || [])
if (!redirects.some((redirect) => redirect?.to?.startsWith(funcDir))) { if (!redirects.some(redirect => redirect?.to?.startsWith(funcDir))) {
redirects.push({ redirects.push({
from: path.join('/', proxyPrefix, '*'), from: path.join('/', proxyPrefix, '*'),
to: path.join(funcDir, ':splat'), to: path.join(funcDir, ':splat'),
@ -56,10 +50,7 @@ const resolveRedirects = (
} }
} }
export const generateNetlifyConfig = ( export function generateNetlifyConfig(app: App, options: NetlifyFunctionsPluginOptions): NetlifyConfig {
app: App,
options: NetlifyFunctionsPluginOptions
): NetlifyConfig => {
const configPath = path.join(process.cwd(), configName) const configPath = path.join(process.cwd(), configName)
const config = readConfig(configPath) const config = readConfig(configPath)

View File

@ -1,3 +1,5 @@
import { Buffer } from 'node:buffer'
import process from 'node:process'
import { fs, getDirname, path } from '@vuepress/utils' import { fs, getDirname, path } from '@vuepress/utils'
import dotenv from 'dotenv' import dotenv from 'dotenv'
import { execa } from 'execa' import { execa } from 'execa'
@ -6,15 +8,16 @@ import type { NetlifyFunctionsPluginOptions } from '../../shared/index.js'
const __dirname = getDirname(import.meta.url) const __dirname = getDirname(import.meta.url)
const loadEnvConfig = (): Record<string, string | undefined> => { function loadEnvConfig(): Record<string, string | undefined> {
const configPath = path.resolve(process.cwd(), '.env') const configPath = path.resolve(process.cwd(), '.env')
if (!fs.existsSync(configPath)) { if (!fs.existsSync(configPath))
return {} return {}
}
try { try {
const content = fs.readFileSync(configPath, 'utf-8') const content = fs.readFileSync(configPath, 'utf-8')
return dotenv.parse(Buffer.from(content)) return dotenv.parse(Buffer.from(content))
} catch { }
catch {
return {} return {}
} }
} }
@ -23,15 +26,15 @@ export interface NetlifyServe {
host: string host: string
close: () => void close: () => void
} }
export const netlifyServe = async ({ export async function netlifyServe({
directory, directory,
}: NetlifyFunctionsPluginOptions): Promise<NetlifyServe> => { }: NetlifyFunctionsPluginOptions): Promise<NetlifyServe> {
const port = await portFinder.getPortPromise({ port: 9000 }) const port = await portFinder.getPortPromise({ port: 9000 })
const argv = [ const argv = [
'functions:serve', 'functions:serve',
'--port', '--port',
port + '', `${port}`,
'--functions', '--functions',
path.join('./', path.relative(process.cwd(), directory.temp)), path.join('./', path.relative(process.cwd(), directory.temp)),
// '--debug', // '--debug',
@ -45,12 +48,12 @@ export const netlifyServe = async ({
env: { env: {
...loadEnvConfig(), ...loadEnvConfig(),
}, },
} },
) )
stdout?.pipe(process.stdout) stdout?.pipe(process.stdout)
return { return {
host: 'http://localhost:' + port, host: `http://localhost:${port}`,
close: () => cancel(), close: () => cancel(),
} }
} }

View File

@ -39,27 +39,26 @@ import {
netlifyServe, netlifyServe,
} from './netlify/index.js' } from './netlify/index.js'
const initOptions = ( function initOptions(app: App, {
app: App, sourceDirectory,
{ destDirectory,
sourceDirectory, proxyPrefix = '/api',
destDirectory, }: NetlifyFunctionsOptions): NetlifyFunctionsPluginOptions {
proxyPrefix = '/api', return {
}: NetlifyFunctionsOptions directory: {
): NetlifyFunctionsPluginOptions => ({ source: [sourceDirectory || app.dir.source('.vuepress/functions')],
directory: { dest: destDirectory || app.dir.dest('functions'),
source: [sourceDirectory || app.dir.source('.vuepress/functions')], temp: app.dir.temp('functions'),
dest: destDirectory || app.dir.dest('functions'), },
temp: app.dir.temp('functions'), proxyPrefix,
}, }
proxyPrefix, }
})
const cache = { const cache = {
options: {}, options: {},
} }
export const getOptions = (): NetlifyFunctionsPluginOptions => { export function getOptions(): NetlifyFunctionsPluginOptions {
return cache.options as NetlifyFunctionsPluginOptions return cache.options as NetlifyFunctionsPluginOptions
} }
@ -68,11 +67,8 @@ export const getOptions = (): NetlifyFunctionsPluginOptions => {
* netlify function netlify functions * netlify function netlify functions
* *
* @param options * @param options
* @returns
*/ */
export const netlifyFunctionsPlugin = ( export function netlifyFunctionsPlugin(options: NetlifyFunctionsOptions = {}): Plugin {
options: NetlifyFunctionsOptions = {}
): Plugin => {
return (app: App) => { return (app: App) => {
const opts = initOptions(app, options) const opts = initOptions(app, options)
let server: NetlifyServe let server: NetlifyServe

View File

@ -19,27 +19,24 @@ interface UseNetlifyFunctionResult {
*/ */
generatePluginFunctions: () => void generatePluginFunctions: () => void
} }
export const useNetlifyFunctionsPlugin = ( export function useNetlifyFunctionsPlugin(app: App, options: UseNetlifyFunctionPluginsOptions): UseNetlifyFunctionResult {
app: App, if (typeof options === 'undefined')
options: UseNetlifyFunctionPluginsOptions
): UseNetlifyFunctionResult => {
if (typeof options === 'undefined') {
throw new Error('useNetlifyFunctionsPlugin [options] argument not found.') throw new Error('useNetlifyFunctionsPlugin [options] argument not found.')
}
if (typeof options.directory !== 'string' || !options.directory) { if (typeof options.directory !== 'string' || !options.directory) {
throw new Error( throw new Error(
`useNetlifyFunctionsPlugin [options.directory] must be a string\n exp: path.join(__dirname, 'functions')` `useNetlifyFunctionsPlugin [options.directory] must be a string\n exp: path.join(__dirname, 'functions')`,
) )
} }
const plugins = app.pluginApi.plugins const plugins = app.pluginApi.plugins
if ( if (
!plugins.some( !plugins.some(
(plugin: PluginObject) => (plugin: PluginObject) =>
plugin.name === 'vuepress-plugin-netlify-functions' plugin.name === 'vuepress-plugin-netlify-functions',
) )
) { )
app.use(netlifyFunctionsPlugin()) app.use(netlifyFunctionsPlugin())
}
const { proxyPrefix, directory } = getOptions() const { proxyPrefix, directory } = getOptions()
const source = path.join(options.directory, '**/*.js') const source = path.join(options.directory, '**/*.js')

View File

@ -1,19 +1,17 @@
import { fs, path } from '@vuepress/utils' import { fs, path } from '@vuepress/utils'
export const readFileList = ( export function readFileList(source: string, fileList: string[] = []): string[] {
source: string, if (!fs.existsSync(source))
fileList: string[] = [] return []
): string[] => {
if (!fs.existsSync(source)) return []
const files = fs.readdirSync(source) const files = fs.readdirSync(source)
files.forEach((file: string) => { files.forEach((file: string) => {
const filepath = path.join(source, file) const filepath = path.join(source, file)
const stat = fs.statSync(filepath) const stat = fs.statSync(filepath)
if (stat.isDirectory()) { if (stat.isDirectory()) {
if (file !== 'node_modules') { if (file !== 'node_modules')
readFileList(filepath, fileList) readFileList(filepath, fileList)
} }
} else { else {
fileList.push(filepath) fileList.push(filepath)
} }
}) })

View File

@ -9,7 +9,7 @@ yarn add @vuepress-plume/plugin-notes-data
// .vuepress/config.js // .vuepress/config.js
const notesDataPlugin = require('@vuepress-plume/plugin-notes-data') const notesDataPlugin = require('@vuepress-plume/plugin-notes-data')
module.exports = { module.exports = {
//... // ...
plugins: [ plugins: [
notesDataPlugin() notesDataPlugin()
] ]
@ -20,7 +20,7 @@ module.exports = {
## Options ## Options
``` ts ``` ts
type NotesDataOptions = { interface NotesDataOptions {
dir: string dir: string
link: string link: string
include?: string | string[] include?: string | string[]
@ -28,7 +28,7 @@ type NotesDataOptions = {
notes: NotesItem[] notes: NotesItem[]
} }
type NotesItem = { interface NotesItem {
dir: string dir: string
link: string link: string
text: string text: string
@ -37,7 +37,7 @@ type NotesItem = {
type NotesSidebar = (NotesSidebarItem | string)[] type NotesSidebar = (NotesSidebarItem | string)[]
type NotesSidebarItem = { interface NotesSidebarItem {
text?: string text?: string
link?: string link?: string
dir?: string dir?: string

View File

@ -1,18 +1,18 @@
{ {
"name": "@vuepress-plume/plugin-notes-data", "name": "@vuepress-plume/plugin-notes-data",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2", "description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme", "homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git" "url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
}, },
"license": "MIT", "bugs": {
"author": "pengzhanbo <volodymyr@foxmail.com>", "url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
"type": "module", },
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./client": "./lib/client/index.js", "./client": "./lib/client/index.js",

View File

@ -30,7 +30,7 @@ export default defineClientConfig({
value: notesData.value, value: notesData.value,
}) })
}) })
} },
) )
} }
}, },

View File

@ -9,9 +9,11 @@ export type NotesDataRef<T extends NotesData = NotesData> = Ref<T>
export const notesData: NotesDataRef = ref(notesDataRaw) export const notesData: NotesDataRef = ref(notesDataRaw)
export const useNotesData = < export function useNotesData<
T extends NotesData = NotesData T extends NotesData = NotesData,
>(): NotesDataRef<T> => notesData as NotesDataRef<T> >(): NotesDataRef<T> {
return notesData as NotesDataRef<T>
}
if (import.meta.webpackHot || import.meta.hot) { if (import.meta.webpackHot || import.meta.hot) {
__VUE_HMR_RUNTIME__.updateNotesData = (data: NotesData) => { __VUE_HMR_RUNTIME__.updateNotesData = (data: NotesData) => {

View File

@ -3,13 +3,13 @@ import { getDirname, path } from '@vuepress/utils'
import type { NotesDataOptions } from '../shared/index.js' import type { NotesDataOptions } from '../shared/index.js'
import { prepareNotesData, watchNotesData } from './prepareNotesData.js' import { prepareNotesData, watchNotesData } from './prepareNotesData.js'
export const notesDataPlugin = (options: NotesDataOptions): Plugin => { export function notesDataPlugin(options: NotesDataOptions): Plugin {
return (app: App) => { return (app: App) => {
return { return {
name: '@vuepress-plume/plugin-notes-data', name: '@vuepress-plume/plugin-notes-data',
clientConfigFile: path.resolve( clientConfigFile: path.resolve(
getDirname(import.meta.url), getDirname(import.meta.url),
'../client/clientConfig.js' '../client/clientConfig.js',
), ),
onPrepared: () => prepareNotesData(app, options), onPrepared: () => prepareNotesData(app, options),
onWatched: (app, watchers) => watchNotesData(app, watchers, options), onWatched: (app, watchers) => watchNotesData(app, watchers, options),

View File

@ -32,11 +32,9 @@ interface NotePage {
link: string link: string
} }
export const prepareNotesData = async ( export async function prepareNotesData(app: App, { include, exclude, notes, dir, link }: NotesDataOptions) {
app: App, if (!notes || notes.length === 0)
{ include, exclude, notes, dir, link }: NotesDataOptions return
) => {
if (!notes || notes.length === 0) return
dir = normalizePath(dir) dir = normalizePath(dir)
const filter = createFilter(ensureArray(include), ensureArray(exclude), { const filter = createFilter(ensureArray(include), ensureArray(exclude), {
resolve: false, resolve: false,
@ -44,10 +42,10 @@ export const prepareNotesData = async (
const DIR_PATTERN = new RegExp(`^${normalizePath(path.join(dir, '/'))}`) const DIR_PATTERN = new RegExp(`^${normalizePath(path.join(dir, '/'))}`)
const notesPageList: NotePage[] = app.pages const notesPageList: NotePage[] = app.pages
.filter( .filter(
(page) => page =>
page.filePathRelative && page.filePathRelative
page.filePathRelative.startsWith(dir) && && page.filePathRelative.startsWith(dir)
filter(page.filePathRelative) && filter(page.filePathRelative),
) )
.map((page) => { .map((page) => {
return { return {
@ -61,27 +59,23 @@ export const prepareNotesData = async (
notes.forEach((note) => { notes.forEach((note) => {
notesData[normalizePath(path.join('/', link, note.link))] = initSidebar( notesData[normalizePath(path.join('/', link, note.link))] = initSidebar(
note, note,
notesPageList.filter((page) => notesPageList.filter(page =>
page.relativePath.startsWith(note.dir.trim().replace(/^\/|\/$/g, '')) page.relativePath.startsWith(note.dir.trim().replace(/^\/|\/$/g, '')),
) ),
) )
}) })
let content = ` let content = `
export const notesData = ${JSON.stringify(notesData, null, 2)} export const notesData = ${JSON.stringify(notesData, null, 2)}
` `
if (app.env.isDev) { if (app.env.isDev)
content += HMR_CODE content += HMR_CODE
}
await app.writeTemp('internal/notesData.js', content) await app.writeTemp('internal/notesData.js', content)
} }
export const watchNotesData = ( export function watchNotesData(app: App, watchers: any[], options: NotesDataOptions): void {
app: App, if (!options.notes || options.notes.length === 0 || !options.dir)
watchers: any[], return
options: NotesDataOptions
): void => {
if (!options.notes || options.notes.length === 0 || !options.dir) return
const dir = path.join('pages', options.dir, '**/*') const dir = path.join('pages', options.dir, '**/*')
const watcher = chokidar.watch(dir, { const watcher = chokidar.watch(dir, {
cwd: app.dir.temp(), cwd: app.dir.temp(),
@ -95,14 +89,16 @@ export const watchNotesData = (
} }
function initSidebar(note: NotesItem, pages: NotePage[]): NotesSidebarItem[] { function initSidebar(note: NotesItem, pages: NotePage[]): NotesSidebarItem[] {
if (!note.sidebar) return [] if (!note.sidebar)
if (note.sidebar === 'auto') return initSidebarByAuto(note, pages) return []
if (note.sidebar === 'auto')
return initSidebarByAuto(note, pages)
return initSidebarByConfig(note, pages) return initSidebarByConfig(note, pages)
} }
function initSidebarByAuto( function initSidebarByAuto(
note: NotesItem, note: NotesItem,
pages: NotePage[] pages: NotePage[],
): NotesSidebarItem[] { ): NotesSidebarItem[] {
pages = pages.sort((prev, next) => { pages = pages.sort((prev, next) => {
const pi = prev.relativePath.match(/\//g)?.length || 0 const pi = prev.relativePath.match(/\//g)?.length || 0
@ -119,9 +115,10 @@ function initSidebarByAuto(
let index = 0 let index = 0
let dir: string let dir: string
let items = result let items = result
// eslint-disable-next-line no-cond-assign
while ((dir = paths[index])) { while ((dir = paths[index])) {
const text = dir.replace(/\.md$/, '') const text = dir.replace(/\.md$/, '')
let current = items.find((item) => item.text === text) let current = items.find(item => item.text === text)
if (!current) { if (!current) {
current = { text, link: undefined, items: [] } current = { text, link: undefined, items: [] }
!RE_INDEX.includes(dir) ? items.push(current) : items.unshift(current) !RE_INDEX.includes(dir) ? items.push(current) : items.unshift(current)
@ -139,7 +136,7 @@ function initSidebarByAuto(
function initSidebarByConfig( function initSidebarByConfig(
{ text, dir, sidebar }: NotesItem, { text, dir, sidebar }: NotesItem,
pages: NotePage[] pages: NotePage[],
): NotesSidebarItem[] { ): NotesSidebarItem[] {
return (sidebar as NotesSidebar).map((item) => { return (sidebar as NotesSidebar).map((item) => {
if (typeof item === 'string') { if (typeof item === 'string') {
@ -149,7 +146,8 @@ function initSidebarByConfig(
link: current?.link, link: current?.link,
items: [], items: [],
} }
} else { }
else {
const current = findNotePage(item.link || '', dir, pages) const current = findNotePage(item.link || '', dir, pages)
return { return {
text: item.text || item.dir || current?.title, text: item.text || item.dir || current?.title,
@ -162,7 +160,7 @@ function initSidebarByConfig(
sidebar: item.items, sidebar: item.items,
dir: normalizePath(path.join(dir, item.dir || '')), dir: normalizePath(path.join(dir, item.dir || '')),
}, },
pages pages,
), ),
} }
} }
@ -172,23 +170,24 @@ function initSidebarByConfig(
function findNotePage( function findNotePage(
sidebar: string, sidebar: string,
dir: string, dir: string,
notePageList: NotePage[] notePageList: NotePage[],
): NotePage | undefined { ): NotePage | undefined {
if (sidebar === '' || sidebar === 'README.md' || sidebar === 'index.md') { if (sidebar === '' || sidebar === 'README.md' || sidebar === 'index.md') {
return notePageList.find((page) => { return notePageList.find((page) => {
const relative = page.relativePath const relative = page.relativePath
return ( return (
relative === normalizePath(path.join(dir, 'README.md')) || relative === normalizePath(path.join(dir, 'README.md'))
relative === normalizePath(path.join(dir, 'index.md')) || relative === normalizePath(path.join(dir, 'index.md'))
) )
}) })
} else { }
else {
return notePageList.find((page) => { return notePageList.find((page) => {
const relative = page.relativePath const relative = page.relativePath
return ( return (
relative === normalizePath(path.join(dir, sidebar)) || relative === normalizePath(path.join(dir, sidebar))
relative === normalizePath(path.join(dir, sidebar + '.md')) || || relative === normalizePath(path.join(dir, `${sidebar}.md`))
page.link === sidebar || page.link === sidebar
) )
}) })
} }

View File

@ -1,6 +1,8 @@
export function ensureArray<T>(thing: T | T[] | null | undefined): T[] { export function ensureArray<T>(thing: T | T[] | null | undefined): T[] {
if (Array.isArray(thing)) return thing if (Array.isArray(thing))
if (thing === null || thing === undefined) return [] return thing
if (thing === null || thing === undefined)
return []
return [thing] return [thing]
} }

View File

@ -2,11 +2,11 @@
"extends": "../tsconfig.build.json", "extends": "../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./lib",
"paths": { "paths": {
"@internal/notesData": ["./src/client/notesData.d.ts"] "@internal/notesData": ["./src/client/notesData.d.ts"]
}, },
"types": ["@vuepress/client/types", "vite/client", "webpack-env"] "types": ["@vuepress/client/types", "vite/client", "webpack-env"],
"outDir": "./lib"
}, },
"include": ["./src"] "include": ["./src"]
} }

View File

@ -9,7 +9,7 @@
- 为什么是在 `netlify functions` 中 连接 `leancloud` ,而不是直接在 web客户端中请求 `leancloud` ? - 为什么是在 `netlify functions` 中 连接 `leancloud` ,而不是直接在 web客户端中请求 `leancloud` ?
这是出于数据安全的角度考虑,避免直接在浏览器可见的代码中暴露私密的鉴权信息。 这是出于数据安全的角度考虑,避免直接在浏览器可见的代码中暴露私密的鉴权信息。
## 使用方式 ## 使用方式
> (仅示例,本插件未发布到 npm) > (仅示例,本插件未发布到 npm)
@ -47,7 +47,7 @@
├── database # leancloud-storage ├── database # leancloud-storage
│ └── Page.json # Object Page 在 leancloud控制台中导入即可使用 │ └── Page.json # Object Page 在 leancloud控制台中导入即可使用
├── src # 源码 ├── src # 源码
│ ├── client │ ├── client
│ │ ├── clientAppEnhance.ts # 注入全局组件 │ │ ├── clientAppEnhance.ts # 注入全局组件
│ │ ├── components │ │ ├── components
| | | └── PageCollection.ts # 组件 | | | └── PageCollection.ts # 组件

View File

@ -1,19 +1,19 @@
{ {
"name": "plugin-page-collection", "name": "plugin-page-collection",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"private": true, "private": true,
"description": "The Plugin for VuePres 2", "description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme", "homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git" "url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
}, },
"license": "MIT", "bugs": {
"author": "pengzhanbo <volodymyr@foxmail.com>", "url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
"type": "module", },
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./client": "./lib/client/index.js", "./client": "./lib/client/index.js",

View File

@ -3,7 +3,6 @@ import Collection from './components/PageCollection.js'
export default defineClientConfig({ export default defineClientConfig({
enhance({ app }) { enhance({ app }) {
// eslint-disable-next-line vue/match-component-file-name
app.component('PageCollection', Collection) app.component('PageCollection', Collection)
}, },
}) })

View File

@ -11,7 +11,7 @@ export default defineComponent({
{ {
class: 'page-collection', class: 'page-collection',
}, },
`阅读数:${collection.visitCount}` `阅读数:${collection.visitCount}`,
) )
}, },
}) })

View File

@ -14,7 +14,7 @@ interface ResponseData {
result: Record<string, any> result: Record<string, any>
} }
const fetchCollection = async (url: string): Promise<PageCollection> => { async function fetchCollection(url: string): Promise<PageCollection> {
// 发起 netlify functions 请求 // 发起 netlify functions 请求
// 你已经注意到,接口名就是在 node/functions 目录下的 文件名 // 你已经注意到,接口名就是在 node/functions 目录下的 文件名
const response = await fetch(`${prefix}/page_collection`, { const response = await fetch(`${prefix}/page_collection`, {
@ -24,14 +24,15 @@ const fetchCollection = async (url: string): Promise<PageCollection> => {
const result = (await response.json()) as unknown as ResponseData const result = (await response.json()) as unknown as ResponseData
if (result.code === 200) { if (result.code === 200) {
return (result.result || {}) as PageCollection return (result.result || {}) as PageCollection
} else { }
else {
return { return {
visitCount: 0, visitCount: 0,
} }
} }
} }
export const usePageCollection = (): PageCollection => { export function usePageCollection(): PageCollection {
const collection = reactive({ const collection = reactive({
visitCount: 0, visitCount: 0,
}) })

View File

@ -1,4 +1,5 @@
import Collection from './components/PageCollection.js' import Collection from './components/PageCollection.js'
export * from '../shared/index.js' export * from '../shared/index.js'
export { Collection } export { Collection }

View File

@ -5,6 +5,7 @@
* {host}:{port}/{proxyPrefix}/page_collection * {host}:{port}/{proxyPrefix}/page_collection
*/ */
// 引入这个包来提供类型支持 // 引入这个包来提供类型支持
import process from 'node:process'
import type { Handler } from '@netlify/functions' import type { Handler } from '@netlify/functions'
import * as lean from 'leancloud-storage' import * as lean from 'leancloud-storage'
@ -24,11 +25,7 @@ interface ResponseRes {
message?: string message?: string
} }
const response = ( function response(code: number, message: string, data?: Record<string, any>): ResponseRes {
code: number,
message: string,
data?: Record<string, any>
): ResponseRes => {
return { return {
statusCode: 200, statusCode: 200,
body: JSON.stringify({ body: JSON.stringify({
@ -39,11 +36,11 @@ const response = (
} }
} }
const successRes = (data: Record<string, any>): ResponseRes => { function successRes(data: Record<string, any>): ResponseRes {
return response(200, 'success', data) return response(200, 'success', data)
} }
const errorRes = (message: string, code = 500): ResponseRes => { function errorRes(message: string, code = 500): ResponseRes {
return response(code, message) return response(code, message)
} }
@ -55,12 +52,11 @@ const useMasterKey = Boolean(process.env.LEAN_CLOUD_MASTER_KEY)
// netlify functions 的 functions 格式规范 // netlify functions 的 functions 格式规范
// 通过导出一个 handler 函数作为 钩子 // 通过导出一个 handler 函数作为 钩子
// event 中包含了 请求相关的信息,你可以通过 console.log(event) 查看具体信息 // event 中包含了 请求相关的信息,你可以通过 console.log(event) 查看具体信息
export const handler: Handler = async (event, context) => { export const handler: Handler = async (event) => {
// body 即为 请求体 // body 即为 请求体
const { url } = JSON.parse(event.body || '') || {} const { url } = JSON.parse(event.body || '') || {}
if (!url) { if (!url)
return errorRes('params [url] not found') return errorRes('params [url] not found')
}
// 出于安全考虑,你可能还需要在这里做 域名请求白名单的校验 // 出于安全考虑,你可能还需要在这里做 域名请求白名单的校验
// 可以在 event.headers 中拿到相关 域名信息 // 可以在 event.headers 中拿到相关 域名信息
@ -72,11 +68,12 @@ export const handler: Handler = async (event, context) => {
query.equalTo('url', url) query.equalTo('url', url)
const current = await query.first({ useMasterKey }) const current = await query.first({ useMasterKey })
let page: lean.Object let page: lean.Object
if (current) { if (current)
page = lean.Object.createWithoutData('Page', current.get('objectId')) page = lean.Object.createWithoutData('Page', current.get('objectId'))
} else {
else
page = new Page() page = new Page()
}
page.increment('visitCount', 1) page.increment('visitCount', 1)
page = await page.save(null, { page = await page.save(null, {
useMasterKey, useMasterKey,
@ -85,7 +82,8 @@ export const handler: Handler = async (event, context) => {
return successRes({ return successRes({
visitCount: page.get('visitCount'), visitCount: page.get('visitCount'),
}) })
} catch (e: any) { }
catch (e: any) {
return errorRes(e.message, e.code || e.status || e.statusCode) return errorRes(e.message, e.code || e.status || e.statusCode)
} }
} }

View File

@ -13,9 +13,7 @@ import type { PageCollectionOptions } from '../shared/index.js'
const __dirname = getDirname(import.meta.url) const __dirname = getDirname(import.meta.url)
export const pageCollectionPlugin = ( export function pageCollectionPlugin(_options: PageCollectionOptions = {}): Plugin {
options: PageCollectionOptions = {}
): Plugin => {
return (app: App) => { return (app: App) => {
const { const {
// 客户端发起 functions 请求时的代理前缀 // 客户端发起 functions 请求时的代理前缀

View File

@ -1 +1 @@
export type PageCollectionOptions = Record<string, unknown>; export type PageCollectionOptions = Record<string, unknown>

View File

@ -11,7 +11,7 @@ yarn add @vuepress-plume/plugin-shikiji
// .vuepress/config.js // .vuepress/config.js
const shikijiPlugin = require('@vuepress-plume/plugin-shikiji') const shikijiPlugin = require('@vuepress-plume/plugin-shikiji')
module.exports = { module.exports = {
//... // ...
plugins: [ plugins: [
shikijiPlugin() shikijiPlugin()
] ]
@ -63,5 +63,4 @@ interface ShikijiOptions {
*/ */
codeTransformers?: ShikijiTransformer[] codeTransformers?: ShikijiTransformer[]
} }
``` ```

View File

@ -1,18 +1,18 @@
{ {
"name": "@vuepress-plume/plugin-shikiji", "name": "@vuepress-plume/plugin-shikiji",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2", "description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme", "homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git" "url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
}, },
"license": "MIT", "bugs": {
"author": "pengzhanbo <volodymyr@foxmail.com>", "url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
"type": "module", },
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./package.json": "./package.json" "./package.json": "./package.json"

View File

@ -7,13 +7,13 @@ import {
bundledLanguages, bundledLanguages,
getHighlighter, getHighlighter,
isPlaintext as isPlainLang, isPlaintext as isPlainLang,
isSpecialLang isSpecialLang,
} from 'shikiji' } from 'shikiji'
import { import {
transformerNotationDiff, transformerNotationDiff,
transformerNotationErrorLevel, transformerNotationErrorLevel,
transformerNotationFocus, transformerNotationFocus,
transformerNotationHighlight transformerNotationHighlight,
} from 'shikiji-transformers' } from 'shikiji-transformers'
import type { HighlighterOptions, ThemeOptions } from './types.js' import type { HighlighterOptions, ThemeOptions } from './types.js'
@ -25,7 +25,7 @@ export async function highlight(
): Promise<(str: string, lang: string, attrs: string) => string> { ): Promise<(str: string, lang: string, attrs: string) => string> {
const { const {
defaultHighlightLang: defaultLang = '', defaultHighlightLang: defaultLang = '',
codeTransformers: userTransformers = [] codeTransformers: userTransformers = [],
} = options } = options
const highlighter = await getHighlighter({ const highlighter = await getHighlighter({
@ -34,7 +34,7 @@ export async function highlight(
? [theme] ? [theme]
: [theme.light, theme.dark], : [theme.light, theme.dark],
langs: [...Object.keys(bundledLanguages), ...(options.languages || [])], langs: [...Object.keys(bundledLanguages), ...(options.languages || [])],
langAlias: options.languageAlias langAlias: options.languageAlias,
}) })
await options?.shikijiSetup?.(highlighter) await options?.shikijiSetup?.(highlighter)
@ -43,7 +43,7 @@ export async function highlight(
transformerNotationDiff(), transformerNotationDiff(),
transformerNotationFocus({ transformerNotationFocus({
classActiveLine: 'has-focus', classActiveLine: 'has-focus',
classActivePre: 'has-focused-lines' classActivePre: 'has-focused-lines',
}), }),
transformerNotationHighlight(), transformerNotationHighlight(),
transformerNotationErrorLevel(), transformerNotationErrorLevel(),
@ -51,15 +51,15 @@ export async function highlight(
name: 'vuepress:add-class', name: 'vuepress:add-class',
pre(node) { pre(node) {
addClassToHast(node, 'vp-code') addClassToHast(node, 'vp-code')
} },
}, },
{ {
name: 'vuepress:clean-up', name: 'vuepress:clean-up',
pre(node) { pre(node) {
delete node.properties.tabindex delete node.properties.tabindex
delete node.properties.style delete node.properties.style
} },
} },
] ]
const vueRE = /-vue$/ const vueRE = /-vue$/
@ -69,8 +69,8 @@ export async function highlight(
return (str: string, lang: string, attrs: string) => { return (str: string, lang: string, attrs: string) => {
const vPre = vueRE.test(lang) ? '' : 'v-pre' const vPre = vueRE.test(lang) ? '' : 'v-pre'
lang = lang
lang = lang
.replace(lineNoStartRE, '') .replace(lineNoStartRE, '')
.replace(lineNoRE, '') .replace(lineNoRE, '')
.replace(vueRE, '') .replace(vueRE, '')
@ -82,8 +82,8 @@ export async function highlight(
logger.warn( logger.warn(
c.yellow( c.yellow(
`\nThe language '${lang}' is not loaded, falling back to '${defaultLang || 'txt' `\nThe language '${lang}' is not loaded, falling back to '${defaultLang || 'txt'
}' for syntax highlighting.` }' for syntax highlighting.`,
) ),
) )
lang = defaultLang lang = defaultLang
} }
@ -94,7 +94,8 @@ export async function highlight(
const mustaches = new Map<string, string>() const mustaches = new Map<string, string>()
const removeMustache = (s: string) => { const removeMustache = (s: string) => {
if (vPre) return s if (vPre)
return s
return s.replace(mustacheRE, (match) => { return s.replace(mustacheRE, (match) => {
let marker = mustaches.get(match) let marker = mustaches.get(match)
if (!marker) { if (!marker) {
@ -113,10 +114,10 @@ export async function highlight(
} }
const fillEmptyHighlightedLine = (s: string) => { const fillEmptyHighlightedLine = (s: string) => {
return s.replace( return `${s.replace(
/(<span class="line highlighted">)(<\/span>)/g, /(<span class="line highlighted">)(<\/span>)/g,
'$1<wbr>$2' '$1<wbr>$2',
).replace(/(\/\/\s*?\[)\\(!code.*?\])/g, '$1$2') + '\n' ).replace(/(\/\/\s*?\[)\\(!code.*?\])/g, '$1$2')}\n`
} }
str = removeMustache(str).trimEnd() str = removeMustache(str).trimEnd()
@ -125,17 +126,17 @@ export async function highlight(
lang, lang,
transformers: [ transformers: [
...transformers, ...transformers,
...userTransformers ...userTransformers,
], ],
meta: { meta: {
__raw: attrs __raw: attrs,
}, },
...(typeof theme === 'string' || 'name' in theme ...(typeof theme === 'string' || 'name' in theme
? { theme } ? { theme }
: { : {
themes: theme, themes: theme,
defaultColor: false defaultColor: false,
}) }),
}) })
return fillEmptyHighlightedLine(restoreMustache(highlighted)) return fillEmptyHighlightedLine(restoreMustache(highlighted))

View File

@ -1,19 +1,21 @@
import type { Plugin } from '@vuepress/core' import type { Plugin } from '@vuepress/core'
import { highlight } from './highlight.js' import { highlight } from './highlight.js'
import type { HighlighterOptions } from './types' import type { HighlighterOptions } from './types'
/** /**
* Options of @vuepress/plugin-shiki * Options of @vuepress/plugin-shiki
*/ */
export type ShikijiPluginOptions = HighlighterOptions export type ShikijiPluginOptions = HighlighterOptions
export const shikijiPlugin = ( export function shikijiPlugin(options: ShikijiPluginOptions = {}): Plugin {
options: ShikijiPluginOptions = {}): Plugin => ({ return {
name: '@vuepress-plume/plugin-shikiji', name: '@vuepress-plume/plugin-shikiji',
extendsMarkdown: async (md) => { extendsMarkdown: async (md) => {
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' } const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
const highlighter = await highlight(theme, options) const highlighter = await highlight(theme, options)
md.options.highlight = highlighter md.options.highlight = highlighter
}, },
}) }
}

View File

@ -5,6 +5,7 @@ import type {
ShikijiTransformer, ShikijiTransformer,
ThemeRegistration, ThemeRegistration,
} from 'shikiji' } from 'shikiji'
export type ThemeOptions = export type ThemeOptions =
| ThemeRegistration | ThemeRegistration
| BuiltinTheme | BuiltinTheme

View File

@ -3,7 +3,6 @@
"compilerOptions": { "compilerOptions": {
"composite": true "composite": true
}, },
"files": [],
"references": [ "references": [
{ "path": "./plugin-auto-frontmatter/tsconfig.build.json" }, { "path": "./plugin-auto-frontmatter/tsconfig.build.json" },
{ "path": "./plugin-baidu-tongji/tsconfig.build.json" }, { "path": "./plugin-baidu-tongji/tsconfig.build.json" },
@ -15,5 +14,6 @@
{ "path": "./plugin-notes-data/tsconfig.build.json" }, { "path": "./plugin-notes-data/tsconfig.build.json" },
{ "path": "./plugin-page-collection/tsconfig.build.json" }, { "path": "./plugin-page-collection/tsconfig.build.json" },
{ "path": "./plugin-shikiji/tsconfig.build.json" } { "path": "./plugin-shikiji/tsconfig.build.json" }
] ],
"files": []
} }

1441
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@
[![npm download](https://img.shields.io/npm/dy/vuepress-theme-plume?color=32A9C3&labelColor=1B3C4A&label=downloads)](https://www.npmjs.com/package/vuepress-theme-plume) [![npm download](https://img.shields.io/npm/dy/vuepress-theme-plume?color=32A9C3&labelColor=1B3C4A&label=downloads)](https://www.npmjs.com/package/vuepress-theme-plume)
![GitHub License](https://img.shields.io/github/license/pengzhanbo/vuepress-theme-plume?color=32A9C3&labelColor=1B3C4A) ![GitHub License](https://img.shields.io/github/license/pengzhanbo/vuepress-theme-plume?color=32A9C3&labelColor=1B3C4A)
### [查看文档](https://pengzhanbo.cn/note/vuepress-theme-plume) ### [查看文档](https://pengzhanbo.cn/note/vuepress-theme-plume)
## Install ## Install
@ -54,7 +53,6 @@ __options__ : `PlumeThemeOptions`
![](/docs/preview-post.png?a=1) ![](/docs/preview-post.png?a=1)
![](/docs/preview-note.png?a=1) ![](/docs/preview-note.png?a=1)
## 内置插件 ## 内置插件

View File

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

View File

@ -1,18 +1,18 @@
import { readTemplateList } from './readTpl.js' import path from 'node:path'
import path from 'path' import fs from 'node:fs'
import fs from 'fs' import { fileURLToPath } from 'node:url'
import { upperCase, lowerCase, packageName } from './utils.js'
import handlebars from 'handlebars' import handlebars from 'handlebars'
import { writeFile } from './writeFile.js'
import chalk from 'chalk' import chalk from 'chalk'
import ora from 'ora' import ora from 'ora'
import { execa } from 'execa' import { execa } from 'execa'
import { fileURLToPath } from 'url' import { readTemplateList } from './readTpl.js'
import { lowerCase, packageName, upperCase } from './utils.js'
import { writeFile } from './writeFile.js'
const compile = handlebars.compile const compile = handlebars.compile
const _dirname = const _dirname
typeof __dirname !== 'undefined' = typeof __dirname !== 'undefined'
? __dirname ? __dirname
: path.dirname(fileURLToPath(import.meta.url)) : path.dirname(fileURLToPath(import.meta.url))
@ -20,10 +20,10 @@ const packagesRoot = path.join(_dirname, '../../packages')
const spinner = ora() const spinner = ora()
const pkg = JSON.parse( const pkg = JSON.parse(
fs.readFileSync(path.join(_dirname, '../../package.json'), 'utf-8') fs.readFileSync(path.join(_dirname, '../../package.json'), 'utf-8'),
) )
const generatorFile = async (config) => { async function generatorFile(config) {
const templateList = readTemplateList(path.join(_dirname, './template')) const templateList = readTemplateList(path.join(_dirname, './template'))
const { name, client, shared } = config const { name, client, shared } = config
@ -40,7 +40,7 @@ const generatorFile = async (config) => {
const include = [ const include = [
!client && 'client', !client && 'client',
!shared && 'shared', !shared && 'shared',
!shared && 'client/index.js' !shared && 'client/index.js',
] ]
.filter(Boolean) .filter(Boolean)
.join('|') .join('|')
@ -60,7 +60,8 @@ const generatorFile = async (config) => {
try { try {
const filepath = path.join(targetDir, file) const filepath = path.join(targetDir, file)
await writeFile(filepath, template(data)) await writeFile(filepath, template(data))
} catch (e) { }
catch (e) {
spinner.fail(`Failed to generate ${chalk.cyan(pkgName)}`) spinner.fail(`Failed to generate ${chalk.cyan(pkgName)}`)
throw e throw e
} }
@ -68,7 +69,7 @@ const generatorFile = async (config) => {
spinner.succeed(`${chalk.cyan(pkgName)} generated !`) spinner.succeed(`${chalk.cyan(pkgName)} generated !`)
} }
const initPackage = async (config) => { async function initPackage(config) {
const { name, client } = config const { name, client } = config
const pkgName = packageName(name) const pkgName = packageName(name)
const targetDir = path.join(packagesRoot, pkgName) const targetDir = path.join(packagesRoot, pkgName)
@ -83,13 +84,14 @@ const initPackage = async (config) => {
try { try {
await execa('pnpm', ['add', ...dependencies], { cwd: targetDir }) await execa('pnpm', ['add', ...dependencies], { cwd: targetDir })
spinner.succeed('Installed.') spinner.succeed('Installed.')
} catch (e) { }
catch (e) {
spinner.fail('Failed to Installed') spinner.fail('Failed to Installed')
throw e throw e
} }
} }
export const generator = async (config) => { export async function generator(config) {
await generatorFile(config) await generatorFile(config)
await initPackage(config) await initPackage(config)
} }

View File

@ -1,3 +1,4 @@
import process from 'node:process'
import minimist from 'minimist' import minimist from 'minimist'
const defaultOptions = { const defaultOptions = {
@ -6,23 +7,23 @@ const defaultOptions = {
c: false, c: false,
client: false, client: false,
h: false, h: false,
help: false help: false,
} }
const normalizeArgv = (argv) => { function normalizeArgv(argv) {
return { return {
name: argv._[0] || '', name: argv._[0] || '',
client: argv.client || argv.c, client: argv.client || argv.c,
shared: argv.shared || argv.s, shared: argv.shared || argv.s,
help: argv.h || argv.help help: argv.h || argv.help,
} }
} }
export const getConfig = () => { export function getConfig() {
const argv = Object.assign( const argv = Object.assign(
{}, {},
defaultOptions, defaultOptions,
minimist(process.argv.slice(2)) minimist(process.argv.slice(2)),
) )
return normalizeArgv(argv) return normalizeArgv(argv)
} }

View File

@ -1,6 +1,6 @@
import chalk from 'chalk' import chalk from 'chalk'
export const getHelp = () => { export function getHelp() {
console.log(` this command will generator a package to ${chalk.cyan('packages')}. console.log(` this command will generator a package to ${chalk.cyan('packages')}.
command: ${chalk.green('pnpm pkg <package-name> [--options]')} command: ${chalk.green('pnpm pkg <package-name> [--options]')}

View File

@ -1,3 +1,4 @@
import process from 'node:process'
import { getConfig } from './getConfig.js' import { getConfig } from './getConfig.js'
import { getHelp } from './getHelp.js' import { getHelp } from './getHelp.js'
import { generator } from './generator.js' import { generator } from './generator.js'
@ -7,7 +8,8 @@ const config = getConfig()
if (config.help) { if (config.help) {
getHelp() getHelp()
process.exit(0) process.exit(0)
} else { }
else {
generator(config).catch((err) => { generator(config).catch((err) => {
console.error(err) console.error(err)
process.exit(1) process.exit(1)

View File

@ -1,32 +1,32 @@
import fs from 'fs' import fs from 'node:fs'
import path from 'path' import path from 'node:path'
const tplRE = /\.tpl$/ const tplRE = /\.tpl$/
const readFileList = (dir, fileList = {}) => { function readFileList(dir, fileList = {}) {
const files = fs.readdirSync(dir) const files = fs.readdirSync(dir)
files.forEach((file) => { files.forEach((file) => {
const filepath = path.join(dir, file) const filepath = path.join(dir, file)
const stat = fs.statSync(filepath) const stat = fs.statSync(filepath)
if (stat.isDirectory()) { if (stat.isDirectory()) {
readFileList(filepath, fileList) readFileList(filepath, fileList)
} else { }
else {
const extname = path.extname(filepath) const extname = path.extname(filepath)
if (tplRE.test(extname)) { if (tplRE.test(extname))
fileList[filepath.replace(tplRE, '')] = fs.readFileSync(filepath, 'utf-8') fileList[filepath.replace(tplRE, '')] = fs.readFileSync(filepath, 'utf-8')
}
} }
}) })
return fileList return fileList
} }
export const readTemplateList = (dir) => { export function readTemplateList(dir) {
const templateMap= readFileList(dir) const templateMap = readFileList(dir)
const result = [] const result = []
Object.keys(templateMap).forEach((key) => { Object.keys(templateMap).forEach((key) => {
const file = path.relative(dir, key) const file = path.relative(dir, key)
result.push({ result.push({
file, file,
content: templateMap[key] content: templateMap[key],
}) })
}) })
return result return result

View File

@ -1,14 +1,14 @@
export const upperCase = (str) => { export function upperCase(str) {
return str.split(/-|\s+/).filter(Boolean).map((s) => { return str.split(/-|\s+/).filter(Boolean).map((s) => {
return s[0].toUpperCase() + s.slice(1) return s[0].toUpperCase() + s.slice(1)
}).join('') }).join('')
} }
export const lowerCase = (str) => { export function lowerCase(str) {
str = upperCase(str) str = upperCase(str)
return str[0].toLowerCase() + str.slice(1) return str[0].toLowerCase() + str.slice(1)
} }
export const packageName = (name) => { export function packageName(name) {
return 'plugin-' + name.trim().split(/-|\s+/).filter(Boolean).join('-') return `plugin-${name.trim().split(/-|\s+/).filter(Boolean).join('-')}`
} }

View File

@ -1,18 +1,17 @@
import path from 'path' import path from 'node:path'
import fs from 'fs' import fs from 'node:fs'
export const writeFile = async (filepath, content) => { export async function writeFile(filepath, content) {
const dirname = path.dirname(filepath) const dirname = path.dirname(filepath)
if (!fs.existsSync(dirname)) { if (!fs.existsSync(dirname))
fs.mkdirSync(dirname, { recursive: true }) fs.mkdirSync(dirname, { recursive: true })
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.writeFile(filepath, content, 'utf-8', (err) => { fs.writeFile(filepath, content, 'utf-8', (err) => {
if (err) { if (err)
reject(err) reject(err)
} else { else
resolve() resolve()
}
}) })
}) })
} }

View File

@ -1,7 +1,18 @@
{ {
"name": "vuepress-theme-plume", "name": "vuepress-theme-plume",
"type": "module",
"version": "1.0.0-rc.5", "version": "1.0.0-rc.5",
"description": "A Blog Theme for VuePress 2.0", "description": "A Blog Theme for VuePress 2.0",
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
"license": "MIT",
"homepage": "https://pengzhanbo.cn/note/vuepress-theme-plume",
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
},
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"keywords": [ "keywords": [
"VuePress", "VuePress",
"Theme", "Theme",
@ -10,17 +21,6 @@
"vuepress-theme-plume", "vuepress-theme-plume",
"theme-plume" "theme-plume"
], ],
"homepage": "https://pengzhanbo.cn/note/vuepress-theme-plume",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
},
"license": "MIT",
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
"type": "module",
"exports": { "exports": {
".": "./lib/node/index.js", ".": "./lib/node/index.js",
"./client": "./lib/client/index.js", "./client": "./lib/client/index.js",

View File

@ -1,12 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useArchives, useBlogExtract } from '../composables/index.js' import { useArchives, useBlogExtract } from '../composables/index.js'
import IconArchive from './icons/IconArchive.vue' import IconArchive from './icons/IconArchive.vue'
import ShortPostList from './ShortPostList.vue'; import ShortPostList from './ShortPostList.vue'
const { archives: archivesLink } = useBlogExtract() const { archives: archivesLink } = useBlogExtract()
const { archives } = useArchives() const { archives } = useArchives()
</script> </script>
<template> <template>
<div class="archives-wrapper"> <div class="archives-wrapper">
<h2 class="archives-title"> <h2 class="archives-title">
@ -16,7 +16,9 @@ const { archives } = useArchives()
<div v-if="archives.length" class="archives"> <div v-if="archives.length" class="archives">
<template v-for="archive in archives" :key="archive.label"> <template v-for="archive in archives" :key="archive.label">
<div class="archive"> <div class="archive">
<h3 class="archive-title">{{ archive.label }}</h3> <h3 class="archive-title">
{{ archive.label }}
</h3>
<ShortPostList :post-list="archive.list" /> <ShortPostList :post-list="archive.list" />
</div> </div>
</template> </template>

View File

@ -16,15 +16,14 @@ const router = useRouter()
const tag = computed(() => props.tag ?? (props.href ? 'a' : 'span')) const tag = computed(() => props.tag ?? (props.href ? 'a' : 'span'))
const isExternal = computed( const isExternal = computed(
() => props.href && EXTERNAL_URL_RE.test(props.href) () => props.href && EXTERNAL_URL_RE.test(props.href),
) )
const linkTo = (e: Event) => { function linkTo(e: Event) {
if (!isExternal.value) { if (!isExternal.value) {
e.preventDefault() e.preventDefault()
if (props.href) { if (props.href)
router.push({ path: props.href }) router.push({ path: props.href })
}
} }
} }
</script> </script>

View File

@ -8,8 +8,8 @@ import PostList from './PostList.vue'
import Tags from './Tags.vue' import Tags from './Tags.vue'
const page = usePageData<PlumeThemePageData>() const page = usePageData<PlumeThemePageData>()
</script> </script>
<template> <template>
<div class="blog-wrapper"> <div class="blog-wrapper">
<PostList v-if="page.type === 'blog'" /> <PostList v-if="page.type === 'blog'" />

View File

@ -5,7 +5,6 @@ import AutoLink from './AutoLink.vue'
import IconArchive from './icons/IconArchive.vue' import IconArchive from './icons/IconArchive.vue'
import IconTag from './icons/IconTag.vue' import IconTag from './icons/IconTag.vue'
const theme = useThemeLocaleData() const theme = useThemeLocaleData()
const avatar = computed(() => theme.value.avatar) const avatar = computed(() => theme.value.avatar)
@ -16,7 +15,7 @@ const { hasBlogExtract, tags, archives } = useBlogExtract()
<div v-if="avatar" class="blog-aside-wrapper"> <div v-if="avatar" class="blog-aside-wrapper">
<div class="avatar-profile"> <div class="avatar-profile">
<p v-if="avatar.url"> <p v-if="avatar.url">
<img :src="avatar.url" :alt="avatar.name" /> <img :src="avatar.url" :alt="avatar.name">
</p> </p>
<div> <div>
<h3>{{ avatar.name }}</h3> <h3>{{ avatar.name }}</h3>

View File

@ -9,7 +9,6 @@ import IconArchive from './icons/IconArchive.vue'
import IconBlogExt from './icons/IconBlogExt.vue' import IconBlogExt from './icons/IconBlogExt.vue'
import IconTag from './icons/IconTag.vue' import IconTag from './icons/IconTag.vue'
const theme = useThemeLocaleData() const theme = useThemeLocaleData()
const route = useRoute() const route = useRoute()
@ -26,17 +25,19 @@ watch(() => route.path, () => {
watch( watch(
[() => open.value], [() => open.value],
() => { () => {
if (open.value) { if (open.value)
isLocked.value = true isLocked.value = true
} else isLocked.value = false
else isLocked.value = false
}, },
{ immediate: true, flush: 'post' } { immediate: true, flush: 'post' },
) )
const showBlogExtract = computed(() => { const showBlogExtract = computed(() => {
return avatar.value || hasBlogExtract.value return avatar.value || hasBlogExtract.value
}) })
</script> </script>
<template> <template>
<div v-if="showBlogExtract" class="blog-extract" @click="open = !open"> <div v-if="showBlogExtract" class="blog-extract" @click="open = !open">
<IconBlogExt class="icon" /> <IconBlogExt class="icon" />
@ -45,11 +46,13 @@ const showBlogExtract = computed(() => {
<div class="blog-modal-container"> <div class="blog-modal-container">
<div v-if="avatar" class="avatar-profile"> <div v-if="avatar" class="avatar-profile">
<p v-if="avatar.url" class="avatar"> <p v-if="avatar.url" class="avatar">
<img :src="avatar.url" :alt="avatar.name" /> <img :src="avatar.url" :alt="avatar.name">
</p> </p>
<div> <div>
<h3>{{ avatar.name }}</h3> <h3>{{ avatar.name }}</h3>
<p class="desc">{{ avatar.description }}</p> <p class="desc">
{{ avatar.description }}
</p>
</div> </div>
</div> </div>
<div v-if="hasBlogExtract" class="blog-nav"> <div v-if="hasBlogExtract" class="blog-nav">

View File

@ -12,7 +12,7 @@ defineProps<{
<div class="menu-group"> <div class="menu-group">
<p v-if="text" class="title"> <p v-if="text" class="title">
<Icon v-if="icon" :name="icon" /> <Icon v-if="icon" :name="icon" />
<span v-text="text"></span> <span v-text="text" />
</p> </p>
<template v-for="item in items"> <template v-for="item in items">

View File

@ -17,13 +17,13 @@ const page = usePageData()
active: isActive( active: isActive(
page.path, page.path,
item.activeMatch || item.link, item.activeMatch || item.link,
!!item.activeMatch !!item.activeMatch,
), ),
}" }"
:href="item.link" :href="item.link"
> >
<Icon v-if="item.icon" :name="item.icon" /> <Icon v-if="item.icon" :name="item.icon" />
<i v-text="item.text"></i> <i v-text="item.text" />
</AutoLink> </AutoLink>
</div> </div>
</template> </template>

View File

@ -25,7 +25,7 @@ function onBlur() {
<script lang="ts"> <script lang="ts">
export default { export default {
// eslint-disable-next-line vue/match-component-file-name
name: 'Flyout', name: 'Flyout',
} }
</script> </script>

View File

@ -7,30 +7,33 @@ import AutoLink from './AutoLink.vue'
import FriendsItem from './FriendsItem.vue' import FriendsItem from './FriendsItem.vue'
import IconEdit from './icons/IconEdit.vue' import IconEdit from './icons/IconEdit.vue'
const matter = usePageFrontmatter<PlumeThemeFriendsFrontmatter>() const matter = usePageFrontmatter<PlumeThemeFriendsFrontmatter>()
const editNavLink = useEditNavLink() const editNavLink = useEditNavLink()
const list = computed(() => matter.value.list || []) const list = computed(() => matter.value.list || [])
</script> </script>
<template> <template>
<div class="friends-wrapper"> <div class="friends-wrapper">
<h2 class="title">{{ matter.title || 'My Friends' }}</h2> <h2 class="title">
<p v-if="matter.description" class="description">{{ matter.description }}</p> {{ matter.title || 'My Friends' }}
</h2>
<p v-if="matter.description" class="description">
{{ matter.description }}
</p>
<section v-if="list.length" class="friends-list"> <section v-if="list.length" class="friends-list">
<FriendsItem v-for="(friend, index) in list" :key="friend.name + index" :friend="friend" /> <FriendsItem v-for="(friend, index) in list" :key="friend.name + index" :friend="friend" />
</section> </section>
<div v-if="editNavLink" class="edit-link"> <div v-if="editNavLink" class="edit-link">
<AutoLink class="edit-link-button" :href="editNavLink.link" :no-icon="true"> <AutoLink class="edit-link-button" :href="editNavLink.link" :no-icon="true">
<IconEdit class="edit-link-icon" aria-label="edit icon"/> <IconEdit class="edit-link-icon" aria-label="edit icon" />
{{ editNavLink.text }} {{ editNavLink.text }}
</AutoLink> </AutoLink>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.friends-wrapper { .friends-wrapper {
width: 100%; width: 100%;

View File

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type {FriendsItem} from '../../shared/index'; import type { FriendsItem } from '../../shared/index'
import AutoLink from './AutoLink.vue' import AutoLink from './AutoLink.vue'
defineProps<{ defineProps<{
@ -10,11 +10,15 @@ defineProps<{
<template> <template>
<div class="friend"> <div class="friend">
<AutoLink class="avatar-link" :href="friend.link" no-icon> <AutoLink class="avatar-link" :href="friend.link" no-icon>
<div class="avatar" :style="{ backgroundImage: `url(${friend.avatar})` }"></div> <div class="avatar" :style="{ backgroundImage: `url(${friend.avatar})` }" />
</AutoLink> </AutoLink>
<div class="content"> <div class="content">
<AutoLink class="title" :href="friend.link" no-icon>{{ friend.name }}</AutoLink> <AutoLink class="title" :href="friend.link" no-icon>
<p v-if="friend.desc">{{ friend.desc }}</p> {{ friend.name }}
</AutoLink>
<p v-if="friend.desc">
{{ friend.desc }}
</p>
</div> </div>
</div> </div>
</template> </template>

View File

@ -2,6 +2,7 @@
import { usePageFrontmatter, withBase } from '@vuepress/client' import { usePageFrontmatter, withBase } from '@vuepress/client'
import { computed } from 'vue' import { computed } from 'vue'
import type { PlumeThemeHomeFrontmatter } from '../../shared/index.js' import type { PlumeThemeHomeFrontmatter } from '../../shared/index.js'
// import { useThemeLocaleData } from '../composables/index.js' // import { useThemeLocaleData } from '../composables/index.js'
import { useDarkMode } from '../composables/darkMode.js' import { useDarkMode } from '../composables/darkMode.js'
import VButton from './VButton.vue' import VButton from './VButton.vue'
@ -10,9 +11,9 @@ const matter = usePageFrontmatter<PlumeThemeHomeFrontmatter>()
const isDark = useDarkMode() const isDark = useDarkMode()
const mask = computed(() => { const mask = computed(() => {
if (typeof matter.value.bannerMask !== 'object') { if (typeof matter.value.bannerMask !== 'object')
return matter.value.bannerMask || 0 return matter.value.bannerMask || 0
}
return ( return (
(isDark.value (isDark.value
? matter.value.bannerMask.dark ? matter.value.bannerMask.dark
@ -41,15 +42,20 @@ const actions = computed(() => {
return matter.value.hero?.actions ?? [] return matter.value.hero?.actions ?? []
}) })
</script> </script>
<template> <template>
<div class="plume-home" :style="homeStyle"> <div class="plume-home" :style="homeStyle">
<div class="container"> <div class="container">
<div v-if="matter.hero" class="content"> <div v-if="matter.hero" class="content">
<h2 v-if="name" class="hero-name">{{ name }}</h2> <h2 v-if="name" class="hero-name">
{{ name }}
</h2>
<p v-if="tagline" class="hero-tagline"> <p v-if="tagline" class="hero-tagline">
<span class="line"></span> <span>{{ tagline }}</span> <span class="line" /> <span>{{ tagline }}</span>
</p>
<p v-if="text" class="hero-text">
{{ text }}
</p> </p>
<p v-if="text" class="hero-text">{{ text }}</p>
<div v-if="actions" class="actions"> <div v-if="actions" class="actions">
<div v-for="action in actions" :key="action.link" class="action"> <div v-for="action in actions" :key="action.link" class="action">
<VButton <VButton

View File

@ -7,6 +7,7 @@ const props = defineProps<{
const { hasSidebar } = useSidebar() const { hasSidebar } = useSidebar()
</script> </script>
<template> <template>
<div <div
id="LayoutContent" id="LayoutContent"

View File

@ -13,7 +13,7 @@ defineProps<{
open: boolean open: boolean
}>() }>()
defineEmits<(e: 'open-menu') => void>() defineEmits<(e: 'openMenu') => void>()
const page = usePageData<PlumeThemePageData>() const page = usePageData<PlumeThemePageData>()
const themeData = useThemeLocaleData() const themeData = useThemeLocaleData()
@ -29,36 +29,35 @@ const empty = computed(() => {
}) })
onMounted(() => { onMounted(() => {
navHeight.value = parseInt( navHeight.value = Number.parseInt(
getComputedStyle(document.documentElement).getPropertyValue( getComputedStyle(document.documentElement).getPropertyValue(
'--vp-nav-height' '--vp-nav-height',
) ),
) )
}) })
const classes = computed(() => { const classes = computed(() => {
return { return {
'local-nav': true, 'local-nav': true,
fixed: empty.value, 'fixed': empty.value,
'reached-top': y.value >= navHeight.value 'reached-top': y.value >= navHeight.value,
} }
}) })
const showLocalNav = computed(() => { const showLocalNav = computed(() => {
return (hasSidebar.value || page.value.isBlogPost) && (!empty.value || y.value >= navHeight.value) return (hasSidebar.value || page.value.isBlogPost) && (!empty.value || y.value >= navHeight.value)
}) })
</script> </script>
<template> <template>
<div v-if="showLocalNav" :class="classes"> <div v-if="showLocalNav" :class="classes">
<button <button
class="menu" class="menu"
:class="{ 'hidden': page.isBlogPost }" :class="{ hidden: page.isBlogPost }"
:disabled="page.isBlogPost" :disabled="page.isBlogPost"
:aria-expanded="open" :aria-expanded="open"
aria-controls="SidebarNav" aria-controls="SidebarNav"
@click="$emit('open-menu')" @click="$emit('openMenu')"
> >
<IconAlignLeft class="menu-icon" /> <IconAlignLeft class="menu-icon" />
<span class="menu-text"> {{ themeData.sidebarMenuLabel || 'Menu' }} </span> <span class="menu-text"> {{ themeData.sidebarMenuLabel || 'Menu' }} </span>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type {PageHeader} from '@vuepress/client'; import type { PageHeader } from '@vuepress/client'
import {onClickOutside} from '@vueuse/core' import { onClickOutside } from '@vueuse/core'
import { nextTick, ref, watch } from 'vue' import { nextTick, ref, watch } from 'vue'
import { useThemeLocaleData } from '../composables/index.js' import { useThemeLocaleData } from '../composables/index.js'
import DocOutlineItem from './DocOutlineItem.vue' import DocOutlineItem from './DocOutlineItem.vue'
@ -33,9 +33,9 @@ function toggle() {
function onItemClick(e: Event) { function onItemClick(e: Event) {
if ((e.target as HTMLElement).classList.contains('outline-link')) { if ((e.target as HTMLElement).classList.contains('outline-link')) {
// disable animation on hash navigation when page jumps // disable animation on hash navigation when page jumps
if (items.value) { if (items.value)
items.value.style.transition = 'none' items.value.style.transition = 'none'
}
nextTick(() => { nextTick(() => {
open.value = false open.value = false
}) })
@ -49,7 +49,7 @@ function scrollToTop() {
</script> </script>
<template> <template>
<div class="local-nav-outline-dropdown" :style="{ '--vp-vh': vh + 'px' }"> <div class="local-nav-outline-dropdown" :style="{ '--vp-vh': `${vh}px` }">
<button v-if="headers.length > 0" ref="btn" :class="{ open }" @click="toggle"> <button v-if="headers.length > 0" ref="btn" :class="{ open }" @click="toggle">
{{ theme.outlineLabel || 'On this page' }} {{ theme.outlineLabel || 'On this page' }}
<IconChevronRight class="icon" /> <IconChevronRight class="icon" />

View File

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { usePageFrontmatter, } from '@vuepress/client' import { usePageFrontmatter } from '@vuepress/client'
import { useWindowScroll } from '@vueuse/core' import { useWindowScroll } from '@vueuse/core'
import { ref, watchPostEffect } from 'vue' import { ref, watchPostEffect } from 'vue'
import { useSidebar } from '../../composables/sidebar.js' import { useSidebar } from '../../composables/sidebar.js'
@ -15,7 +15,7 @@ import NavBarTranslations from './NavBarTranslations.vue'
defineProps<{ defineProps<{
isScreenOpen: boolean isScreenOpen: boolean
}>() }>()
defineEmits<(e: 'toggle-screen') => void>() defineEmits<(e: 'toggleScreen') => void>()
const matter = usePageFrontmatter() const matter = usePageFrontmatter()
@ -26,7 +26,7 @@ const classes = ref<Record<string, boolean>>({})
watchPostEffect(() => { watchPostEffect(() => {
classes.value = { classes.value = {
'has-sidebar': hasSidebar.value, 'has-sidebar': hasSidebar.value,
top: !!matter.value.home && y.value === 0, 'top': !!matter.value.home && y.value === 0,
} }
}) })
</script> </script>
@ -39,7 +39,7 @@ watchPostEffect(() => {
</div> </div>
<div class="content"> <div class="content">
<div class="curtain"></div> <div class="curtain" />
<div class="content-body"> <div class="content-body">
<NavBarSearch class="search" /> <NavBarSearch class="search" />
<NavBarMenu class="menu" /> <NavBarMenu class="menu" />
@ -50,7 +50,7 @@ watchPostEffect(() => {
<NavBarHamburger <NavBarHamburger
class="hamburger" class="hamburger"
:active="isScreenOpen" :active="isScreenOpen"
@click="$emit('toggle-screen')" @click="$emit('toggleScreen')"
/> />
</div> </div>
</div> </div>
@ -68,7 +68,6 @@ watchPostEffect(() => {
white-space: nowrap; white-space: nowrap;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.navbar-wrapper { .navbar-wrapper {
padding: 0 32px; padding: 0 32px;

Some files were not shown because too many files have changed in this diff Show More