mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
docs: improve jsdoc (#852)
This commit is contained in:
parent
77da8a3470
commit
5c201e3ed0
@ -1,27 +1,86 @@
|
||||
import type { Bundler, Langs, Options } from './types.js'
|
||||
|
||||
/**
|
||||
* Language options for VuePress configuration
|
||||
*
|
||||
* 语言选项,用于 VuePress 配置
|
||||
*/
|
||||
export const languageOptions: Options<Langs> = [
|
||||
{ label: 'English', value: 'en-US' },
|
||||
{ label: '简体中文', value: 'zh-CN' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Bundler options for VuePress build tool
|
||||
*
|
||||
* 构建器选项,用于 VuePress 构建工具
|
||||
*/
|
||||
export const bundlerOptions: Options<Bundler> = [
|
||||
{ label: 'Vite', value: 'vite' },
|
||||
{ label: 'Webpack', value: 'webpack' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Operation mode for VuePress CLI
|
||||
*
|
||||
* VuePress CLI 操作模式
|
||||
* @readonly
|
||||
* @enum {number}
|
||||
*/
|
||||
export enum Mode {
|
||||
/**
|
||||
* Initialize existing directory
|
||||
*
|
||||
* 初始化现有目录
|
||||
*/
|
||||
init,
|
||||
/**
|
||||
* Create new project
|
||||
*
|
||||
* 创建新项目
|
||||
*/
|
||||
create,
|
||||
}
|
||||
|
||||
/**
|
||||
* Deployment type for VuePress site
|
||||
*
|
||||
* VuePress 站点部署类型
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export enum DeployType {
|
||||
/**
|
||||
* GitHub Pages deployment
|
||||
*
|
||||
* GitHub Pages 部署
|
||||
*/
|
||||
github = 'github',
|
||||
/**
|
||||
* Vercel deployment
|
||||
*
|
||||
* Vercel 部署
|
||||
*/
|
||||
vercel = 'vercel',
|
||||
/**
|
||||
* Netlify deployment
|
||||
*
|
||||
* Netlify 部署
|
||||
*/
|
||||
netlify = 'netlify',
|
||||
/**
|
||||
* Custom deployment
|
||||
*
|
||||
* 自定义部署
|
||||
*/
|
||||
custom = 'custom',
|
||||
}
|
||||
|
||||
/**
|
||||
* Deployment options for hosting platforms
|
||||
*
|
||||
* 部署选项,用于托管平台
|
||||
*/
|
||||
export const deployOptions: Options<DeployType> = [
|
||||
{ label: 'Custom', value: DeployType.custom },
|
||||
{ label: 'GitHub Pages', value: DeployType.github },
|
||||
|
||||
@ -8,6 +8,15 @@ import { createPackageJson } from './packageJson.js'
|
||||
import { createRender } from './render.js'
|
||||
import { getTemplate, readFiles, readJsonFile, writeFiles } from './utils/index.js'
|
||||
|
||||
/**
|
||||
* Generate VuePress project files
|
||||
*
|
||||
* 生成 VuePress 项目文件
|
||||
*
|
||||
* @param mode - Operation mode (init or create) / 操作模式(初始化或创建)
|
||||
* @param data - Resolved configuration data / 解析后的配置数据
|
||||
* @param cwd - Current working directory / 当前工作目录
|
||||
*/
|
||||
export async function generate(
|
||||
mode: Mode,
|
||||
data: ResolvedData,
|
||||
@ -106,6 +115,14 @@ export async function generate(
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create documentation files based on configuration
|
||||
*
|
||||
* 根据配置创建文档文件
|
||||
*
|
||||
* @param data - Resolved configuration data / 解析后的配置数据
|
||||
* @returns Array of file objects / 文件对象数组
|
||||
*/
|
||||
async function createDocsFiles(data: ResolvedData): Promise<File[]> {
|
||||
const fileList: File[] = []
|
||||
if (data.multiLanguage) {
|
||||
@ -131,6 +148,15 @@ async function createDocsFiles(data: ResolvedData): Promise<File[]> {
|
||||
return updateFileListTarget(fileList, data.docsDir)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update file list target path
|
||||
*
|
||||
* 更新文件列表的目标路径
|
||||
*
|
||||
* @param fileList - Array of files / 文件数组
|
||||
* @param target - Target directory path / 目标目录路径
|
||||
* @returns Updated file array / 更新后的文件数组
|
||||
*/
|
||||
function updateFileListTarget(fileList: File[], target: string): File[] {
|
||||
return fileList.map(({ filepath, content }) => ({
|
||||
filepath: path.join(target, filepath),
|
||||
|
||||
@ -11,6 +11,23 @@ function sortPackageJson(json: Record<any, any>) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create package.json file for VuePress project
|
||||
*
|
||||
* 为 VuePress 项目创建 package.json 文件
|
||||
*
|
||||
* @param mode - Operation mode (init or create) / 操作模式(初始化或创建)
|
||||
* @param pkg - Existing package.json data / 现有的 package.json 数据
|
||||
* @param data - Resolved configuration data / 解析后的配置数据
|
||||
* @param data.packageManager - Package manager to use / 要使用的包管理器
|
||||
* @param data.siteName - Site name / 站点名称
|
||||
* @param data.siteDescription - Site description / 站点描述
|
||||
* @param data.docsDir - Documentation directory path / 文档目录路径
|
||||
* @param data.bundler - Bundler to use / 要使用的打包器
|
||||
* @param data.injectNpmScripts - Whether to inject npm scripts / 是否注入 npm 脚本
|
||||
*
|
||||
* @returns File object with package.json content / 包含 package.json 内容的文件对象
|
||||
*/
|
||||
export async function createPackageJson(
|
||||
mode: Mode,
|
||||
pkg: Record<string, any>,
|
||||
|
||||
@ -2,16 +2,33 @@ import type { ResolvedData } from './types.js'
|
||||
import { kebabCase } from '@pengzhanbo/utils'
|
||||
import handlebars from 'handlebars'
|
||||
|
||||
/**
|
||||
* Extended resolved data with additional rendering information
|
||||
*
|
||||
* 扩展的解析数据,包含额外的渲染信息
|
||||
*/
|
||||
export interface RenderData extends ResolvedData {
|
||||
/** Project name in kebab-case / 项目名称(kebab-case 格式) */
|
||||
name: string
|
||||
/** Site name / 网站名称 */
|
||||
siteName: string
|
||||
/** Locale configuration array / 语言配置数组 */
|
||||
locales: { path: string, lang: string, isEn: boolean, prefix: string }[]
|
||||
/** Whether default language is English / 默认语言是否为英语 */
|
||||
isEN: boolean
|
||||
}
|
||||
|
||||
handlebars.registerHelper('removeLeadingSlash', (path: string) => path.replace(/^\//, ''))
|
||||
handlebars.registerHelper('equal', (a: string, b: string) => a === b)
|
||||
|
||||
/**
|
||||
* Create render function with Handlebars template engine
|
||||
*
|
||||
* 使用 Handlebars 模板引擎创建渲染函数
|
||||
*
|
||||
* @param result - Resolved configuration data / 解析后的配置数据
|
||||
* @returns Render function that processes Handlebars templates / 处理 Handlebars 模板的渲染函数
|
||||
*/
|
||||
export function createRender(result: ResolvedData) {
|
||||
const data: RenderData = {
|
||||
...result,
|
||||
|
||||
@ -11,6 +11,14 @@ import { prompt } from './prompt.js'
|
||||
import { t } from './translate.js'
|
||||
import { getPackageManager } from './utils/index.js'
|
||||
|
||||
/**
|
||||
* Run the CLI workflow for VuePress project initialization or creation
|
||||
*
|
||||
* 执行 VuePress 项目初始化或创建的 CLI 工作流程
|
||||
*
|
||||
* @param mode - Operation mode (init or create) / 操作模式(初始化或创建)
|
||||
* @param root - Root directory path / 根目录路径
|
||||
*/
|
||||
export async function run(mode: Mode, root?: string): Promise<void> {
|
||||
intro(colors.cyan('Welcome to VuePress and vuepress-theme-plume !'))
|
||||
|
||||
|
||||
@ -6,6 +6,14 @@ interface Translate {
|
||||
t: (key: keyof Locale) => string
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a translate instance with specified language
|
||||
*
|
||||
* 创建指定语言的翻译实例
|
||||
*
|
||||
* @param lang - Language code / 语言代码
|
||||
* @returns Translate interface / 翻译接口
|
||||
*/
|
||||
function createTranslate(lang?: Langs): Translate {
|
||||
let current: Langs = lang || 'en-US'
|
||||
|
||||
@ -19,5 +27,21 @@ function createTranslate(lang?: Langs): Translate {
|
||||
|
||||
const translate = createTranslate()
|
||||
|
||||
/**
|
||||
* Get translated string by key
|
||||
*
|
||||
* 根据键获取翻译后的字符串
|
||||
*
|
||||
* @param key - Locale key / 本地化键名
|
||||
* @returns Translated string / 翻译后的字符串
|
||||
*/
|
||||
export const t: Translate['t'] = translate.t
|
||||
|
||||
/**
|
||||
* Set current language
|
||||
*
|
||||
* 设置当前语言
|
||||
*
|
||||
* @param lang - Language code to set / 要设置的语言代码
|
||||
*/
|
||||
export const setLang: Translate['setLang'] = translate.setLang
|
||||
|
||||
221
cli/src/types.ts
221
cli/src/types.ts
@ -1,57 +1,276 @@
|
||||
import type { DeployType } from './constants.js'
|
||||
|
||||
/**
|
||||
* Supported language codes for VuePress site
|
||||
*
|
||||
* VuePress 站点支持的语言代码
|
||||
*/
|
||||
export type Langs = 'zh-CN' | 'en-US'
|
||||
|
||||
/**
|
||||
* Locale configuration for CLI prompts and messages
|
||||
*
|
||||
* CLI 提示和消息的国际化配置
|
||||
*/
|
||||
export interface Locale {
|
||||
/**
|
||||
* Question: Project root directory name
|
||||
*
|
||||
* 问题:项目根目录名称
|
||||
*/
|
||||
'question.root': string
|
||||
/**
|
||||
* Question: Site name
|
||||
*
|
||||
* 问题:站点名称
|
||||
*/
|
||||
'question.site.name': string
|
||||
/**
|
||||
* Question: Site description
|
||||
*
|
||||
* 问题:站点描述
|
||||
*/
|
||||
'question.site.description': string
|
||||
/**
|
||||
* Question: Enable multi-language support
|
||||
*
|
||||
* 问题:启用多语言支持
|
||||
*/
|
||||
'question.multiLanguage': string
|
||||
/**
|
||||
* Question: Default language
|
||||
*
|
||||
* 问题:默认语言
|
||||
*/
|
||||
'question.defaultLanguage': string
|
||||
/**
|
||||
* Question: Build tool bundler
|
||||
*
|
||||
* 问题:构建工具
|
||||
*/
|
||||
'question.bundler': string
|
||||
/**
|
||||
* Question: Use TypeScript
|
||||
*
|
||||
* 问题:使用 TypeScript
|
||||
*/
|
||||
'question.useTs': string
|
||||
/**
|
||||
* Question: Inject npm scripts
|
||||
*
|
||||
* 问题:注入 npm 脚本
|
||||
*/
|
||||
'question.injectNpmScripts': string
|
||||
/**
|
||||
* Question: Initialize git repository
|
||||
*
|
||||
* 问题:初始化 git 仓库
|
||||
*/
|
||||
'question.git': string
|
||||
/**
|
||||
* Question: Deployment type
|
||||
*
|
||||
* 问题:部署类型
|
||||
*/
|
||||
'question.deploy': string
|
||||
/**
|
||||
* Question: Install dependencies
|
||||
*
|
||||
* 问题:安装依赖
|
||||
*/
|
||||
'question.installDeps': string
|
||||
|
||||
/**
|
||||
* Spinner: Start message
|
||||
*
|
||||
* 加载动画:开始消息
|
||||
*/
|
||||
'spinner.start': string
|
||||
/**
|
||||
* Spinner: Stop message
|
||||
*
|
||||
* 加载动画:停止消息
|
||||
*/
|
||||
'spinner.stop': string
|
||||
/**
|
||||
* Spinner: Git init message
|
||||
*
|
||||
* 加载动画:Git 初始化消息
|
||||
*/
|
||||
'spinner.git': string
|
||||
/**
|
||||
* Spinner: Install message
|
||||
*
|
||||
* 加载动画:安装消息
|
||||
*/
|
||||
'spinner.install': string
|
||||
/**
|
||||
* Spinner: Command hint message
|
||||
*
|
||||
* 加载动画:命令提示消息
|
||||
*/
|
||||
'spinner.command': string
|
||||
|
||||
/**
|
||||
* Hint: Cancel operation
|
||||
*
|
||||
* 提示:取消操作
|
||||
*/
|
||||
'hint.cancel': string
|
||||
/**
|
||||
* Hint: Root directory
|
||||
*
|
||||
* 提示:根目录
|
||||
*/
|
||||
'hint.root': string
|
||||
/**
|
||||
* Hint: Illegal root directory name
|
||||
*
|
||||
* 提示:非法的根目录名称
|
||||
*/
|
||||
'hint.root.illegal': string
|
||||
}
|
||||
|
||||
/**
|
||||
* Package manager types
|
||||
*
|
||||
* 包管理器类型
|
||||
*/
|
||||
export type PackageManager = 'npm' | 'yarn' | 'pnpm'
|
||||
|
||||
/**
|
||||
* Build tool bundler types
|
||||
*
|
||||
* 构建工具类型
|
||||
*/
|
||||
export type Bundler = 'vite' | 'webpack'
|
||||
|
||||
/**
|
||||
* Generic options type for CLI prompts
|
||||
*
|
||||
* CLI 提示的通用选项类型
|
||||
*
|
||||
* @template Value - The value type for options
|
||||
* @template Label - The label type for options
|
||||
*/
|
||||
export type Options<Value = string, Label = string> = { label: Label, value: Value }[]
|
||||
|
||||
/**
|
||||
* File structure for generated project
|
||||
*
|
||||
* 生成项目的文件结构
|
||||
*/
|
||||
export interface File {
|
||||
/**
|
||||
* File path relative to project root
|
||||
*
|
||||
* 相对于项目根目录的文件路径
|
||||
*/
|
||||
filepath: string
|
||||
/**
|
||||
* File content
|
||||
*
|
||||
* 文件内容
|
||||
*/
|
||||
content: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Result from CLI prompts
|
||||
*
|
||||
* CLI 提示结果
|
||||
*/
|
||||
export interface PromptResult {
|
||||
displayLang: string // cli display language
|
||||
/**
|
||||
* CLI display language
|
||||
*
|
||||
* CLI 显示语言
|
||||
*/
|
||||
displayLang: string
|
||||
/**
|
||||
* Project root directory name
|
||||
*
|
||||
* 项目根目录名称
|
||||
*/
|
||||
root: string
|
||||
/**
|
||||
* Site name
|
||||
*
|
||||
* 站点名称
|
||||
*/
|
||||
siteName: string
|
||||
/**
|
||||
* Site description
|
||||
*
|
||||
* 站点描述
|
||||
*/
|
||||
siteDescription: string
|
||||
/**
|
||||
* Build tool bundler
|
||||
*
|
||||
* 构建工具
|
||||
*/
|
||||
bundler: Bundler
|
||||
/**
|
||||
* Enable multi-language support
|
||||
*
|
||||
* 启用多语言支持
|
||||
*/
|
||||
multiLanguage: boolean
|
||||
/**
|
||||
* Default language
|
||||
*
|
||||
* 默认语言
|
||||
*/
|
||||
defaultLanguage: Langs
|
||||
/**
|
||||
* Use TypeScript
|
||||
*
|
||||
* 使用 TypeScript
|
||||
*/
|
||||
useTs: boolean
|
||||
/**
|
||||
* Inject npm scripts
|
||||
*
|
||||
* 注入 npm 脚本
|
||||
*/
|
||||
injectNpmScripts: boolean
|
||||
/**
|
||||
* Deployment type
|
||||
*
|
||||
* 部署类型
|
||||
*/
|
||||
deploy: DeployType
|
||||
/**
|
||||
* Initialize git repository
|
||||
*
|
||||
* 初始化 git 仓库
|
||||
*/
|
||||
git: boolean
|
||||
/**
|
||||
* Install dependencies
|
||||
*
|
||||
* 安装依赖
|
||||
*/
|
||||
install: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved data after processing prompts
|
||||
*
|
||||
* 处理提示后的解析数据
|
||||
*/
|
||||
export interface ResolvedData extends PromptResult {
|
||||
/**
|
||||
* Selected package manager
|
||||
*
|
||||
* 选择的包管理器
|
||||
*/
|
||||
packageManager: PackageManager
|
||||
/**
|
||||
* Documentation directory name
|
||||
*
|
||||
* 文档目录名称
|
||||
*/
|
||||
docsDir: string
|
||||
}
|
||||
|
||||
@ -2,6 +2,14 @@ import type { File } from '../types.js'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
|
||||
/**
|
||||
* Read all files from a directory recursively
|
||||
*
|
||||
* 递归读取目录下的所有文件
|
||||
*
|
||||
* @param root - Root directory path to read from / 要读取的根目录路径
|
||||
* @returns Array of file objects / 文件对象数组
|
||||
*/
|
||||
export async function readFiles(root: string): Promise<File[]> {
|
||||
const filepaths = await fs.readdir(root, { recursive: true })
|
||||
const files: File[] = []
|
||||
@ -18,6 +26,15 @@ export async function readFiles(root: string): Promise<File[]> {
|
||||
return files
|
||||
}
|
||||
|
||||
/**
|
||||
* Write files to target directory
|
||||
*
|
||||
* 将文件写入目标目录
|
||||
*
|
||||
* @param files - Array of file objects to write / 要写入的文件对象数组
|
||||
* @param target - Target directory path / 目标目录路径
|
||||
* @param rewrite - Optional function to rewrite file paths / 可选的文件路径重写函数
|
||||
*/
|
||||
export async function writeFiles(
|
||||
files: File[],
|
||||
target: string,
|
||||
@ -32,6 +49,14 @@ export async function writeFiles(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse JSON file
|
||||
*
|
||||
* 读取并解析 JSON 文件
|
||||
*
|
||||
* @param filepath - Path to JSON file / JSON 文件路径
|
||||
* @returns Parsed JSON object or null if parsing fails / 解析后的 JSON 对象,解析失败返回 null
|
||||
*/
|
||||
export async function readJsonFile<T extends Record<string, any> = Record<string, any>>(filepath: string): Promise<T | null> {
|
||||
try {
|
||||
const content = await fs.readFile(filepath, 'utf-8')
|
||||
|
||||
@ -3,8 +3,24 @@ import { fileURLToPath } from 'node:url'
|
||||
|
||||
export const __dirname: string = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
/**
|
||||
* Resolve path relative to the project root
|
||||
*
|
||||
* 相对于项目根目录解析路径
|
||||
*
|
||||
* @param args - Path segments to resolve / 要解析的路径段
|
||||
* @returns Resolved absolute path / 解析后的绝对路径
|
||||
*/
|
||||
export const resolve = (...args: string[]): string => path.resolve(__dirname, '../', ...args)
|
||||
|
||||
/**
|
||||
* Get template directory path
|
||||
*
|
||||
* 获取模板目录路径
|
||||
*
|
||||
* @param dir - Subdirectory name within templates / templates 中的子目录名称
|
||||
* @returns Resolved template directory path / 解析后的模板目录路径
|
||||
*/
|
||||
export const getTemplate = (dir: string): string => resolve('templates', dir)
|
||||
|
||||
export * from './fs.js'
|
||||
|
||||
@ -3,6 +3,36 @@ import { parseRect } from '../utils/parseRect.js'
|
||||
import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
import { createContainerPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Flex container attributes
|
||||
*
|
||||
* Flex 容器属性
|
||||
*/
|
||||
interface FlexContainerAttrs {
|
||||
center?: boolean
|
||||
wrap?: boolean
|
||||
between?: boolean
|
||||
around?: boolean
|
||||
start?: boolean
|
||||
end?: boolean
|
||||
gap?: string
|
||||
column?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Align plugin - Enable text alignment containers
|
||||
*
|
||||
* 对齐插件 - 启用文本对齐容器
|
||||
*
|
||||
* Syntax:
|
||||
* - ::: left
|
||||
* - ::: center
|
||||
* - ::: right
|
||||
* - ::: justify
|
||||
* - ::: flex [options]
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
*/
|
||||
export function alignPlugin(md: Markdown): void {
|
||||
const alignList = ['left', 'center', 'right', 'justify']
|
||||
|
||||
@ -38,14 +68,3 @@ export function alignPlugin(md: Markdown): void {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
interface FlexContainerAttrs {
|
||||
center?: boolean
|
||||
wrap?: boolean
|
||||
between?: boolean
|
||||
around?: boolean
|
||||
start?: boolean
|
||||
end?: boolean
|
||||
gap?: string
|
||||
column?: boolean
|
||||
}
|
||||
|
||||
@ -3,16 +3,38 @@ import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
||||
import { createContainerPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Card container attributes
|
||||
*
|
||||
* 卡片容器属性
|
||||
*/
|
||||
interface CardAttrs {
|
||||
title?: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Card masonry container attributes
|
||||
*
|
||||
* 卡片瀑布流容器属性
|
||||
*/
|
||||
interface CardMasonryAttrs {
|
||||
cols?: number
|
||||
gap?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Card plugin - Enable card containers
|
||||
*
|
||||
* 卡片插件 - 启用卡片容器
|
||||
*
|
||||
* Syntax:
|
||||
* - ::: card title="xxx" icon="xxx"
|
||||
* - ::: card-grid
|
||||
* - ::: card-masonry cols="2" gap="10"
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
*/
|
||||
export function cardPlugin(md: Markdown): void {
|
||||
/**
|
||||
* ::: card title="xxx" icon="xxx"
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
/**
|
||||
* Chat container plugin
|
||||
*
|
||||
* 聊天容器插件
|
||||
*
|
||||
* Syntax:
|
||||
* ```md
|
||||
* ::: chat
|
||||
* {:time}
|
||||
*
|
||||
@ -8,12 +14,18 @@
|
||||
* {.}
|
||||
* xxxxxx
|
||||
* :::
|
||||
* ```
|
||||
*/
|
||||
import type { PluginSimple } from 'markdown-it'
|
||||
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
|
||||
import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv.js'
|
||||
import { createContainerSyntaxPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Chat message structure
|
||||
*
|
||||
* 聊天消息结构
|
||||
*/
|
||||
interface ChatMessage {
|
||||
sender: 'user' | 'self'
|
||||
date: string
|
||||
@ -21,6 +33,16 @@ interface ChatMessage {
|
||||
content: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Render chat messages to HTML
|
||||
*
|
||||
* 渲染聊天消息为 HTML
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
* @param env - Markdown environment / Markdown 环境
|
||||
* @param messages - Chat messages / 聊天消息数组
|
||||
* @returns Rendered HTML / 渲染后的 HTML
|
||||
*/
|
||||
function chatMessagesRender(md: Markdown, env: MarkdownEnv, messages: ChatMessage[]): string {
|
||||
let currentDate = ''
|
||||
return messages.map(({ sender, username, date, content }) => {
|
||||
@ -43,7 +65,14 @@ function chatMessagesRender(md: Markdown, env: MarkdownEnv, messages: ChatMessag
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
// 解析聊天内容的核心逻辑
|
||||
/**
|
||||
* Parse chat content to messages
|
||||
*
|
||||
* 解析聊天内容为消息数组
|
||||
*
|
||||
* @param content - Raw chat content / 原始聊天内容
|
||||
* @returns Parsed chat messages / 解析后的聊天消息
|
||||
*/
|
||||
function parseChatContent(content: string): ChatMessage[] {
|
||||
const lines = content.split('\n')
|
||||
const messages: ChatMessage[] = []
|
||||
@ -53,13 +82,13 @@ function parseChatContent(content: string): ChatMessage[] {
|
||||
|
||||
for (const line of lines) {
|
||||
const lineStr = line.trim()
|
||||
// 解析时间标记
|
||||
// Parse time marker
|
||||
if (lineStr.startsWith('{:') && lineStr.endsWith('}')) {
|
||||
currentDate = lineStr.slice(2, -1).trim()
|
||||
continue
|
||||
}
|
||||
|
||||
// 解析用户 / 本人标记
|
||||
// Parse user/self marker
|
||||
if (lineStr.startsWith('{') && lineStr.endsWith('}')) {
|
||||
const username = lineStr.slice(1, -1).trim()
|
||||
message = {
|
||||
@ -72,7 +101,7 @@ function parseChatContent(content: string): ChatMessage[] {
|
||||
continue
|
||||
}
|
||||
|
||||
// 收集消息内容
|
||||
// Collect message content
|
||||
if (message?.sender) {
|
||||
message.content.push(line)
|
||||
}
|
||||
@ -80,6 +109,13 @@ function parseChatContent(content: string): ChatMessage[] {
|
||||
return messages
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat plugin - Enable chat container
|
||||
*
|
||||
* 聊天插件 - 启用聊天容器
|
||||
*
|
||||
* @param md - Markdown-it instance / Markdown-it 实例
|
||||
*/
|
||||
export const chatPlugin: PluginSimple = md => createContainerSyntaxPlugin(
|
||||
md,
|
||||
'chat',
|
||||
|
||||
@ -6,6 +6,14 @@ import { definitions, getFileIconName, getFileIconTypeFromExtension } from '../f
|
||||
import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv.js'
|
||||
import { stringifyProp } from '../utils/stringifyProp.js'
|
||||
|
||||
/**
|
||||
* Create code tab icon getter function
|
||||
*
|
||||
* 创建代码标签页图标获取函数
|
||||
*
|
||||
* @param options - Code tabs options / 代码标签页选项
|
||||
* @returns Icon getter function / 图标获取函数
|
||||
*/
|
||||
export function createCodeTabIconGetter(
|
||||
options: CodeTabsOptions = {},
|
||||
): (filename: string) => string | void {
|
||||
@ -35,6 +43,14 @@ export function createCodeTabIconGetter(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Code tabs plugin - Enable code tabs container
|
||||
*
|
||||
* 代码标签页插件 - 启用代码标签页容器
|
||||
*
|
||||
* @param md - Markdown-it instance / Markdown-it 实例
|
||||
* @param options - Code tabs options / 代码标签页选项
|
||||
*/
|
||||
export const codeTabs: PluginWithOptions<CodeTabsOptions> = (md, options: CodeTabsOptions = {}) => {
|
||||
const getIcon = createCodeTabIconGetter(options)
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @module CodeTree
|
||||
* Code tree container plugin
|
||||
*
|
||||
* code-tree 容器
|
||||
* ````md
|
||||
@ -10,7 +10,7 @@
|
||||
* :::
|
||||
* ````
|
||||
*
|
||||
* embed syntax
|
||||
* Embed syntax
|
||||
*
|
||||
* `@[code-tree title="Project Name" height="400px" entry="filepath"](dir_path)`
|
||||
*/
|
||||
@ -32,6 +32,11 @@ import { resolveAttr, resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
||||
import { createContainerPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Unsupported file types for code tree
|
||||
*
|
||||
* 代码树不支持的文件类型
|
||||
*/
|
||||
const UNSUPPORTED_FILE_TYPES = [
|
||||
/* image */
|
||||
'jpg',
|
||||
@ -62,26 +67,36 @@ const UNSUPPORTED_FILE_TYPES = [
|
||||
]
|
||||
|
||||
/**
|
||||
* Code tree metadata
|
||||
*
|
||||
* code-tree 容器元信息
|
||||
*/
|
||||
interface CodeTreeMeta {
|
||||
title?: string
|
||||
/**
|
||||
* File icon type
|
||||
*
|
||||
* 文件图标类型
|
||||
*/
|
||||
icon?: FileTreeIconMode
|
||||
/**
|
||||
* Code tree container height
|
||||
*
|
||||
* 代码树容器高度
|
||||
*/
|
||||
height?: string
|
||||
|
||||
/**
|
||||
* Entry file, opened by default
|
||||
*
|
||||
* 入口文件,默认打开
|
||||
*/
|
||||
entry?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* File tree node type
|
||||
*
|
||||
* 文件树节点类型
|
||||
*/
|
||||
interface FileTreeNode {
|
||||
@ -92,9 +107,12 @@ interface FileTreeNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse file paths to file tree node structure
|
||||
*
|
||||
* 将文件路径数组解析为文件树节点结构
|
||||
* @param files 文件路径数组
|
||||
* @returns 文件树节点数组
|
||||
*
|
||||
* @param files - File path array / 文件路径数组
|
||||
* @returns File tree node array / 文件树节点数组
|
||||
*/
|
||||
function parseFileNodes(files: string[]): FileTreeNode[] {
|
||||
const nodes: FileTreeNode[] = []
|
||||
@ -123,13 +141,18 @@ function parseFileNodes(files: string[]): FileTreeNode[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Code tree plugin - Register code-tree container and embed syntax
|
||||
*
|
||||
* 注册 code-tree 容器和嵌入语法的 markdown 插件
|
||||
* @param md markdown-it 实例
|
||||
* @param app vuepress app 实例
|
||||
* @param options code-tree 配置项
|
||||
*
|
||||
* @param md - Markdown-it instance / markdown-it 实例
|
||||
* @param app - VuePress app instance / vuepress app 实例
|
||||
* @param options - Code tree options / code-tree 配置项
|
||||
*/
|
||||
export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions = {}): void {
|
||||
/**
|
||||
* Get file or folder icon
|
||||
*
|
||||
* 获取文件或文件夹的图标
|
||||
*/
|
||||
const getIcon = (filename: string, type: 'folder' | 'file', mode?: FileTreeIconMode): string => {
|
||||
@ -140,6 +163,8 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Render file tree nodes to component string
|
||||
*
|
||||
* 渲染文件树节点为组件字符串
|
||||
*/
|
||||
function renderFileTree(nodes: FileTreeNode[], mode?: FileTreeIconMode): string {
|
||||
@ -159,10 +184,10 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
// 注册 ::: code-tree 容器
|
||||
// Register ::: code-tree container
|
||||
createContainerPlugin(md, 'code-tree', {
|
||||
before: (info, tokens, index) => {
|
||||
// 收集 code-tree 容器内的文件名和激活文件
|
||||
// Collect filenames and active file in code-tree container
|
||||
const files: string[] = []
|
||||
let activeFile: string | undefined
|
||||
for (
|
||||
@ -197,7 +222,7 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
|
||||
after: () => '</VPCodeTree>',
|
||||
})
|
||||
|
||||
// 注册 @[code-tree](dir) 语法
|
||||
// Register @[code-tree](dir) syntax
|
||||
createEmbedRuleBlock(md, {
|
||||
type: 'code-tree',
|
||||
syntaxPattern: /^@\[code-tree([^\]]*)\]\(([^)]*)\)/,
|
||||
@ -213,10 +238,10 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
|
||||
}
|
||||
},
|
||||
content: ({ dir, icon, ...props }, _, env) => {
|
||||
// codeTreeFiles 用于页面依赖收集
|
||||
// codeTreeFiles for page dependency collection
|
||||
const codeTreeFiles = ((env as any).codeTreeFiles ??= []) as string[]
|
||||
const root = findFile(app, env, dir)
|
||||
// 获取目录下所有文件
|
||||
// Get all files in directory
|
||||
const files = tinyglobby.globSync('**/*', {
|
||||
cwd: root,
|
||||
onlyFiles: true,
|
||||
@ -229,7 +254,7 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
|
||||
})
|
||||
props.entryFile ||= files[0]
|
||||
|
||||
// 生成所有文件的代码块内容
|
||||
// Generate code block content for all files
|
||||
const codeContent = files.map((file) => {
|
||||
const ext = path.extname(file).slice(1)
|
||||
if (UNSUPPORTED_FILE_TYPES.includes(ext)) {
|
||||
@ -250,8 +275,11 @@ export function codeTreePlugin(md: Markdown, app: App, options: CodeTreeOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend page dependencies with codeTreeFiles
|
||||
*
|
||||
* 扩展页面依赖,将 codeTreeFiles 添加到页面依赖中
|
||||
* @param page vuepress 页面对象
|
||||
*
|
||||
* @param page - VuePress page object / vuepress 页面对象
|
||||
*/
|
||||
export function extendsPageWithCodeTree(page: Page): void {
|
||||
const markdownEnv = page.markdownEnv
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
/**
|
||||
* Collapse container plugin
|
||||
*
|
||||
* 折叠面板容器插件
|
||||
*
|
||||
* Syntax:
|
||||
* ```md
|
||||
* ::: collapse accordion
|
||||
* - + 标题
|
||||
* 内容
|
||||
* - - 标题
|
||||
* 内容
|
||||
* :::
|
||||
* ```
|
||||
*/
|
||||
import type Token from 'markdown-it/lib/token.mjs'
|
||||
import type { Markdown } from 'vuepress/markdown'
|
||||
@ -12,16 +19,33 @@ import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
||||
import { createContainerPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Collapse metadata
|
||||
*
|
||||
* 折叠面板元数据
|
||||
*/
|
||||
interface CollapseMeta {
|
||||
accordion?: boolean
|
||||
expand?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse item metadata
|
||||
*
|
||||
* 折叠面板项元数据
|
||||
*/
|
||||
interface CollapseItemMeta {
|
||||
expand?: boolean
|
||||
index?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse plugin - Enable collapse container
|
||||
*
|
||||
* 折叠面板插件 - 启用折叠面板容器
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
*/
|
||||
export function collapsePlugin(md: Markdown): void {
|
||||
createContainerPlugin(md, 'collapse', {
|
||||
before: (info, tokens, index) => {
|
||||
@ -43,9 +67,19 @@ export function collapsePlugin(md: Markdown): void {
|
||||
md.renderer.rules.collapse_item_title_close = () => '</template>'
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse collapse tokens
|
||||
*
|
||||
* 解析折叠面板令牌
|
||||
*
|
||||
* @param tokens - Token array / 令牌数组
|
||||
* @param index - Start index / 起始索引
|
||||
* @param attrs - Collapse metadata / 折叠面板元数据
|
||||
* @returns Default expanded index / 默认展开索引
|
||||
*/
|
||||
function parseCollapse(tokens: Token[], index: number, attrs: CollapseMeta): number | void {
|
||||
const listStack: number[] = [] // 记录列表嵌套深度
|
||||
let idx = -1 // 记录当前列表项下标
|
||||
const listStack: number[] = [] // Track list nesting depth
|
||||
let idx = -1 // Current list item index
|
||||
let defaultIndex: number | undefined
|
||||
let hashExpand = false
|
||||
for (let i = index + 1; i < tokens.length; i++) {
|
||||
@ -53,9 +87,9 @@ function parseCollapse(tokens: Token[], index: number, attrs: CollapseMeta): num
|
||||
if (token.type === 'container_collapse_close') {
|
||||
break
|
||||
}
|
||||
// 列表层级追踪
|
||||
// Track list level
|
||||
if (token.type === 'bullet_list_open' || token.type === 'ordered_list_open') {
|
||||
listStack.push(0) // 每个新列表初始层级为0
|
||||
listStack.push(0) // Each new list starts at level 0
|
||||
if (listStack.length === 1)
|
||||
token.hidden = true
|
||||
}
|
||||
@ -66,7 +100,7 @@ function parseCollapse(tokens: Token[], index: number, attrs: CollapseMeta): num
|
||||
}
|
||||
else if (token.type === 'list_item_open') {
|
||||
const currentLevel = listStack.length
|
||||
// 仅处理根级列表项(层级1)
|
||||
// Only process root level list items (level 1)
|
||||
if (currentLevel === 1) {
|
||||
token.type = 'collapse_item_open'
|
||||
tokens[i + 1].type = 'collapse_item_title_open'
|
||||
|
||||
@ -5,59 +5,73 @@ import container from 'markdown-it-container'
|
||||
import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
|
||||
/**
|
||||
* RenderRuleParams 类型用于获取 RenderRule 的参数类型。
|
||||
* Type for getting RenderRule parameters
|
||||
*
|
||||
* 获取 RenderRule 参数的类型
|
||||
*/
|
||||
type RenderRuleParams = Parameters<RenderRule> extends [...infer Args, infer _] ? Args : never
|
||||
|
||||
/**
|
||||
* 自定义容器的配置项。
|
||||
* - before: 渲染容器起始标签时的回调
|
||||
* - after: 渲染容器结束标签时的回调
|
||||
* Container options
|
||||
*
|
||||
* 自定义容器的配置项
|
||||
*/
|
||||
export interface ContainerOptions {
|
||||
/**
|
||||
* Callback for rendering container opening tag
|
||||
*
|
||||
* 渲染容器起始标签时的回调
|
||||
*/
|
||||
before?: (info: string, ...args: RenderRuleParams) => string
|
||||
/**
|
||||
* Callback for rendering container closing tag
|
||||
*
|
||||
* 渲染容器结束标签时的回调
|
||||
*/
|
||||
after?: (info: string, ...args: RenderRuleParams) => string
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 markdown-it 的自定义容器插件。
|
||||
* Create markdown-it custom container plugin
|
||||
*
|
||||
* @param md markdown-it 实例
|
||||
* @param type 容器类型(如 'tip', 'warning' 等)
|
||||
* @param options 可选的 before/after 渲染钩子
|
||||
* @param options.before 渲染容器起始标签时的回调函数
|
||||
* @param options.after 渲染容器结束标签时的回调函数
|
||||
* 创建 markdown-it 的自定义容器插件
|
||||
*
|
||||
* @param md - Markdown-it instance / Markdown-it 实例
|
||||
* @param type - Container type (e.g., 'tip', 'warning') / 容器类型(如 'tip', 'warning' 等)
|
||||
* @param options - Optional before/after render hooks / 可选的 before/after 渲染钩子
|
||||
* @param options.before - Callback for rendering container opening tag / 渲染容器起始标签时的回调函数
|
||||
* @param options.after - Callback for rendering container closing tag / 渲染容器结束标签时的回调函数
|
||||
*/
|
||||
export function createContainerPlugin(
|
||||
md: Markdown,
|
||||
type: string,
|
||||
{ before, after }: ContainerOptions = {},
|
||||
): void {
|
||||
// 自定义渲染规则
|
||||
// Custom render rule
|
||||
const render: RenderRule = (tokens, index, options, env): string => {
|
||||
const token = tokens[index]
|
||||
// 提取 ::: 后的 info 信息
|
||||
// Extract info after :::
|
||||
const info = token.info.trim().slice(type.length).trim() || ''
|
||||
if (token.nesting === 1) {
|
||||
// 容器起始标签
|
||||
// Container opening tag
|
||||
return before?.(info, tokens, index, options, env) ?? `<div class="custom-container ${type}">`
|
||||
}
|
||||
else {
|
||||
// 容器结束标签
|
||||
// Container closing tag
|
||||
return after?.(info, tokens, index, options, env) ?? '</div>'
|
||||
}
|
||||
}
|
||||
|
||||
// 注册 markdown-it-container 插件
|
||||
// Register markdown-it-container plugin
|
||||
md.use(container, type, { render })
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个自定义的容器规则,内容不会交给 markdown-it 处理。
|
||||
* 需要自定义 content 的处理逻辑
|
||||
* Create a custom container rule where content is not processed by markdown-it
|
||||
* Requires custom content processing logic
|
||||
* ```md
|
||||
* ::: type
|
||||
* xxxx <-- content: 这部分的内容不会交给 markdown-it 处理
|
||||
* xxxx <-- content: this part will not be processed by markdown-it
|
||||
* :::
|
||||
* ```
|
||||
*
|
||||
@ -68,6 +82,10 @@ export function createContainerPlugin(
|
||||
* return `<div class="example">${meta.title} | ${content}</div>`
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @param md - Markdown-it instance / Markdown-it 实例
|
||||
* @param type - Container type / 容器类型
|
||||
* @param render - Custom render rule / 自定义渲染规则
|
||||
*/
|
||||
export function createContainerSyntaxPlugin(
|
||||
md: Markdown,
|
||||
@ -78,12 +96,15 @@ export function createContainerSyntaxPlugin(
|
||||
const markerMinLen = 3
|
||||
|
||||
/**
|
||||
* 自定义容器的 block 规则定义。
|
||||
* @param state 当前 block 状态
|
||||
* @param startLine 起始行
|
||||
* @param endLine 结束行
|
||||
* @param silent 是否为静默模式
|
||||
* @returns 是否匹配到自定义容器
|
||||
* Custom container block rule definition
|
||||
*
|
||||
* 自定义容器的 block 规则定义
|
||||
*
|
||||
* @param state - Current block state / 当前 block 状态
|
||||
* @param startLine - Start line / 起始行
|
||||
* @param endLine - End line / 结束行
|
||||
* @param silent - Silent mode / 是否为静默模式
|
||||
* @returns Whether matched / 是否匹配到自定义容器
|
||||
*/
|
||||
function defineContainer(state: StateBlock, startLine: number, endLine: number, silent: boolean): boolean {
|
||||
const start = state.bMarks[startLine] + state.tShift[startLine]
|
||||
@ -91,11 +112,11 @@ export function createContainerSyntaxPlugin(
|
||||
let pos = start
|
||||
|
||||
// check marker
|
||||
// 检查是否以指定的 maker(:)开头
|
||||
// Check if starts with specified maker (:)
|
||||
if (state.src[pos] !== maker)
|
||||
return false
|
||||
|
||||
// 检查 marker 长度是否满足要求
|
||||
// Check if marker length meets requirements
|
||||
for (pos = start + 1; pos <= max; pos++) {
|
||||
if (state.src[pos] !== maker)
|
||||
break
|
||||
@ -108,7 +129,7 @@ export function createContainerSyntaxPlugin(
|
||||
const info = state.src.slice(pos, max).trim()
|
||||
|
||||
// ::: type
|
||||
// 检查 info 是否以 type 开头
|
||||
// Check if info starts with type
|
||||
if (!info.startsWith(type))
|
||||
return false
|
||||
|
||||
@ -118,7 +139,7 @@ export function createContainerSyntaxPlugin(
|
||||
|
||||
let line = startLine
|
||||
let content = ''
|
||||
// 收集容器内容,直到遇到结束的 marker
|
||||
// Collect container content until end marker
|
||||
while (++line < endLine) {
|
||||
if (state.src.slice(state.bMarks[line], state.eMarks[line]).trim() === markup) {
|
||||
break
|
||||
@ -127,7 +148,7 @@ export function createContainerSyntaxPlugin(
|
||||
content += `${state.src.slice(state.bMarks[line], state.eMarks[line])}\n`
|
||||
}
|
||||
|
||||
// 创建 token,保存内容和属性
|
||||
// Create token, save content and attributes
|
||||
const token = state.push(`${type}_container`, '', 0)
|
||||
token.meta = resolveAttrs(info.slice(type.length)).attrs
|
||||
token.content = content
|
||||
@ -139,13 +160,13 @@ export function createContainerSyntaxPlugin(
|
||||
return true
|
||||
}
|
||||
|
||||
// 默认渲染函数
|
||||
// Default render function
|
||||
const defaultRender: RenderRule = (tokens, index) => {
|
||||
const { content } = tokens[index]
|
||||
return `<div class="custom-container ${type}">${content}</div>`
|
||||
}
|
||||
|
||||
// 注册 block 规则和渲染规则
|
||||
// Register block rule and render rule
|
||||
md.block.ruler.before('fence', `${type}_definition`, defineContainer)
|
||||
md.renderer.rules[`${type}_container`] = render ?? defaultRender
|
||||
}
|
||||
|
||||
@ -2,6 +2,11 @@ import type { Markdown } from 'vuepress/markdown'
|
||||
import { resolveAttrs } from '.././utils/resolveAttrs.js'
|
||||
import { createContainerPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Demo wrapper attributes
|
||||
*
|
||||
* 演示包装器属性
|
||||
*/
|
||||
interface DemoWrapperAttrs {
|
||||
title?: string
|
||||
img?: string
|
||||
@ -10,8 +15,14 @@ interface DemoWrapperAttrs {
|
||||
}
|
||||
|
||||
/**
|
||||
* :::demo-wrapper img no-padding title="xxx" height="100px"
|
||||
* :::
|
||||
* Demo wrapper plugin - Enable demo wrapper container
|
||||
*
|
||||
* 演示包装器插件 - 启用演示包装器容器
|
||||
*
|
||||
* Syntax: :::demo-wrapper img no-padding title="xxx" height="100px"
|
||||
* 语法::::demo-wrapper img no-padding title="xxx" height="100px"
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
*/
|
||||
export function demoWrapperPlugin(md: Markdown): void {
|
||||
createContainerPlugin(md, 'demo-wrapper', {
|
||||
|
||||
@ -10,16 +10,35 @@ import { encryptContent } from '../utils/encryptContent'
|
||||
import { logger } from '../utils/logger'
|
||||
import { createContainerSyntaxPlugin } from './createContainer'
|
||||
|
||||
/**
|
||||
* Encryption options
|
||||
*
|
||||
* 加密选项
|
||||
*/
|
||||
interface EncryptOptions {
|
||||
password: string
|
||||
salt: Uint8Array
|
||||
iv: Uint8Array
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt plugin - Enable encrypted content container
|
||||
*
|
||||
* 加密插件 - 启用加密内容容器
|
||||
*
|
||||
* @param app - VuePress app / VuePress 应用
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
* @param options - Encrypt snippet options / 加密片段选项
|
||||
*/
|
||||
export function encryptPlugin(app: App, md: Markdown, options: EncryptSnippetOptions): void {
|
||||
const encrypted: Set<string> = new Set()
|
||||
const entryFile = 'internal/encrypt-snippets/index.js'
|
||||
|
||||
/**
|
||||
* Write encrypted content to temp file
|
||||
*
|
||||
* 将加密内容写入临时文件
|
||||
*/
|
||||
const writeTemp = async (
|
||||
hash: string,
|
||||
content: string,
|
||||
@ -29,6 +48,11 @@ export function encryptPlugin(app: App, md: Markdown, options: EncryptSnippetOpt
|
||||
await app.writeTemp(`internal/encrypt-snippets/${hash}.js`, `export default ${JSON.stringify(encrypted)}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write entry file with all encrypted snippets
|
||||
*
|
||||
* 写入包含所有加密片段的入口文件
|
||||
*/
|
||||
const writeEntry = debounce(150, async () => {
|
||||
let content = `export default {\n`
|
||||
for (const hash of encrypted) {
|
||||
@ -39,11 +63,17 @@ export function encryptPlugin(app: App, md: Markdown, options: EncryptSnippetOpt
|
||||
})
|
||||
|
||||
if (!fs.existsSync(app.dir.temp(entryFile))) {
|
||||
// 初始化
|
||||
// Initialize
|
||||
app.writeTemp(entryFile, 'export default {}\n')
|
||||
}
|
||||
|
||||
const localKeys = Object.keys(app.options.locales || {}).filter(key => key !== '/')
|
||||
|
||||
/**
|
||||
* Get locale from relative path
|
||||
*
|
||||
* 从相对路径获取本地化
|
||||
*/
|
||||
const getLocale = (relativePath: string) => {
|
||||
const relative = ensureLeadingSlash(relativePath)
|
||||
return localKeys.find(key => relative.startsWith(key)) || '/'
|
||||
|
||||
@ -4,6 +4,11 @@ import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
||||
import { createContainerPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Field attributes
|
||||
*
|
||||
* 字段属性
|
||||
*/
|
||||
interface FieldAttrs {
|
||||
name: string
|
||||
type?: string
|
||||
@ -13,6 +18,16 @@ interface FieldAttrs {
|
||||
default?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Field plugin - Enable field container for API documentation
|
||||
*
|
||||
* 字段插件 - 启用用于 API 文档的字段容器
|
||||
*
|
||||
* Syntax: ::: field name="xxx" type="string" required
|
||||
* 语法:::: field name="xxx" type="string" required
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
*/
|
||||
export function fieldPlugin(md: Markdown): void {
|
||||
createContainerPlugin(md, 'field', {
|
||||
before: (info) => {
|
||||
|
||||
@ -7,6 +7,8 @@ import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
||||
import { createContainerSyntaxPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* File tree node structure
|
||||
*
|
||||
* 文件树节点结构
|
||||
*/
|
||||
interface FileTreeNode extends FileTreeNodeProps {
|
||||
@ -15,6 +17,8 @@ interface FileTreeNode extends FileTreeNodeProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* File tree container attributes
|
||||
*
|
||||
* 文件树容器属性
|
||||
*/
|
||||
interface FileTreeAttrs {
|
||||
@ -23,6 +27,8 @@ interface FileTreeAttrs {
|
||||
}
|
||||
|
||||
/**
|
||||
* File tree node props (for rendering component)
|
||||
*
|
||||
* 文件树节点属性(用于渲染组件)
|
||||
*/
|
||||
export interface FileTreeNodeProps {
|
||||
@ -36,25 +42,28 @@ export interface FileTreeNodeProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse raw file tree content to node tree structure
|
||||
*
|
||||
* 解析原始文件树内容为节点树结构
|
||||
* @param content 文件树的原始文本内容
|
||||
* @returns 文件树节点数组
|
||||
*
|
||||
* @param content - Raw file tree text content / 文件树的原始文本内容
|
||||
* @returns File tree node array / 文件树节点数组
|
||||
*/
|
||||
export function parseFileTreeRawContent(content: string): FileTreeNode[] {
|
||||
const root: FileTreeNode = { level: -1, children: [] } as unknown as FileTreeNode
|
||||
const stack: FileTreeNode[] = [root]
|
||||
const lines = content.trimEnd().split('\n')
|
||||
const spaceLength = lines[0].match(/^\s*/)?.[0].length ?? 0 // 去除行首空格/)
|
||||
const spaceLength = lines[0].match(/^\s*/)?.[0].length ?? 0 // Remove leading spaces
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^(\s*)-(.*)$/)
|
||||
if (!match)
|
||||
continue
|
||||
|
||||
const level = Math.floor((match[1].length - spaceLength) / 2) // 每两个空格为一个层级
|
||||
const level = Math.floor((match[1].length - spaceLength) / 2) // Two spaces per level
|
||||
const info = match[2].trim()
|
||||
|
||||
// 检索当前层级的父节点
|
||||
// Find parent node at current level
|
||||
while (stack.length > 0 && stack[stack.length - 1].level >= level) {
|
||||
stack.pop()
|
||||
}
|
||||
@ -68,12 +77,20 @@ export function parseFileTreeRawContent(content: string): FileTreeNode[] {
|
||||
return root.children
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex for focus marker
|
||||
*
|
||||
* 高亮标记正则
|
||||
*/
|
||||
const RE_FOCUS = /^\*\*(.*)\*\*(?:$|\s+)/
|
||||
|
||||
/**
|
||||
* Parse single node info string, extract filename, comment, type, etc.
|
||||
*
|
||||
* 解析单个节点的 info 字符串,提取文件名、注释、类型等属性
|
||||
* @param info 节点描述字符串
|
||||
* @returns 文件树节点属性
|
||||
*
|
||||
* @param info - Node description string / 节点描述字符串
|
||||
* @returns File tree node props / 文件树节点属性
|
||||
*/
|
||||
export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
|
||||
let filename = ''
|
||||
@ -83,7 +100,7 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
|
||||
let type: 'folder' | 'file' = 'file'
|
||||
let diff: 'add' | 'remove' | undefined
|
||||
|
||||
// 处理 diff 标记
|
||||
// Process diff marker
|
||||
if (info.startsWith('++')) {
|
||||
info = info.slice(2).trim()
|
||||
diff = 'add'
|
||||
@ -93,14 +110,14 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
|
||||
diff = 'remove'
|
||||
}
|
||||
|
||||
// 处理高亮(focus)标记
|
||||
// Process focus marker
|
||||
info = info.replace(RE_FOCUS, (_, matched) => {
|
||||
filename = matched
|
||||
focus = true
|
||||
return ''
|
||||
})
|
||||
|
||||
// 提取文件名和注释
|
||||
// Extract filename and comment
|
||||
if (filename === '' && !focus) {
|
||||
const spaceIndex = info.indexOf(' ')
|
||||
filename = info.slice(0, spaceIndex === -1 ? info.length : spaceIndex)
|
||||
@ -109,7 +126,7 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
|
||||
|
||||
comment = info.trim()
|
||||
|
||||
// 判断是否为文件夹
|
||||
// Determine if folder
|
||||
if (filename.endsWith('/')) {
|
||||
type = 'folder'
|
||||
expanded = false
|
||||
@ -120,9 +137,13 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* File tree markdown plugin main function
|
||||
*
|
||||
* 文件树 markdown 插件主函数
|
||||
* @param md markdown 实例
|
||||
* @param options 文件树渲染选项
|
||||
*
|
||||
* @param md - Markdown instance / markdown 实例
|
||||
* @param options - File tree render options / 文件树渲染选项
|
||||
* @param locales - Locale data / 本地化数据
|
||||
*/
|
||||
export function fileTreePlugin(
|
||||
md: Markdown,
|
||||
@ -130,6 +151,8 @@ export function fileTreePlugin(
|
||||
locales: Record<string, CommonLocaleData>,
|
||||
): void {
|
||||
/**
|
||||
* Get file or folder icon
|
||||
*
|
||||
* 获取文件或文件夹的图标
|
||||
*/
|
||||
const getIcon = (filename: string, type: 'folder' | 'file', mode?: FileTreeIconMode): string => {
|
||||
@ -140,6 +163,8 @@ export function fileTreePlugin(
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively render file tree nodes
|
||||
*
|
||||
* 递归渲染文件树节点
|
||||
*/
|
||||
const renderFileTree = (nodes: FileTreeNode[], meta: FileTreeAttrs): string =>
|
||||
@ -147,7 +172,7 @@ export function fileTreePlugin(
|
||||
const { level, children, filename, comment, focus, expanded, type, diff } = node
|
||||
const isOmit = filename === '…' || filename === '...' /* fallback */
|
||||
|
||||
// 文件夹无子节点时补充省略号
|
||||
// Add ellipsis for folder without children
|
||||
if (children.length === 0 && type === 'folder') {
|
||||
children.push({ level: level + 1, children: [], filename: '…', type: 'file' } as unknown as FileTreeNode)
|
||||
}
|
||||
@ -172,7 +197,7 @@ ${renderedIcon}${renderedComment}${children.length > 0 ? renderFileTree(children
|
||||
</FileTreeNode>`
|
||||
}).join('\n')
|
||||
|
||||
// 注册自定义容器语法插件
|
||||
// Register custom container syntax plugin
|
||||
return createContainerSyntaxPlugin(
|
||||
md,
|
||||
'file-tree',
|
||||
@ -192,6 +217,15 @@ ${renderedIcon}${renderedComment}${children.length > 0 ? renderFileTree(children
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert file tree to command line text format
|
||||
*
|
||||
* 将文件树转换为命令行文本格式
|
||||
*
|
||||
* @param nodes - File tree nodes / 文件树节点
|
||||
* @param prefix - Line prefix / 行前缀
|
||||
* @returns CMD text / CMD 文本
|
||||
*/
|
||||
function fileTreeToCMDText(nodes: FileTreeNode[], prefix = ''): string {
|
||||
let content = prefix ? '' : '.\n'
|
||||
for (let i = 0, l = nodes.length; i < l; i++) {
|
||||
|
||||
@ -21,6 +21,16 @@ import { tablePlugin } from './table.js'
|
||||
import { tabs } from './tabs.js'
|
||||
import { timelinePlugin } from './timeline.js'
|
||||
|
||||
/**
|
||||
* Container plugin - Register all container plugins
|
||||
*
|
||||
* 容器插件 - 注册所有容器插件
|
||||
*
|
||||
* @param app - VuePress app / VuePress 应用
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
* @param options - Plugin options / 插件选项
|
||||
* @param locales - Locale configuration / 本地化配置
|
||||
*/
|
||||
export async function containerPlugin(
|
||||
app: App,
|
||||
md: Markdown,
|
||||
|
||||
@ -8,11 +8,32 @@ import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
||||
import { createContainerPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Code REPL metadata
|
||||
*
|
||||
* 代码 REPL 元数据
|
||||
*/
|
||||
interface CodeReplMeta {
|
||||
editable?: boolean
|
||||
title?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Language REPL plugin - Enable interactive code playgrounds
|
||||
*
|
||||
* 语言 REPL 插件 - 启用交互式代码游乐场
|
||||
*
|
||||
* Supports: kotlin, go, rust, python
|
||||
*
|
||||
* @param app - VuePress app / VuePress 应用
|
||||
* @param md - Markdown-it instance / Markdown-it 实例
|
||||
* @param options - REPL options / REPL 选项
|
||||
* @param options.theme - Editor theme / 编辑器主题
|
||||
* @param options.go - Enable Go playground / 启用 Go 游乐场
|
||||
* @param options.kotlin - Enable Kotlin playground / 启用 Kotlin 游乐场
|
||||
* @param options.rust - Enable Rust playground / 启用 Rust 游乐场
|
||||
* @param options.python - Enable Python playground / 启用 Python 游乐场
|
||||
*/
|
||||
export async function langReplPlugin(app: App, md: markdownIt, {
|
||||
theme,
|
||||
go = false,
|
||||
@ -20,6 +41,11 @@ export async function langReplPlugin(app: App, md: markdownIt, {
|
||||
rust = false,
|
||||
python = false,
|
||||
}: ReplOptions): Promise<void> {
|
||||
/**
|
||||
* Create container for specific language
|
||||
*
|
||||
* 为特定语言创建容器
|
||||
*/
|
||||
const container = (lang: string): void => createContainerPlugin(md, `${lang}-repl`, {
|
||||
before(info) {
|
||||
const { attrs } = resolveAttrs<CodeReplMeta>(info)
|
||||
@ -85,6 +111,14 @@ export async function langReplPlugin(app: App, md: markdownIt, {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Read JSON file
|
||||
*
|
||||
* 读取 JSON 文件
|
||||
*
|
||||
* @param file - File path / 文件路径
|
||||
* @returns Parsed JSON / 解析后的 JSON
|
||||
*/
|
||||
async function read(file: string): Promise<any> {
|
||||
try {
|
||||
const content = await fs.readFile(file, 'utf-8')
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
/**
|
||||
* npm-to container plugin
|
||||
*
|
||||
* 只写一个 npm 代码块,自动转为 代码块分组 [npm, pnpm, yarn, bun, deno]
|
||||
*
|
||||
* ::: npm-to
|
||||
@ -34,7 +36,12 @@ import { createContainerPlugin } from './createContainer.js'
|
||||
import { ALLOW_LIST, BOOL_FLAGS, DEFAULT_TABS, MANAGERS_CONFIG } from './npmToPreset.js'
|
||||
|
||||
/**
|
||||
* npm-to plugin - Convert npm commands to multiple package manager commands
|
||||
*
|
||||
* 注册 npm-to 容器插件,将 npm 代码块自动转换为多包管理器命令分组
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
* @param options - npm-to options / npm-to 选项
|
||||
*/
|
||||
export function npmToPlugins(md: Markdown, options: NpmToOptions = {}): void {
|
||||
const opt = isArray(options) ? { tabs: options } : options
|
||||
@ -50,14 +57,14 @@ export function npmToPlugins(md: Markdown, options: NpmToOptions = {}): void {
|
||||
token.hidden = true
|
||||
token.type = 'text'
|
||||
token.content = ''
|
||||
// 拆分命令行内容,转换为多包管理器命令
|
||||
// Split command line content, convert to multiple package manager commands
|
||||
const lines = content.split(/(\n|\s*&&\s*)/)
|
||||
return md.render(
|
||||
resolveNpmTo(lines, token.info.trim(), idx, tabs),
|
||||
cleanMarkdownEnv(env),
|
||||
)
|
||||
}
|
||||
// 非法容器警告
|
||||
// Invalid container warning
|
||||
logger.warn('npm-to', `Invalid npm-to container in ${colors.gray(env.filePathRelative || env.filePath)}`)
|
||||
return ''
|
||||
},
|
||||
@ -66,11 +73,15 @@ export function npmToPlugins(md: Markdown, options: NpmToOptions = {}): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert npm commands to package manager command groups
|
||||
*
|
||||
* 将 npm 命令转换为各包管理器命令分组
|
||||
* @param lines 命令行数组
|
||||
* @param info 代码块类型
|
||||
* @param idx token 索引
|
||||
* @param tabs 需要支持的包管理器
|
||||
*
|
||||
* @param lines - Command line array / 命令行数组
|
||||
* @param info - Code block type / 代码块类型
|
||||
* @param idx - Token index / token 索引
|
||||
* @param tabs - Package managers to support / 需要支持的包管理器
|
||||
* @returns code-tabs formatted string / code-tabs 格式字符串
|
||||
*/
|
||||
function resolveNpmTo(lines: string[], info: string, idx: number, tabs: NpmToPackageManager[]): string {
|
||||
tabs = validateTabs(tabs)
|
||||
@ -81,7 +92,7 @@ function resolveNpmTo(lines: string[], info: string, idx: number, tabs: NpmToPac
|
||||
for (const line of lines) {
|
||||
const config = findConfig(line)
|
||||
if (config && config[tab]) {
|
||||
// 解析并替换命令参数
|
||||
// Parse and replace command arguments
|
||||
const parsed = (map[line] ??= parseLine(line)) as LineParsed
|
||||
const { cli, flags } = config[tab] as CommandConfigItem
|
||||
|
||||
@ -105,14 +116,19 @@ function resolveNpmTo(lines: string[], info: string, idx: number, tabs: NpmToPac
|
||||
newLines.push(line)
|
||||
}
|
||||
}
|
||||
// 拼接为 code-tabs 格式
|
||||
// Concatenate as code-tabs format
|
||||
res.push(`@tab ${tab}\n\`\`\`${info}\n${newLines.join('')}\n\`\`\``)
|
||||
}
|
||||
return `:::code-tabs#npm-to-${tabs.join('-')}\n${res.join('\n')}\n:::`
|
||||
}
|
||||
|
||||
/**
|
||||
* Find package manager config by command line content
|
||||
*
|
||||
* 根据命令行内容查找对应的包管理器配置
|
||||
*
|
||||
* @param line - Command line / 命令行
|
||||
* @returns Command config / 命令配置
|
||||
*/
|
||||
function findConfig(line: string): CommandConfig | undefined {
|
||||
for (const { pattern, ...config } of Object.values(MANAGERS_CONFIG)) {
|
||||
@ -124,7 +140,12 @@ function findConfig(line: string): CommandConfig | undefined {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate tabs, return allowed package manager list
|
||||
*
|
||||
* 校验 tabs 合法性,返回允许的包管理器列表
|
||||
*
|
||||
* @param tabs - Package managers / 包管理器
|
||||
* @returns Validated tabs / 验证后的标签
|
||||
*/
|
||||
function validateTabs(tabs: NpmToPackageManager[]): NpmToPackageManager[] {
|
||||
tabs = tabs.filter(tab => ALLOW_LIST.includes(tab))
|
||||
@ -135,20 +156,32 @@ function validateTabs(tabs: NpmToPackageManager[]): NpmToPackageManager[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Command line parse result type
|
||||
*
|
||||
* 命令行解析结果类型
|
||||
*/
|
||||
interface LineParsed {
|
||||
env: string // 环境变量前缀
|
||||
cli: string // 命令行工具(npm/npx ...)
|
||||
cmd: string // 命令/脚本名
|
||||
args?: string // 参数
|
||||
scriptArgs?: string // 脚本参数
|
||||
env: string // Environment variable prefix / 环境变量前缀
|
||||
cli: string // CLI tool (npm/npx ...) / 命令行工具
|
||||
cmd: string // Command/script name / 命令/脚本名
|
||||
args?: string // Arguments / 参数
|
||||
scriptArgs?: string // Script arguments / 脚本参数
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex for parsing npm/npx commands
|
||||
*
|
||||
* 解析 npm/npx 命令的正则
|
||||
*/
|
||||
const LINE_REG = /(.*)(npm|npx)\s+(.*)/
|
||||
|
||||
/**
|
||||
* Parse a line of npm/npx command, split env, command, args, etc.
|
||||
*
|
||||
* 解析一行 npm/npx 命令,拆分出环境变量、命令、参数等
|
||||
*
|
||||
* @param line - Command line / 命令行
|
||||
* @returns Parsed result / 解析结果
|
||||
*/
|
||||
export function parseLine(line: string): false | LineParsed {
|
||||
const match = line.match(LINE_REG)
|
||||
@ -177,7 +210,12 @@ export function parseLine(line: string): false | LineParsed {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse npm command arguments, distinguish command, args, script args
|
||||
*
|
||||
* 解析 npm 命令参数,区分命令、参数、脚本参数
|
||||
*
|
||||
* @param line - Arguments line / 参数字符串
|
||||
* @returns Parsed args / 解析后的参数
|
||||
*/
|
||||
function parseArgs(line: string): { cmd: string, args?: string, scriptArgs?: string } {
|
||||
line = line?.trim()
|
||||
@ -186,7 +224,7 @@ function parseArgs(line: string): { cmd: string, args?: string, scriptArgs?: str
|
||||
let cmd = ''
|
||||
let args = ''
|
||||
if (npmArgs[0] !== '-') {
|
||||
// 处理命令和参数
|
||||
// Process command and args
|
||||
if (npmArgs[0] === '"' || npmArgs[0] === '\'') {
|
||||
const idx = npmArgs.slice(1).indexOf(npmArgs[0])
|
||||
cmd = npmArgs.slice(0, idx + 2)
|
||||
@ -204,7 +242,7 @@ function parseArgs(line: string): { cmd: string, args?: string, scriptArgs?: str
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 处理仅有参数的情况
|
||||
// Process args only
|
||||
let newLine = ''
|
||||
let value = ''
|
||||
let isQuote = false
|
||||
|
||||
@ -1,22 +1,62 @@
|
||||
import type { NpmToPackageManager } from '../../shared/index.js'
|
||||
|
||||
/**
|
||||
* Package command types
|
||||
*
|
||||
* 包命令类型
|
||||
*/
|
||||
export type PackageCommand = 'install' | 'add' | 'remove' | 'run' | 'create' | 'init' | 'npx' | 'ci'
|
||||
|
||||
/**
|
||||
* Command config item
|
||||
*
|
||||
* 命令配置项
|
||||
*/
|
||||
export interface CommandConfigItem {
|
||||
cli: string
|
||||
flags?: Record<string, string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Command config for package managers (excluding npm)
|
||||
*
|
||||
* 包管理器的命令配置(不包括 npm)
|
||||
*/
|
||||
export type CommandConfig = Record<Exclude<NpmToPackageManager, 'npm'>, CommandConfigItem | false>
|
||||
|
||||
/**
|
||||
* Command configs for all package commands
|
||||
*
|
||||
* 所有包命令的配置
|
||||
*/
|
||||
export type CommandConfigs = Record<PackageCommand, { pattern: RegExp } & CommandConfig>
|
||||
|
||||
/**
|
||||
* Allowed package managers list
|
||||
*
|
||||
* 允许的包管理器列表
|
||||
*/
|
||||
export const ALLOW_LIST = ['npm', 'pnpm', 'yarn', 'bun', 'deno'] as const
|
||||
|
||||
/**
|
||||
* Boolean flags for npm commands
|
||||
*
|
||||
* npm 命令的布尔标志
|
||||
*/
|
||||
export const BOOL_FLAGS: string[] = ['--no-save', '-B', '--save-bundle', '--save-dev', '-D', '--save-prod', '-P', '--save-peer', '-O', '--save-optional', '-E', '--save-exact', '-y', '--yes', '-g', '--global']
|
||||
|
||||
/**
|
||||
* Default tabs to display
|
||||
*
|
||||
* 默认显示的标签
|
||||
*/
|
||||
export const DEFAULT_TABS: NpmToPackageManager[] = ['npm', 'pnpm', 'yarn']
|
||||
|
||||
/**
|
||||
* Package manager configurations
|
||||
*
|
||||
* 包管理器配置
|
||||
*/
|
||||
export const MANAGERS_CONFIG: CommandConfigs = {
|
||||
install: {
|
||||
pattern: /(?:^|\s)npm\s+(?:install|i)$/,
|
||||
|
||||
@ -2,6 +2,12 @@ import type { Markdown } from 'vuepress/markdown'
|
||||
import { createContainerPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Steps container plugin
|
||||
*
|
||||
* 步骤容器插件
|
||||
*
|
||||
* Syntax:
|
||||
* ```md
|
||||
* :::steps
|
||||
* 1. 步骤 1
|
||||
* xxx
|
||||
@ -9,6 +15,9 @@ import { createContainerPlugin } from './createContainer.js'
|
||||
* xxx
|
||||
* 3. ...
|
||||
* :::
|
||||
* ```
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
*/
|
||||
export function stepsPlugin(md: Markdown): void {
|
||||
createContainerPlugin(md, 'steps', {
|
||||
|
||||
@ -4,19 +4,30 @@ import { encodeData } from '@vuepress/helper'
|
||||
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
||||
import { createContainerSyntaxPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Table container attributes
|
||||
*
|
||||
* 表格容器属性
|
||||
*/
|
||||
export interface TableContainerAttrs extends TableContainerOptions {
|
||||
/**
|
||||
* Table title
|
||||
*
|
||||
* 表格标题
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* Highlighted rows
|
||||
*
|
||||
* 表格高亮的行
|
||||
*
|
||||
* @example hl-rows="warning:1,2,3;error:4,5,6"
|
||||
*/
|
||||
hlRows?: string
|
||||
/**
|
||||
* Highlighted columns
|
||||
*
|
||||
* 表格高亮的列
|
||||
*
|
||||
* @example hl-cols="warning:1;error:2,3"
|
||||
@ -24,6 +35,8 @@ export interface TableContainerAttrs extends TableContainerOptions {
|
||||
hlCols?: string
|
||||
|
||||
/**
|
||||
* Highlighted cells
|
||||
*
|
||||
* 表格高亮的单元格
|
||||
*
|
||||
* @example hl-cells="warning:(1,2)(2,3);error:(3,4)(4,5)"
|
||||
@ -32,7 +45,9 @@ export interface TableContainerAttrs extends TableContainerOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在不破坏表格语法的前提下,通过容器语法将表格包裹起来,为表格提供增强功能
|
||||
* Table plugin - Wrap table with container for enhanced features
|
||||
*
|
||||
* 表格插件 - 通过容器语法将表格包裹起来,为表格提供增强功能
|
||||
*
|
||||
* @example
|
||||
* ```md
|
||||
@ -43,6 +58,9 @@ export interface TableContainerAttrs extends TableContainerOptions {
|
||||
* | xx | xx | xx |
|
||||
* :::
|
||||
* ```
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
* @param options - Table container options / 表格容器选项
|
||||
*/
|
||||
export function tablePlugin(md: Markdown, options: TableContainerOptions = {}): void {
|
||||
createContainerSyntaxPlugin(md, 'table', (tokens, index, opt, env) => {
|
||||
@ -96,6 +114,14 @@ export function tablePlugin(md: Markdown, options: TableContainerOptions = {}):
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse highlight string
|
||||
*
|
||||
* 解析高亮字符串
|
||||
*
|
||||
* @param hl - Highlight string / 高亮字符串
|
||||
* @returns Parsed highlight map / 解析后的高亮映射
|
||||
*/
|
||||
function parseHl(hl: string) {
|
||||
const res: Record<number, string> = {}
|
||||
if (!hl)
|
||||
@ -110,6 +136,14 @@ function parseHl(hl: string) {
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse highlight cells string
|
||||
*
|
||||
* 解析高亮单元格字符串
|
||||
*
|
||||
* @param hl - Highlight cells string / 高亮单元格字符串
|
||||
* @returns Parsed highlight cells map / 解析后的高亮单元格映射
|
||||
*/
|
||||
function parseHlCells(hl: string) {
|
||||
const res: Record<string, Record<number, string>> = {}
|
||||
if (!hl)
|
||||
|
||||
@ -3,6 +3,23 @@ import { tab } from '@mdit/plugin-tab'
|
||||
import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv.js'
|
||||
import { stringifyProp } from '../utils/stringifyProp.js'
|
||||
|
||||
/**
|
||||
* Tabs plugin - Enable tabs container
|
||||
*
|
||||
* 标签页插件 - 启用标签页容器
|
||||
*
|
||||
* Syntax:
|
||||
* ```md
|
||||
* :::tabs
|
||||
* @tab Tab 1
|
||||
* Content 1
|
||||
* @tab Tab 2
|
||||
* Content 2
|
||||
* :::
|
||||
* ```
|
||||
*
|
||||
* @param md - Markdown-it instance / Markdown-it 实例
|
||||
*/
|
||||
export const tabs: PluginSimple = (md) => {
|
||||
tab(md, {
|
||||
name: 'tabs',
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
/**
|
||||
* Timeline container plugin
|
||||
*
|
||||
* 时间线容器插件
|
||||
*
|
||||
* Syntax:
|
||||
* ```md
|
||||
* ::: timeline
|
||||
*
|
||||
* - title
|
||||
@ -11,6 +17,7 @@
|
||||
*
|
||||
* content
|
||||
* :::
|
||||
* ```
|
||||
*/
|
||||
import type Token from 'markdown-it/lib/token.mjs'
|
||||
import type { Markdown } from 'vuepress/markdown'
|
||||
@ -19,6 +26,11 @@ import { resolveAttrs } from '.././utils/resolveAttrs.js'
|
||||
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
||||
import { createContainerPlugin } from './createContainer.js'
|
||||
|
||||
/**
|
||||
* Timeline attributes
|
||||
*
|
||||
* 时间线属性
|
||||
*/
|
||||
export interface TimelineAttrs {
|
||||
horizontal?: boolean
|
||||
card?: boolean
|
||||
@ -26,6 +38,11 @@ export interface TimelineAttrs {
|
||||
line?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeline item metadata
|
||||
*
|
||||
* 时间线项元数据
|
||||
*/
|
||||
export interface TimelineItemMeta {
|
||||
time?: string
|
||||
type?: string
|
||||
@ -36,10 +53,34 @@ export interface TimelineItemMeta {
|
||||
placement?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex for matching attribute keys
|
||||
*
|
||||
* 匹配属性键的正则
|
||||
*/
|
||||
const RE_KEY = /(\w+)=\s*/
|
||||
|
||||
/**
|
||||
* Regex for searching next attribute key
|
||||
*
|
||||
* 搜索下一个属性键的正则
|
||||
*/
|
||||
const RE_SEARCH_KEY = /\s+\w+=\s*|$/
|
||||
|
||||
/**
|
||||
* Regex for cleaning quote values
|
||||
*
|
||||
* 清理引号值的正则
|
||||
*/
|
||||
const RE_CLEAN_VALUE = /(?<quote>["'])(.*?)(\k<quote>)/
|
||||
|
||||
/**
|
||||
* Timeline plugin - Enable timeline container
|
||||
*
|
||||
* 时间线插件 - 启用时间线容器
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
*/
|
||||
export function timelinePlugin(md: Markdown): void {
|
||||
createContainerPlugin(md, 'timeline', {
|
||||
before(info, tokens, index) {
|
||||
@ -65,17 +106,25 @@ export function timelinePlugin(md: Markdown): void {
|
||||
md.renderer.rules.timeline_item_title_close = () => '</template>'
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse timeline tokens
|
||||
*
|
||||
* 解析时间线令牌
|
||||
*
|
||||
* @param tokens - Token array / 令牌数组
|
||||
* @param index - Start index / 起始索引
|
||||
*/
|
||||
function parseTimeline(tokens: Token[], index: number) {
|
||||
const listStack: number[] = [] // 记录列表嵌套深度
|
||||
const listStack: number[] = [] // Track list nesting depth
|
||||
|
||||
for (let i = index + 1; i < tokens.length; i++) {
|
||||
const token = tokens[i]
|
||||
if (token.type === 'container_timeline_close') {
|
||||
break
|
||||
}
|
||||
// 列表层级追踪
|
||||
// Track list level
|
||||
if (token.type === 'bullet_list_open') {
|
||||
listStack.push(0) // 每个新列表初始层级为0
|
||||
listStack.push(0) // Each new list starts at level 0
|
||||
if (listStack.length === 1)
|
||||
token.hidden = true
|
||||
}
|
||||
@ -86,7 +135,7 @@ function parseTimeline(tokens: Token[], index: number) {
|
||||
}
|
||||
else if (token.type === 'list_item_open') {
|
||||
const currentLevel = listStack.length
|
||||
// 仅处理根级列表项(层级1)
|
||||
// Only process root level list items (level 1)
|
||||
if (currentLevel === 1) {
|
||||
token.type = 'timeline_item_open'
|
||||
tokens[i + 1].type = 'timeline_item_title_open'
|
||||
@ -94,9 +143,9 @@ function parseTimeline(tokens: Token[], index: number) {
|
||||
|
||||
// - title
|
||||
// attrs
|
||||
// 列表项 `-` 后面包括紧跟随的后续行均在 type=inline 的 token 中, 并作为 children
|
||||
// List item `-` followed by subsequent lines are in type=inline token as children
|
||||
const inlineToken = tokens[i + 2]
|
||||
// 找到最后一个 softbreak,最后一行作为 attrs 进行解析
|
||||
// Find last softbreak, last line as attrs
|
||||
const softbreakIndex = inlineToken.children!.findLastIndex(
|
||||
token => token.type === 'softbreak',
|
||||
)
|
||||
@ -121,39 +170,47 @@ function parseTimeline(tokens: Token[], index: number) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract timeline attributes from raw text
|
||||
*
|
||||
* 从原始文本中提取时间线属性
|
||||
*
|
||||
* @param rawText - Raw attribute text / 原始属性文本
|
||||
* @returns Timeline item metadata / 时间线项元数据
|
||||
*/
|
||||
export function extractTimelineAttributes(rawText: string): TimelineItemMeta {
|
||||
const attrKeys = ['time', 'type', 'icon', 'line', 'color', 'card', 'placement'] as const
|
||||
const attrs: TimelineItemMeta = {}
|
||||
let buffer = rawText.trim()
|
||||
|
||||
while (buffer.length) {
|
||||
// 匹配属性键 (支持大小写)
|
||||
// Match attribute key (case insensitive)
|
||||
const keyMatch = buffer.match(RE_KEY)
|
||||
if (!keyMatch) {
|
||||
break
|
||||
}
|
||||
|
||||
// 提取可能的关键字
|
||||
// Extract possible keyword
|
||||
const matchedKey = keyMatch[1].toLowerCase()
|
||||
if (!attrKeys.includes(matchedKey as any)) {
|
||||
break
|
||||
}
|
||||
const keyStart = keyMatch.index!
|
||||
|
||||
// 跳过已匹配的 key:
|
||||
// Skip matched key:
|
||||
const keyEnd = keyStart + keyMatch[0].length
|
||||
buffer = buffer.slice(keyEnd)
|
||||
|
||||
// 提取属性值 (到下一个属性或行尾)
|
||||
// Extract attribute value (to next attribute or end of line)
|
||||
let valueEnd = buffer.search(RE_SEARCH_KEY)
|
||||
/* istanbul ignore if -- @preserve */
|
||||
if (valueEnd === -1)
|
||||
valueEnd = buffer.length
|
||||
const value = buffer.slice(0, valueEnd).trim()
|
||||
// 存储属性
|
||||
// Store attribute
|
||||
attrs[matchedKey as keyof TimelineItemMeta] = value.replace(RE_CLEAN_VALUE, '$2')
|
||||
|
||||
// 跳过已处理的值
|
||||
// Skip processed value
|
||||
buffer = buffer.slice(valueEnd)
|
||||
}
|
||||
|
||||
|
||||
@ -1,23 +1,78 @@
|
||||
import type { RuleOptions } from 'markdown-it/lib/ruler.mjs'
|
||||
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
|
||||
|
||||
/**
|
||||
* Embed rule block options
|
||||
*
|
||||
* 嵌入规则块选项
|
||||
*
|
||||
* @typeParam Meta - Metadata type / 元数据类型
|
||||
*/
|
||||
export interface EmbedRuleBlockOptions<Meta extends Record<string, any>> {
|
||||
/**
|
||||
* @[type]()
|
||||
* Embed type syntax: @[type]()
|
||||
*
|
||||
* 嵌入类型语法:@[type]()
|
||||
*/
|
||||
type: string
|
||||
/**
|
||||
* token name
|
||||
* Token name
|
||||
*
|
||||
* 令牌名称
|
||||
*/
|
||||
name?: string
|
||||
/**
|
||||
* Name of the rule to insert before
|
||||
*
|
||||
* 要插入在其前面的规则名称
|
||||
*/
|
||||
beforeName?: string
|
||||
/**
|
||||
* Syntax pattern regular expression
|
||||
*
|
||||
* 语法模式正则表达式
|
||||
*/
|
||||
syntaxPattern: RegExp
|
||||
/**
|
||||
* Rule options
|
||||
*
|
||||
* 规则选项
|
||||
*/
|
||||
ruleOptions?: RuleOptions
|
||||
/**
|
||||
* Extract metadata from match
|
||||
*
|
||||
* 从匹配中提取元数据
|
||||
*
|
||||
* @param match - RegExp match array / 正则表达式匹配数组
|
||||
* @returns Metadata object / 元数据对象
|
||||
*/
|
||||
meta: (match: RegExpMatchArray) => Meta
|
||||
/**
|
||||
* Generate content from metadata
|
||||
*
|
||||
* 从元数据生成内容
|
||||
*
|
||||
* @param meta - Metadata / 元数据
|
||||
* @param content - Original content / 原始内容
|
||||
* @param env - Markdown environment / Markdown 环境
|
||||
* @returns Generated content / 生成的内容
|
||||
*/
|
||||
content: (meta: Meta, content: string, env: MarkdownEnv) => string
|
||||
}
|
||||
|
||||
// @[name]()
|
||||
/**
|
||||
* Create embed rule block
|
||||
*
|
||||
* 创建嵌入规则块
|
||||
*
|
||||
* Syntax: @[name]()
|
||||
* 语法:@[name]()
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
* @param options - Embed rule block options / 嵌入规则块选项
|
||||
* @typeParam Meta - Metadata type / 元数据类型
|
||||
*/
|
||||
export function createEmbedRuleBlock<Meta extends Record<string, any> = Record<string, any>>(
|
||||
md: Markdown,
|
||||
{
|
||||
@ -41,22 +96,26 @@ export function createEmbedRuleBlock<Meta extends Record<string, any> = Record<s
|
||||
const max = state.eMarks[startLine]
|
||||
|
||||
// return false if the length is shorter than min length
|
||||
// 如果长度小于最小长度,返回 false
|
||||
if (pos + MIN_LENGTH > max)
|
||||
return false
|
||||
|
||||
// check if it's matched the start
|
||||
// 检查是否匹配开始
|
||||
for (let i = 0; i < START_CODES.length; i += 1) {
|
||||
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
|
||||
return false
|
||||
}
|
||||
|
||||
// check if it's matched the syntax
|
||||
// 检查是否匹配语法
|
||||
const content = state.src.slice(pos, max)
|
||||
const match = content.match(syntaxPattern)
|
||||
if (!match)
|
||||
return false
|
||||
|
||||
// return true as we have matched the syntax
|
||||
// 返回 true 表示已匹配语法
|
||||
/* istanbul ignore if -- @preserve */
|
||||
if (silent)
|
||||
return true
|
||||
|
||||
@ -13,6 +13,14 @@ import { artPlayerPlugin } from './video/artPlayer.js'
|
||||
import { bilibiliPlugin } from './video/bilibili.js'
|
||||
import { youtubePlugin } from './video/youtube.js'
|
||||
|
||||
/**
|
||||
* Embed syntax plugin - Register all embed syntax plugins
|
||||
*
|
||||
* 嵌入语法插件 - 注册所有嵌入语法插件
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
* @param options - Plugin options / 插件选项
|
||||
*/
|
||||
export function embedSyntaxPlugin(md: Markdown, options: MarkdownPowerPluginOptions): void {
|
||||
if (options.caniuse) {
|
||||
const caniuse = options.caniuse === true ? {} : options.caniuse
|
||||
|
||||
@ -1,10 +1,24 @@
|
||||
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
|
||||
|
||||
/**
|
||||
* Regular expression for matching h1 heading
|
||||
*
|
||||
* 匹配 h1 标题的正则表达式
|
||||
*/
|
||||
const REG_HEADING = /^#\s*?([^#\s].*)?\n/
|
||||
|
||||
/**
|
||||
* 适配 主题的 文档页面标题,将 markdown 中的 h1 标题提取到 frontmatter 中,并将其删除,
|
||||
* Docs title plugin - Extract h1 title from markdown to frontmatter
|
||||
*
|
||||
* 文档标题插件 - 从 markdown 中提取 h1 标题到 frontmatter
|
||||
*
|
||||
* Adapts to theme's document page title by extracting h1 title from markdown to frontmatter
|
||||
* and removing it from content to avoid duplicate display.
|
||||
*
|
||||
* 适配主题的文档页面标题,将 markdown 中的 h1 标题提取到 frontmatter 中,并将其删除,
|
||||
* 以避免重复显示标题。
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
*/
|
||||
export function docsTitlePlugin(md: Markdown): void {
|
||||
const render = md.render
|
||||
@ -28,6 +42,14 @@ export function docsTitlePlugin(md: Markdown): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse markdown source to separate frontmatter and content
|
||||
*
|
||||
* 解析 markdown 源文件,分离 frontmatter 和内容
|
||||
*
|
||||
* @param source - Markdown source / Markdown 源文件
|
||||
* @returns Object with matter and content / 包含 matter 和 content 的对象
|
||||
*/
|
||||
function parseSource(source: string) {
|
||||
const char = '---'
|
||||
|
||||
|
||||
@ -10,14 +10,49 @@ import imageSize from 'image-size'
|
||||
import { fs, logger, path } from 'vuepress/utils'
|
||||
import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
|
||||
/**
|
||||
* Image size interface
|
||||
*
|
||||
* 图片尺寸接口
|
||||
*/
|
||||
interface ImgSize {
|
||||
/**
|
||||
* Image width
|
||||
*
|
||||
* 图片宽度
|
||||
*/
|
||||
width: number
|
||||
/**
|
||||
* Image height
|
||||
*
|
||||
* 图片高度
|
||||
*/
|
||||
height: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular expression for matching markdown image syntax
|
||||
*
|
||||
* 匹配 markdown 图片语法的正则表达式
|
||||
*/
|
||||
const REG_IMG = /!\[.*?\]\(.*?\)/g
|
||||
/**
|
||||
* Regular expression for matching HTML img tag
|
||||
*
|
||||
* 匹配 HTML img 标签的正则表达式
|
||||
*/
|
||||
const REG_IMG_TAG = /<img(.*?)>/g
|
||||
/**
|
||||
* Regular expression for matching src/srcset attribute
|
||||
*
|
||||
* 匹配 src/srcset 属性的正则表达式
|
||||
*/
|
||||
const REG_IMG_TAG_SRC = /src(?:set)?=(['"])(.+?)\1/g
|
||||
/**
|
||||
* List of badge URLs to exclude
|
||||
*
|
||||
* 要排除的徽章 URL 列表
|
||||
*/
|
||||
const BADGE_LIST = [
|
||||
'https://img.shields.io',
|
||||
'https://badge.fury.io',
|
||||
@ -26,8 +61,22 @@ const BADGE_LIST = [
|
||||
'https://vercel.com/button',
|
||||
]
|
||||
|
||||
/**
|
||||
* Cache for image sizes
|
||||
*
|
||||
* 图片尺寸缓存
|
||||
*/
|
||||
const cache = new Map<string, ImgSize>()
|
||||
|
||||
/**
|
||||
* Image size plugin - Add width and height attributes to images
|
||||
*
|
||||
* 图片尺寸插件 - 为图片添加宽度和高度属性
|
||||
*
|
||||
* @param app - VuePress app / VuePress 应用
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
* @param type - Image size type: 'local', 'all', or false / 图片尺寸类型:'local'、'all' 或 false
|
||||
*/
|
||||
export async function imageSizePlugin(
|
||||
app: App,
|
||||
md: Markdown,
|
||||
@ -71,6 +120,14 @@ export async function imageSizePlugin(
|
||||
md.renderer.rules.html_block = createHtmlRule(rawHtmlBlockRule)
|
||||
md.renderer.rules.html_inline = createHtmlRule(rawHtmlInlineRule)
|
||||
|
||||
/**
|
||||
* Create HTML rule for processing img tags
|
||||
*
|
||||
* 创建处理 img 标签的 HTML 规则
|
||||
*
|
||||
* @param rawHtmlRule - Original HTML rule / 原始 HTML 规则
|
||||
* @returns New HTML rule / 新的 HTML 规则
|
||||
*/
|
||||
function createHtmlRule(rawHtmlRule: RenderRule): RenderRule {
|
||||
return (tokens, idx, options, env, self) => {
|
||||
const token = tokens[idx]
|
||||
@ -95,6 +152,17 @@ export async function imageSizePlugin(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve image size from source
|
||||
*
|
||||
* 从源解析图片尺寸
|
||||
*
|
||||
* @param src - Image source / 图片源
|
||||
* @param width - Existing width / 现有宽度
|
||||
* @param height - Existing height / 现有高度
|
||||
* @param env - Markdown environment / Markdown 环境
|
||||
* @returns Image size or false / 图片尺寸或 false
|
||||
*/
|
||||
function resolveSize(
|
||||
src: string | null | undefined,
|
||||
width: string | null | undefined,
|
||||
@ -150,6 +218,16 @@ export async function imageSizePlugin(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve image URL from source
|
||||
*
|
||||
* 从源解析图片 URL
|
||||
*
|
||||
* @param src - Image source / 图片源
|
||||
* @param env - Markdown environment / Markdown 环境
|
||||
* @param app - VuePress app / VuePress 应用
|
||||
* @returns Resolved image URL / 解析后的图片 URL
|
||||
*/
|
||||
function resolveImageUrl(src: string, env: MarkdownEnv, app: App): string {
|
||||
if (src[0] === '/')
|
||||
return app.dir.public(src.slice(1))
|
||||
@ -164,6 +242,13 @@ function resolveImageUrl(src: string, env: MarkdownEnv, app: App): string {
|
||||
return path.resolve(src)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan remote image sizes in markdown files
|
||||
*
|
||||
* 扫描 markdown 文件中的远程图片尺寸
|
||||
*
|
||||
* @param app - VuePress app / VuePress 应用
|
||||
*/
|
||||
export async function scanRemoteImageSize(app: App): Promise<void> {
|
||||
if (!app.env.isBuild)
|
||||
return
|
||||
@ -194,6 +279,13 @@ export async function scanRemoteImageSize(app: App): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add source to image list
|
||||
*
|
||||
* 将源添加到图片列表
|
||||
*
|
||||
* @param src - Image source / 图片源
|
||||
*/
|
||||
function addList(src: string) {
|
||||
if (src && isLinkHttp(src)
|
||||
&& !imgList.includes(src)
|
||||
@ -211,6 +303,14 @@ export async function scanRemoteImageSize(app: App): Promise<void> {
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch image size from remote URL
|
||||
*
|
||||
* 从远程 URL 获取图片尺寸
|
||||
*
|
||||
* @param src - Image URL / 图片 URL
|
||||
* @returns Image size / 图片尺寸
|
||||
*/
|
||||
function fetchImageSize(src: string): Promise<ImgSize> {
|
||||
const link = new URL(src)
|
||||
|
||||
@ -248,6 +348,16 @@ function fetchImageSize(src: string): Promise<ImgSize> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve image size from URL
|
||||
*
|
||||
* 从 URL 解析图片尺寸
|
||||
*
|
||||
* @param app - VuePress app / VuePress 应用
|
||||
* @param url - Image URL / 图片 URL
|
||||
* @param remote - Whether to fetch remote images / 是否获取远程图片
|
||||
* @returns Image size / 图片尺寸
|
||||
*/
|
||||
export async function resolveImageSize(app: App, url: string, remote = false): Promise<ImgSize> {
|
||||
if (cache.has(url))
|
||||
return cache.get(url)!
|
||||
|
||||
@ -4,8 +4,16 @@ import { removeLeadingSlash } from '@vuepress/shared'
|
||||
import { path } from '@vuepress/utils'
|
||||
import { isLinkWithProtocol } from 'vuepress/shared'
|
||||
|
||||
/**
|
||||
* Links plugin - Process internal and external links
|
||||
*
|
||||
* 链接插件 - 处理内部和外部链接
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
*/
|
||||
export function linksPlugin(md: Markdown): void {
|
||||
// attrs that going to be added to external links
|
||||
// 要添加到外部链接的属性
|
||||
const externalAttrs = {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
@ -14,24 +22,37 @@ export function linksPlugin(md: Markdown): void {
|
||||
let hasOpenInternalLink = false
|
||||
const internalTag = 'VPLink'
|
||||
|
||||
/**
|
||||
* Handle link open token
|
||||
*
|
||||
* 处理链接打开令牌
|
||||
*
|
||||
* @param tokens - Token array / 令牌数组
|
||||
* @param idx - Token index / 令牌索引
|
||||
* @param env - Markdown environment / Markdown 环境
|
||||
*/
|
||||
function handleLinkOpen(tokens: Token[], idx: number, env: MarkdownEnv) {
|
||||
hasOpenInternalLink = false
|
||||
const token = tokens[idx]
|
||||
// get `href` attr index
|
||||
// 获取 `href` 属性索引
|
||||
const hrefIndex = token.attrIndex('href')
|
||||
|
||||
// if `href` attr does not exist, skip
|
||||
// 如果 `href` 属性不存在,跳过
|
||||
/* istanbul ignore if -- @preserve */
|
||||
if (hrefIndex < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// if `href` attr exists, `token.attrs` is not `null`
|
||||
// 如果 `href` 属性存在,`token.attrs` 不为 `null`
|
||||
const hrefAttr = token.attrs![hrefIndex]
|
||||
const hrefLink: string = hrefAttr[1]
|
||||
|
||||
if (isLinkWithProtocol(hrefLink)) {
|
||||
// set `externalAttrs` to current token
|
||||
// 将 `externalAttrs` 设置到当前令牌
|
||||
Object.entries(externalAttrs).forEach(([key, val]) => {
|
||||
token.attrSet(key, val)
|
||||
})
|
||||
@ -42,6 +63,7 @@ export function linksPlugin(md: Markdown): void {
|
||||
return
|
||||
|
||||
// convert starting tag of internal link
|
||||
// 转换内部链接的开始标签
|
||||
hasOpenInternalLink = true
|
||||
token.tag = internalTag
|
||||
|
||||
@ -72,6 +94,7 @@ export function linksPlugin(md: Markdown): void {
|
||||
|
||||
md.renderer.rules.link_close = (tokens, idx, opts, _env, self) => {
|
||||
// convert ending tag of internal link
|
||||
// 转换内部链接的结束标签
|
||||
if (hasOpenInternalLink) {
|
||||
hasOpenInternalLink = false
|
||||
tokens[idx].tag = internalTag
|
||||
@ -82,6 +105,13 @@ export function linksPlugin(md: Markdown): void {
|
||||
|
||||
/**
|
||||
* Resolve relative and absolute paths according to the `base` and `filePathRelative`
|
||||
*
|
||||
* 根据 `base` 和 `filePathRelative` 解析相对和绝对路径
|
||||
*
|
||||
* @param rawPath - Raw path / 原始路径
|
||||
* @param base - Base URL / 基础 URL
|
||||
* @param filePathRelative - Relative file path / 相对文件路径
|
||||
* @returns Object with absolutePath and relativePath / 包含 absolutePath 和 relativePath 的对象
|
||||
*/
|
||||
export function resolvePaths(rawPath: string, base: string, filePathRelative: string | null): {
|
||||
absolutePath: string | null
|
||||
@ -91,37 +121,50 @@ export function resolvePaths(rawPath: string, base: string, filePathRelative: st
|
||||
let relativePath: string
|
||||
|
||||
// if raw path is absolute
|
||||
// 如果原始路径是绝对路径
|
||||
if (rawPath.startsWith('/')) {
|
||||
// if raw path is a link to markdown file
|
||||
// 如果原始路径是 markdown 文件链接
|
||||
if (rawPath.endsWith('.md')) {
|
||||
// prepend `base` to the link
|
||||
// 将 `base` 添加到链接前面
|
||||
absolutePath = path.join(base, rawPath)
|
||||
relativePath = removeLeadingSlash(rawPath)
|
||||
}
|
||||
// if raw path is a link to other kind of file
|
||||
// 如果原始路径是其他类型文件链接
|
||||
else {
|
||||
// keep the link as is
|
||||
// 保持链接不变
|
||||
absolutePath = rawPath
|
||||
relativePath = path.relative(base, absolutePath)
|
||||
}
|
||||
}
|
||||
// if raw path is relative
|
||||
// 如果原始路径是相对路径
|
||||
// if `filePathRelative` is available
|
||||
// 如果 `filePathRelative` 可用
|
||||
else if (filePathRelative) {
|
||||
// resolve relative path according to `filePathRelative`
|
||||
// 根据 `filePathRelative` 解析相对路径
|
||||
relativePath = path.join(
|
||||
// file path may contain non-ASCII characters
|
||||
// 文件路径可能包含非 ASCII 字符
|
||||
path.dirname(encodeURI(filePathRelative)),
|
||||
rawPath,
|
||||
)
|
||||
// resolve absolute path according to `base`
|
||||
// 根据 `base` 解析绝对路径
|
||||
absolutePath = path.join(base, relativePath)
|
||||
}
|
||||
// if `filePathRelative` is not available
|
||||
// 如果 `filePathRelative` 不可用
|
||||
else {
|
||||
// remove leading './'
|
||||
// 移除开头的 './'
|
||||
relativePath = rawPath.replace(/^(?:\.\/)?(.*)$/, '$1')
|
||||
// just take relative link as absolute link
|
||||
// 将相对链接视为绝对链接
|
||||
absolutePath = null
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
/**
|
||||
* Forked and modified from https://github.com/markdown-it/markdown-it-abbr/blob/master/index.mjs
|
||||
*
|
||||
* 从 https://github.com/markdown-it/markdown-it-abbr/blob/master/index.mjs 分叉并修改
|
||||
*/
|
||||
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
@ -11,18 +13,59 @@ import type Token from 'markdown-it/lib/token.mjs'
|
||||
import { isEmptyObject, objectMap } from '@pengzhanbo/utils'
|
||||
import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv.js'
|
||||
|
||||
/**
|
||||
* Abbreviation state block
|
||||
*
|
||||
* 缩写词状态块
|
||||
*/
|
||||
interface AbbrStateBlock extends StateBlock {
|
||||
/**
|
||||
* Environment
|
||||
*
|
||||
* 环境
|
||||
*/
|
||||
env: {
|
||||
/**
|
||||
* Abbreviations record
|
||||
*
|
||||
* 缩写词记录
|
||||
*/
|
||||
abbreviations?: Record<string, string>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abbreviation state core
|
||||
*
|
||||
* 缩写词核心状态
|
||||
*/
|
||||
interface AbbrStateCore extends StateCore {
|
||||
/**
|
||||
* Environment
|
||||
*
|
||||
* 环境
|
||||
*/
|
||||
env: {
|
||||
/**
|
||||
* Abbreviations record
|
||||
*
|
||||
* 缩写词记录
|
||||
*/
|
||||
abbreviations?: Record<string, string>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abbreviation plugin - Enable abbreviation syntax
|
||||
*
|
||||
* 缩写词插件 - 启用缩写词语法
|
||||
*
|
||||
* Definition syntax: *[ABBREV]: Full description
|
||||
* 定义语法:*[缩写]: 完整描述
|
||||
*
|
||||
* @param md - Markdown-it instance / Markdown-it 实例
|
||||
* @param globalAbbreviations - Global abbreviations preset / 全局缩写词预设
|
||||
*/
|
||||
export const abbrPlugin: PluginWithOptions<Record<string, string>> = (md, globalAbbreviations = {}) => {
|
||||
const { arrayReplaceAt, escapeRE, lib } = md.utils
|
||||
globalAbbreviations = objectMap(
|
||||
@ -37,6 +80,17 @@ export const abbrPlugin: PluginWithOptions<Record<string, string>> = (md, global
|
||||
const UNICODE_SPACE_REGEXP = (lib.ucmicro.Z as RegExp).source
|
||||
const WORDING_REGEXP_TEXT = `${UNICODE_PUNCTUATION_REGEXP}|${UNICODE_SPACE_REGEXP}|[${OTHER_CHARS.split('').map(escapeRE).join('')}]`
|
||||
|
||||
/**
|
||||
* Abbreviation definition rule
|
||||
*
|
||||
* 缩写词定义规则
|
||||
*
|
||||
* @param state - State block / 状态块
|
||||
* @param startLine - Start line number / 开始行号
|
||||
* @param _endLine - End line number / 结束行号
|
||||
* @param silent - Silent mode / 静默模式
|
||||
* @returns Whether matched / 是否匹配
|
||||
*/
|
||||
const abbrDefinition: RuleBlock = (
|
||||
state: AbbrStateBlock,
|
||||
startLine,
|
||||
@ -90,6 +144,13 @@ export const abbrPlugin: PluginWithOptions<Record<string, string>> = (md, global
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Abbreviation replace rule
|
||||
*
|
||||
* 缩写词替换规则
|
||||
*
|
||||
* @param state - State core / 核心状态
|
||||
*/
|
||||
const abbrReplace: RuleCore = (state: AbbrStateCore) => {
|
||||
const tokens = state.tokens
|
||||
const { abbreviations: localAbbreviations } = state.env
|
||||
|
||||
@ -7,29 +7,105 @@ import type Token from 'markdown-it/lib/token.mjs'
|
||||
import { objectMap, toArray } from '@pengzhanbo/utils'
|
||||
import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv'
|
||||
|
||||
/**
|
||||
* Annotation token with meta information
|
||||
*
|
||||
* 带元信息的注释令牌
|
||||
*/
|
||||
interface AnnotationToken extends Token {
|
||||
/**
|
||||
* Token meta information
|
||||
*
|
||||
* 令牌元信息
|
||||
*/
|
||||
meta: {
|
||||
/**
|
||||
* Annotation label
|
||||
*
|
||||
* 注释标签
|
||||
*/
|
||||
label: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation environment
|
||||
*
|
||||
* 注释环境
|
||||
*/
|
||||
interface AnnotationEnv extends Record<string, unknown> {
|
||||
/**
|
||||
* Annotations record
|
||||
*
|
||||
* 注释记录
|
||||
*/
|
||||
annotations: Record<string, {
|
||||
/**
|
||||
* Source texts
|
||||
*
|
||||
* 源文本
|
||||
*/
|
||||
sources: string[]
|
||||
/**
|
||||
* Rendered contents
|
||||
*
|
||||
* 渲染后的内容
|
||||
*/
|
||||
rendered: string[]
|
||||
}>
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation state block
|
||||
*
|
||||
* 注释状态块
|
||||
*/
|
||||
interface AnnotationStateBlock extends StateBlock {
|
||||
/**
|
||||
* Tokens array
|
||||
*
|
||||
* 令牌数组
|
||||
*/
|
||||
tokens: AnnotationToken[]
|
||||
/**
|
||||
* Environment
|
||||
*
|
||||
* 环境
|
||||
*/
|
||||
env: AnnotationEnv
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation state inline
|
||||
*
|
||||
* 注释行内状态
|
||||
*/
|
||||
interface AnnotationStateInline extends StateInline {
|
||||
/**
|
||||
* Tokens array
|
||||
*
|
||||
* 令牌数组
|
||||
*/
|
||||
tokens: AnnotationToken[]
|
||||
/**
|
||||
* Environment
|
||||
*
|
||||
* 环境
|
||||
*/
|
||||
env: AnnotationEnv
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation definition rule
|
||||
*
|
||||
* 注释定义规则
|
||||
*
|
||||
* @param state - State block / 状态块
|
||||
* @param startLine - Start line number / 开始行号
|
||||
* @param endLine - End line number / 结束行号
|
||||
* @param silent - Silent mode / 静默模式
|
||||
* @returns Whether matched / 是否匹配
|
||||
*/
|
||||
const annotationDef: RuleBlock = (
|
||||
state: AnnotationStateBlock,
|
||||
startLine: number,
|
||||
@ -100,6 +176,15 @@ const annotationDef: RuleBlock = (
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation reference rule
|
||||
*
|
||||
* 注释引用规则
|
||||
*
|
||||
* @param state - State inline / 行内状态
|
||||
* @param silent - Silent mode / 静默模式
|
||||
* @returns Whether matched / 是否匹配
|
||||
*/
|
||||
const annotationRef: RuleInline = (
|
||||
state: AnnotationStateInline,
|
||||
silent: boolean,
|
||||
@ -155,6 +240,20 @@ const annotationRef: RuleInline = (
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation plugin - Enable annotation syntax
|
||||
*
|
||||
* 注释插件 - 启用注释语法
|
||||
*
|
||||
* Definition syntax: [+label]: annotation content
|
||||
* Reference syntax: [+label]
|
||||
*
|
||||
* 定义语法:[+label]: 注释内容
|
||||
* 引用语法:[+label]
|
||||
*
|
||||
* @param md - Markdown-it instance / Markdown-it 实例
|
||||
* @param globalAnnotations - Global annotations preset / 全局注释预设
|
||||
*/
|
||||
export const annotationPlugin: PluginWithOptions<Record<string, string | string[]>> = (
|
||||
md,
|
||||
globalAnnotations = {},
|
||||
|
||||
@ -3,7 +3,12 @@ import type { MarkdownEnvPreset } from '../../shared/index.js'
|
||||
import { isEmptyObject, isString, objectMap } from '@pengzhanbo/utils'
|
||||
|
||||
/**
|
||||
* inject preset to markdown env
|
||||
* Environment preset plugin - Inject preset references to markdown env
|
||||
*
|
||||
* 环境预设插件 - 向 Markdown 环境注入预设引用
|
||||
*
|
||||
* @param md - Markdown-it instance / Markdown-it 实例
|
||||
* @param env - Environment preset configuration / 环境预设配置
|
||||
*/
|
||||
export const envPresetPlugin: PluginWithOptions<MarkdownEnvPreset> = (md, env = {}) => {
|
||||
if (isEmptyObject(env))
|
||||
|
||||
@ -13,6 +13,14 @@ import { annotationPlugin } from './annotation.js'
|
||||
import { envPresetPlugin } from './env-preset.js'
|
||||
import { plotPlugin } from './plot.js'
|
||||
|
||||
/**
|
||||
* Inline syntax plugin - Register all inline markdown syntax plugins
|
||||
*
|
||||
* 行内语法插件 - 注册所有行内 Markdown 语法插件
|
||||
*
|
||||
* @param md - Markdown instance / Markdown 实例
|
||||
* @param options - Plugin options / 插件选项
|
||||
*/
|
||||
export function inlineSyntaxPlugin(
|
||||
md: Markdown,
|
||||
options: MarkdownPowerPluginOptions,
|
||||
|
||||
@ -1,9 +1,20 @@
|
||||
/**
|
||||
* !!这里的文本将被黑幕隐藏,通过点击或者 hover 才可重现!!
|
||||
*
|
||||
* !!The text here will be hidden by a spoiler, and can only be revealed by clicking or hovering!!
|
||||
*/
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
import type { RuleInline } from 'markdown-it/lib/parser_inline.mjs'
|
||||
|
||||
/**
|
||||
* Plot inline rule definition
|
||||
*
|
||||
* 黑幕行内规则定义
|
||||
*
|
||||
* @param state - Markdown-it state / Markdown-it 状态
|
||||
* @param silent - Silent mode / 静默模式
|
||||
* @returns Whether matched / 是否匹配
|
||||
*/
|
||||
const plotDef: RuleInline = (state, silent) => {
|
||||
let found = false
|
||||
const max = state.posMax
|
||||
@ -76,6 +87,16 @@ const plotDef: RuleInline = (state, silent) => {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Plot plugin - Hide text with spoiler effect
|
||||
*
|
||||
* 黑幕插件 - 使用黑幕效果隐藏文本
|
||||
*
|
||||
* Syntax: !!hidden text!!
|
||||
* 语法:!!隐藏文本!!
|
||||
*
|
||||
* @param md - Markdown-it instance / Markdown-it 实例
|
||||
*/
|
||||
export const plotPlugin: PluginWithOptions<never> = (md) => {
|
||||
md.inline.ruler.before('emphasis', 'plot', plotDef)
|
||||
}
|
||||
|
||||
@ -1,15 +1,49 @@
|
||||
import type { MarkdownEnv } from 'vuepress/markdown'
|
||||
|
||||
/**
|
||||
* Clean Markdown Environment
|
||||
*
|
||||
* 清理后的 Markdown 环境
|
||||
*/
|
||||
export interface CleanMarkdownEnv extends MarkdownEnv {
|
||||
/**
|
||||
* References
|
||||
*
|
||||
* 引用链接
|
||||
*/
|
||||
references?: unknown
|
||||
/**
|
||||
* Abbreviations
|
||||
*
|
||||
* 缩写词
|
||||
*/
|
||||
abbreviations?: unknown
|
||||
/**
|
||||
* Annotations
|
||||
*
|
||||
* 注释
|
||||
*/
|
||||
annotations?: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitelist of environment keys to preserve
|
||||
*
|
||||
* 要保留的环境键白名单
|
||||
*/
|
||||
const WHITE_LIST = ['base', 'filePath', 'filePathRelative', 'references', 'abbreviations', 'annotations'] as const
|
||||
|
||||
type WhiteListUnion = (typeof WHITE_LIST)[number]
|
||||
|
||||
/**
|
||||
* Clean markdown environment, keeping only whitelisted keys
|
||||
*
|
||||
* 清理 Markdown 环境,仅保留白名单中的键
|
||||
*
|
||||
* @param env - Markdown environment / Markdown 环境
|
||||
* @param excludes - Keys to exclude / 要排除的键
|
||||
* @returns Cleaned environment / 清理后的环境
|
||||
*/
|
||||
export function cleanMarkdownEnv(env: CleanMarkdownEnv, excludes: WhiteListUnion[] = []): CleanMarkdownEnv {
|
||||
const result: CleanMarkdownEnv = {}
|
||||
for (const key of WHITE_LIST) {
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
import { webcrypto } from 'node:crypto'
|
||||
|
||||
/**
|
||||
* Get key material from password
|
||||
*
|
||||
* 从密码获取密钥材料
|
||||
*
|
||||
* @see https://developer.mozilla.org/zh-CN/docs/Web/API/SubtleCrypto/deriveKey#pbkdf2_2
|
||||
* @param password
|
||||
* @param password - Password string / 密码字符串
|
||||
* @returns CryptoKey / 加密密钥
|
||||
*/
|
||||
function getKeyMaterial(password: string) {
|
||||
const enc = new TextEncoder()
|
||||
@ -15,6 +20,15 @@ function getKeyMaterial(password: string) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get derived key from key material
|
||||
*
|
||||
* 从密钥材料获取派生密钥
|
||||
*
|
||||
* @param keyMaterial - Key material / 密钥材料
|
||||
* @param salt - Salt for key derivation / 密钥派生盐值
|
||||
* @returns Derived CryptoKey / 派生加密密钥
|
||||
*/
|
||||
function getCryptoDeriveKey(keyMaterial: CryptoKey | webcrypto.CryptoKey, salt: Uint8Array) {
|
||||
return webcrypto.subtle.deriveKey(
|
||||
{
|
||||
@ -34,8 +48,17 @@ function getCryptoDeriveKey(keyMaterial: CryptoKey | webcrypto.CryptoKey, salt:
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt content using AES-CBC
|
||||
*
|
||||
* 使用 AES-CBC 加密内容
|
||||
*
|
||||
* @see https://github.com/mdn/dom-examples/blob/main/web-crypto/encrypt-decrypt/aes-cbc.js
|
||||
* @param content
|
||||
* @param content - Content to encrypt / 要加密的内容
|
||||
* @param options - Encryption options / 加密选项
|
||||
* @param options.password - Password for encryption / 加密密码
|
||||
* @param options.iv - Initialization vector / 初始化向量
|
||||
* @param options.salt - Salt for key derivation / 密钥派生盐值
|
||||
* @returns Encrypted content / 加密后的内容
|
||||
*/
|
||||
export async function encryptContent(content: string, options: {
|
||||
password: string
|
||||
|
||||
@ -1,6 +1,15 @@
|
||||
import type { LocaleConfig } from 'vuepress'
|
||||
import type { MDPowerLocaleData } from '../../shared/index.js'
|
||||
|
||||
/**
|
||||
* Find locale configurations for a specific key
|
||||
*
|
||||
* 查找特定键的本地化配置
|
||||
*
|
||||
* @param locales - Locale configuration / 本地化配置
|
||||
* @param key - Key to find / 要查找的键
|
||||
* @returns Record of locale paths to locale data / 本地化路径到本地化数据的记录
|
||||
*/
|
||||
export function findLocales<
|
||||
T extends MDPowerLocaleData,
|
||||
K extends keyof T,
|
||||
|
||||
@ -5,16 +5,31 @@ import { colors, ora } from 'vuepress/utils'
|
||||
type Ora = ReturnType<typeof ora>
|
||||
|
||||
/**
|
||||
* Logger utils
|
||||
* Logger utility class for plugin
|
||||
*
|
||||
* 插件日志工具类
|
||||
*/
|
||||
export class Logger {
|
||||
/**
|
||||
* Create a logger instance
|
||||
*
|
||||
* 创建日志记录器实例
|
||||
*
|
||||
* @param name - Plugin/Theme name / 插件/主题名称
|
||||
*/
|
||||
public constructor(
|
||||
/**
|
||||
* Plugin/Theme name
|
||||
*/
|
||||
private readonly name = '',
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Initialize spinner
|
||||
*
|
||||
* 初始化加载动画
|
||||
*
|
||||
* @param subname - Subname / 子名称
|
||||
* @param text - Loading text / 加载文本
|
||||
* @returns Ora spinner instance / Ora 加载动画实例
|
||||
*/
|
||||
private init(subname: string, text: string): Ora {
|
||||
return ora({
|
||||
prefixText: colors.blue(`${this.name}${subname ? `:${subname}` : ''}: `),
|
||||
@ -24,6 +39,12 @@ export class Logger {
|
||||
|
||||
/**
|
||||
* Create a loading spinner with text
|
||||
*
|
||||
* 创建带文本的加载动画
|
||||
*
|
||||
* @param subname - Subname / 子名称
|
||||
* @param msg - Message / 消息
|
||||
* @returns Object with succeed and fail methods / 包含 succeed 和 fail 方法的对象
|
||||
*/
|
||||
public load(subname: string, msg: string): {
|
||||
succeed: (text?: string) => void
|
||||
@ -37,6 +58,15 @@ export class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log info message
|
||||
*
|
||||
* 记录信息消息
|
||||
*
|
||||
* @param subname - Subname / 子名称
|
||||
* @param text - Message text / 消息文本
|
||||
* @param args - Additional arguments / 额外参数
|
||||
*/
|
||||
public info(subname: string, text = '', ...args: unknown[]): void {
|
||||
this.init(subname, colors.blue(text)).info()
|
||||
|
||||
@ -45,7 +75,13 @@ export class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Log success msg
|
||||
* Log success message
|
||||
*
|
||||
* 记录成功消息
|
||||
*
|
||||
* @param subname - Subname / 子名称
|
||||
* @param text - Message text / 消息文本
|
||||
* @param args - Additional arguments / 额外参数
|
||||
*/
|
||||
public succeed(subname: string, text = '', ...args: unknown[]): void {
|
||||
this.init(subname, colors.green(text)).succeed()
|
||||
@ -55,7 +91,13 @@ export class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Log warning msg
|
||||
* Log warning message
|
||||
*
|
||||
* 记录警告消息
|
||||
*
|
||||
* @param subname - Subname / 子名称
|
||||
* @param text - Message text / 消息文本
|
||||
* @param args - Additional arguments / 额外参数
|
||||
*/
|
||||
public warn(subname: string, text = '', ...args: unknown[]): void {
|
||||
this.init(subname, colors.yellow(text)).warn()
|
||||
@ -65,7 +107,13 @@ export class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error msg
|
||||
* Log error message
|
||||
*
|
||||
* 记录错误消息
|
||||
*
|
||||
* @param subname - Subname / 子名称
|
||||
* @param text - Message text / 消息文本
|
||||
* @param args - Additional arguments / 额外参数
|
||||
*/
|
||||
public error(subname: string, text = '', ...args: unknown[]): void {
|
||||
this.init(subname, colors.red(text)).fail()
|
||||
@ -75,4 +123,9 @@ export class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default logger instance for vuepress-plugin-md-power
|
||||
*
|
||||
* vuepress-plugin-md-power 的默认日志记录器实例
|
||||
*/
|
||||
export const logger: Logger = new Logger('vuepress-plugin-md-power')
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
import { customAlphabet } from 'nanoid'
|
||||
|
||||
/**
|
||||
* Generate a nanoid with custom alphabet
|
||||
*
|
||||
* 使用自定义字符集生成 nanoid
|
||||
*
|
||||
* @param size - ID length / ID 长度
|
||||
* @returns Nanoid string / Nanoid 字符串
|
||||
*/
|
||||
export const nanoid: (size?: number) => string = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5)
|
||||
|
||||
@ -1,5 +1,21 @@
|
||||
/**
|
||||
* Type for values that can be either a value or a Promise of that value
|
||||
*
|
||||
* 可以是值或该值 Promise 的类型
|
||||
*
|
||||
* @typeParam T - The type of the value / 值的类型
|
||||
*/
|
||||
export type Awaitable<T> = T | Promise<T>
|
||||
|
||||
/**
|
||||
* Get the default export from a module, or the module itself if no default export
|
||||
*
|
||||
* 从模块获取默认导出,如果没有默认导出则返回模块本身
|
||||
*
|
||||
* @param m - Module or Promise of module / 模块或模块的 Promise
|
||||
* @returns Default export or module itself / 默认导出或模块本身
|
||||
* @typeParam T - Module type / 模块类型
|
||||
*/
|
||||
export async function interopDefault<T>(m: Awaitable<T>): Promise<T extends { default: infer U } ? U : T> {
|
||||
const resolved = await m
|
||||
return (resolved as any).default || resolved
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
/**
|
||||
* Parse rect size string, add unit if it's a number
|
||||
*
|
||||
* 解析矩形尺寸字符串,如果是数字则添加单位
|
||||
*
|
||||
* @param str - Size string / 尺寸字符串
|
||||
* @param unit - Unit to append (default: 'px') / 要添加的单位(默认:'px')
|
||||
* @returns Size string with unit / 带单位的尺寸字符串
|
||||
*/
|
||||
export function parseRect(str: string, unit = 'px'): string {
|
||||
if (Number.parseFloat(str) === Number(str))
|
||||
return `${str}${unit}`
|
||||
|
||||
@ -1,7 +1,21 @@
|
||||
import { camelCase } from '@pengzhanbo/utils'
|
||||
|
||||
/**
|
||||
* Regular expression for matching attribute values
|
||||
*
|
||||
* 匹配属性值的正则表达式
|
||||
*/
|
||||
const RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w-]+)(?:=(?<quote>['"])(?<valueWithQuote>.+?)\k<quote>|=(?<valueWithoutQuote>\S+))?(?:\s+|$)/
|
||||
|
||||
/**
|
||||
* Resolve attribute string to object
|
||||
*
|
||||
* 将属性字符串解析为对象
|
||||
*
|
||||
* @param info - Attribute string / 属性字符串
|
||||
* @returns Object with attrs and rawAttrs / 包含 attrs 和 rawAttrs 的对象
|
||||
* @typeParam T - Attribute type / 属性类型
|
||||
*/
|
||||
export function resolveAttrs<T extends Record<string, any> = Record<string, any>>(info: string): {
|
||||
attrs: T
|
||||
rawAttrs: string
|
||||
@ -35,6 +49,15 @@ export function resolveAttrs<T extends Record<string, any> = Record<string, any>
|
||||
return { attrs: attrs as T, rawAttrs }
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve single attribute value from info string
|
||||
*
|
||||
* 从信息字符串中解析单个属性值
|
||||
*
|
||||
* @param info - Info string / 信息字符串
|
||||
* @param key - Attribute key / 属性键
|
||||
* @returns Attribute value or undefined / 属性值或 undefined
|
||||
*/
|
||||
export function resolveAttr(info: string, key: string): string | undefined {
|
||||
const pattern = new RegExp(`(?:^|\\s+)${key}(?:=(?<quote>['"])(?<valueWithQuote>.+?)\\k<quote>|=(?<valueWithoutQuote>\\S+))?(?:\\s+|$)`)
|
||||
const groups = info.match(pattern)?.groups
|
||||
|
||||
@ -1,5 +1,16 @@
|
||||
import { isBoolean, isNull, isNumber, isString, isUndefined, kebabCase } from '@pengzhanbo/utils'
|
||||
|
||||
/**
|
||||
* Stringify attributes object to HTML attribute string
|
||||
*
|
||||
* 将属性对象字符串化为 HTML 属性字符串
|
||||
*
|
||||
* @param attrs - Attributes object / 属性对象
|
||||
* @param withUndefinedOrNull - Whether to include undefined/null values / 是否包含 undefined/null 值
|
||||
* @param forceStringify - Keys to force stringify / 强制字符串化的键
|
||||
* @returns HTML attribute string / HTML 属性字符串
|
||||
* @typeParam T - Attribute type / 属性类型
|
||||
*/
|
||||
export function stringifyAttrs<T extends object = object>(
|
||||
attrs: T,
|
||||
withUndefinedOrNull = false,
|
||||
|
||||
@ -1,4 +1,13 @@
|
||||
// Single quote will break @vue/compiler-sfc
|
||||
/**
|
||||
* Stringify a property value for use in Vue templates
|
||||
*
|
||||
* 将属性值字符串化为可在 Vue 模板中使用的格式
|
||||
*
|
||||
* @param data - Data to stringify / 要字符串化的数据
|
||||
* @returns Stringified data with single quotes escaped / 字符串化后的数据,单引号已转义
|
||||
*/
|
||||
export function stringifyProp(data: unknown): string {
|
||||
// Single quote will break @vue/compiler-sfc
|
||||
// 单引号会破坏 @vue/compiler-sfc
|
||||
return JSON.stringify(data).replace(/'/g, ''')
|
||||
}
|
||||
|
||||
@ -1,17 +1,53 @@
|
||||
/**
|
||||
* CanIUse Display Mode
|
||||
*
|
||||
* CanIUse 显示模式
|
||||
*/
|
||||
export type CanIUseMode
|
||||
= | 'embed'
|
||||
| 'baseline'
|
||||
/** @deprecated */
|
||||
| 'image'
|
||||
|
||||
/**
|
||||
* CanIUse Token Metadata
|
||||
*
|
||||
* CanIUse 令牌元数据
|
||||
*/
|
||||
export interface CanIUseTokenMeta {
|
||||
/**
|
||||
* Feature name
|
||||
*
|
||||
* 特性名称
|
||||
*/
|
||||
feature: string
|
||||
/**
|
||||
* Display mode
|
||||
*
|
||||
* 显示模式
|
||||
*/
|
||||
mode: CanIUseMode
|
||||
/**
|
||||
* Browser versions to display
|
||||
*
|
||||
* 要显示的浏览器版本
|
||||
*/
|
||||
versions: string
|
||||
}
|
||||
|
||||
/**
|
||||
* CanIUse Options
|
||||
*
|
||||
* CanIUse 配置选项
|
||||
*/
|
||||
export interface CanIUseOptions {
|
||||
/**
|
||||
* Embed mode
|
||||
*
|
||||
* embed - embed via iframe, providing interactive view
|
||||
*
|
||||
* image - embed via image, static
|
||||
*
|
||||
* 嵌入模式
|
||||
*
|
||||
* embed 通过iframe嵌入,提供可交互视图
|
||||
|
||||
@ -1,12 +1,57 @@
|
||||
import type { SizeOptions } from './size.js'
|
||||
|
||||
/**
|
||||
* CodeSandbox Token Metadata
|
||||
*
|
||||
* CodeSandbox 令牌元数据
|
||||
*/
|
||||
export interface CodeSandboxTokenMeta extends SizeOptions {
|
||||
/**
|
||||
* User name
|
||||
*
|
||||
* 用户名
|
||||
*/
|
||||
user?: string
|
||||
/**
|
||||
* Sandbox ID
|
||||
*
|
||||
* 沙箱 ID
|
||||
*/
|
||||
id?: string
|
||||
/**
|
||||
* Layout
|
||||
*
|
||||
* 布局
|
||||
*/
|
||||
layout?: string
|
||||
/**
|
||||
* Embed type
|
||||
*
|
||||
* 嵌入类型
|
||||
*/
|
||||
type?: 'button' | 'embed'
|
||||
/**
|
||||
* Title
|
||||
*
|
||||
* 标题
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* File path
|
||||
*
|
||||
* 文件路径
|
||||
*/
|
||||
filepath?: string
|
||||
/**
|
||||
* Whether to show navbar
|
||||
*
|
||||
* 是否显示导航栏
|
||||
*/
|
||||
navbar?: boolean
|
||||
/**
|
||||
* Whether to show console
|
||||
*
|
||||
* 是否显示控制台
|
||||
*/
|
||||
console?: boolean
|
||||
}
|
||||
|
||||
@ -1,3 +1,18 @@
|
||||
/**
|
||||
* Code tabs options
|
||||
*
|
||||
* 代码选项卡配置选项
|
||||
*/
|
||||
export interface CodeTabsOptions {
|
||||
/**
|
||||
* Icon configuration for code tabs
|
||||
*
|
||||
* 代码选项卡的图标配置
|
||||
*
|
||||
* - `boolean`: Whether to enable icons / 是否启用图标
|
||||
* - `object`: Detailed icon configuration / 详细的图标配置
|
||||
* - `named`: Named icons to use / 要使用的命名图标
|
||||
* - `extensions`: File extensions to show icons for / 要显示图标的文件扩展名
|
||||
*/
|
||||
icon?: boolean | { named?: false | string[], extensions?: false | string[] }
|
||||
}
|
||||
|
||||
@ -1,11 +1,51 @@
|
||||
import type { SizeOptions } from './size'
|
||||
|
||||
/**
|
||||
* CodePen Token Metadata
|
||||
*
|
||||
* CodePen 令牌元数据
|
||||
*/
|
||||
export interface CodepenTokenMeta extends SizeOptions {
|
||||
/**
|
||||
* Pen title
|
||||
*
|
||||
* Pen 标题
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* User name
|
||||
*
|
||||
* 用户名
|
||||
*/
|
||||
user?: string
|
||||
/**
|
||||
* Pen slug
|
||||
*
|
||||
* Pen 标识
|
||||
*/
|
||||
slash?: string
|
||||
/**
|
||||
* Display tabs
|
||||
*
|
||||
* 显示的选项卡
|
||||
*/
|
||||
tab?: string
|
||||
/**
|
||||
* Theme
|
||||
*
|
||||
* 主题
|
||||
*/
|
||||
theme?: string
|
||||
/**
|
||||
* Whether to show preview
|
||||
*
|
||||
* 是否显示预览
|
||||
*/
|
||||
preview?: boolean
|
||||
/**
|
||||
* Whether editable
|
||||
*
|
||||
* 是否可编辑
|
||||
*/
|
||||
editable?: boolean
|
||||
}
|
||||
|
||||
@ -2,27 +2,107 @@ import type Token from 'markdown-it/lib/token.mjs'
|
||||
import type { App } from 'vuepress'
|
||||
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
|
||||
|
||||
/**
|
||||
* Demo File Type
|
||||
*
|
||||
* 演示文件类型
|
||||
*/
|
||||
export interface DemoFile {
|
||||
/**
|
||||
* File type
|
||||
*
|
||||
* 文件类型
|
||||
*/
|
||||
type: 'vue' | 'normal' | 'css' | 'markdown'
|
||||
/**
|
||||
* Export name
|
||||
*
|
||||
* 导出名称
|
||||
*/
|
||||
export?: string
|
||||
/**
|
||||
* File path
|
||||
*
|
||||
* 文件路径
|
||||
*/
|
||||
path: string
|
||||
/**
|
||||
* Whether to add to gitignore
|
||||
*
|
||||
* 是否添加到 gitignore
|
||||
*/
|
||||
gitignore?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Markdown Demo Environment
|
||||
*
|
||||
* Markdown 演示环境
|
||||
*/
|
||||
export interface MarkdownDemoEnv extends MarkdownEnv {
|
||||
/**
|
||||
* Demo files
|
||||
*
|
||||
* 演示文件列表
|
||||
*/
|
||||
demoFiles?: DemoFile[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Demo Metadata
|
||||
*
|
||||
* 演示元数据
|
||||
*/
|
||||
export interface DemoMeta {
|
||||
/**
|
||||
* Demo type
|
||||
*
|
||||
* 演示类型
|
||||
*/
|
||||
type: 'vue' | 'normal' | 'markdown'
|
||||
/**
|
||||
* URL
|
||||
*
|
||||
* 链接地址
|
||||
*/
|
||||
url: string
|
||||
/**
|
||||
* Title
|
||||
*
|
||||
* 标题
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* Description
|
||||
*
|
||||
* 描述
|
||||
*/
|
||||
desc?: string
|
||||
/**
|
||||
* Code settings
|
||||
*
|
||||
* 代码设置
|
||||
*/
|
||||
codeSetting?: string
|
||||
/**
|
||||
* Whether expanded
|
||||
*
|
||||
* 是否展开
|
||||
*/
|
||||
expanded?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Demo Container Render
|
||||
*
|
||||
* 演示容器渲染器
|
||||
*/
|
||||
export interface DemoContainerRender {
|
||||
/**
|
||||
* Before render
|
||||
*
|
||||
* 渲染前
|
||||
*/
|
||||
before: (
|
||||
app: App,
|
||||
md: Markdown,
|
||||
@ -30,6 +110,16 @@ export interface DemoContainerRender {
|
||||
meta: DemoMeta,
|
||||
codeMap: Record<string, string>,
|
||||
) => string
|
||||
/**
|
||||
* After render
|
||||
*
|
||||
* 渲染后
|
||||
*/
|
||||
after: () => string
|
||||
/**
|
||||
* Token processor
|
||||
*
|
||||
* 令牌处理器
|
||||
*/
|
||||
token?: (token: Token, tokens: Token[], index: number) => void
|
||||
}
|
||||
|
||||
@ -1,36 +1,66 @@
|
||||
import type { LocaleData } from 'vuepress'
|
||||
|
||||
/**
|
||||
* Encrypt Snippet Locale
|
||||
*
|
||||
* 加密片段本地化
|
||||
*/
|
||||
export interface EncryptSnippetLocale extends LocaleData {
|
||||
/**
|
||||
* @default 'The content is encrypted, please unlock to view.''
|
||||
* Hint message
|
||||
*
|
||||
* 提示信息
|
||||
* @default 'The content is encrypted, please unlock to view.'
|
||||
*/
|
||||
hint?: string
|
||||
/**
|
||||
* Password placeholder
|
||||
*
|
||||
* 密码占位符
|
||||
* @default 'Enter password'
|
||||
*/
|
||||
placeholder?: string
|
||||
/**
|
||||
* Incorrect password message
|
||||
*
|
||||
* 密码错误消息
|
||||
* @default 'Incorrect password'
|
||||
*/
|
||||
incPwd?: string
|
||||
/**
|
||||
* No content message
|
||||
*
|
||||
* 无内容消息
|
||||
* @default 'Unlocked, but content failed to load, please try again later.'
|
||||
*/
|
||||
noContent?: string
|
||||
|
||||
/**
|
||||
* Security warning title
|
||||
*
|
||||
* 安全警告标题
|
||||
* @default '🚨 Security Warning:'
|
||||
*/
|
||||
warningTitle?: string
|
||||
/**
|
||||
* Security warning text
|
||||
*
|
||||
* 安全警告文本
|
||||
* @default 'Your connection is not encrypted with HTTPS, posing a risk of content leakage and preventing access to encrypted content.'
|
||||
*/
|
||||
warningText?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt Snippet Options
|
||||
*
|
||||
* 加密片段选项
|
||||
*/
|
||||
export interface EncryptSnippetOptions {
|
||||
/**
|
||||
* default password
|
||||
* Default password
|
||||
*
|
||||
* 默认密码
|
||||
*/
|
||||
password?: string
|
||||
}
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
/* eslint-disable jsdoc/no-multi-asterisks */
|
||||
|
||||
/**
|
||||
* Markdown Environment Preset Configuration
|
||||
*
|
||||
* Markdown 环境预设配置
|
||||
*/
|
||||
export interface MarkdownEnvPreset {
|
||||
/**
|
||||
* markdown reference preset, use in any markdown file
|
||||
@ -20,7 +26,7 @@ export interface MarkdownEnvPreset {
|
||||
* [link][label-1]
|
||||
* [link][label-2]
|
||||
* ```
|
||||
* same as 等价于
|
||||
* same as / 等价于
|
||||
* ```markdown
|
||||
* [label-1]: http://example.com/
|
||||
* [label-2]: http://example.com/ "title"
|
||||
@ -47,7 +53,7 @@ export interface MarkdownEnvPreset {
|
||||
* ```markdown
|
||||
* The HTML specification is maintained by the W3C.
|
||||
* ```
|
||||
* same as 等价于
|
||||
* same as / 等价于
|
||||
* ```markdown
|
||||
* *[HTML]: Hyper Text Markup Language
|
||||
* *[W3C]: World Wide Web Consortium
|
||||
@ -73,7 +79,7 @@ export interface MarkdownEnvPreset {
|
||||
* ```markdown
|
||||
* [+vuepress-theme-plume] is a theme for [+vuepress]
|
||||
* ```
|
||||
* same as 等价于
|
||||
* same as / 等价于
|
||||
* ```markdown
|
||||
* [+vuepress]: vuepress is a Vue.js based documentation generator
|
||||
* [+vuepress-theme-plume]: vuepress-theme-plume is a theme for vuepress
|
||||
|
||||
@ -1,5 +1,23 @@
|
||||
/**
|
||||
* File tree icon mode
|
||||
*
|
||||
* 文件树图标模式
|
||||
*/
|
||||
export type FileTreeIconMode = 'simple' | 'colored'
|
||||
|
||||
/**
|
||||
* File tree options
|
||||
*
|
||||
* 文件树配置选项
|
||||
*/
|
||||
export interface FileTreeOptions {
|
||||
/**
|
||||
* Icon mode for file tree
|
||||
*
|
||||
* 文件树的图标模式
|
||||
*
|
||||
* - `simple`: Simple icons / 简单图标
|
||||
* - `colored`: Colored icons / 彩色图标
|
||||
*/
|
||||
icon?: FileTreeIconMode
|
||||
}
|
||||
|
||||
@ -1,8 +1,33 @@
|
||||
import type { SizeOptions } from './size'
|
||||
|
||||
/**
|
||||
* JSFiddle token metadata
|
||||
*
|
||||
* JSFiddle 令牌元数据
|
||||
*/
|
||||
export interface JSFiddleTokenMeta extends SizeOptions {
|
||||
/**
|
||||
* Source URL
|
||||
*
|
||||
* 源 URL
|
||||
*/
|
||||
source: string
|
||||
/**
|
||||
* Fiddle title
|
||||
*
|
||||
* Fiddle 标题
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* Theme
|
||||
*
|
||||
* 主题
|
||||
*/
|
||||
theme?: string
|
||||
/**
|
||||
* Display tabs
|
||||
*
|
||||
* 显示的选项卡
|
||||
*/
|
||||
tab?: string
|
||||
}
|
||||
|
||||
@ -1,12 +1,42 @@
|
||||
import type { LocaleData } from 'vuepress'
|
||||
import type { EncryptSnippetLocale } from './encrypt'
|
||||
|
||||
/**
|
||||
* Markdown Power Plugin Locale Data
|
||||
*
|
||||
* Markdown Power 插件本地化数据
|
||||
*/
|
||||
export interface MDPowerLocaleData extends LocaleData {
|
||||
/**
|
||||
* Common locale data
|
||||
*
|
||||
* 通用本地化数据
|
||||
*/
|
||||
common?: CommonLocaleData
|
||||
/**
|
||||
* Encrypt snippet locale data
|
||||
*
|
||||
* 加密片段本地化数据
|
||||
*/
|
||||
encrypt?: EncryptSnippetLocale
|
||||
}
|
||||
|
||||
/**
|
||||
* Common Locale Data
|
||||
*
|
||||
* 通用本地化数据
|
||||
*/
|
||||
export interface CommonLocaleData extends LocaleData {
|
||||
/**
|
||||
* Copy button text
|
||||
*
|
||||
* 复制按钮文本
|
||||
*/
|
||||
copy?: string
|
||||
/**
|
||||
* Copied button text
|
||||
*
|
||||
* 已复制按钮文本
|
||||
*/
|
||||
copied?: string
|
||||
}
|
||||
|
||||
@ -1,5 +1,20 @@
|
||||
/**
|
||||
* Supported package managers
|
||||
*
|
||||
* 支持的包管理器
|
||||
*/
|
||||
export type NpmToPackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'deno'
|
||||
|
||||
/**
|
||||
* npm-to options
|
||||
*
|
||||
* npm-to 配置选项
|
||||
*/
|
||||
export type NpmToOptions = NpmToPackageManager[] | {
|
||||
/**
|
||||
* Tabs to display
|
||||
*
|
||||
* 要显示的选项卡
|
||||
*/
|
||||
tabs?: NpmToPackageManager[]
|
||||
}
|
||||
|
||||
@ -1,18 +1,60 @@
|
||||
import type { SizeOptions } from './size'
|
||||
|
||||
/**
|
||||
* PDF embed type
|
||||
*
|
||||
* PDF 嵌入类型
|
||||
*/
|
||||
export type PDFEmbedType = 'iframe' | 'embed' | 'pdfjs'
|
||||
|
||||
/**
|
||||
* PDF token metadata
|
||||
*
|
||||
* PDF 令牌元数据
|
||||
*/
|
||||
export interface PDFTokenMeta extends SizeOptions {
|
||||
/**
|
||||
* Page number to display
|
||||
*
|
||||
* 要显示的页码
|
||||
*/
|
||||
page?: number
|
||||
/**
|
||||
* Whether to hide toolbar
|
||||
*
|
||||
* 是否隐藏工具栏
|
||||
*/
|
||||
noToolbar?: boolean
|
||||
/**
|
||||
* Zoom level
|
||||
*
|
||||
* 缩放级别
|
||||
*/
|
||||
zoom?: number
|
||||
/**
|
||||
* PDF source URL
|
||||
*
|
||||
* PDF 源 URL
|
||||
*/
|
||||
src?: string
|
||||
/**
|
||||
* Title of the PDF
|
||||
*
|
||||
* PDF 标题
|
||||
*/
|
||||
title?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* PDF options
|
||||
*
|
||||
* PDF 配置选项
|
||||
*/
|
||||
export interface PDFOptions {
|
||||
/**
|
||||
* pdfjs url
|
||||
* PDF.js library URL
|
||||
*
|
||||
* PDF.js 库 URL
|
||||
*/
|
||||
pdfjsUrl?: string
|
||||
}
|
||||
|
||||
@ -14,11 +14,20 @@ import type { PlotOptions } from './plot.js'
|
||||
import type { ReplOptions } from './repl.js'
|
||||
import type { TableContainerOptions } from './table.js'
|
||||
|
||||
/**
|
||||
* Markdown Power Plugin Options
|
||||
*
|
||||
* Markdown Power 插件配置选项
|
||||
*/
|
||||
export interface MarkdownPowerPluginOptions {
|
||||
|
||||
/**
|
||||
* Whether to preset markdown env, such as preset link references, abbreviations, content annotations, etc.
|
||||
*
|
||||
* 是否预设 markdown env,如 预设链接引用、缩写词、内容注释等
|
||||
*
|
||||
* Presets can be used in any markdown file
|
||||
*
|
||||
* 预设可以在任何 markdown 文件中使用
|
||||
*
|
||||
* @example
|
||||
@ -43,78 +52,98 @@ export interface MarkdownPowerPluginOptions {
|
||||
*/
|
||||
env?: MarkdownEnvPreset
|
||||
/**
|
||||
* Whether to enable annotation, or preset content annotations
|
||||
*
|
||||
* 是否启用注释, 或者预设内容注释
|
||||
* @default false
|
||||
*/
|
||||
annotation?: boolean | MarkdownEnvPreset['annotations']
|
||||
|
||||
/**
|
||||
* Whether to enable abbr syntax, or preset abbreviations
|
||||
*
|
||||
* 是否启用 abbr 语法, 或者预设缩写词
|
||||
* @default false
|
||||
*/
|
||||
abbr?: boolean | MarkdownEnvPreset['abbreviations']
|
||||
/**
|
||||
* Mark pen animation mode
|
||||
*
|
||||
* 马克笔动画模式
|
||||
* @default 'eager'
|
||||
*/
|
||||
mark?: MarkOptions
|
||||
|
||||
/**
|
||||
* Whether to enable content snippet encryption container
|
||||
*
|
||||
* 是否启用 内容片段加密容器
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
encrypt?: boolean | EncryptSnippetOptions
|
||||
/**
|
||||
* Configure code block grouping
|
||||
*
|
||||
* 配置代码块分组
|
||||
*/
|
||||
codeTabs?: CodeTabsOptions
|
||||
|
||||
/**
|
||||
* Whether to enable npm-to container
|
||||
*
|
||||
* 是否启用 npm-to 容器
|
||||
*/
|
||||
npmTo?: boolean | NpmToOptions
|
||||
|
||||
/**
|
||||
* 是否启用 PDF 嵌入语法
|
||||
* Whether to enable PDF embed syntax
|
||||
*
|
||||
* `@[pdf](pdf_url)`
|
||||
*
|
||||
* 是否启用 PDF 嵌入语法
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
pdf?: boolean | PDFOptions
|
||||
|
||||
// new syntax
|
||||
/**
|
||||
* 是否启用 图标支持
|
||||
* Whether to enable icon support
|
||||
* - iconify - `::collect:icon_name::` => `<VPIcon name="collect:icon_name" />`
|
||||
* - iconfont - `::name::` => `<i class="iconfont icon-name"></i>`
|
||||
* - fontawesome - `::fas:name::` => `<i class="fa-solid fa-name"></i>`
|
||||
*
|
||||
* 是否启用 图标支持
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
icon?: IconOptions
|
||||
|
||||
/**
|
||||
* 是否启用 iconify 图标嵌入语法
|
||||
* Whether to enable iconify icon embed syntax
|
||||
*
|
||||
* `::collect:icon_name::`
|
||||
*
|
||||
* 是否启用 iconify 图标嵌入语法
|
||||
*
|
||||
* @default false
|
||||
* @deprecated use `icon` instead 该配置已弃用,请使用 `icon` 代替
|
||||
* @deprecated use `icon` instead / 该配置已弃用,请使用 `icon` 代替
|
||||
*/
|
||||
icons?: boolean | IconOptions
|
||||
/**
|
||||
* 是否启用 隐秘文本 语法
|
||||
* Whether to enable hidden text syntax
|
||||
*
|
||||
* `!!plot_content!!`
|
||||
*
|
||||
* 是否启用 隐秘文本 语法
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
plot?: boolean | PlotOptions
|
||||
|
||||
/**
|
||||
* 是否启用 timeline 语法
|
||||
* Whether to enable timeline syntax
|
||||
*
|
||||
* ```md
|
||||
* ::: timeline
|
||||
@ -125,12 +154,14 @@ export interface MarkdownPowerPluginOptions {
|
||||
* :::
|
||||
* ```
|
||||
*
|
||||
* 是否启用 timeline 语法
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
timeline?: boolean
|
||||
|
||||
/**
|
||||
* 是否启用 collapse 折叠面板 语法
|
||||
* Whether to enable collapse folding panel syntax
|
||||
*
|
||||
* ```md
|
||||
* ::: collapse accordion
|
||||
@ -144,12 +175,14 @@ export interface MarkdownPowerPluginOptions {
|
||||
* :::
|
||||
* ```
|
||||
*
|
||||
* 是否启用 collapse 折叠面板 语法
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
collapse?: boolean
|
||||
|
||||
/**
|
||||
* 是否启用 chat 容器 语法
|
||||
* Whether to enable chat container syntax
|
||||
*
|
||||
* ```md
|
||||
* ::: chat
|
||||
@ -162,11 +195,16 @@ export interface MarkdownPowerPluginOptions {
|
||||
* message
|
||||
* :::
|
||||
* ```
|
||||
*
|
||||
* 是否启用 chat 容器 语法
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
chat?: boolean
|
||||
|
||||
/**
|
||||
* Whether to enable field / field-group container
|
||||
*
|
||||
* 是否启用 field / field-group 容器
|
||||
*
|
||||
* @default false
|
||||
@ -174,50 +212,62 @@ export interface MarkdownPowerPluginOptions {
|
||||
field?: boolean
|
||||
// video embed
|
||||
/**
|
||||
* 是否启用 acfun 视频嵌入
|
||||
* Whether to enable acfun video embed
|
||||
*
|
||||
* `@[acfun](acid)`
|
||||
*
|
||||
* 是否启用 acfun 视频嵌入
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
acfun?: boolean
|
||||
/**
|
||||
* 是否启用 bilibili 视频嵌入
|
||||
* Whether to enable bilibili video embed
|
||||
*
|
||||
* `@[bilibili](bid)`
|
||||
*
|
||||
* 是否启用 bilibili 视频嵌入
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
bilibili?: boolean
|
||||
/**
|
||||
* 是否启用 youtube 视频嵌入
|
||||
* Whether to enable youtube video embed
|
||||
*
|
||||
* `@[youtube](video_id)`
|
||||
*
|
||||
* 是否启用 youtube 视频嵌入
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
youtube?: boolean
|
||||
|
||||
/**
|
||||
* 是否启用 artPlayer 视频嵌入
|
||||
* Whether to enable artPlayer video embed
|
||||
*
|
||||
* `@[artPlayer](url)`
|
||||
*
|
||||
* 是否启用 artPlayer 视频嵌入
|
||||
*/
|
||||
artPlayer?: boolean
|
||||
|
||||
/**
|
||||
* 是否启用 audioReader 音频嵌入
|
||||
* Whether to enable audioReader audio embed
|
||||
*
|
||||
* `@[audioReader](url)`
|
||||
*
|
||||
* 是否启用 audioReader 音频嵌入
|
||||
*/
|
||||
audioReader?: boolean
|
||||
|
||||
// code embed
|
||||
/**
|
||||
* 是否启用 codepen 嵌入
|
||||
* Whether to enable codepen embed
|
||||
*
|
||||
* `@[codepen](pen_id)`
|
||||
*
|
||||
* 是否启用 codepen 嵌入
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
codepen?: boolean
|
||||
@ -226,30 +276,38 @@ export interface MarkdownPowerPluginOptions {
|
||||
*/
|
||||
replit?: boolean
|
||||
/**
|
||||
* 是否启用 codeSandbox 嵌入
|
||||
* Whether to enable codeSandbox embed
|
||||
*
|
||||
* `@[codesandbox](codesandbox_id)`
|
||||
*
|
||||
* 是否启用 codeSandbox 嵌入
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
codeSandbox?: boolean
|
||||
/**
|
||||
* 是否启用 jsfiddle 嵌入
|
||||
* Whether to enable jsfiddle embed
|
||||
*
|
||||
* `@[jsfiddle](jsfiddle_id)`
|
||||
*
|
||||
* 是否启用 jsfiddle 嵌入
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
jsfiddle?: boolean
|
||||
|
||||
// container
|
||||
/**
|
||||
* Whether to enable REPL container syntax
|
||||
*
|
||||
* 是否启用 REPL 容器语法
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
repl?: false | ReplOptions
|
||||
/**
|
||||
* Whether to enable file tree container syntax
|
||||
*
|
||||
* 是否启用 文件树 容器语法
|
||||
*
|
||||
* @default false
|
||||
@ -257,7 +315,7 @@ export interface MarkdownPowerPluginOptions {
|
||||
fileTree?: boolean | FileTreeOptions
|
||||
|
||||
/**
|
||||
* 是否启用 代码树 容器语法 和 嵌入语法
|
||||
* Whether to enable code tree container syntax and embed syntax
|
||||
*
|
||||
* ```md
|
||||
* ::: code-tree
|
||||
@ -267,25 +325,35 @@ export interface MarkdownPowerPluginOptions {
|
||||
* `@[code-tree](file_path)`
|
||||
*
|
||||
*
|
||||
* 是否启用 代码树 容器语法 和 嵌入语法
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
codeTree?: boolean | CodeTreeOptions
|
||||
|
||||
/**
|
||||
* Whether to enable demo syntax
|
||||
*
|
||||
* 是否启用 demo 语法
|
||||
*/
|
||||
demo?: boolean
|
||||
|
||||
/**
|
||||
* 是否启用 caniuse 嵌入语法
|
||||
* Whether to enable caniuse embed syntax
|
||||
*
|
||||
* `@[caniuse](feature_name)`
|
||||
*
|
||||
* 是否启用 caniuse 嵌入语法
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
caniuse?: boolean | CanIUseOptions
|
||||
|
||||
/**
|
||||
* Whether to enable table container syntax, providing enhanced functionality for tables
|
||||
*
|
||||
* - `copy`: Whether to enable copy functionality, supports copying as html format and markdown format
|
||||
*
|
||||
* 是否启用 table 容器语法,为表格提供增强功能
|
||||
*
|
||||
* - `copy`: 是否启用复制功能,支持复制为 html 格式 和 markdown 格式
|
||||
@ -295,6 +363,8 @@ export interface MarkdownPowerPluginOptions {
|
||||
table?: boolean | TableContainerOptions
|
||||
|
||||
/**
|
||||
* Whether to enable QR code embed syntax
|
||||
*
|
||||
* 是否启用 二维码 嵌入语法
|
||||
*
|
||||
* @default false
|
||||
@ -303,6 +373,21 @@ export interface MarkdownPowerPluginOptions {
|
||||
|
||||
// enhance
|
||||
/**
|
||||
* Whether to enable automatic filling of image width and height attributes
|
||||
*
|
||||
* __Please note that regardless of whether it is enabled, this feature only takes effect when building production packages__
|
||||
*
|
||||
* - If `true`, equivalent to `'local'`
|
||||
* - If `local`, only add width and height for local images
|
||||
* - If `all`, add width and height for all images (including local and remote)
|
||||
*
|
||||
* If images load slowly, the process from loading to completion can cause unstable page layout and content flickering.
|
||||
* This feature solves this problem by adding `width` and `height` attributes to images.
|
||||
*
|
||||
* Please use the `all` option with caution. This option will initiate network requests during the build phase,
|
||||
* attempting to load remote images to obtain image size information,
|
||||
* which may cause build times to become longer (fortunately, obtaining size information only requires loading a few KB of image data packets, so the time consumption will not be too long)
|
||||
*
|
||||
* 是否启用 自动填充 图片宽高属性
|
||||
*
|
||||
* __请注意,无论是否启用,该功能仅在构建生产包时生效__
|
||||
|
||||
@ -1,27 +1,51 @@
|
||||
/**
|
||||
* QR code metadata
|
||||
*
|
||||
* 二维码元数据
|
||||
*/
|
||||
export interface QRCodeMeta extends QRCodeProps {
|
||||
/**
|
||||
* Alias for mode: 'card'
|
||||
*
|
||||
* mode: 'card' 的别名
|
||||
*/
|
||||
card?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* QR code props
|
||||
*
|
||||
* 二维码属性
|
||||
*/
|
||||
export interface QRCodeProps {
|
||||
/**
|
||||
* QR code title
|
||||
* Used as HTML `title` and `alt` attributes
|
||||
*
|
||||
* 二维码标题
|
||||
* 作为 HTML 标签的 `title` 属性、`alt` 属性
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* QR code content
|
||||
*
|
||||
* 二维码内容
|
||||
*/
|
||||
text?: string
|
||||
/**
|
||||
* QR code width
|
||||
*
|
||||
* 二维码宽度
|
||||
*/
|
||||
width?: number | string
|
||||
|
||||
/**
|
||||
* Display mode
|
||||
* - img: Display QR code as image
|
||||
* - card: Display as card with left-right layout, QR code on left, title + content on right
|
||||
* @default 'img'
|
||||
*
|
||||
* 显示模式
|
||||
* - img: 以图片的形式显示二维码
|
||||
* - card: 以卡片的形式显示,卡片以左右布局,左侧二维码,右侧 标题 + 内容
|
||||
@ -30,50 +54,79 @@ export interface QRCodeProps {
|
||||
mode?: 'img' | 'card'
|
||||
|
||||
/**
|
||||
* Whether to reverse layout in card mode
|
||||
*
|
||||
* 在 card 模式下是否翻转布局
|
||||
*/
|
||||
reverse?: boolean
|
||||
|
||||
/**
|
||||
* QR code alignment
|
||||
* @default 'left'
|
||||
*
|
||||
* 二维码的对齐方式
|
||||
* @default 'left'
|
||||
*/
|
||||
align?: 'left' | 'center' | 'right'
|
||||
|
||||
/**
|
||||
* Whether to render as SVG format
|
||||
* Default output is PNG format dataURL
|
||||
* @default false
|
||||
*
|
||||
* 是否渲染为 SVG 格式的二维码
|
||||
* 默认输出为 PNG 格式的 dataURL
|
||||
* @default false
|
||||
*/
|
||||
svg?: boolean
|
||||
/**
|
||||
* Error correction level.
|
||||
* Possible values: Low, Medium, Quartile, High, corresponding to L, M, Q, H.
|
||||
* @default 'M'
|
||||
*
|
||||
* 纠错等级。
|
||||
* 可能的取值为低、中、四分位、高,分别对应 L、M、Q、H。
|
||||
* @default 'M'
|
||||
*/
|
||||
level?: 'L' | 'M' | 'Q' | 'H' | 'l' | 'm' | 'q' | 'h'
|
||||
/**
|
||||
* QR code version. If not specified, will automatically calculate more suitable value.
|
||||
* Range: 1-40
|
||||
*
|
||||
* 二维码版本。若未指定,将自动计算更合适的值。
|
||||
* 取值范围 1-40
|
||||
*/
|
||||
version?: number
|
||||
/**
|
||||
* Mask pattern used to mask symbols.
|
||||
* Possible values: 0, 1, 2, 3, 4, 5, 6, 7.
|
||||
* If not specified, system will automatically calculate more suitable value.
|
||||
*
|
||||
* 用于遮蔽符号的掩码模式。
|
||||
* 可能的取值为0、1、2、3、4、5、6、7。
|
||||
* 若未指定,系统将自动计算更合适的值。
|
||||
*/
|
||||
mask?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
/**
|
||||
* Define how wide the quiet zone should be.
|
||||
* @default 4
|
||||
*
|
||||
* 定义静区应有多宽。
|
||||
* @default 4
|
||||
*/
|
||||
margin?: number
|
||||
/**
|
||||
* Scale factor. Value of 1 means 1 pixel per module (black dot).
|
||||
*
|
||||
* 缩放因子。值为1表示每个模块(黑点)对应1像素。
|
||||
*/
|
||||
scale?: number
|
||||
|
||||
/**
|
||||
* Color of dark modules. Value must be in hexadecimal format (RGBA).
|
||||
* Note: Dark should always be darker than light module color.
|
||||
* @default '#000000ff'
|
||||
*
|
||||
* 暗色模块的颜色。值必须为十六进制格式(RGBA)。
|
||||
* 注意:暗色应始终比浅色模块的颜色更深。
|
||||
* @default '#000000ff'
|
||||
@ -81,6 +134,10 @@ export interface QRCodeProps {
|
||||
light?: string
|
||||
|
||||
/**
|
||||
* Color of light modules. Value must be in hexadecimal format (RGBA).
|
||||
* Note: Light should always be lighter than dark module color.
|
||||
* @default '#ffffffff'
|
||||
*
|
||||
* 亮色模块的颜色。值必须为十六进制格式(RGBA)。
|
||||
* 注意:亮色应始终比暗色模块的颜色更浅。
|
||||
* @default '#ffffffff'
|
||||
|
||||
@ -1,28 +1,88 @@
|
||||
import type { BuiltinTheme, ThemeRegistration } from 'shiki'
|
||||
|
||||
/**
|
||||
* Theme options for REPL
|
||||
*
|
||||
* REPL 主题选项
|
||||
*/
|
||||
export type ThemeOptions
|
||||
= | BuiltinTheme
|
||||
| {
|
||||
/**
|
||||
* Light theme
|
||||
*
|
||||
* 浅色主题
|
||||
*/
|
||||
light: BuiltinTheme
|
||||
/**
|
||||
* Dark theme
|
||||
*
|
||||
* 深色主题
|
||||
*/
|
||||
dark: BuiltinTheme
|
||||
}
|
||||
|
||||
/**
|
||||
* REPL options
|
||||
*
|
||||
* REPL 配置选项
|
||||
*/
|
||||
export interface ReplOptions {
|
||||
/**
|
||||
* Theme for code editor
|
||||
*
|
||||
* 代码编辑器主题
|
||||
*/
|
||||
theme?: ThemeOptions
|
||||
|
||||
/**
|
||||
* Whether to enable Go language support
|
||||
*
|
||||
* 是否启用 Go 语言支持
|
||||
*/
|
||||
go?: boolean
|
||||
/**
|
||||
* Whether to enable Kotlin language support
|
||||
*
|
||||
* 是否启用 Kotlin 语言支持
|
||||
*/
|
||||
kotlin?: boolean
|
||||
/**
|
||||
* Whether to enable Rust language support
|
||||
*
|
||||
* 是否启用 Rust 语言支持
|
||||
*/
|
||||
rust?: boolean
|
||||
/**
|
||||
* Whether to enable Python language support
|
||||
*
|
||||
* 是否启用 Python 语言支持
|
||||
*/
|
||||
python?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* REPL editor data
|
||||
*
|
||||
* REPL 编辑器数据
|
||||
*/
|
||||
export interface ReplEditorData {
|
||||
/**
|
||||
* Grammar definitions for languages
|
||||
*
|
||||
* 语言的语法定义
|
||||
*/
|
||||
grammars: {
|
||||
go?: any
|
||||
kotlin?: any
|
||||
rust?: any
|
||||
python?: any
|
||||
}
|
||||
/**
|
||||
* Theme registration
|
||||
*
|
||||
* 主题注册
|
||||
*/
|
||||
theme: ThemeRegistration | {
|
||||
light: ThemeRegistration
|
||||
dark: ThemeRegistration
|
||||
|
||||
@ -1,5 +1,25 @@
|
||||
/**
|
||||
* Size Options
|
||||
*
|
||||
* 尺寸选项
|
||||
*/
|
||||
export interface SizeOptions {
|
||||
/**
|
||||
* Width
|
||||
*
|
||||
* 宽度
|
||||
*/
|
||||
width?: string
|
||||
/**
|
||||
* Height
|
||||
*
|
||||
* 高度
|
||||
*/
|
||||
height?: string
|
||||
/**
|
||||
* Aspect ratio
|
||||
*
|
||||
* 宽高比
|
||||
*/
|
||||
ratio?: number | string
|
||||
}
|
||||
|
||||
@ -1,41 +1,191 @@
|
||||
import type { SizeOptions } from './size'
|
||||
|
||||
/**
|
||||
* Video options
|
||||
*
|
||||
* 视频配置选项
|
||||
*/
|
||||
export interface VideoOptions {
|
||||
/**
|
||||
* Whether to enable Bilibili video embed
|
||||
*
|
||||
* 是否启用 Bilibili 视频嵌入
|
||||
*/
|
||||
bilibili?: boolean
|
||||
/**
|
||||
* Whether to enable YouTube video embed
|
||||
*
|
||||
* 是否启用 YouTube 视频嵌入
|
||||
*/
|
||||
youtube?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* AcFun token metadata
|
||||
*
|
||||
* AcFun 令牌元数据
|
||||
*/
|
||||
export interface AcFunTokenMeta extends SizeOptions {
|
||||
/**
|
||||
* Video title
|
||||
*
|
||||
* 视频标题
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* Video ID
|
||||
*
|
||||
* 视频 ID
|
||||
*/
|
||||
id: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Bilibili token metadata
|
||||
*
|
||||
* Bilibili 令牌元数据
|
||||
*/
|
||||
export interface BilibiliTokenMeta extends SizeOptions {
|
||||
/**
|
||||
* Video title
|
||||
*
|
||||
* 视频标题
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* BV ID
|
||||
*
|
||||
* BV ID
|
||||
*/
|
||||
bvid?: string
|
||||
/**
|
||||
* AV ID
|
||||
*
|
||||
* AV ID
|
||||
*/
|
||||
aid?: string
|
||||
/**
|
||||
* CID
|
||||
*
|
||||
* CID
|
||||
*/
|
||||
cid?: string
|
||||
/**
|
||||
* Whether to autoplay
|
||||
*
|
||||
* 是否自动播放
|
||||
*/
|
||||
autoplay?: boolean
|
||||
/**
|
||||
* Start time
|
||||
*
|
||||
* 开始时间
|
||||
*/
|
||||
time?: string | number
|
||||
/**
|
||||
* Page number
|
||||
*
|
||||
* 页码
|
||||
*/
|
||||
page?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube token metadata
|
||||
*
|
||||
* YouTube 令牌元数据
|
||||
*/
|
||||
export interface YoutubeTokenMeta extends SizeOptions {
|
||||
/**
|
||||
* Video title
|
||||
*
|
||||
* 视频标题
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* Video ID
|
||||
*
|
||||
* 视频 ID
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* Whether to autoplay
|
||||
*
|
||||
* 是否自动播放
|
||||
*/
|
||||
autoplay?: boolean
|
||||
/**
|
||||
* Whether to loop
|
||||
*
|
||||
* 是否循环播放
|
||||
*/
|
||||
loop?: boolean
|
||||
/**
|
||||
* Start time
|
||||
*
|
||||
* 开始时间
|
||||
*/
|
||||
start?: string | number
|
||||
/**
|
||||
* End time
|
||||
*
|
||||
* 结束时间
|
||||
*/
|
||||
end?: string | number
|
||||
}
|
||||
|
||||
/**
|
||||
* ArtPlayer token metadata
|
||||
*
|
||||
* ArtPlayer 令牌元数据
|
||||
*/
|
||||
export interface ArtPlayerTokenMeta extends SizeOptions {
|
||||
/**
|
||||
* Whether muted
|
||||
*
|
||||
* 是否静音
|
||||
*/
|
||||
muted?: boolean
|
||||
/**
|
||||
* Whether to autoplay
|
||||
*
|
||||
* 是否自动播放
|
||||
*/
|
||||
autoplay?: boolean
|
||||
/**
|
||||
* Whether auto mini mode
|
||||
*
|
||||
* 是否自动迷你模式
|
||||
*/
|
||||
autoMini?: boolean
|
||||
/**
|
||||
* Whether to loop
|
||||
*
|
||||
* 是否循环播放
|
||||
*/
|
||||
loop?: boolean
|
||||
volume?: number // 0-1
|
||||
/**
|
||||
* Volume level (0-1)
|
||||
*
|
||||
* 音量级别 (0-1)
|
||||
*/
|
||||
volume?: number
|
||||
/**
|
||||
* Poster image URL
|
||||
*
|
||||
* 封面图片 URL
|
||||
*/
|
||||
poster?: string
|
||||
/**
|
||||
* Video URL
|
||||
*
|
||||
* 视频 URL
|
||||
*/
|
||||
url: string
|
||||
/**
|
||||
* Video type
|
||||
*
|
||||
* 视频类型
|
||||
*/
|
||||
type?: string
|
||||
}
|
||||
|
||||
@ -3,12 +3,31 @@ import { useDark, useEventListener } from '@vueuse/core'
|
||||
import { inject, ref } from 'vue'
|
||||
import { useThemeData } from './theme-data.js'
|
||||
|
||||
/**
|
||||
* Dark mode reference type
|
||||
*
|
||||
* 暗黑模式引用类型
|
||||
*/
|
||||
type DarkModeRef = Ref<boolean>
|
||||
|
||||
/**
|
||||
* Injection key for dark mode
|
||||
*
|
||||
* 暗黑模式的注入键
|
||||
*/
|
||||
export const darkModeSymbol: InjectionKey<DarkModeRef> = Symbol(
|
||||
__VUEPRESS_DEV__ ? 'darkMode' : '',
|
||||
)
|
||||
|
||||
/**
|
||||
* Check if view transitions are enabled and supported
|
||||
* Considers prefers-reduced-motion preference
|
||||
*
|
||||
* 检查视图过渡是否启用且受支持
|
||||
* 考虑 prefers-reduced-motion 偏好设置
|
||||
*
|
||||
* @returns Whether transitions are enabled / 过渡是否启用
|
||||
*/
|
||||
export function enableTransitions(): boolean {
|
||||
if (typeof document === 'undefined')
|
||||
return false
|
||||
@ -16,6 +35,15 @@ export function enableTransitions(): boolean {
|
||||
&& window.matchMedia('(prefers-reduced-motion: no-preference)').matches
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup dark mode for the Vue application
|
||||
* Configures dark mode based on theme settings and user preferences
|
||||
*
|
||||
* 为 Vue 应用设置暗黑模式
|
||||
* 根据主题设置和用户偏好配置暗黑模式
|
||||
*
|
||||
* @param app - Vue application instance / Vue 应用实例
|
||||
*/
|
||||
export function setupDarkMode(app: App): void {
|
||||
const theme = useThemeData()
|
||||
|
||||
@ -51,6 +79,7 @@ export function setupDarkMode(app: App): void {
|
||||
get: () => isDark,
|
||||
})
|
||||
|
||||
// Handle print events - switch to light mode for printing
|
||||
useEventListener('beforeprint', () => {
|
||||
if (isDark.value)
|
||||
document.documentElement.dataset.theme = 'light'
|
||||
@ -63,7 +92,13 @@ export function setupDarkMode(app: App): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject dark mode global computed
|
||||
* Use dark mode
|
||||
* Returns the dark mode reactive reference
|
||||
*
|
||||
* 获取暗黑模式状态
|
||||
*
|
||||
* @returns Dark mode reference / 暗黑模式引用
|
||||
* @throws Error if called without provider / 如果没有提供者则抛出错误
|
||||
*/
|
||||
export function useDarkMode(): DarkModeRef {
|
||||
const isDarkMode = inject(darkModeSymbol)
|
||||
|
||||
@ -45,6 +45,11 @@ export interface Data<T extends FrontmatterType = 'page', C extends FrontmatterC
|
||||
collection: CollectionItemRef<C extends 'doc' ? ThemeDocCollection : ThemePostCollection>
|
||||
}
|
||||
|
||||
/**
|
||||
* Use data
|
||||
*
|
||||
* 获取页面数据,包括主题配置、页面数据、frontmatter、语言、站点信息、暗黑模式和集合信息
|
||||
*/
|
||||
export function useData<T extends FrontmatterType = 'page', C extends FrontmatterCollectionType = 'doc'>(): Data<T, C> {
|
||||
const theme = useThemeLocaleData()
|
||||
const page = usePageData<ThemePageData>()
|
||||
|
||||
@ -3,34 +3,94 @@ import { encrypt as rawEncrypt } from '@internal/encrypt'
|
||||
import { decodeData } from '@vuepress/helper/client'
|
||||
import { ref } from 'vue'
|
||||
|
||||
/**
|
||||
* Encrypt configuration tuple type
|
||||
* Contains keys, rules, global flag, and admin passwords
|
||||
*
|
||||
* 加密配置元组类型
|
||||
* 包含密钥、规则、全局标志和管理员密码
|
||||
*/
|
||||
export type EncryptConfig = readonly [
|
||||
keys: string, // keys
|
||||
rules: string, // rules
|
||||
global: number, // global
|
||||
admin: string, // admin
|
||||
keys: string, // keys / 密钥
|
||||
rules: string, // rules / 规则
|
||||
global: number, // global / 全局标志
|
||||
admin: string, // admin / 管理员密码
|
||||
]
|
||||
|
||||
/**
|
||||
* Encrypt data rule interface
|
||||
* Defines a single encryption rule with matching pattern and passwords
|
||||
*
|
||||
* 加密数据规则接口
|
||||
* 定义单个加密规则,包含匹配模式和密码
|
||||
*/
|
||||
export interface EncryptDataRule {
|
||||
/** Unique key for the rule / 规则的唯一键 */
|
||||
key: string
|
||||
/** Match pattern for the rule / 规则的匹配模式 */
|
||||
match: string
|
||||
/** Array of valid passwords / 有效密码数组 */
|
||||
rules: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt data interface
|
||||
* Contains all encryption configuration and rules
|
||||
*
|
||||
* 加密数据接口
|
||||
* 包含所有加密配置和规则
|
||||
*/
|
||||
export interface EncryptData {
|
||||
/** Whether global encryption is enabled / 是否启用全局加密 */
|
||||
global: boolean
|
||||
/** Array of admin password hashes / 管理员密码哈希数组 */
|
||||
admins: string[]
|
||||
/** Array of match patterns / 匹配模式数组 */
|
||||
matches: string[]
|
||||
/** Array of encryption rules / 加密规则数组 */
|
||||
ruleList: EncryptDataRule[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt data reference type
|
||||
*
|
||||
* 加密数据引用类型
|
||||
*/
|
||||
export type EncryptRef = Ref<EncryptData>
|
||||
|
||||
/**
|
||||
* Global encrypt data reference
|
||||
*
|
||||
* 全局加密数据引用
|
||||
*/
|
||||
export const encrypt: EncryptRef = ref(resolveEncryptData(rawEncrypt))
|
||||
|
||||
/**
|
||||
* Use encrypt data
|
||||
* Returns the global encrypt data reference
|
||||
*
|
||||
* 获取加密数据
|
||||
* 返回全局加密数据引用
|
||||
*
|
||||
* @returns Encrypt data reference / 加密数据引用
|
||||
*/
|
||||
export function useEncryptData(): EncryptRef {
|
||||
return encrypt as EncryptRef
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve encrypt data from raw configuration
|
||||
* Decodes and parses the raw encrypt configuration
|
||||
*
|
||||
* 从原始配置解析加密数据
|
||||
* 解码并解析原始加密配置
|
||||
* @param data - Raw encrypt configuration / 原始加密配置
|
||||
* @param data."0" rawKeys - Encoded keys string / 编码的密钥字符串
|
||||
* @param data."1" rawRules - Encoded rules string / 编码的规则字符串
|
||||
* @param data."2" global - Global encryption flag / 全局加密标志
|
||||
* @param data."3" admin - Admin passwords string / 管理员密码字符串
|
||||
* @returns Parsed encrypt data / 解析后的加密数据
|
||||
*/
|
||||
function resolveEncryptData(
|
||||
[rawKeys, rawRules, global, admin]: EncryptConfig,
|
||||
): EncryptData {
|
||||
@ -49,6 +109,14 @@ function resolveEncryptData(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap and decode data from raw string
|
||||
*
|
||||
* 从原始字符串解包和解码数据
|
||||
*
|
||||
* @param raw - Raw encoded string / 原始编码字符串
|
||||
* @returns Parsed data / 解析后的数据
|
||||
*/
|
||||
function unwrapData<T>(raw: string): T {
|
||||
return JSON.parse(decodeData(raw)) as T
|
||||
}
|
||||
|
||||
@ -8,17 +8,40 @@ import { removeLeadingSlash } from 'vuepress/shared'
|
||||
import { useData } from './data.js'
|
||||
import { useEncryptData } from './encrypt-data.js'
|
||||
|
||||
/**
|
||||
* Encrypt interface
|
||||
* Provides encryption-related reactive states and properties
|
||||
*
|
||||
* 加密接口
|
||||
* 提供加密相关的响应式状态和属性
|
||||
*/
|
||||
export interface Encrypt {
|
||||
/** Whether the current page has encryption / 当前页面是否有加密 */
|
||||
hasPageEncrypt: Ref<boolean>
|
||||
/** Whether global encryption is decrypted / 全局加密是否已解密 */
|
||||
isGlobalDecrypted: Ref<boolean>
|
||||
/** Whether page encryption is decrypted / 页面加密是否已解密 */
|
||||
isPageDecrypted: Ref<boolean>
|
||||
/** List of encryption rules for the current page / 当前页面的加密规则列表 */
|
||||
hashList: Ref<EncryptDataRule[]>
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection key for encrypt functionality
|
||||
*
|
||||
* 加密功能的注入键
|
||||
*/
|
||||
export const EncryptSymbol: InjectionKey<Encrypt> = Symbol(
|
||||
__VUEPRESS_DEV__ ? 'Encrypt' : '',
|
||||
)
|
||||
|
||||
/**
|
||||
* Session storage for encryption state
|
||||
* Stores global and page decryption states
|
||||
*
|
||||
* 加密状态的会话存储
|
||||
* 存储全局和页面解密状态
|
||||
*/
|
||||
const storage = useSessionStorage('2a0a3d6afb2fdf1f', () => {
|
||||
if (__VUEPRESS_SSR__) {
|
||||
return { g: '', p: [] as string[] }
|
||||
@ -29,8 +52,27 @@ const storage = useSessionStorage('2a0a3d6afb2fdf1f', () => {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Cache for password comparison results
|
||||
* Improves performance by caching bcrypt verification results
|
||||
*
|
||||
* 密码比较结果的缓存
|
||||
* 通过缓存 bcrypt 验证结果提高性能
|
||||
*/
|
||||
const compareCache = new Map<string, boolean>()
|
||||
const separator = ':'
|
||||
|
||||
/**
|
||||
* Compare password with hash using bcrypt
|
||||
* Caches results to avoid repeated computations
|
||||
*
|
||||
* 使用 bcrypt 比较密码和哈希
|
||||
* 缓存结果以避免重复计算
|
||||
*
|
||||
* @param content - Password to verify / 要验证的密码
|
||||
* @param hash - Bcrypt hash to compare against / 要比较的 bcrypt 哈希
|
||||
* @returns Whether the password matches / 密码是否匹配
|
||||
*/
|
||||
async function compareDecrypt(content: string, hash: string): Promise<boolean> {
|
||||
const key = [content, hash].join(separator)
|
||||
if (compareCache.has(key))
|
||||
@ -47,7 +89,23 @@ async function compareDecrypt(content: string, hash: string): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache for regex patterns
|
||||
* Improves performance by caching compiled regexes
|
||||
*
|
||||
* 正则表达式模式的缓存
|
||||
* 通过缓存编译后的正则表达式提高性能
|
||||
*/
|
||||
const matchCache = new Map<string, RegExp>()
|
||||
|
||||
/**
|
||||
* Create or retrieve cached regex pattern
|
||||
*
|
||||
* 创建或获取缓存的正则表达式模式
|
||||
*
|
||||
* @param match - Pattern string / 模式字符串
|
||||
* @returns Compiled regex / 编译后的正则表达式
|
||||
*/
|
||||
function createMatchRegex(match: string) {
|
||||
if (matchCache.has(match))
|
||||
return matchCache.get(match)!
|
||||
@ -57,6 +115,18 @@ function createMatchRegex(match: string) {
|
||||
return regex
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a match pattern applies to the current page
|
||||
* Supports regex patterns (starting with ^) and path patterns
|
||||
*
|
||||
* 检查匹配模式是否适用于当前页面
|
||||
* 支持正则表达式模式(以 ^ 开头)和路径模式
|
||||
*
|
||||
* @param match - Match pattern / 匹配模式
|
||||
* @param pagePath - Current page path / 当前页面路径
|
||||
* @param filePathRelative - Relative file path / 相对文件路径
|
||||
* @returns Whether the pattern matches / 模式是否匹配
|
||||
*/
|
||||
function toMatch(match: string, pagePath: string, filePathRelative?: string | null) {
|
||||
const relativePath = filePathRelative || ''
|
||||
if (match[0] === '^') {
|
||||
@ -69,11 +139,22 @@ function toMatch(match: string, pagePath: string, filePathRelative?: string | nu
|
||||
return pagePath.startsWith(match) || relativePath.startsWith(removeLeadingSlash(match))
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup encrypt functionality for the application
|
||||
* Initializes encryption state and provides it to child components
|
||||
*
|
||||
* 为应用程序设置加密功能
|
||||
* 初始化加密状态并提供给子组件
|
||||
*/
|
||||
export function setupEncrypt(): void {
|
||||
const { page } = useData()
|
||||
const route = useRoute()
|
||||
const encrypt = useEncryptData()
|
||||
|
||||
/**
|
||||
* Whether the current page has encryption enabled
|
||||
* Checks page-specific encryption and rule-based encryption
|
||||
*/
|
||||
const hasPageEncrypt = computed(() => {
|
||||
const pagePath = route.path
|
||||
const filePathRelative = page.value.filePathRelative
|
||||
@ -85,6 +166,10 @@ export function setupEncrypt(): void {
|
||||
: false
|
||||
})
|
||||
|
||||
/**
|
||||
* Whether global encryption is decrypted
|
||||
* Checks if any admin password hash matches the stored hash
|
||||
*/
|
||||
const isGlobalDecrypted = computedAsync(async () => {
|
||||
const hash = storage.value.g
|
||||
if (!encrypt.value.global)
|
||||
@ -97,6 +182,10 @@ export function setupEncrypt(): void {
|
||||
return false
|
||||
}, !encrypt.value.global)
|
||||
|
||||
/**
|
||||
* List of encryption rules applicable to the current page
|
||||
* Includes page-specific rules and matching pattern rules
|
||||
*/
|
||||
const hashList = computed(() => {
|
||||
const pagePath = route.path
|
||||
const filePathRelative = page.value.filePathRelative
|
||||
@ -112,6 +201,10 @@ export function setupEncrypt(): void {
|
||||
return [pageRule, ...rules].filter(Boolean) as EncryptDataRule[]
|
||||
})
|
||||
|
||||
/**
|
||||
* Whether the current page is decrypted
|
||||
* Checks admin passwords and page-specific passwords
|
||||
*/
|
||||
const isPageDecrypted = computedAsync(async () => {
|
||||
if (!hasPageEncrypt.value)
|
||||
return true
|
||||
@ -142,6 +235,15 @@ export function setupEncrypt(): void {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Use encrypt
|
||||
* Returns the encryption state and properties
|
||||
*
|
||||
* 获取加密功能的状态和方法,包括全局解密、页面解密、密码验证等
|
||||
*
|
||||
* @returns Encrypt state and properties / 加密状态和属性
|
||||
* @throws Error if called without setup / 如果没有设置则抛出错误
|
||||
*/
|
||||
export function useEncrypt(): Encrypt {
|
||||
const result = inject(EncryptSymbol)
|
||||
|
||||
@ -151,6 +253,14 @@ export function useEncrypt(): Encrypt {
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Use encrypt compare
|
||||
* Provides password verification functions for global and page encryption
|
||||
*
|
||||
* 密码比较功能,提供全局密码和页面密码的验证方法
|
||||
*
|
||||
* @returns Object with compareGlobal and comparePage functions / 包含 compareGlobal 和 comparePage 函数的对象
|
||||
*/
|
||||
export function useEncryptCompare(): {
|
||||
compareGlobal: (password: string) => Promise<boolean>
|
||||
comparePage: (password: string) => Promise<boolean>
|
||||
@ -160,6 +270,13 @@ export function useEncryptCompare(): {
|
||||
const route = useRoute()
|
||||
const { hashList } = useEncrypt()
|
||||
|
||||
/**
|
||||
* Compare global password
|
||||
* Verifies against admin passwords
|
||||
*
|
||||
* @param password - Password to verify / 要验证的密码
|
||||
* @returns Whether the password is valid / 密码是否有效
|
||||
*/
|
||||
async function compareGlobal(password: string): Promise<boolean> {
|
||||
if (!password)
|
||||
return false
|
||||
@ -174,6 +291,13 @@ export function useEncryptCompare(): {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare page password
|
||||
* Verifies against page-specific rules and falls back to global password
|
||||
*
|
||||
* @param password - Password to verify / 要验证的密码
|
||||
* @returns Whether the password is valid / 密码是否有效
|
||||
*/
|
||||
async function comparePage(password: string): Promise<boolean> {
|
||||
if (!password)
|
||||
return false
|
||||
|
||||
@ -10,6 +10,11 @@ import { useCloseSidebarOnEscape, useSidebarControl } from './sidebar.js'
|
||||
const is960 = shallowRef(false)
|
||||
const is1280 = shallowRef(false)
|
||||
|
||||
/**
|
||||
* Use layout
|
||||
*
|
||||
* 获取当前页面布局相关信息,包括是否首页、是否有侧边栏、是否显示目录等
|
||||
*/
|
||||
export function useLayout() {
|
||||
const { frontmatter, theme } = useData()
|
||||
const { isPageDecrypted } = useEncrypt()
|
||||
|
||||
@ -4,19 +4,44 @@ import { computed, toValue } from 'vue'
|
||||
import { resolveRoute, resolveRouteFullPath, useRoute } from 'vuepress/client'
|
||||
import { useData } from './data.js'
|
||||
|
||||
/**
|
||||
* Link resolution result interface
|
||||
* Provides information about the resolved link
|
||||
*
|
||||
* 链接解析结果接口
|
||||
* 提供有关解析后链接的信息
|
||||
*/
|
||||
interface UseLinkResult {
|
||||
/**
|
||||
* 外部链接
|
||||
* Whether the link is external
|
||||
* 是否为外部链接
|
||||
*/
|
||||
isExternal: ComputedRef<boolean>
|
||||
/**
|
||||
* 外部链接协议
|
||||
* Whether the link uses an external protocol
|
||||
* Does not include target="_blank" cases
|
||||
* 是否使用外部链接协议
|
||||
* 此项不包含 target="_blank" 的情况
|
||||
*/
|
||||
isExternalProtocol: ComputedRef<boolean>
|
||||
/**
|
||||
* The resolved link URL
|
||||
* 解析后的链接 URL
|
||||
*/
|
||||
link: ComputedRef<string | undefined>
|
||||
}
|
||||
|
||||
/**
|
||||
* Use link
|
||||
* Resolves and processes a link URL with smart handling of internal/external links
|
||||
*
|
||||
* 使用链接
|
||||
* 解析并处理链接 URL,智能处理内部/外部链接
|
||||
*
|
||||
* @param href - Link URL or reference / 链接 URL 或引用
|
||||
* @param target - Link target or reference / 链接目标或引用
|
||||
* @returns Link resolution result / 链接解析结果
|
||||
*/
|
||||
export function useLink(
|
||||
href: MaybeRefOrGetter<string | undefined>,
|
||||
target?: MaybeRefOrGetter<string | undefined>,
|
||||
@ -24,6 +49,8 @@ export function useLink(
|
||||
const route = useRoute()
|
||||
const { page } = useData()
|
||||
|
||||
// Pre-determine if it can be considered an external link
|
||||
// At this point, it cannot be fully confirmed if it must be an internal link
|
||||
// 预判断是否可以直接认为是外部链接
|
||||
// 在此时并不能完全确认是否一定是内部链接
|
||||
const maybeIsExternal = computed(() => {
|
||||
@ -36,6 +63,7 @@ export function useLink(
|
||||
return false
|
||||
})
|
||||
|
||||
// Pre-process link, try to convert to internal link
|
||||
// 预处理链接,尝试转为内部的链接
|
||||
const preProcessLink = computed(() => {
|
||||
const link = toValue(href)
|
||||
@ -45,6 +73,8 @@ export function useLink(
|
||||
const currentPath = page.value.filePathRelative ? `/${page.value.filePathRelative}` : undefined
|
||||
const path = resolveRouteFullPath(link, currentPath)
|
||||
if (path.includes('#')) {
|
||||
// Compare path + anchor with current route path
|
||||
// Convert to anchor link to avoid page refresh
|
||||
// 将路径 + 锚点 与 当前路由路径进行比较
|
||||
// 转为锚点链接,避免页面发生刷新
|
||||
if (path.slice(0, path.indexOf('#')) === route.path) {
|
||||
@ -62,6 +92,7 @@ export function useLink(
|
||||
if (!link || link[0] === '#')
|
||||
return false
|
||||
|
||||
// Check if it's a non-existent route
|
||||
// 判断是否为不存在的路由
|
||||
const routePath = link.split(/[?#]/)[0]
|
||||
const currentPath = page.value.filePathRelative ? `/${page.value.filePathRelative}` : undefined
|
||||
@ -74,6 +105,7 @@ export function useLink(
|
||||
})
|
||||
|
||||
const link = computed(() => {
|
||||
// Keep external links as-is
|
||||
// 外部链接保持原样
|
||||
if (isExternal.value) {
|
||||
return toValue(href)
|
||||
|
||||
@ -10,12 +10,32 @@ import { useRoute } from 'vuepress/client'
|
||||
import { normalizeLink, resolveNavLink } from '../utils/index.js'
|
||||
import { useData } from './data.js'
|
||||
|
||||
/**
|
||||
* Use navbar data
|
||||
* Returns the resolved navbar items based on theme configuration
|
||||
*
|
||||
* 获取导航栏数据
|
||||
* 根据主题配置返回解析后的导航栏项目
|
||||
*
|
||||
* @returns Reactive reference to resolved navbar items / 解析后的导航栏项目的响应式引用
|
||||
*/
|
||||
export function useNavbarData(): Ref<ResolvedNavItem[]> {
|
||||
const { theme } = useData()
|
||||
|
||||
return computed(() => resolveNavbar(theme.value.navbar || []))
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve navbar configuration to resolved items
|
||||
* Recursively processes navbar items and resolves links
|
||||
*
|
||||
* 将导航栏配置解析为解析后的项目
|
||||
* 递归处理导航栏项目并解析链接
|
||||
*
|
||||
* @param navbar - Raw navbar configuration / 原始导航栏配置
|
||||
* @param _prefix - URL prefix for nested items / 嵌套项目的 URL 前缀
|
||||
* @returns Array of resolved navbar items / 解析后的导航栏项目数组
|
||||
*/
|
||||
function resolveNavbar(navbar: ThemeNavItem[], _prefix = ''): ResolvedNavItem[] {
|
||||
const resolved: ResolvedNavItem[] = []
|
||||
navbar.forEach((item) => {
|
||||
@ -40,26 +60,56 @@ function resolveNavbar(navbar: ThemeNavItem[], _prefix = ''): ResolvedNavItem[]
|
||||
return resolved
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigation control return type
|
||||
* Provides state and methods for mobile navigation menu
|
||||
*
|
||||
* 导航控制返回类型
|
||||
* 提供移动端导航菜单的状态和方法
|
||||
*/
|
||||
export interface UseNavReturn {
|
||||
/** Whether the mobile screen menu is open / 移动端屏幕菜单是否打开 */
|
||||
isScreenOpen: Ref<boolean>
|
||||
/** Open the mobile screen menu / 打开移动端屏幕菜单 */
|
||||
openScreen: () => void
|
||||
/** Close the mobile screen menu / 关闭移动端屏幕菜单 */
|
||||
closeScreen: () => void
|
||||
/** Toggle the mobile screen menu / 切换移动端屏幕菜单 */
|
||||
toggleScreen: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Use nav
|
||||
* Provides mobile navigation menu control functionality
|
||||
*
|
||||
* 导航栏状态控制,提供移动端导航菜单的打开、关闭和切换功能
|
||||
*
|
||||
* @returns Navigation control state and methods / 导航控制状态和方法
|
||||
*/
|
||||
export function useNav(): UseNavReturn {
|
||||
const isScreenOpen = ref(false)
|
||||
|
||||
/**
|
||||
* Open the mobile navigation screen
|
||||
* Adds resize listener to auto-close on larger screens
|
||||
*/
|
||||
function openScreen(): void {
|
||||
isScreenOpen.value = true
|
||||
window.addEventListener('resize', closeScreenOnTabletWindow)
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the mobile navigation screen
|
||||
* Removes resize listener
|
||||
*/
|
||||
function closeScreen(): void {
|
||||
isScreenOpen.value = false
|
||||
window.removeEventListener('resize', closeScreenOnTabletWindow)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the mobile navigation screen
|
||||
*/
|
||||
function toggleScreen(): void {
|
||||
if (isScreenOpen.value) {
|
||||
closeScreen()
|
||||
@ -71,6 +121,7 @@ export function useNav(): UseNavReturn {
|
||||
|
||||
/**
|
||||
* Close screen when the user resizes the window wider than tablet size.
|
||||
* Automatically closes the mobile menu on larger screens.
|
||||
*/
|
||||
function closeScreenOnTabletWindow(): void {
|
||||
if (window.outerWidth >= 768) {
|
||||
|
||||
@ -7,46 +7,81 @@ import { onContentUpdated, useRouter } from 'vuepress/client'
|
||||
import { useData } from './data.js'
|
||||
import { useLayout } from './layout.js'
|
||||
|
||||
/**
|
||||
* Header interface representing a page heading
|
||||
*
|
||||
* Header 接口表示页面标题
|
||||
*/
|
||||
export interface Header {
|
||||
/**
|
||||
* The level of the header
|
||||
*
|
||||
* `1` to `6` for `<h1>` to `<h6>`
|
||||
*
|
||||
* 标题级别
|
||||
* `1` 到 `6` 对应 `<h1>` 到 `<h6>`
|
||||
*/
|
||||
level: number
|
||||
/**
|
||||
* The title of the header
|
||||
*
|
||||
* 标题文本
|
||||
*/
|
||||
title: string
|
||||
/**
|
||||
* The slug of the header
|
||||
*
|
||||
* Typically the `id` attr of the header anchor
|
||||
*
|
||||
* 标题的 slug
|
||||
* 通常是标题锚点的 `id` 属性
|
||||
*/
|
||||
slug: string
|
||||
/**
|
||||
* Link of the header
|
||||
*
|
||||
* Typically using `#${slug}` as the anchor hash
|
||||
*
|
||||
* 标题链接
|
||||
* 通常使用 `#${slug}` 作为锚点哈希
|
||||
*/
|
||||
link: string
|
||||
/**
|
||||
* The children of the header
|
||||
*
|
||||
* 子标题
|
||||
*/
|
||||
children: Header[]
|
||||
}
|
||||
|
||||
// cached list of anchor elements from resolveHeaders
|
||||
// 从 resolveHeaders 缓存的锚点元素列表
|
||||
const resolvedHeaders: { element: HTMLHeadElement, link: string }[] = []
|
||||
|
||||
/**
|
||||
* Menu item type for outline navigation
|
||||
* Extends Header with element reference and additional properties
|
||||
*
|
||||
* 目录导航的菜单项类型
|
||||
* 扩展 Header 并添加元素引用和额外属性
|
||||
*/
|
||||
export type MenuItem = Omit<Header, 'slug' | 'children'> & {
|
||||
/** Reference to the DOM element / DOM 元素引用 */
|
||||
element: HTMLHeadElement
|
||||
/** Child menu items / 子菜单项 */
|
||||
children?: MenuItem[]
|
||||
/** Lowest level for outline display / 目录显示的最低级别 */
|
||||
lowLevel?: number
|
||||
}
|
||||
|
||||
const headers = ref<MenuItem[]>([])
|
||||
|
||||
/**
|
||||
* Setup headers for the current page
|
||||
* Initializes header extraction based on frontmatter and theme configuration
|
||||
*
|
||||
* 为当前页面设置标题
|
||||
* 根据 frontmatter 和主题配置初始化标题提取
|
||||
*
|
||||
* @returns Reference to the headers array / 标题数组的引用
|
||||
*/
|
||||
export function setupHeaders(): Ref<MenuItem[]> {
|
||||
const { frontmatter, theme } = useData()
|
||||
|
||||
@ -57,10 +92,28 @@ export function setupHeaders(): Ref<MenuItem[]> {
|
||||
return headers
|
||||
}
|
||||
|
||||
/**
|
||||
* Use headers
|
||||
* Returns the reactive headers reference for the current page
|
||||
*
|
||||
* 获取当前页面的标题列表
|
||||
*
|
||||
* @returns Reactive reference to menu items / 菜单项的响应式引用
|
||||
*/
|
||||
export function useHeaders(): Ref<MenuItem[]> {
|
||||
return headers
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers from the page content
|
||||
* Extracts and filters headings based on the outline configuration
|
||||
*
|
||||
* 从页面内容获取标题
|
||||
* 根据目录配置提取和过滤标题
|
||||
*
|
||||
* @param range - Outline configuration for header levels / 标题级别的目录配置
|
||||
* @returns Array of menu items representing headers / 表示标题的菜单项数组
|
||||
*/
|
||||
export function getHeaders(range?: ThemeOutline): MenuItem[] {
|
||||
const heading = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
|
||||
const ignores = Array.from(document.querySelectorAll(
|
||||
@ -87,6 +140,14 @@ export function getHeaders(range?: ThemeOutline): MenuItem[] {
|
||||
return resolveSubRangeHeader(resolveHeaders(headers, high), low)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the header level range from outline configuration
|
||||
*
|
||||
* 从目录配置获取标题级别范围
|
||||
*
|
||||
* @param range - Outline configuration / 目录配置
|
||||
* @returns Tuple of [high, low] levels / [高, 低] 级别元组
|
||||
*/
|
||||
function getRange(range?: Exclude<ThemeOutline, boolean>): readonly [number, number] {
|
||||
const levelsRange = range || 2
|
||||
// [high, low]
|
||||
@ -97,6 +158,17 @@ function getRange(range?: Exclude<ThemeOutline, boolean>): readonly [number, num
|
||||
: levelsRange
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lowest outline level for a specific header
|
||||
* Checks for data-outline or outline attributes
|
||||
*
|
||||
* 获取特定标题的最低目录级别
|
||||
* 检查 data-outline 或 outline 属性
|
||||
*
|
||||
* @param el - Header element / 标题元素
|
||||
* @param level - Current header level / 当前标题级别
|
||||
* @returns Lowest level or undefined / 最低级别或未定义
|
||||
*/
|
||||
function getLowLevel(el: HTMLHeadElement, level: number): number | undefined {
|
||||
if (!el.hasAttribute('data-outline') && !el.hasAttribute('outline'))
|
||||
return
|
||||
@ -114,6 +186,16 @@ function getLowLevel(el: HTMLHeadElement, level: number): number | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a header element to text
|
||||
* Extracts text content while ignoring badges and ignored elements
|
||||
*
|
||||
* 将标题元素序列化为文本
|
||||
* 提取文本内容,同时忽略徽章和被忽略的元素
|
||||
*
|
||||
* @param h - Header element / 标题元素
|
||||
* @returns Serialized header text / 序列化的标题文本
|
||||
*/
|
||||
function serializeHeader(h: Element): string {
|
||||
// <hx><a href="#"><span>title</span></a></hx>
|
||||
const anchor = h.firstChild
|
||||
@ -146,6 +228,15 @@ function serializeHeader(h: Element): string {
|
||||
return ret.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear ignored nodes from a list of child nodes
|
||||
* Recursively removes elements with 'ignore-header' class
|
||||
*
|
||||
* 从子节点列表中清除被忽略的节点
|
||||
* 递归移除带有 'ignore-header' 类的元素
|
||||
*
|
||||
* @param list - Array of child nodes / 子节点数组
|
||||
*/
|
||||
function clearHeaderNodeList(list?: ChildNode[]) {
|
||||
if (list?.length) {
|
||||
for (const node of list) {
|
||||
@ -161,6 +252,17 @@ function clearHeaderNodeList(list?: ChildNode[]) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve headers into a hierarchical structure
|
||||
* Organizes flat headers into nested menu items based on levels
|
||||
*
|
||||
* 将标题解析为层次结构
|
||||
* 根据级别将平面标题组织为嵌套菜单项
|
||||
*
|
||||
* @param headers - Flat array of menu items / 平面菜单项数组
|
||||
* @param high - Minimum header level to include / 要包含的最小标题级别
|
||||
* @returns Hierarchical array of menu items / 层次化的菜单项数组
|
||||
*/
|
||||
export function resolveHeaders(headers: MenuItem[], high: number): MenuItem[] {
|
||||
headers = headers.filter(h => h.level >= high)
|
||||
// clear previous caches
|
||||
@ -175,6 +277,7 @@ export function resolveHeaders(headers: MenuItem[], high: number): MenuItem[] {
|
||||
const cur = headers[i]
|
||||
if (i === 0) {
|
||||
ret.push(cur)
|
||||
continue
|
||||
}
|
||||
else {
|
||||
for (let j = i - 1; j >= 0; j--) {
|
||||
@ -192,6 +295,17 @@ export function resolveHeaders(headers: MenuItem[], high: number): MenuItem[] {
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve sub range header
|
||||
* Filters children headers based on the lowest level
|
||||
*
|
||||
* 解析子范围标题
|
||||
* 根据最低级别过滤子标题
|
||||
*
|
||||
* @param headers - Array of menu items / 菜单项数组
|
||||
* @param low - Lowest level to include / 要包含的最低级别
|
||||
* @returns Filtered menu items / 过滤后的菜单项
|
||||
*/
|
||||
function resolveSubRangeHeader(headers: MenuItem[], low: number): MenuItem[] {
|
||||
return headers.map((header) => {
|
||||
if (header.children?.length) {
|
||||
@ -203,6 +317,15 @@ function resolveSubRangeHeader(headers: MenuItem[], low: number): MenuItem[] {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Use active anchor
|
||||
* Tracks scroll position and updates the active outline item
|
||||
*
|
||||
* 活跃锚点处理,监听滚动并更新目录中高亮的锚点
|
||||
*
|
||||
* @param container - Reference to the outline container / 目录容器的引用
|
||||
* @param marker - Reference to the active marker element / 活动标记元素的引用
|
||||
*/
|
||||
export function useActiveAnchor(container: Ref<HTMLElement | null>, marker: Ref<HTMLElement | null>): void {
|
||||
const { isAsideEnabled } = useLayout()
|
||||
const router = useRouter()
|
||||
@ -210,6 +333,10 @@ export function useActiveAnchor(container: Ref<HTMLElement | null>, marker: Ref<
|
||||
|
||||
let prevActiveLink: HTMLAnchorElement | null = null
|
||||
|
||||
/**
|
||||
* Set the active link based on scroll position
|
||||
* Determines which header is currently in view
|
||||
*/
|
||||
const setActiveLink = (): void => {
|
||||
if (!isAsideEnabled.value)
|
||||
return
|
||||
@ -257,6 +384,12 @@ export function useActiveAnchor(container: Ref<HTMLElement | null>, marker: Ref<
|
||||
activateLink(activeLink)
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a specific link in the outline
|
||||
* Updates visual indicators and marker position
|
||||
*
|
||||
* @param hash - Hash of the link to activate / 要激活的链接哈希
|
||||
*/
|
||||
function activateLink(hash: string | null): void {
|
||||
routeHash.value = hash || ''
|
||||
if (prevActiveLink)
|
||||
@ -311,6 +444,16 @@ export function useActiveAnchor(container: Ref<HTMLElement | null>, marker: Ref<
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the absolute top position of an element
|
||||
* Accounts for fixed positioned ancestors
|
||||
*
|
||||
* 获取元素的绝对顶部位置
|
||||
* 考虑固定定位的祖先元素
|
||||
*
|
||||
* @param element - Element to measure / 要测量的元素
|
||||
* @returns Absolute top position or NaN / 绝对顶部位置或 NaN
|
||||
*/
|
||||
function getAbsoluteTop(element: HTMLElement): number {
|
||||
let offsetTop = 0
|
||||
while (element && element !== document.body) {
|
||||
@ -324,7 +467,14 @@ function getAbsoluteTop(element: HTMLElement): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current hash and do not trigger `scrollBehavior`
|
||||
* Update current hash without triggering scrollBehavior
|
||||
* Temporarily disables scroll behavior during hash update
|
||||
*
|
||||
* 更新当前哈希而不触发 scrollBehavior
|
||||
* 在哈希更新期间临时禁用滚动行为
|
||||
*
|
||||
* @param router - Vue Router instance / Vue Router 实例
|
||||
* @param hash - New hash value / 新的哈希值
|
||||
*/
|
||||
async function updateHash(router: Router, hash: string): Promise<void> {
|
||||
const { path, query } = router.currentRoute.value
|
||||
|
||||
@ -2,6 +2,11 @@ import type { ComputedRef } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import { useData } from './data.js'
|
||||
|
||||
/**
|
||||
* Use posts page data
|
||||
*
|
||||
* 获取文章页面数据,判断当前页面是否为文章类型或文章列表布局
|
||||
*/
|
||||
export function usePostsPageData(): {
|
||||
isPosts: ComputedRef<boolean>
|
||||
isPostsLayout: ComputedRef<boolean>
|
||||
|
||||
@ -9,12 +9,27 @@ import { useCollection } from './collections.js'
|
||||
|
||||
export type PostsDataRef = Ref<Record<string, ThemePosts>>
|
||||
|
||||
/**
|
||||
* Posts data ref
|
||||
*
|
||||
* 文章数据引用,包含所有语言环境的文章列表
|
||||
*/
|
||||
export const postsData: PostsDataRef = ref(postsDataRaw)
|
||||
|
||||
/**
|
||||
* Use posts data
|
||||
*
|
||||
* 获取文章数据
|
||||
*/
|
||||
export function usePostsData(): PostsDataRef {
|
||||
return postsData as PostsDataRef
|
||||
}
|
||||
|
||||
/**
|
||||
* Use locale post list
|
||||
*
|
||||
* 获取当前语言环境的文章列表
|
||||
*/
|
||||
export function useLocalePostList(): ComputedRef<ThemePosts> {
|
||||
const collection = useCollection()
|
||||
const routeLocale = useRouteLocale()
|
||||
|
||||
@ -8,6 +8,11 @@ import { useRouteQuery } from './route-query.js'
|
||||
|
||||
const DEFAULT_PER_PAGE = 15
|
||||
|
||||
/**
|
||||
* Use post list control result
|
||||
*
|
||||
* 文章列表控制结果类型,包含分页相关的数据和方法
|
||||
*/
|
||||
interface UsePostListControlResult {
|
||||
postList: ComputedRef<ThemePostsItem[]>
|
||||
page: Ref<number>
|
||||
@ -22,6 +27,11 @@ interface UsePostListControlResult {
|
||||
changePage: (page: number) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Use post list control
|
||||
*
|
||||
* 文章列表控制,管理分页逻辑和文章列表数据
|
||||
*/
|
||||
export function usePostListControl(homePage: Ref<boolean>): UsePostListControlResult {
|
||||
const { collection } = useData<'page', 'post'>()
|
||||
|
||||
|
||||
@ -9,12 +9,22 @@ import { useTagColors } from './tag-colors.js'
|
||||
|
||||
type ShortPostItem = Pick<ThemePostsItem, 'title' | 'path' | 'createTime'>
|
||||
|
||||
/**
|
||||
* Posts tag item
|
||||
*
|
||||
* 标签项,包含名称、数量和样式类名
|
||||
*/
|
||||
interface PostsTagItem {
|
||||
name: string
|
||||
count: string | number
|
||||
className: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Use tags result
|
||||
*
|
||||
* 标签结果类型
|
||||
*/
|
||||
interface UseTagsResult {
|
||||
tags: ComputedRef<PostsTagItem[]>
|
||||
currentTag: Ref<string>
|
||||
@ -22,6 +32,11 @@ interface UseTagsResult {
|
||||
handleTagClick: (tag: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Use tags
|
||||
*
|
||||
* 获取标签列表和当前选中的标签,处理标签相关逻辑
|
||||
*/
|
||||
export function useTags(): UseTagsResult {
|
||||
const { collection } = useData<'page', 'post'>()
|
||||
const list = useLocalePostList()
|
||||
|
||||
@ -9,6 +9,11 @@ import { usePostsPageData } from './page.js'
|
||||
import { useLocalePostList } from './posts-data.js'
|
||||
import { useSidebar } from './sidebar.js'
|
||||
|
||||
/**
|
||||
* Use prev next result
|
||||
*
|
||||
* 上一页/下一页结果类型
|
||||
*/
|
||||
interface UsePrevNextResult {
|
||||
prev: ComputedRef<NavItemWithLink | null>
|
||||
next: ComputedRef<NavItemWithLink | null>
|
||||
@ -16,6 +21,11 @@ interface UsePrevNextResult {
|
||||
|
||||
const SEPARATOR_RE = /^-{3,}$/
|
||||
|
||||
/**
|
||||
* Use prev next
|
||||
*
|
||||
* 获取上一页和下一页链接
|
||||
*/
|
||||
export function usePrevNext(): UsePrevNextResult {
|
||||
const route = useRoute()
|
||||
const { frontmatter, theme } = useData()
|
||||
|
||||
@ -66,6 +66,11 @@ export function setupSidebar(): void {
|
||||
}, { immediate: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Use sidebar data
|
||||
*
|
||||
* 获取侧边栏数据
|
||||
*/
|
||||
export function useSidebarData(): Ref<ResolvedSidebarItem[]> {
|
||||
return sidebar
|
||||
}
|
||||
|
||||
@ -10,6 +10,8 @@ import { getSidebarGroups, sidebarData, useSidebarData } from './sidebar-data.js
|
||||
|
||||
/**
|
||||
* Check if the given sidebar item contains any active link.
|
||||
*
|
||||
* 检查给定的侧边栏项是否包含活动链接
|
||||
*/
|
||||
export function hasActiveLink(path: string, items: ResolvedSidebarItem | ResolvedSidebarItem[]): boolean {
|
||||
if (Array.isArray(items)) {
|
||||
@ -31,6 +33,11 @@ const containsActiveLink = hasActiveLink
|
||||
const isSidebarEnabled = ref(false)
|
||||
const isSidebarCollapsed = ref(false)
|
||||
|
||||
/**
|
||||
* Use sidebar control
|
||||
*
|
||||
* 侧边栏控制,提供启用/禁用侧边栏和折叠/展开的控制函数
|
||||
*/
|
||||
export function useSidebarControl() {
|
||||
const enableSidebar = (): void => {
|
||||
isSidebarEnabled.value = true
|
||||
@ -63,6 +70,11 @@ export function useSidebarControl() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use sidebar
|
||||
*
|
||||
* 侧边栏数据,获取当前路由的侧边栏项目和分组
|
||||
*/
|
||||
export function useSidebar(): {
|
||||
sidebar: Ref<ResolvedSidebarItem[]>
|
||||
sidebarKey: ComputedRef<string>
|
||||
@ -93,8 +105,9 @@ export function useSidebar(): {
|
||||
}
|
||||
|
||||
/**
|
||||
* a11y: cache the element that opened the Sidebar (the menu button) then
|
||||
* focus that button again when Menu is closed with Escape key.
|
||||
* Use close sidebar on escape
|
||||
*
|
||||
* a11y: 缓存打开侧边栏的元素(菜单按钮),当使用 Escape 关闭菜单时重新聚焦该按钮
|
||||
*/
|
||||
export function useCloseSidebarOnEscape(): void {
|
||||
const { disableSidebar } = useSidebarControl()
|
||||
@ -122,6 +135,11 @@ export function useCloseSidebarOnEscape(): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use sidebar item control
|
||||
*
|
||||
* 侧边栏项目控制,管理单个侧边栏项目的折叠状态、激活状态等
|
||||
*/
|
||||
export function useSidebarItemControl(item: ComputedRef<ResolvedSidebarItem>): SidebarItemControl {
|
||||
const { page } = useData()
|
||||
const route = useRoute()
|
||||
|
||||
@ -7,16 +7,47 @@ import { clientDataSymbol } from 'vuepress/client'
|
||||
|
||||
declare const __VUE_HMR_RUNTIME__: Record<string, any>
|
||||
|
||||
/**
|
||||
* Theme data reference type
|
||||
*
|
||||
* 主题数据引用类型
|
||||
*/
|
||||
export type ThemeDataRef<T extends ThemeData = ThemeData> = Ref<T>
|
||||
|
||||
/**
|
||||
* Theme locale data reference type
|
||||
*
|
||||
* 主题本地化数据引用类型
|
||||
*/
|
||||
export type ThemeLocaleDataRef<T extends ThemeData = ThemeData> = ComputedRef<T>
|
||||
|
||||
/**
|
||||
* Injection key for theme locale data
|
||||
*
|
||||
* 主题本地化数据的注入键
|
||||
*/
|
||||
export const themeLocaleDataSymbol: InjectionKey<ThemeLocaleDataRef> = Symbol(
|
||||
__VUEPRESS_DEV__ ? 'themeLocaleData' : '',
|
||||
)
|
||||
|
||||
/**
|
||||
* Theme data ref
|
||||
* Global reference to the theme configuration data
|
||||
*
|
||||
* 主题数据引用
|
||||
* 主题配置数据的全局引用
|
||||
*/
|
||||
export const themeData: ThemeDataRef = ref(themeDataRaw)
|
||||
|
||||
/**
|
||||
* Use theme data
|
||||
* Returns the global theme data reference
|
||||
*
|
||||
* 获取主题数据
|
||||
* 返回全局主题数据引用
|
||||
*
|
||||
* @returns Theme data reference / 主题数据引用
|
||||
*/
|
||||
export function useThemeData<
|
||||
T extends ThemeData = ThemeData,
|
||||
>(): ThemeDataRef<T> {
|
||||
@ -29,6 +60,15 @@ if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use theme locale data
|
||||
* Returns the theme data for the current route's locale
|
||||
*
|
||||
* 获取当前路由对应的语言环境的主题数据
|
||||
*
|
||||
* @returns Theme locale data computed reference / 主题本地化数据计算引用
|
||||
* @throws Error if called without provider / 如果没有提供者则抛出错误
|
||||
*/
|
||||
export function useThemeLocaleData<
|
||||
T extends ThemeData = ThemeData,
|
||||
>(): ThemeLocaleDataRef<T> {
|
||||
@ -40,8 +80,15 @@ export function useThemeLocaleData<
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the locales fields to the root fields
|
||||
* according to the route path
|
||||
* Merge the locales fields to the root fields according to the route path
|
||||
* Combines base theme options with locale-specific options
|
||||
*
|
||||
* 根据路由路径将本地化字段合并到根字段
|
||||
* 将基础主题选项与特定语言环境的选项合并
|
||||
*
|
||||
* @param theme - Base theme data / 基础主题数据
|
||||
* @param routeLocale - Current route locale / 当前路由的语言环境
|
||||
* @returns Merged theme data for the locale / 合并后的语言环境主题数据
|
||||
*/
|
||||
function resolveThemeLocaleData(theme: ThemeData, routeLocale: RouteLocale): ThemeData {
|
||||
const { locales, ...baseOptions } = theme
|
||||
@ -52,6 +99,15 @@ function resolveThemeLocaleData(theme: ThemeData, routeLocale: RouteLocale): The
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup theme data for the Vue app
|
||||
* Provides theme data and theme locale data to the application
|
||||
*
|
||||
* 为 Vue 应用设置主题数据
|
||||
* 向应用程序提供主题数据和主题本地化数据
|
||||
*
|
||||
* @param app - Vue application instance / Vue 应用实例
|
||||
*/
|
||||
export function setupThemeData(app: App): void {
|
||||
// provide theme data & theme locale data
|
||||
const themeData = useThemeData()
|
||||
|
||||
@ -1,14 +1,33 @@
|
||||
/**
|
||||
* @method 缓动算法
|
||||
* t: current time(当前时间);
|
||||
* b: beginning value(初始值);
|
||||
* c: change in value(变化量);
|
||||
* d: duration(持续时间)。
|
||||
* Tweening algorithm for smooth animation
|
||||
* Implements cubic easing function for natural motion
|
||||
*
|
||||
* 缓动算法,用于平滑动画
|
||||
* 实现立方缓动函数以获得自然的运动效果
|
||||
*
|
||||
* @param t - Current time (progress from 0 to d) / 当前时间(从 0 到 d 的进度)
|
||||
* @param b - Beginning value (initial value) / 初始值
|
||||
* @param c - Change in value (target - initial) / 变化量(目标值 - 初始值)
|
||||
* @param d - Duration (total time) / 持续时间(总时间)
|
||||
* @returns The current value at time t / 时间 t 时的当前值
|
||||
*/
|
||||
export function tween(t: number, b: number, c: number, d: number): number {
|
||||
return c * (t /= d) * t * t + b
|
||||
}
|
||||
|
||||
/**
|
||||
* Linear interpolation for animation
|
||||
* Provides constant speed motion without easing
|
||||
*
|
||||
* 线性插值动画
|
||||
* 提供匀速运动,无缓动效果
|
||||
*
|
||||
* @param t - Current time (progress from 0 to d) / 当前时间(从 0 到 d 的进度)
|
||||
* @param b - Beginning value (initial value) / 初始值
|
||||
* @param c - Change in value (target - initial) / 变化量(目标值 - 初始值)
|
||||
* @param d - Duration (total time) / 持续时间(总时间)
|
||||
* @returns The current value at time t / 时间 t 时的当前值
|
||||
*/
|
||||
export function linear(t: number, b: number, c: number, d: number): number {
|
||||
return (c * t) / d + b
|
||||
}
|
||||
|
||||
@ -1,5 +1,14 @@
|
||||
import { tween } from './animate.js'
|
||||
|
||||
/**
|
||||
* Get the computed CSS value of an element as a number
|
||||
*
|
||||
* 获取元素的计算 CSS 值并转换为数字
|
||||
*
|
||||
* @param el - Target element / 目标元素
|
||||
* @param property - CSS property name / CSS 属性名
|
||||
* @returns The numeric value of the CSS property, 0 if not found or invalid / CSS 属性的数值,如果未找到或无效则返回 0
|
||||
*/
|
||||
export function getCssValue(el: HTMLElement | null, property: string): number {
|
||||
const val = el?.ownerDocument?.defaultView?.getComputedStyle(el, null)?.[
|
||||
property as any
|
||||
@ -8,6 +17,14 @@ export function getCssValue(el: HTMLElement | null, property: string): number {
|
||||
return Number.isNaN(num) ? 0 : num
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scrollTop value of a target element or document
|
||||
*
|
||||
* 获取目标元素或文档的 scrollTop 值
|
||||
*
|
||||
* @param target - Target element or document, defaults to document / 目标元素或文档,默认为 document
|
||||
* @returns Current scrollTop value / 当前 scrollTop 值
|
||||
*/
|
||||
export function getScrollTop(
|
||||
target: Document | HTMLElement = document,
|
||||
): number {
|
||||
@ -24,6 +41,14 @@ export function getScrollTop(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scrollTop value of a target element or document
|
||||
*
|
||||
* 设置目标元素或文档的 scrollTop 值
|
||||
*
|
||||
* @param target - Target element or document, defaults to document / 目标元素或文档,默认为 document
|
||||
* @param scrollTop - ScrollTop value to set / 要设置的 scrollTop 值
|
||||
*/
|
||||
export function setScrollTop(
|
||||
target: Document | HTMLElement = document,
|
||||
scrollTop = 0,
|
||||
@ -45,6 +70,15 @@ export function setScrollTop(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Smoothly scroll to a specific position
|
||||
*
|
||||
* 平滑滚动到指定位置
|
||||
*
|
||||
* @param target - Target element or document / 目标元素或文档
|
||||
* @param top - Target scrollTop position / 目标 scrollTop 位置
|
||||
* @param time - Animation duration in milliseconds, defaults to 300ms / 动画持续时间(毫秒),默认为 300ms
|
||||
*/
|
||||
export function scrollTo(
|
||||
target: Document | HTMLElement,
|
||||
top: number,
|
||||
@ -68,6 +102,14 @@ export function scrollTo(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the offset top of an element relative to the document
|
||||
*
|
||||
* 获取元素相对于文档的 offsetTop 值
|
||||
*
|
||||
* @param target - Target element / 目标元素
|
||||
* @returns The total offsetTop value / 总的 offsetTop 值
|
||||
*/
|
||||
export function getOffsetTop<T extends HTMLElement = HTMLElement>(target: T | null): number {
|
||||
if (!target)
|
||||
return 0
|
||||
|
||||
@ -6,6 +6,13 @@ import {
|
||||
} from 'vuepress/shared'
|
||||
import { resolveRepoType } from './resolveRepoType.js'
|
||||
|
||||
/**
|
||||
* Edit link patterns for different repository platforms
|
||||
* Maps repository types to their respective edit URL patterns
|
||||
*
|
||||
* 不同仓库平台的编辑链接模式
|
||||
* 将仓库类型映射到各自的编辑 URL 模式
|
||||
*/
|
||||
export const editLinkPatterns: Record<Exclude<RepoType, null>, string> = {
|
||||
GitHub: ':repo/edit/:branch/:path',
|
||||
GitLab: ':repo/-/edit/:branch/:path',
|
||||
@ -14,6 +21,16 @@ export const editLinkPatterns: Record<Exclude<RepoType, null>, string> = {
|
||||
':repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default',
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the edit link pattern based on repository configuration
|
||||
*
|
||||
* 根据仓库配置解析编辑链接模式
|
||||
*
|
||||
* @param params - Parameters object / 参数对象
|
||||
* @param params.docsRepo - Repository URL / 仓库 URL
|
||||
* @param params.editLinkPattern - Custom edit link pattern / 自定义编辑链接模式
|
||||
* @returns The resolved edit link pattern or null / 解析后的编辑链接模式,如果没有则返回 null
|
||||
*/
|
||||
function resolveEditLinkPatterns({
|
||||
docsRepo,
|
||||
editLinkPattern,
|
||||
@ -31,6 +48,21 @@ function resolveEditLinkPatterns({
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the complete edit link URL for a file
|
||||
* Generates an edit link based on repository configuration and file path
|
||||
*
|
||||
* 解析文件的完整编辑链接 URL
|
||||
* 根据仓库配置和文件路径生成编辑链接
|
||||
*
|
||||
* @param params - Parameters object / 参数对象
|
||||
* @param params.docsRepo - Repository URL / 仓库 URL
|
||||
* @param params.docsBranch - Branch name / 分支名称
|
||||
* @param params.docsDir - Documentation directory / 文档目录
|
||||
* @param params.filePathRelative - Relative file path / 相对文件路径
|
||||
* @param params.editLinkPattern - Custom edit link pattern / 自定义编辑链接模式
|
||||
* @returns The complete edit link URL or null / 完整的编辑链接 URL,如果无法生成则返回 null
|
||||
*/
|
||||
export function resolveEditLink({
|
||||
docsRepo,
|
||||
docsBranch,
|
||||
|
||||
@ -9,7 +9,13 @@ import { resolveRoute } from 'vuepress/client'
|
||||
|
||||
/**
|
||||
* Resolve NavLink props from string
|
||||
* Converts a link string to a resolved navigation item with metadata
|
||||
*
|
||||
* 从字符串解析 NavLink 属性
|
||||
* 将链接字符串转换为带有元数据的解析导航项
|
||||
*
|
||||
* @param link - The link string to resolve / 要解析的链接字符串
|
||||
* @returns Resolved navigation item with link, text, icon and badge / 解析后的导航项,包含链接、文本、图标和徽章
|
||||
* @example
|
||||
* - Input: '/README.md'
|
||||
* - Output: { text: 'Home', link: '/' }
|
||||
@ -31,17 +37,47 @@ export function resolveNavLink(link: string): ResolvedNavItemWithLink {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a path to extract a readable title
|
||||
* Removes index.html, .html extension and trailing slash
|
||||
*
|
||||
* 规范化路径以提取可读的标题
|
||||
* 移除 index.html、.html 扩展名和尾部斜杠
|
||||
*
|
||||
* @param path - The path to normalize / 要规范化的路径
|
||||
* @returns The extracted title from path / 从路径提取的标题
|
||||
*/
|
||||
function normalizeTitleWithPath(path: string): string {
|
||||
path = path.replace(/index\.html?$/i, '').replace(/\.html?$/i, '').replace(/\/$/, '')
|
||||
return decodeURIComponent(path.slice(path.lastIndexOf('/') + 1))
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a link by combining base and link
|
||||
* Handles absolute links and protocol links correctly
|
||||
*
|
||||
* 通过组合 base 和 link 来规范化链接
|
||||
* 正确处理绝对链接和协议链接
|
||||
*
|
||||
* @param base - Base URL / 基础 URL
|
||||
* @param link - Link to normalize / 要规范化的链接
|
||||
* @returns Normalized link / 规范化后的链接
|
||||
*/
|
||||
export function normalizeLink(base = '', link = ''): string {
|
||||
return isLinkAbsolute(link) || isLinkWithProtocol(link)
|
||||
? link
|
||||
: ensureLeadingSlash(`${base}/${link}`.replace(/\/+/g, '/'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a prefix by ensuring it ends with a slash
|
||||
*
|
||||
* 规范化前缀,确保它以斜杠结尾
|
||||
*
|
||||
* @param base - Base URL / 基础 URL
|
||||
* @param link - Link to normalize / 要规范化的链接
|
||||
* @returns Normalized prefix with trailing slash / 带有尾部斜杠的规范化前缀
|
||||
*/
|
||||
export function normalizePrefix(base: string, link = ''): string {
|
||||
return ensureEndingSlash(normalizeLink(base, link))
|
||||
}
|
||||
|
||||
@ -1,7 +1,24 @@
|
||||
import { isLinkHttp } from 'vuepress/shared'
|
||||
|
||||
/**
|
||||
* Supported repository types
|
||||
* Represents the type of code hosting platform
|
||||
*
|
||||
* 支持的仓库类型
|
||||
* 表示代码托管平台的类型
|
||||
*/
|
||||
export type RepoType = 'GitHub' | 'GitLab' | 'Gitee' | 'Bitbucket' | null
|
||||
|
||||
/**
|
||||
* Resolve the repository type from a repository URL
|
||||
* Detects the platform based on URL patterns
|
||||
*
|
||||
* 从仓库 URL 解析仓库类型
|
||||
* 基于 URL 模式检测平台
|
||||
*
|
||||
* @param repo - Repository URL or path / 仓库 URL 或路径
|
||||
* @returns The detected repository type or null if unknown / 检测到的仓库类型,如果未知则返回 null
|
||||
*/
|
||||
export function resolveRepoType(repo: string): RepoType {
|
||||
if (!isLinkHttp(repo) || /github\.com/.test(repo))
|
||||
return 'GitHub'
|
||||
|
||||
@ -1,14 +1,62 @@
|
||||
/**
|
||||
* Regular expression to match external URLs
|
||||
*
|
||||
* 匹配外部 URL 的正则表达式
|
||||
*/
|
||||
export const EXTERNAL_URL_RE: RegExp = /^[a-z]+:/i
|
||||
|
||||
/**
|
||||
* Regular expression to match pathname protocol
|
||||
*
|
||||
* 匹配 pathname 协议的正则表达式
|
||||
*/
|
||||
export const PATHNAME_PROTOCOL_RE: RegExp = /^pathname:\/\//
|
||||
export const HASH_RE: RegExp = /#.*$/
|
||||
|
||||
/**
|
||||
* Regular expression to match hash
|
||||
*
|
||||
* 匹配哈希值的正则表达式
|
||||
*/
|
||||
export const HASH_RE: RegExp = /#.*/
|
||||
|
||||
/**
|
||||
* Regular expression to match file extension
|
||||
*
|
||||
* 匹配文件扩展名的正则表达式
|
||||
*/
|
||||
export const EXT_RE: RegExp = /(index|README)?\.(md|html)$/
|
||||
|
||||
/**
|
||||
* Whether running in browser
|
||||
*
|
||||
* 是否在浏览器中运行
|
||||
*/
|
||||
export const inBrowser: boolean = typeof document !== 'undefined'
|
||||
|
||||
/**
|
||||
* Convert value to array
|
||||
*
|
||||
* 将值转换为数组
|
||||
*
|
||||
* @param value - Value to convert, can be single value or array / 要转换的值,可以是单个值或数组
|
||||
* @returns Array containing the value(s) / 包含值的数组
|
||||
*/
|
||||
export function toArray<T>(value: T | T[]): T[] {
|
||||
return Array.isArray(value) ? value : [value]
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current path matches the given match path
|
||||
* Supports both exact matching and regex matching
|
||||
*
|
||||
* 检查当前路径是否匹配给定的匹配路径
|
||||
* 支持精确匹配和正则匹配
|
||||
*
|
||||
* @param currentPath - Current path to check / 要检查的当前路径
|
||||
* @param matchPath - Path pattern to match against / 要匹配的路径模式
|
||||
* @param asRegex - Whether to treat matchPath as regex / 是否将 matchPath 视为正则表达式
|
||||
* @returns True if paths match / 如果路径匹配则返回 true
|
||||
*/
|
||||
export function isActive(
|
||||
currentPath: string,
|
||||
matchPath?: string,
|
||||
@ -33,10 +81,28 @@ export function isActive(
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a path by removing hash and file extension
|
||||
*
|
||||
* 通过移除哈希值和文件扩展名来规范化路径
|
||||
*
|
||||
* @param path - Path to normalize / 要规范化的路径
|
||||
* @returns Normalized path / 规范化后的路径
|
||||
*/
|
||||
export function normalize(path: string): string {
|
||||
return decodeURI(path).replace(HASH_RE, '').replace(EXT_RE, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a numeric value to CSS unit string
|
||||
* Adds 'px' suffix if the value is a plain number
|
||||
*
|
||||
* 将数值转换为 CSS 单位字符串
|
||||
* 如果值是纯数字则添加 'px' 后缀
|
||||
*
|
||||
* @param value - Value to convert, can be number or string with unit / 要转换的值,可以是数字或带单位的字符串
|
||||
* @returns CSS unit string / CSS 单位字符串
|
||||
*/
|
||||
export function numToUnit(value?: string | number): string {
|
||||
if (typeof value === 'undefined')
|
||||
return ''
|
||||
@ -48,6 +114,14 @@ export function numToUnit(value?: string | number): string {
|
||||
|
||||
const gradient: string[] = ['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient']
|
||||
|
||||
/**
|
||||
* Check if a value is a CSS gradient
|
||||
*
|
||||
* 检查值是否为 CSS 渐变
|
||||
*
|
||||
* @param value - Value to check / 要检查的值
|
||||
* @returns True if value is a gradient / 如果值是渐变则返回 true
|
||||
*/
|
||||
export function isGradient(value: string): boolean {
|
||||
return gradient.some(v => value.startsWith(v))
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import type { ThemeCollectionItem, ThemeCollections, ThemeConfig, ThemeNavItem } from '../shared/index.js'
|
||||
|
||||
/**
|
||||
* Theme configuration helper function, used in separate `plume.config.ts`
|
||||
*
|
||||
* 主题配置,在单独的 `plume.config.ts` 中使用的类型帮助函数
|
||||
*/
|
||||
export function defineThemeConfig(config: ThemeConfig): ThemeConfig {
|
||||
@ -8,6 +10,8 @@ export function defineThemeConfig(config: ThemeConfig): ThemeConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme navbar configuration helper function
|
||||
*
|
||||
* 主题导航栏配置帮助函数
|
||||
*/
|
||||
export function defineNavbarConfig(navbar: ThemeNavItem[]): ThemeNavItem[] {
|
||||
@ -15,6 +19,8 @@ export function defineNavbarConfig(navbar: ThemeNavItem[]): ThemeNavItem[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme notes configuration helper function
|
||||
*
|
||||
* 主题 notes 配置帮助函数
|
||||
* @deprecated 使用 `defineCollections` 代替
|
||||
*/
|
||||
@ -23,6 +29,8 @@ export function defineNotesConfig(notes: unknown): unknown {
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme note item configuration helper function
|
||||
*
|
||||
* 主题 notes item 配置帮助函数
|
||||
* @deprecated 使用 `defineCollection` 代替
|
||||
*/
|
||||
@ -31,6 +39,8 @@ export function defineNoteConfig(note: unknown): unknown {
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme collections configuration helper function
|
||||
*
|
||||
* 主题 collections 配置帮助函数
|
||||
*/
|
||||
export function defineCollections(collections: ThemeCollections): ThemeCollections {
|
||||
@ -38,7 +48,9 @@ export function defineCollections(collections: ThemeCollections): ThemeCollectio
|
||||
}
|
||||
|
||||
/**
|
||||
* 主题 collections item 配置帮助函数
|
||||
* Theme collection item configuration helper function
|
||||
*
|
||||
* 主题 collection item 配置帮助函数
|
||||
*/
|
||||
export function defineCollection(collection: ThemeCollectionItem): ThemeCollectionItem {
|
||||
return collection
|
||||
|
||||
@ -5,6 +5,8 @@ import { detectMarkdown } from './markdown.js'
|
||||
import { detectPlugins } from './plugins.js'
|
||||
|
||||
/**
|
||||
* Detect theme options
|
||||
*
|
||||
* 检测主题选项
|
||||
*/
|
||||
export function detectThemeOptions({
|
||||
|
||||
@ -16,6 +16,11 @@ const t = createTranslate({
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Detect version compatibility
|
||||
*
|
||||
* 检测版本兼容性
|
||||
*/
|
||||
export function detectVersions(app: App): void {
|
||||
detectVuepressVersion()
|
||||
detectThemeVersion(app)
|
||||
|
||||
@ -7,5 +7,7 @@ export { plumeTheme }
|
||||
|
||||
/**
|
||||
* @deprecated 请使用 具名导出 替代 默认导出
|
||||
*
|
||||
* @deprecated Please use named exports instead of default export
|
||||
*/
|
||||
export default plumeTheme
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
/**
|
||||
* Multilingual presets
|
||||
* Except for /zh/ and /en/, other language presets are generated by AI and are not guaranteed to be accurate
|
||||
* If there are any errors, welcome to submit an issue
|
||||
*
|
||||
* 多语言预设
|
||||
* 除 /zh/ 、 /en/ 外,其它语言预设通过 AI 生成,不保证准确
|
||||
* 如有错误,欢迎提 issue
|
||||
|
||||
@ -10,6 +10,11 @@ const cache: Record<string, number> = {}
|
||||
|
||||
const RE_CATEGORY = /^(?:(\d+)\.)?([\s\S]+)$/
|
||||
|
||||
/**
|
||||
* Auto category for page
|
||||
*
|
||||
* 自动为页面生成分类信息,根据文件路径和集合配置自动确定页面所属的分类
|
||||
*/
|
||||
export function autoCategory(page: Page<ThemePageData>): void {
|
||||
const collection = findCollection(page)
|
||||
|
||||
|
||||
@ -13,6 +13,11 @@ function getRootLang(app: App): string {
|
||||
return app.siteData.lang
|
||||
}
|
||||
|
||||
/**
|
||||
* Create additional pages
|
||||
*
|
||||
* 创建额外页面,根据集合配置生成文章列表页、标签页、分类页、归档页等
|
||||
*/
|
||||
export async function createPages(app: App): Promise<void> {
|
||||
const options = getThemeConfig()
|
||||
|
||||
|
||||
@ -4,6 +4,11 @@ import { toArray } from '@pengzhanbo/utils'
|
||||
import pMap from 'p-map'
|
||||
import { genEncrypt } from '../utils/index.js'
|
||||
|
||||
/**
|
||||
* Encrypt page
|
||||
*
|
||||
* 加密页面,将页面的密码转换为加密后的哈希值并存储在页面数据中
|
||||
*/
|
||||
export async function encryptPage(
|
||||
page: Page<ThemePageData>,
|
||||
): Promise<void> {
|
||||
|
||||
@ -5,6 +5,11 @@ import { autoCategory } from './autoCategory.js'
|
||||
import { encryptPage } from './encryptPage.js'
|
||||
import { enableBulletin } from './pageBulletin.js'
|
||||
|
||||
/**
|
||||
* Extend page data
|
||||
*
|
||||
* 扩展页面数据,清理页面数据、自动分类、启用公告栏、加密页面
|
||||
*/
|
||||
export async function extendsPageData(
|
||||
page: Page<ThemePageData>,
|
||||
): Promise<void> {
|
||||
|
||||
@ -3,6 +3,11 @@ import type { ThemePageData } from '../../shared/index.js'
|
||||
import { isFunction, isPlainObject } from '@vuepress/helper'
|
||||
import { getThemeConfig } from '../loadConfig/index.js'
|
||||
|
||||
/**
|
||||
* Enable bulletin for page
|
||||
*
|
||||
* 为页面启用公告栏,根据全局或语言环境配置决定是否显示公告栏
|
||||
*/
|
||||
export function enableBulletin(
|
||||
page: Page<ThemePageData>,
|
||||
): void {
|
||||
|
||||
@ -7,6 +7,11 @@ import { shikiPlugin } from '@vuepress/plugin-shiki'
|
||||
import { createCodeTabIconGetter } from 'vuepress-plugin-md-power'
|
||||
import { getThemeConfig } from '../loadConfig/index.js'
|
||||
|
||||
/**
|
||||
* Setup code-related plugins
|
||||
*
|
||||
* 设置代码相关插件,包括代码复制和代码高亮
|
||||
*/
|
||||
export function codePlugins(pluginOptions: ThemeBuiltinPlugins): PluginConfig {
|
||||
const options = getThemeConfig()
|
||||
const plugins: PluginConfig = []
|
||||
|
||||
@ -4,6 +4,11 @@ import { isPlainObject } from '@vuepress/helper'
|
||||
import { gitPlugin as rawGitPlugin } from '@vuepress/plugin-git'
|
||||
import { getThemeConfig } from '../loadConfig/index.js'
|
||||
|
||||
/**
|
||||
* Setup git plugin
|
||||
*
|
||||
* 设置 Git 插件,用于显示文章的最后更新时间、贡献者和变更历史
|
||||
*/
|
||||
export function gitPlugin(app: App, pluginOptions: ThemeBuiltinPlugins): PluginConfig {
|
||||
const options = getThemeConfig()
|
||||
|
||||
|
||||
@ -11,6 +11,11 @@ const CODE_BLOCK_RE = /(?:^|\n)(?<marker>\s*`{3,})([\s\w])[\s\S]*?\n\k<marker>(?
|
||||
const ENCRYPT_CONTAINER_RE = /(?:^|\n)(?<marker>\s*:{3,})\s*encrypt\b[\s\S]*?\n\k<marker>(?:\n|$)/g
|
||||
const RESTORE_RE = /<!-- llms-code-block:(\w+) -->/g
|
||||
|
||||
/**
|
||||
* Setup LLMs plugin
|
||||
*
|
||||
* 设置 LLM 插件,用于生成 LLM 友好的网站内容,支持自定义 Markdown 转换和模板
|
||||
*/
|
||||
export function llmsPlugin(app: App, userOptions: true | LlmsPluginOptions): PluginConfig {
|
||||
if (!app.env.isBuild)
|
||||
return []
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user