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 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",

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

@ -16,7 +16,8 @@ export default {
autoFrontmatterPlugin({
formatter: {
createTime(formatTime, file, matter) {
if (formatTime) return formatTime
if (formatTime)
return formatTime
return file.createTime
}
}
@ -70,14 +71,15 @@ export default {
/**
* formatterObj 对象中的 key 即为 frontmatter 配置中的key
* 其方法返回的值将作为 frontmatter[key] 的值
* *.md
* .md
* ---
* createTime: 2022-03-26T11:46:50.000Z
* ---
*/
const formatterObj: Formatter = {
createTime(formatTime, file, matter) {
if (formatTime) return formatTime
if (formatTime)
return formatTime
return file.createTime
}
}
@ -92,6 +94,7 @@ export default {
return value
}
},
},
{
// 通配如果文件没有被其他精细glob命中
// 则使用 通配 formatter
@ -103,9 +106,7 @@ export default {
}
}
}
}
]
```
## 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 !== '*')
@ -62,8 +62,9 @@ export const autoFrontmatterPlugin = ({
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

@ -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

@ -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

@ -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

@ -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(() => {
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

@ -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

@ -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.
@ -49,7 +48,7 @@ yarn add vuepress-plugin-netlify-functions
``` js
import { useNetlifyFunctionsPlugin } from 'vuepress-plugin-netlify-functions'
const myPlugin = (): Plugin => {
function myPlugin(): Plugin {
return (app: App) => {
const {
// proxy prefix, default: /api

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,
{
function initOptions(app: App, {
sourceDirectory,
destDirectory,
proxyPrefix = '/api',
}: NetlifyFunctionsOptions
): NetlifyFunctionsPluginOptions => ({
}: 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

@ -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

@ -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

@ -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
.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
})
defaultColor: false,
}),
})
return fillEmptyHighlightedLine(restoreMustache(highlighted))

View File

