Merge pull request #29 from pengzhanbo/rc-6

RC-6
This commit is contained in:
pengzhanbo 2023-12-27 14:33:51 +08:00 committed by GitHub
commit 5a923fdf49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
190 changed files with 2004 additions and 2225 deletions

View File

@ -1,5 +1,5 @@
const fs = require('fs')
const path = require('path')
const fs = require('node:fs')
const path = require('node:path')
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
},
"typescript.tsdk": "node_modules/typescript/lib",
"eslint.experimental.useFlatConfig": false,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue"
],
"cSpell.words": [
"bumpp",
"caniuse",
@ -35,6 +27,7 @@
"portfinder",
"shiki",
"shikiji",
"taze",
"Tongji",
"tsbuildinfo",
"vite",

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",
"type": "module",
"version": "1.0.0-rc.5",
"private": true,
"packageManager": "pnpm@8.12.1",
"author": "pengzhanbo",
"license": "MIT",
"keywords": [
"vuepress",
"vuepress-next",
@ -9,9 +13,10 @@
"vuepress theme",
"vuepress-theme-plume"
],
"license": "MIT",
"author": "pengzhanbo",
"type": "module",
"engines": {
"node": ">=16",
"pnpm": ">=7"
},
"scripts": {
"autoUpdate": "node scripts/autoInstall.js",
"build": "pnpm run build:package",
@ -24,7 +29,7 @@
"docs:build": "pnpm --filter=docs docs:build",
"docs:clean": "pnpm --filter=docs docs:clean",
"docs:serve": "pnpm --filter=docs docs:serve",
"lint": "eslint --ext .js,.ts,.vue .",
"lint": "eslint .",
"pkg": "node scripts/create/index.js",
"prepare": "husky install",
"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",
"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": {
"@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3",
"@pengzhanbo/eslint-config-vue": "^1.4.0",
"@types/minimist": "^1.2.5",
"@types/node": "20.9.1",
"@types/webpack-env": "^1.18.4",
@ -60,8 +55,7 @@
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.56.0",
"eslint-config-vuepress": "^4.9.0",
"eslint-config-vuepress-typescript": "^4.9.0",
"eslint-plugin-format": "^0.1.0",
"execa": "^8.0.1",
"handlebars": "^4.7.8",
"husky": "^8.0.3",
@ -69,8 +63,6 @@
"minimist": "^1.2.8",
"ora": "^8.0.1",
"pnpm": "^8.12.1",
"prettier": "^3.1.1",
"prettier-config-vuepress": "^4.4.0",
"rimraf": "^5.0.5",
"sort-package-json": "^2.6.0",
"taze": "^0.13.0",
@ -78,11 +70,6 @@
"typescript": "^5.3.3",
"vite": "^5.0.10"
},
"packageManager": "pnpm@8.12.1",
"engines": {
"node": ">=16",
"pnpm": ">=7"
},
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
@ -99,5 +86,13 @@
"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
``` js
// .vuepress/config.js
import { autoFrontmatterPlugin } from '@vuepress-plume/plugin-auto-frontmatter'
import { autoFrontmatterPlugin } from '@vuepress-plume/plugin-auto-frontmatter'
export default {
//...
// ...
plugins: [
autoFrontmatterPlugin({
formatter: {
createTime(formatTime, file, matter) {
if (formatTime) return formatTime
if (formatTime)
return formatTime
return file.createTime
}
}
@ -32,7 +33,7 @@ export default {
`{ include?: string | string[]; exclude?: string | string[]; formatter: Formatter }`
- `include`
- `include`
include 匹配字符串或数组,匹配需要自动生成 `frontmatter` 的 md文件。
默认预设为 `['**/*.md']`
@ -70,14 +71,15 @@ export default {
/**
* formatterObj 对象中的 key 即为 frontmatter 配置中的key
* 其方法返回的值将作为 frontmatter[key] 的值
* *.md
* .md
* ---
* createTime: 2022-03-26T11:46:50.000Z
* ---
*/
const formatterObj: Formatter = {
const formatterObj: Formatter = {
createTime(formatTime, file, matter) {
if (formatTime) return formatTime
if (formatTime)
return formatTime
return file.createTime
}
}
@ -92,25 +94,24 @@ export default {
return value
}
},
{
// 通配如果文件没有被其他精细glob命中
// 则使用 通配 formatter
// 如果是数组,必须有且用一个 include 为 * 的 项
include: '*',
formatter: {
title(title) {
return title || '默认标题'
}
},
{
// 通配如果文件没有被其他精细glob命中
// 则使用 通配 formatter
// 如果是数组,必须有且用一个 include 为 * 的 项
include: '*',
formatter: {
title(title) {
return title || '默认标题'
}
}
}
]
```
```
## Why ?
- **为什么需要这个插件?**
有时候在开发一些主题时,期望使用户更专注于内容的编写,尽可能减少配置性的工作,可以将一些重复性的必要的配置
直接通过本插件自动生成。

View File

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

View File

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

View File

@ -5,24 +5,18 @@ import type { MarkdownFile } from '../shared/index.js'
type MarkdownFileList = MarkdownFile[]
export const readMarkdownList = async (
sourceDir: string,
filter: (id: string) => boolean
): Promise<MarkdownFileList> => {
export async function readMarkdownList(sourceDir: string, filter: (id: string) => boolean): Promise<MarkdownFileList> {
const files: string[] = await fg(['**/*.md'], {
cwd: sourceDir,
ignore: ['node_modules', '.vuepress'],
})
return files
.filter((file) => filter(file))
.map((file) => readMarkdown(sourceDir, file))
.filter(file => filter(file))
.map(file => readMarkdown(sourceDir, file))
}
export const readMarkdown = (
sourceDir: string,
relativePath: string
): MarkdownFile => {
export function readMarkdown(sourceDir: string, relativePath: string): MarkdownFile {
const filepath = path.join(sourceDir, relativePath)
const stats = fs.statSync(filepath)
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
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,18 @@
{
"name": "@vuepress-plume/plugin-baidu-tongji",
"type": "module",
"version": "1.0.0-rc.5",
"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",
"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> (https://github.com/pengzhanbo/)",
"type": "module",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"exports": {
".": "./lib/node/index.js",
"./package.json": "./package.json"

View File

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

View File

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

View File

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

View File

@ -1,18 +1,18 @@
{
"name": "@vuepress-plume/plugin-blog-data",
"type": "module",
"version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"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",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"exports": {
".": "./lib/node/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 useBlogPostData = <
T extends BlogPostData = BlogPostData
>(): BlogDataRef<T> => blogPostData as BlogDataRef<T>
export function useBlogPostData<
T extends BlogPostData = BlogPostData,
>(): BlogDataRef<T> {
return blogPostData as BlogDataRef<T>
}
if (import.meta.webpackHot || import.meta.hot) {
__VUE_HMR_RUNTIME__.updateBlogData = (data: BlogPostData) => {

View File

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

View File

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

View File

@ -9,11 +9,11 @@ const __dirname = getDirname(import.meta.url)
export type PluginOption = Omit<BlogDataPluginOptions, 'include' | 'exclude'>
export const blogDataPlugin = ({
export function blogDataPlugin({
include,
exclude,
...pluginOptions
}: BlogDataPluginOptions = {}): Plugin => {
}: BlogDataPluginOptions = {}): Plugin {
const pageFilter = createFilter(toArray(include), toArray(exclude), {
resolve: false,
})
@ -26,7 +26,7 @@ export const blogDataPlugin = ({
;(page.data as any).isBlogPost = true
}
},
onPrepared: async (app) =>
onPrepared: async app =>
await preparedBlogData(app, pageFilter, pluginOptions),
onWatched(app, watchers) {
const watcher = chokidar.watch('pages/**/*', {
@ -36,15 +36,15 @@ export const blogDataPlugin = ({
watcher.on(
'add',
async () => await preparedBlogData(app, pageFilter, pluginOptions)
async () => await preparedBlogData(app, pageFilter, pluginOptions),
)
watcher.on(
'change',
async () => await preparedBlogData(app, pageFilter, pluginOptions)
async () => await preparedBlogData(app, pageFilter, pluginOptions),
)
watcher.on(
'unlink',
async () => await preparedBlogData(app, pageFilter, pluginOptions)
async () => await preparedBlogData(app, pageFilter, pluginOptions),
)
watchers.push(watcher)
@ -53,6 +53,7 @@ export const blogDataPlugin = ({
}
function toArray(likeArr: string | string[] | undefined): string[] {
if (Array.isArray(likeArr)) return likeArr
if (Array.isArray(likeArr))
return 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()
}
const EXCERPT_SPLIT = '<!-- more -->'
export const preparedBlogData = async (
app: App,
pageFilter: (id: string) => boolean,
options: PluginOption
): Promise<void> => {
export async function preparedBlogData(app: App, pageFilter: (id: string) => boolean, options: PluginOption): Promise<void> {
let pages = app.pages.filter((page) => {
return page.filePathRelative && pageFilter(page.filePathRelative)
})
if (options.pageFilter) {
if (options.pageFilter)
pages = pages.filter(options.pageFilter)
}
if (options.sortBy) {
pages = pages.sort((prev, next) => {
if (options.sortBy === 'createTime') {
return getTimestamp(prev.frontmatter.createTime as Date) <
getTimestamp(next.frontmatter.createTime as Date)
return getTimestamp(prev.frontmatter.createTime as Date)
< getTimestamp(next.frontmatter.createTime as Date)
? 1
: -1
} else {
return typeof options.sortBy === 'function' &&
options.sortBy(prev, next)
}
else {
return typeof options.sortBy === 'function'
&& options.sortBy(prev, next)
? 1
: -1
}
@ -52,9 +49,9 @@ export const preparedBlogData = async (
const blogData: BlogPostData = pages.map((page: Page) => {
let extended: Partial<BlogPostDataItem> = {}
if (typeof options.extendBlogData === 'function') {
if (typeof options.extendBlogData === 'function')
extended = options.extendBlogData(page)
}
const data = {
path: page.path,
title: page.title,
@ -71,14 +68,13 @@ export const preparedBlogData = async (
let content = `\
export const blogPostData = JSON.parse(${JSON.stringify(
JSON.stringify(blogData)
JSON.stringify(blogData),
)})
`
// inject HMR code
if (app.env.isDev) {
if (app.env.isDev)
content += HMR_CODE
}
await app.writeTemp('internal/blogData.js', content)
}

View File

@ -1,13 +1,13 @@
{
"extends": "../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"baseUrl": ".",
"rootDir": "./src",
"paths": {
"@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"]
}

View File

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

View File

@ -1,7 +1,18 @@
{
"name": "@vuepress-plume/plugin-caniuse",
"type": "module",
"version": "1.0.0-rc.5",
"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": [
"VuePress",
"plugin",
@ -9,17 +20,6 @@
"can-i-use",
"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": {
".": "./lib/node/index.js",
"./client": "./lib/client/index.js",

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
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') {
return `<picture>

View File

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

View File

@ -1,18 +1,18 @@
{
"name": "@vuepress-plume/plugin-copy-code",
"type": "module",
"version": "1.0.0-rc.5",
"description": "The Plugin for VuePres 2",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"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",
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"exports": {
".": "./lib/node/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 selectedRange =
selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : false
const selectedRange
= selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : false
const textEl = document.createElement('textarea')

View File

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

View File

@ -1,23 +1,27 @@
:root {
--icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");
--icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E");
--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");
--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E");
}
:root {
--code-copy-code-border-color: #e2e2e3;
--code-copy-code-bg: #f6f6f7;
--code-copy-code-hover-border-color: #e2e2e3;
--code-copy-code-hover-bg: #fff;
--code-copy-code-active-text: rgba(60, 60, 67, 0.78);
--code-copy-copied-text-content: 'Copied';
--vp-code-copy-code-border-color: #e2e2e3;
--vp-code-copy-code-bg: #f6f6f7;
--vp-code-copy-code-hover-border-color: #e2e2e3;
--vp-code-copy-code-hover-bg: #fff;
--vp-code-copy-code-active-text: rgba(60, 60, 67, 0.78);
--vp-code-copy-copied-text-content: 'Copied';
}
html[lang='zh-CN'] {
--vp-code-copy-copied-text-content: '已复制';
}
.dark {
--code-copy-code-border-color: #2e2e32;
--code-copy-code-bg: #202127;
--code-copy-code-hover-bg: #1b1b1f;
--code-copy-code-hover-border-color: #2e2e32;
--code-copy-code-active-text: rgba(235, 235, 245, 0.6);
--vp-code-copy-code-border-color: #2e2e32;
--vp-code-copy-code-bg: #202127;
--vp-code-copy-code-hover-bg: #1b1b1f;
--vp-code-copy-code-hover-border-color: #2e2e32;
--vp-code-copy-code-active-text: rgba(235, 235, 245, 0.6);
}
.copy-code-button {
@ -28,14 +32,14 @@
/*rtl:ignore*/
right: 12px;
z-index: 3;
border: 1px solid var(--code-copy-code-border-color);
border: 1px solid var(--vp-code-copy-code-border-color);
border-radius: 4px;
width: 40px;
height: 40px;
background-color: var(--code-copy-code-bg);
background-color: var(--vp-code-copy-code-bg);
opacity: 0;
cursor: pointer;
background-image: var(--icon-copy);
background-image: var(--vp-icon-copy);
background-position: 50%;
background-size: 20px;
background-repeat: no-repeat;
@ -52,16 +56,16 @@
[class*='language-'] > .copy-code-button:hover,
[class*='language-'] > .copy-code-button.copied {
border-color: var(--code-copy-code-hover-border-color);
background-color: var(--code-copy-code-hover-bg);
border-color: var(--vp-code-copy-code-hover-border-color);
background-color: var(--vp-code-copy-code-hover-bg);
}
[class*='language-'] > .copy-code-button.copied,
[class*='language-'] > .copy-code-button:hover.copied {
/*rtl:ignore*/
border-radius: 0 4px 4px 0;
background-color: var(--code-copy-code-hover-bg);
background-image: var(--icon-copied);
background-color: var(--vp-code-copy-code-hover-bg);
background-image: var(--vp-icon-copied);
}
[class*='language-'] > .copy-code-button.copied::before,
@ -73,7 +77,7 @@
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--code-copy-code-hover-border-color);
border: 1px solid var(--vp-code-copy-code-hover-border-color);
/*rtl:ignore*/
border-right: 0;
border-radius: 4px 0 0 4px;
@ -83,8 +87,8 @@
text-align: center;
font-size: 12px;
font-weight: 500;
color: var(--code-copy-code-active-text);
background-color: var(--code-copy-code-hover-bg);
color: var(--vp-code-copy-code-active-text);
background-color: var(--vp-code-copy-code-hover-bg);
white-space: nowrap;
content: var(--code-copy-copied-text-content);
content: var(--vp-code-copy-copied-text-content);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -5,22 +5,24 @@ import type { ComputedRef, Ref } from 'vue'
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 loaded = ref(true)
async function loadIconComponent() {
if (icon.value) {
if (icon.value)
return
}
if (!__VUEPRESS_SSR__) {
try {
loaded.value = false
iconCache.value[name.value] = await loadIcon(name.value)
} finally {
}
finally {
loaded.value = true
}
} else {
}
else {
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 type { IconifyOptions } from '../shared/index.js'
export const iconifyPlugin = ({
export function iconifyPlugin({
componentName = 'Iconify',
size = '1em',
color = 'currentColor',
}: IconifyOptions = {}): Plugin => {
return (app: App) => {
}: IconifyOptions = {}): Plugin {
return () => {
return {
name: '@vuepress-plume/plugin-iconify',
define: {
@ -17,7 +17,7 @@ export const iconifyPlugin = ({
},
clientConfigFile: path.resolve(
getDirname(import.meta.url),
'../client/clientConfig.js'
'../client/clientConfig.js',
),
}
}

View File

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

View File

@ -1,7 +1,18 @@
{
"name": "vuepress-plugin-netlify-functions",
"type": "module",
"version": "1.0.0-rc.5",
"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": [
"VuePress",
"vuepress plugin",
@ -10,17 +21,6 @@
"netlifyFunctions",
"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": {
".": "./lib/node/index.js",
"./package.json": "./package.json"

View File

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

View File

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

View File

@ -1,3 +1,4 @@
import process from 'node:process'
import type { JsonMap } from '@iarna/toml'
import { parse, stringify } from '@iarna/toml'
import type { App } from '@vuepress/core'
@ -11,39 +12,32 @@ export interface NetlifyConfig {
const configName = 'netlify.toml'
const readConfig = (filepath: string): NetlifyConfig => {
function readConfig(filepath: string): NetlifyConfig {
let netlifyConfig = ''
if (fs.existsSync(filepath)) {
if (fs.existsSync(filepath))
netlifyConfig = fs.readFileSync(filepath, 'utf-8') || ''
}
return (parse(netlifyConfig) as unknown as NetlifyConfig) || {}
}
const writeConfig = (filepath: string, netlifyConfig: NetlifyConfig): void => {
function writeConfig(filepath: string, netlifyConfig: NetlifyConfig): void {
fs.writeFileSync(
filepath,
stringify(netlifyConfig as unknown as JsonMap),
'utf-8'
'utf-8',
)
}
const resolveFunctions = (
config: NetlifyConfig,
{ directory }: NetlifyFunctionsPluginOptions,
app: App
): void => {
function resolveFunctions(config: NetlifyConfig, { directory }: NetlifyFunctionsPluginOptions, app: App): void {
const functions = (config.functions = config.functions || {})
functions.directory =
functions.directory || path.relative(app.dir.dest('../'), directory.dest)
functions.directory
= functions.directory || path.relative(app.dir.dest('../'), directory.dest)
}
const resolveRedirects = (
config: NetlifyConfig,
{ proxyPrefix }: NetlifyFunctionsPluginOptions
): void => {
const funcDir = '/' + (config.functions.directory || '').replace(/^\//, '')
function resolveRedirects(config: NetlifyConfig, { proxyPrefix }: NetlifyFunctionsPluginOptions): void {
const funcDir = `/${(config.functions.directory || '').replace(/^\//, '')}`
const redirects = (config.redirects = config.redirects || [])
if (!redirects.some((redirect) => redirect?.to?.startsWith(funcDir))) {
if (!redirects.some(redirect => redirect?.to?.startsWith(funcDir))) {
redirects.push({
from: path.join('/', proxyPrefix, '*'),
to: path.join(funcDir, ':splat'),
@ -56,10 +50,7 @@ const resolveRedirects = (
}
}
export const generateNetlifyConfig = (
app: App,
options: NetlifyFunctionsPluginOptions
): NetlifyConfig => {
export function generateNetlifyConfig(app: App, options: NetlifyFunctionsPluginOptions): NetlifyConfig {
const configPath = path.join(process.cwd(), configName)
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 dotenv from 'dotenv'
import { execa } from 'execa'
@ -6,15 +8,16 @@ import type { NetlifyFunctionsPluginOptions } from '../../shared/index.js'
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')
if (!fs.existsSync(configPath)) {
if (!fs.existsSync(configPath))
return {}
}
try {
const content = fs.readFileSync(configPath, 'utf-8')
return dotenv.parse(Buffer.from(content))
} catch {
}
catch {
return {}
}
}
@ -23,15 +26,15 @@ export interface NetlifyServe {
host: string
close: () => void
}
export const netlifyServe = async ({
export async function netlifyServe({
directory,
}: NetlifyFunctionsPluginOptions): Promise<NetlifyServe> => {
}: NetlifyFunctionsPluginOptions): Promise<NetlifyServe> {
const port = await portFinder.getPortPromise({ port: 9000 })
const argv = [
'functions:serve',
'--port',
port + '',
`${port}`,
'--functions',
path.join('./', path.relative(process.cwd(), directory.temp)),
// '--debug',
@ -45,12 +48,12 @@ export const netlifyServe = async ({
env: {
...loadEnvConfig(),
},
}
},
)
stdout?.pipe(process.stdout)
return {
host: 'http://localhost:' + port,
host: `http://localhost:${port}`,
close: () => cancel(),
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,7 +30,7 @@ export default defineClientConfig({
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 useNotesData = <
T extends NotesData = NotesData
>(): NotesDataRef<T> => notesData as NotesDataRef<T>
export function useNotesData<
T extends NotesData = NotesData,
>(): NotesDataRef<T> {
return notesData as NotesDataRef<T>
}
if (import.meta.webpackHot || import.meta.hot) {
__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 { prepareNotesData, watchNotesData } from './prepareNotesData.js'
export const notesDataPlugin = (options: NotesDataOptions): Plugin => {
export function notesDataPlugin(options: NotesDataOptions): Plugin {
return (app: App) => {
return {
name: '@vuepress-plume/plugin-notes-data',
clientConfigFile: path.resolve(
getDirname(import.meta.url),
'../client/clientConfig.js'
'../client/clientConfig.js',
),
onPrepared: () => prepareNotesData(app, options),
onWatched: (app, watchers) => watchNotesData(app, watchers, options),

View File

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

View File

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

View File

@ -2,11 +2,11 @@
"extends": "../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"paths": {
"@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"]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,29 +0,0 @@
# `@vuepress-plume/plugin-windicss`
添加 `windicss` 支持
## Install
```
yarn add @vuepress-plume/plugin-windicss
```
## Usage
``` js
// .vuepress/config.js
const { windicssPlugin } = require('@vuepress-plume/plugin-windicss')
const path = require('path')
module.exports = {
//...
plugins: [
windicssPlugin(path.resolve(__dirname, 'windi.config.js'))
]
// ...
}
```
## Method
`windicssPlugin(options)`
- `options`: `{ userOptions?: UserOptions; utilsOptions?: WindiPluginUtilsOptions } | string`
windicss 配置文件路径,或者 windicss配置

View File

@ -1,49 +0,0 @@
{
"name": "@vuepress-plume/plugin-windicss",
"version": "1.0.0-rc.5",
"description": "The Plugin for VuePress 2",
"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": {
".": "./lib/node/index.js",
"./package.json": "./package.json"
},
"main": "lib/node/index.js",
"types": "lib/node/index.d.ts",
"files": [
"lib"
],
"scripts": {
"build": "pnpm run clean && pnpm run copy && pnpm run ts",
"clean": "rimraf lib *.tsbuildinfo",
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
"ts": "tsc -b tsconfig.build.json"
},
"dependencies": {
"@vuepress/client": "2.0.0-rc.0",
"@vuepress/core": "2.0.0-rc.0",
"@vuepress/shared": "2.0.0-rc.0",
"@vuepress/utils": "2.0.0-rc.0",
"vite-plugin-windicss": "^1.9.3",
"windicss": "^3.5.6",
"windicss-webpack-plugin": "^1.8.0"
},
"publishConfig": {
"access": "public"
},
"keyword": [
"VuePress",
"vuepress plugin",
"Windicss",
"vuepress-plugin-windicss"
]
}

View File

@ -1,6 +0,0 @@
import { defineClientConfig } from '@vuepress/client'
// import 'virtual:windi-devtools'
import 'virtual:windi.css'
export default defineClientConfig({})

View File

@ -1,5 +0,0 @@
import { defineClientConfig } from '@vuepress/client'
import 'windi.css'
export default defineClientConfig({})

View File

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

View File

@ -1,55 +0,0 @@
import type { App, Plugin } from '@vuepress/core'
import { getDirname, path } from '@vuepress/utils'
import viteWindiCSS from 'vite-plugin-windicss'
import type { UserOptions, WindiPluginUtilsOptions } from 'vite-plugin-windicss'
import WebpackWindiCSSPlugin from 'windicss-webpack-plugin'
const __dirname = getDirname(import.meta.url)
export interface WindiCSSOptions {
userOptions?: UserOptions
utilsOptions?: WindiPluginUtilsOptions
}
export const windiCSSPlugin = (options?: WindiCSSOptions | string): Plugin => {
let userOptions: UserOptions | undefined
let utilsOptions: WindiPluginUtilsOptions | undefined
if (typeof options === 'string') {
userOptions = {
config: options,
scan: {
include: ['**/.vuepress/**/*.{vue,jsx,tsx}', '**/*.md'],
},
}
} else {
options = options || {}
userOptions = options.userOptions
utilsOptions = options.utilsOptions
}
return (app: App) => {
const clientConfigFile =
app.options.bundler.name === '@vuepress/bundler-vite'
? path.resolve(__dirname, '../client/config.vite.js')
: path.resolve(__dirname, '../client/config.webpack.js')
return {
name: '@vuepress-plume/plugin-windicss',
clientConfigFile,
extendsBundlerOptions: (bundlerOptions, app: App) => {
if (app.options.bundler.name === '@vuepress/bundler-vite') {
bundlerOptions.viteOptions ??= {}
bundlerOptions.viteOptions.plugins ??= []
bundlerOptions.viteOptions.plugins.push(
viteWindiCSS(userOptions, utilsOptions),
)
}
if (app.options.bundler.name === '@vuepress/bundler-webpack') {
bundlerOptions.configureWebpack &&
bundlerOptions.configureWebpack((config: any) => {
config.plugins.push(
new (WebpackWindiCSSPlugin as any)(userOptions),
)
})
}
},
}
}
}

View File

@ -1,17 +0,0 @@
// declare module 'windicss-webpack-plugin' {
// const result: any
// export default result
// }
// declare module 'vite-plugin-windicss' {
// const result: any
// type UserOptions = any
// type WindiPluginUtilsOptions = any
// export default result
// export { UserOptions, WindiPluginUtilsOptions }
// }

View File

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

View File

@ -3,7 +3,6 @@
"compilerOptions": {
"composite": true
},
"files": [],
"references": [
{ "path": "./plugin-auto-frontmatter/tsconfig.build.json" },
{ "path": "./plugin-baidu-tongji/tsconfig.build.json" },
@ -14,7 +13,7 @@
{ "path": "./plugin-netlify-functions/tsconfig.build.json" },
{ "path": "./plugin-notes-data/tsconfig.build.json" },
{ "path": "./plugin-page-collection/tsconfig.build.json" },
{ "path": "./plugin-windicss/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)
![GitHub License](https://img.shields.io/github/license/pengzhanbo/vuepress-theme-plume?color=32A9C3&labelColor=1B3C4A)
### [查看文档](https://pengzhanbo.cn/note/vuepress-theme-plume)
## Install
@ -54,7 +53,6 @@ __options__ : `PlumeThemeOptions`
![](/docs/preview-post.png?a=1)
![](/docs/preview-note.png?a=1)
## 内置插件

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import chalk from 'chalk'
export const getHelp = () => {
export function getHelp() {
console.log(` this command will generator a package to ${chalk.cyan('packages')}.
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 { getHelp } from './getHelp.js'
import { generator } from './generator.js'
@ -7,7 +8,8 @@ const config = getConfig()
if (config.help) {
getHelp()
process.exit(0)
} else {
}
else {
generator(config).catch((err) => {
console.error(err)
process.exit(1)

View File

@ -1,32 +1,32 @@
import fs from 'fs'
import path from 'path'
import fs from 'node:fs'
import path from 'node:path'
const tplRE = /\.tpl$/
const readFileList = (dir, fileList = {}) => {
function readFileList(dir, fileList = {}) {
const files = fs.readdirSync(dir)
files.forEach((file) => {
const filepath = path.join(dir, file)
const stat = fs.statSync(filepath)
if (stat.isDirectory()) {
readFileList(filepath, fileList)
} else {
}
else {
const extname = path.extname(filepath)
if (tplRE.test(extname)) {
if (tplRE.test(extname))
fileList[filepath.replace(tplRE, '')] = fs.readFileSync(filepath, 'utf-8')
}
}
})
return fileList
}
export const readTemplateList = (dir) => {
const templateMap= readFileList(dir)
export function readTemplateList(dir) {
const templateMap = readFileList(dir)
const result = []
Object.keys(templateMap).forEach((key) => {
const file = path.relative(dir, key)
result.push({
file,
content: templateMap[key]
content: templateMap[key],
})
})
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 s[0].toUpperCase() + s.slice(1)
}).join('')
}
export const lowerCase = (str) => {
export function lowerCase(str) {
str = upperCase(str)
return str[0].toLowerCase() + str.slice(1)
}
export const packageName = (name) => {
return 'plugin-' + name.trim().split(/-|\s+/).filter(Boolean).join('-')
export function packageName(name) {
return `plugin-${name.trim().split(/-|\s+/).filter(Boolean).join('-')}`
}

View File

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

View File

@ -1,7 +1,18 @@
{
"name": "vuepress-theme-plume",
"type": "module",
"version": "1.0.0-rc.5",
"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": [
"VuePress",
"Theme",
@ -10,17 +21,6 @@
"vuepress-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": {
".": "./lib/node/index.js",
"./client": "./lib/client/index.js",

View File

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

View File

@ -16,15 +16,14 @@ const router = useRouter()
const tag = computed(() => props.tag ?? (props.href ? 'a' : 'span'))
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) {
e.preventDefault()
if (props.href) {
if (props.href)
router.push({ path: props.href })
}
}
}
</script>

View File

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

View File

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

View File

@ -1,13 +1,14 @@
<script lang="ts" setup>
import { useScrollLock } from '@vueuse/core'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useBlogExtract, useThemeLocaleData } from '../composables/index.js'
import { inBrowser } from '../utils/index.js'
import AutoLink from './AutoLink.vue'
import IconArchive from './icons/IconArchive.vue'
import IconBlogExt from './icons/IconBlogExt.vue'
import IconTag from './icons/IconTag.vue'
const theme = useThemeLocaleData()
const route = useRoute()
@ -15,14 +16,28 @@ const avatar = computed(() => theme.value.avatar)
const { hasBlogExtract, tags, archives } = useBlogExtract()
const open = ref(false)
const isLocked = useScrollLock(inBrowser ? document.body : null)
watch(() => route.path, () => {
open.value = false
})
watch(
[() => open.value],
() => {
if (open.value)
isLocked.value = true
else isLocked.value = false
},
{ immediate: true, flush: 'post' },
)
const showBlogExtract = computed(() => {
return avatar.value || hasBlogExtract.value
})
</script>
<template>
<div v-if="showBlogExtract" class="blog-extract" @click="open = !open">
<IconBlogExt class="icon" />
@ -31,11 +46,13 @@ const showBlogExtract = computed(() => {
<div class="blog-modal-container">
<div v-if="avatar" class="avatar-profile">
<p v-if="avatar.url" class="avatar">
<img :src="avatar.url" :alt="avatar.name" />
<img :src="avatar.url" :alt="avatar.name">
</p>
<div>
<h3>{{ avatar.name }}</h3>
<p class="desc">{{ avatar.description }}</p>
<p class="desc">
{{ avatar.description }}
</p>
</div>
</div>
<div v-if="hasBlogExtract" class="blog-nav">
@ -66,6 +83,7 @@ const showBlogExtract = computed(() => {
box-shadow: var(--vp-shadow-2);
z-index: calc(var(--vp-z-index-nav) - 1);
background-color: var(--vp-c-bg);
cursor: pointer;
}
.blog-extract .icon {

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