chore: improve plugin-md-power code comments

This commit is contained in:
pengzhanbo 2026-03-08 16:35:52 +08:00
parent 552f0f5c32
commit 77856e36c5
11 changed files with 443 additions and 5 deletions

View File

@ -5,18 +5,63 @@ import { http } from '../utils/http.js'
import { sleep } from '../utils/sleep.js'
import { rustExecute } from './rustRepl.js'
/**
* CSS selectors for nodes to ignore when extracting code.
*
* CSS
*/
const ignoredNodes = ['.diff.remove', '.vp-copy-ignore']
/**
* Regular expression for matching language class.
*
*
*/
const RE_LANGUAGE = /language-(\w+)/
/**
* API endpoints for code execution backends.
*
* API
*/
const api = {
go: 'https://api.pengzhanbo.cn/repl/golang/run',
kotlin: 'https://api.pengzhanbo.cn/repl/kotlin/run',
}
/**
* Pyodide instance for Python execution.
*
* Python Pyodide
*/
let pyodide: PyodideInterface | null = null
/**
* Supported languages for code execution.
*
*
*/
type Lang = 'kotlin' | 'go' | 'rust' | 'python'
/**
* Function type for code execution.
*
*
*/
type ExecuteFn = (code: string) => Promise<any>
/**
* Map of language to execution function.
*
*
*/
type ExecuteMap = Record<Lang, ExecuteFn>
/**
* Language alias mapping.
*
*
*/
const langAlias: Record<string, string> = {
kt: 'kotlin',
kotlin: 'kotlin',
@ -27,12 +72,33 @@ const langAlias: Record<string, string> = {
python: 'python',
}
/**
* List of supported languages.
*
*
*/
const supportLang: Lang[] = ['kotlin', 'go', 'rust', 'python']
/**
* Resolve language name from alias.
*
*
*
* @param lang - Language or alias /
* @returns Resolved language name /
*/
function resolveLang(lang?: string) {
return lang ? langAlias[lang] || lang : ''
}
/**
* Resolve code content from HTML element, ignoring specified nodes.
*
* HTML
*
* @param el - HTML element / HTML
* @returns Code content /
*/
export function resolveCode(el: HTMLElement): string {
const clone = el.cloneNode(true) as HTMLElement
clone
@ -42,6 +108,14 @@ export function resolveCode(el: HTMLElement): string {
return clone.textContent || ''
}
/**
* Resolve code information from HTML element.
*
* HTML
*
* @param el - HTML element / HTML
* @returns Object with language and code /
*/
export function resolveCodeInfo(el: HTMLDivElement): {
lang: Lang
code: string
@ -57,19 +131,55 @@ export function resolveCodeInfo(el: HTMLDivElement): {
return { lang: resolveLang(lang) as Lang, code }
}
/**
* Result interface for useCodeRepl composable.
*
* useCodeRepl
*/
interface UseCodeReplResult {
/** Current language / 当前语言 */
lang: Ref<Lang | undefined>
/** Whether the code is loaded / 代码是否已加载 */
loaded: Ref<boolean>
/** Whether this is the first run / 是否为首次运行 */
firstRun: Ref<boolean>
/** Whether execution is finished / 执行是否完成 */
finished: Ref<boolean>
/** Standard output lines / 标准输出行 */
stdout: Ref<string[]>
/** Standard error lines / 标准错误行 */
stderr: Ref<string[]>
/** Error message / 错误信息 */
error: Ref<string>
/** Backend version / 后端版本 */
backendVersion: Ref<string>
/** Clean run state / 清理运行状态 */
onCleanRun: () => void
/** Run code execution / 运行代码执行 */
onRunCode: () => Promise<void>
}
/**
* Composable for code REPL functionality.
*
* REPL
*
* This composable provides functionality to execute code in various languages
* (Kotlin, Go, Rust, Python) and manage the execution state.
*
* KotlinGoRustPython
*
* @param el - Reference to the code element /
* @returns REPL state and methods / REPL
*
* @example
* ```vue
* <script setup>
* const codeEl = ref(null)
* const { onRunCode, stdout, stderr, loaded } = useCodeRepl(codeEl)
* </script>
* ```
*/
export function useCodeRepl(el: Ref<HTMLDivElement | null>): UseCodeReplResult {
const lang = ref<Lang>()
const loaded = ref(true)
@ -227,36 +337,74 @@ export function useCodeRepl(el: Ref<HTMLDivElement | null>): UseCodeReplResult {
}
}
/**
* Request interface for Golang execution API.
*
* Golang API
*/
interface GolangRequest {
/** Code to execute / 要执行的代码 */
code: string
/** Go version / Go 版本 */
version?: '' | 'goprev' | 'gotip'
}
/**
* Response interface for Golang execution API.
*
* Golang API
*/
interface GolangResponse {
/** Execution events / 执行事件 */
events?: {
/** Event message / 事件消息 */
message: ''
/** Event kind / 事件类型 */
kind: 'stdout' | 'stderr'
/** Event delay / 事件延迟 */
delay: number
}[]
/** Error message / 错误信息 */
error?: string
/** Go version / Go 版本 */
version: string
}
/**
* Request interface for Kotlin execution API.
*
* Kotlin API
*/
interface KotlinRequest {
/** Command line arguments / 命令行参数 */
args?: string
/** Files to compile / 要编译的文件 */
files: {
/** File name / 文件名 */
name: string
/** Public ID / 公共 ID */
publicId: string
/** File content / 文件内容 */
text: string
}[]
}
/**
* Response interface for Kotlin execution API.
*
* Kotlin API
*/
interface KotlinResponse {
/** Execution output / 执行输出 */
text: string
/** Kotlin version / Kotlin 版本 */
version: string
/** Compilation errors / 编译错误 */
errors: {
[filename: string]: {
/** Error message / 错误信息 */
message: string
/** Error severity / 错误严重程度 */
severity: 'ERROR' | 'WARNING'
}[]
}

View File

@ -1,11 +1,48 @@
import type { ComputedRef } from 'vue'
/**
* Composable for decrypting encrypted content.
*
*
*
* This composable provides a decrypt function that uses the Web Crypto API
* to decrypt content encrypted with AES-CBC algorithm.
*
* 使 Web Crypto API 使 AES-CBC
*
* @param config - Configuration containing salt and IV / IV
* @returns Object with decrypt function /
*
* @example
* ```ts
* const config = computed(() => ({ salt: [...], iv: [...] }))
* const { decrypt } = useDecrypt(config)
* const content = await decrypt('password', 'encrypted-content')
* ```
*/
export function useDecrypt(
config: ComputedRef<{ salt: number[], iv: number[] }>,
) {
/**
* Convert number array to Uint8Array.
*
* Uint8Array
*
* @param raw - Number array /
* @returns Uint8Array / Uint8Array
*/
const toUnit8Array = (raw: number[]) => Uint8Array.from(raw)
return {
/**
* Decrypt encrypted content using password.
*
* 使
*
* @param password - Decryption password /
* @param text - Encrypted content /
* @returns Decrypted content or undefined / undefined
*/
decrypt: async (password: string, text: string) => {
if (!password)
return
@ -29,6 +66,14 @@ export function useDecrypt(
}
}
/**
* Get key material from password using PBKDF2.
*
* 使 PBKDF2
*
* @param password - Password string /
* @returns CryptoKey for key derivation / CryptoKey
*/
function getKeyMaterial(password: string) {
const enc = new TextEncoder()
return window.crypto.subtle.importKey(
@ -41,7 +86,13 @@ function getKeyMaterial(password: string) {
}
/**
* crypto
* Derive encryption key from key material using PBKDF2.
*
* 使 PBKDF2
*
* @param keyMaterial - Key material from password /
* @param salt - Salt for key derivation /
* @returns Derived CryptoKey for AES-CBC / AES-CBC CryptoKey
*/
function getCryptoDeriveKey(keyMaterial: CryptoKey, salt: BufferSource) {
return window.crypto.subtle.deriveKey(

View File

@ -1,12 +1,61 @@
import { onContentUpdated } from 'vuepress/client'
/**
* Attribute name for mark mode.
*
*
*/
const MARK_MODE_ATTR = 'data-mark-mode'
/**
* Lazy mode constant.
*
*
*/
const MARK_MODE_LAZY = 'lazy'
/**
* CSS class for visible marks.
*
* CSS
*/
const MARK_VISIBLE_CLASS = 'vp-mark-visible'
/**
* Attribute name for mark boundary.
*
*
*/
const MARK_BOUND_ATTR = 'data-vp-mark-bound'
/**
* CSS selector for mark elements.
*
* CSS
*/
const MARK_SELECTOR = 'mark'
/**
* CSS selector for bounded mark elements.
*
* CSS
*/
const BOUND_SELECTOR = `${MARK_SELECTOR}[${MARK_BOUND_ATTR}="1"]`
/**
* Setup mark highlight animation for lazy mode.
*
*
*
* When mode is 'lazy', marks will animate into view using IntersectionObserver.
* When mode is 'eager', marks are immediately visible without animation.
*
* 'lazy' 使 IntersectionObserver
* 'eager'
*
* @param mode - Animation mode: 'lazy' or 'eager' / 'lazy' 'eager'
*
* @example
* ```ts
* // In client config setup
* setupMarkHighlight('lazy')
* ```
*/
export function setupMarkHighlight(mode: 'lazy' | 'eager'): void {
if (typeof window === 'undefined' || __VUEPRESS_SSR__)
return

View File

@ -17,6 +17,14 @@ import { withBase } from 'vuepress/client'
import { ensureEndingSlash, isLinkHttp } from 'vuepress/shared'
import { pluginOptions } from '../options.js'
/**
* Build query string from PDF options.
*
* PDF
*
* @param options - PDF token metadata / PDF
* @returns Query string /
*/
function queryStringify(options: PDFTokenMeta): string {
const { page, noToolbar, zoom } = options
const params = [
@ -32,6 +40,16 @@ function queryStringify(options: PDFTokenMeta): string {
return queryString
}
/**
* Render PDF viewer in the specified element.
*
* PDF
*
* @param el - Container element /
* @param url - PDF URL / PDF URL
* @param embedType - Embed type: 'pdfjs', 'iframe', or 'embed' /
* @param options - PDF token metadata / PDF
*/
export function renderPDF(
el: HTMLElement,
url: string,
@ -72,6 +90,20 @@ export function renderPDF(
el.appendChild(pdf)
}
/**
* Composable for PDF viewer functionality.
*
* PDF
*
* This function detects browser capabilities and chooses the appropriate
* embedding method for PDF display (PDF.js, iframe, or embed).
*
* PDFPDF.jsiframe embed
*
* @param el - Container element /
* @param url - PDF URL / PDF URL
* @param options - PDF token metadata / PDF
*/
export function usePDF(
el: HTMLElement,
url: string,
@ -86,10 +118,10 @@ export function usePDF(
const isModernBrowser = typeof window.Promise === 'function'
// Quick test for mobile devices.
const isMobileDevice = isiPad(userAgent) || isMobile(userAgent)
const isMobileDevice = isiPad() || isMobile()
// Safari desktop requires special handling
const isSafariDesktop = !isMobileDevice && isSafari(userAgent)
const isSafariDesktop = !isMobileDevice && isSafari()
const isFirefoxWithPDFJS
= !isMobileDevice

View File

@ -8,8 +8,18 @@ declare const __MD_POWER_HLSJS_INSTALLED__: boolean
declare const __MD_POWER_MPEGTSJS_INSTALLED__: boolean
declare const __MD_POWER_ENCRYPT_LOCALES__: LocaleConfig<EncryptSnippetLocale>
/**
* Plugin options injected at build time.
*
*
*/
export const pluginOptions: MarkdownPowerPluginOptions = __MD_POWER_INJECT_OPTIONS__
/**
* Package installation status for video streaming libraries.
*
*
*/
export const installed: {
dashjs: boolean
hlsjs: boolean
@ -20,6 +30,11 @@ export const installed: {
mpegtsjs: __MD_POWER_MPEGTSJS_INSTALLED__,
}
/**
* Supported video types for ArtPlayer.
*
* ArtPlayer
*/
export const ART_PLAYER_SUPPORTED_VIDEO_TYPES: string[] = ['mp4', 'mp3', 'webm', 'ogg']
if (installed.dashjs) {
@ -34,12 +49,27 @@ if (installed.mpegtsjs) {
ART_PLAYER_SUPPORTED_VIDEO_TYPES.push('ts', 'flv')
}
/**
* Injection key for timeline component communication.
*
* 线
*/
export const INJECT_TIMELINE_KEY: symbol = Symbol(
__VUEPRESS_DEV__ ? 'timeline' : '',
)
/**
* Injection key for collapse component communication.
*
*
*/
export const INJECT_COLLAPSE_KEY: symbol = Symbol(
__VUEPRESS_DEV__ ? 'collapse' : '',
)
/**
* Encrypt snippet locale data.
*
*
*/
export const ENCRYPT_LOCALES = __MD_POWER_ENCRYPT_LOCALES__

View File

@ -3,6 +3,19 @@ import type { Markdown } from 'vuepress/markdown'
import { demoContainer, demoEmbed } from './demo.js'
import { createDemoRender } from './watcher.js'
/**
* Register demo plugin for markdown-it.
*
* markdown-it demo
*
* This plugin enables demo syntax in markdown files, allowing users to
* create interactive code demonstrations with live preview.
*
* markdown demo
*
* @param app - VuePress app instance / VuePress
* @param md - Markdown-it instance / Markdown-it
*/
export function demoPlugin(app: App, md: Markdown): void {
createDemoRender()
demoEmbed(app, md)

View File

@ -9,6 +9,17 @@ import { ruLocale } from './ru'
import { zhLocale } from './zh'
import { zhTWLocale } from './zh-tw'
/**
* Default locale options for the plugin.
*
*
*
* This constant defines the default locale configurations for all supported languages.
* Each locale entry maps language codes to their respective locale data.
*
*
*
*/
export const LOCALE_OPTIONS: DefaultLocaleInfo<MDPowerLocaleData> = [
[['en', 'en-US'], enLocale],
[['zh', 'zh-CN', 'zh-Hans', 'zh-Hant'], zhLocale],

View File

@ -16,6 +16,41 @@ import { LOCALE_OPTIONS } from './locales/index.js'
import { prepareConfigFile } from './prepareConfigFile.js'
import { provideData } from './provideData.js'
/**
* Create markdown power plugin for VuePress.
*
* VuePress markdown
*
* This plugin provides various markdown enhancements including:
* - Custom containers (tabs, collapse, timeline, etc.)
* - Code blocks enhancements (code tabs, file tree, demo)
* - Embed syntax (video, PDF, code playground)
* - Inline syntax (mark, subscript, superscript, footnote)
* - Icon support
*
* markdown
* - 线
* -
* - PDF
* -
* -
*
* @param options - Plugin options /
* @returns VuePress plugin instance / VuePress
*
* @example
* ```ts
* // Basic usage
* markdownPowerPlugin()
*
* // With options
* markdownPowerPlugin({
* tabs: true,
* collapse: true,
* pdf: true,
* })
* ```
*/
export function markdownPowerPlugin(
options: MarkdownPowerPluginOptions = {},
): Plugin {

View File

@ -7,10 +7,38 @@ import { prepareIcon } from './icon/index.js'
const { url: filepath } = import.meta
const __dirname = getDirname(filepath)
/**
* Client folder path constant.
*
*
*/
const CLIENT_FOLDER = ensureEndingSlash(
path.resolve(__dirname, '../client'),
)
/**
* Prepare client configuration file for the plugin.
*
*
*
* This function dynamically generates the client config file based on the plugin options.
* It imports and registers Vue components conditionally based on which features are enabled.
*
*
* Vue
*
* @param app - VuePress app instance / VuePress
* @param options - Plugin options /
* @returns Path to the generated config file /
*
* @example
* ```ts
* const configPath = await prepareConfigFile(app, {
* pdf: true,
* tabs: true,
* })
* ```
*/
export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOptions): Promise<string> {
const imports = new Set<string>()
const enhances = new Set<string>()

View File

@ -3,6 +3,34 @@ import type { MarkdownPowerPluginOptions, MDPowerLocaleData } from '../shared/in
import { isPackageExists } from 'local-pkg'
import { findLocales } from './utils/findLocales.js'
/**
* Provide data to be injected into the client-side application.
*
*
*
* This function creates a record of data that will be defined as global constants
* in the client bundle, allowing the client to access plugin configuration and
* runtime information.
*
*
* 访
*
* @param options - Plugin options /
* @param locales - Locale configuration /
* @returns Record of data to be defined /
*
* @example
* ```ts
* const data = provideData(options, locales)
* // Returns:
* // {
* // __MD_POWER_INJECT_OPTIONS__: { plot: true, pdf: true },
* // __MD_POWER_DASHJS_INSTALLED__: true,
* // __MD_POWER_HLSJS_INSTALLED__: false,
* // ...
* // }
* ```
*/
export function provideData(
options: MarkdownPowerPluginOptions,
locales: ExactLocaleConfig<MDPowerLocaleData>,

View File

@ -36,13 +36,26 @@ const WHITE_LIST = ['base', 'filePath', 'filePathRelative', 'references', 'abbre
type WhiteListUnion = (typeof WHITE_LIST)[number]
/**
* Clean markdown environment, keeping only whitelisted keys
* Clean markdown environment for inline rendering.
*
* Markdown
* markdown
*
* When using `md.renderInline()` in custom renderers, some environment properties
* may cause issues. This function creates a clean environment object with only
* the necessary properties preserved.
*
* 使 `md.renderInline()`
*
*
* @param env - Markdown environment / Markdown
* @param excludes - Keys to exclude /
* @returns Cleaned environment /
*
* @example
* ```ts
* const cleanEnv = cleanMarkdownEnv(env)
* const rendered = md.renderInline(content, cleanEnv)
* ```
*/
export function cleanMarkdownEnv(env: CleanMarkdownEnv, excludes: WhiteListUnion[] = []): CleanMarkdownEnv {
const result: CleanMarkdownEnv = {}