@ -1,13 +1,14 @@
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 => ({
export function shikijiPlugin(options: ShikijiPluginOptions = {}): Plugin {
return {
name: '@vuepress-plume/plugin-shikiji',
extendsMarkdown: async (md) => {
@ -16,4 +17,5 @@ export const shikijiPlugin = (
md.options.highlight = highlighter
},
})
}
}

View File

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

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" },
@ -15,5 +14,6 @@
{ "path": "./plugin-notes-data/tsconfig.build.json" },
{ "path": "./plugin-page-collection/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
@ -55,7 +54,6 @@ __options__ : `PlumeThemeOptions`
![](/docs/preview-note.png?a=1)
## 内置插件
- [plugin-shikiji](https://github.com/pengzhanbo/vuepress-theme-plume/tree/main/plugins/plugin-shikiji) 使用 [`shikiji`](https://shikiji.netlify.app/) 来为 Markdown 代码块启用代码高亮。

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 {
const extname = path.extname(filepath)
if (tplRE.test(extname)) {
fileList[filepath.replace(tplRE, '')] = fs.readFileSync(filepath, 'utf-8')
}
else {
const extname = path.extname(filepath)
if (tplRE.test(extname))
fileList[filepath.replace(tplRE, '')] = fs.readFileSync(filepath, 'utf-8')
}
})
return fileList
}
export const readTemplateList = (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,17 +16,16 @@ 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>
<template>

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

@ -9,7 +9,6 @@ import IconArchive from './icons/IconArchive.vue'
import IconBlogExt from './icons/IconBlogExt.vue'
import IconTag from './icons/IconTag.vue'
const theme = useThemeLocaleData()
const route = useRoute()
@ -26,17 +25,19 @@ watch(() => route.path, () => {
watch(
[() => open.value],
() => {
if (open.value) {
if (open.value)
isLocked.value = true
} else isLocked.value = false
else isLocked.value = false
},
{ immediate: true, flush: 'post' }
{ 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" />
@ -45,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">

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type {FriendsItem} from '../../shared/index';
import type { FriendsItem } from '../../shared/index'
import AutoLink from './AutoLink.vue'
defineProps<{
@ -10,11 +10,15 @@ defineProps<{
<template>
<div class="friend">
<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>
<div class="content">
<AutoLink class="title" :href="friend.link" no-icon>{{ friend.name }}</AutoLink>
<p v-if="friend.desc">{{ friend.desc }}</p>
<AutoLink class="title" :href="friend.link" no-icon>
{{ friend.name }}
</AutoLink>
<p v-if="friend.desc">
{{ friend.desc }}
</p>
</div>
</div>
</template>

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type {PageHeader} from '@vuepress/client';
import type { PageHeader } from '@vuepress/client'
import { onClickOutside } from '@vueuse/core'
import { nextTick, ref, watch } from 'vue'
import { useThemeLocaleData } from '../composables/index.js'
@ -33,9 +33,9 @@ function toggle() {
function onItemClick(e: Event) {
if ((e.target as HTMLElement).classList.contains('outline-link')) {
// disable animation on hash navigation when page jumps
if (items.value) {
if (items.value)
items.value.style.transition = 'none'
}
nextTick(() => {
open.value = false
})
@ -49,7 +49,7 @@ function scrollToTop() {
</script>
<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">
{{ theme.outlineLabel || 'On this page' }}
<IconChevronRight class="icon" />

View File

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

View File

@ -11,7 +11,7 @@ const theme = useThemeLocaleData()
const { localeLinks, currentLang } = useLangs({ correspondingLink: true })
const hasExtraContent = computed(
() => theme.value.appearance || theme.value.social
() => theme.value.appearance || theme.value.social,
)
</script>
@ -21,7 +21,9 @@ const hasExtraContent = computed(
v-if="localeLinks.length && currentLang.label"
class="group translations"
>
<p class="trans-title">{{ currentLang.label }}</p>
<p class="trans-title">
{{ currentLang.label }}
</p>
<template v-for="locale in localeLinks" :key="locale.link">
<MenuLink :item="locale" />
@ -30,7 +32,9 @@ const hasExtraContent = computed(
<div v-if="theme.appearance" class="group">
<div class="item appearance">
<p class="label">Appearance</p>
<p class="label">
Appearance
</p>
<div class="appearance-action">
<SwitchAppearance />
</div>

View File

@ -12,9 +12,7 @@ const theme = useThemeLocaleData()
aria-labelledby="main-nav-aria-label"
class="navbar-menu"
>
<span id="main-nav-aria-label" class="visually-hidden"
>Main Navigation</span
>
<span id="main-nav-aria-label" class="visually-hidden">Main Navigation</span>
<template v-for="item in theme.navbar" :key="item.text">
<NavBarMenuLink v-if="'link' in item" :item="item" />
<NavBarMenuGroup v-else :item="item" />

View File

@ -11,14 +11,15 @@ const props = defineProps<{
const page = usePageData()
const isChildActive = (navItem: NavItem) => {
function isChildActive(navItem: NavItem) {
if ('link' in navItem) {
return isActive(
page.value.path,
navItem.link,
!!props.item.activeMatch
!!props.item.activeMatch,
)
} else {
}
else {
return navItem.items.some(isChildActive)
}
}
@ -27,9 +28,8 @@ const childrenActive = computed(() => isChildActive(props.item))
<template>
<Flyout
:class="{
'navbar-menu-group': true,
'active': isActive(page.path, item.activeMatch, !!item.activeMatch) || childrenActive,
class="navbar-menu-group" :class="{
active: isActive(page.path, item.activeMatch, !!item.activeMatch) || childrenActive,
}"
:button="item.text"
:items="item.items"

View File

@ -13,19 +13,18 @@ const page = usePageData()
<template>
<AutoLink
:class="{
'navbar-menu-link': true,
'active': isActive(
class="navbar-menu-link" :class="{
active: isActive(
page.path,
item.activeMatch || item.link,
!!item.activeMatch
!!item.activeMatch,
),
}"
:href="item.link"
:no-icon="true"
>
<Icon v-if="item.icon" :name="item.icon" />
<i v-text="item.text"></i>
<i v-text="item.text" />
</AutoLink>
</template>

View File

@ -17,7 +17,9 @@ const { currentLang, localeLinks } = useLangs()
:label="theme.selectLanguageText || 'change language'"
>
<div class="items">
<p class="title">{{ currentLang.label }}</p>
<p class="title">
{{ currentLang.label }}
</p>
<template v-for="locale in localeLinks" :key="locale.link">
<MenuLink :item="locale" />

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