mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
* feat(plugin-md-power): add copy button for file-tree container, close #835 * chore: tweak
This commit is contained in:
parent
b1f996cb0e
commit
2780abd782
@ -6,17 +6,32 @@ exports[`fileTree > parseFileTreeRawContent > should work 1`] = `
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"info": "README.md",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "README.md",
|
||||
"focus": false,
|
||||
"level": 1,
|
||||
"type": "file",
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"info": "foo.md",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "foo.md",
|
||||
"focus": false,
|
||||
"level": 1,
|
||||
"type": "file",
|
||||
},
|
||||
],
|
||||
"info": "docs",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "docs",
|
||||
"focus": false,
|
||||
"level": 0,
|
||||
"type": "file",
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
@ -26,52 +41,97 @@ exports[`fileTree > parseFileTreeRawContent > should work 1`] = `
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"info": "**Navbar.vue**",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "Navbar.vue",
|
||||
"focus": true,
|
||||
"level": 3,
|
||||
"type": "file",
|
||||
},
|
||||
],
|
||||
"info": "components",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "components",
|
||||
"focus": false,
|
||||
"level": 2,
|
||||
"type": "file",
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"info": "index.ts # comment",
|
||||
"comment": "# comment",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "index.ts",
|
||||
"focus": false,
|
||||
"level": 2,
|
||||
"type": "file",
|
||||
},
|
||||
],
|
||||
"info": "client",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "client",
|
||||
"focus": false,
|
||||
"level": 1,
|
||||
"type": "file",
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
"info": "index.ts",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "index.ts",
|
||||
"focus": false,
|
||||
"level": 2,
|
||||
"type": "file",
|
||||
},
|
||||
],
|
||||
"info": "node",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "node",
|
||||
"focus": false,
|
||||
"level": 1,
|
||||
"type": "file",
|
||||
},
|
||||
],
|
||||
"info": "src",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "src",
|
||||
"focus": false,
|
||||
"level": 0,
|
||||
"type": "file",
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"info": ".gitignore",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": ".gitignore",
|
||||
"focus": false,
|
||||
"level": 0,
|
||||
"type": "file",
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"info": "package.json",
|
||||
"comment": "",
|
||||
"diff": undefined,
|
||||
"expanded": true,
|
||||
"filename": "package.json",
|
||||
"focus": false,
|
||||
"level": 0,
|
||||
"type": "file",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`fileTreePlugin > should work with default options 1`] = `
|
||||
"<div class="vp-file-tree"><FileTreeNode expanded type="folder" filename="docs" :level="0">
|
||||
"<div class="vp-file-tree"><VPCopyButton text="eJzT43o0Zc6jKQ1ApJCSn1wM5DYpKCggBINcHV18XfVyU+AyU6Ayafn5EGGY0uKiZAztyTmZqXklcGE0yfzcgvw8oDzCWmQSZpFfYllSYpFeWWkqmjKYgsy8lNQKPSRTYBJ5+SkwPSCAVQfMMXrpmSWZ6Xn5RSAdMHUFicnZiempelnF+XkANX+Jpw==" encode aria-label="Copy" data-copied="Copied" /><FileTreeNode expanded type="folder" filename="docs" :level="0">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-docs" /></template><FileTreeNode type="file" filename="README.md" :level="1">
|
||||
<template #icon><VPIcon provider="iconify" name="flat-color-icons:info" /></template>
|
||||
</FileTreeNode>
|
||||
@ -102,7 +162,7 @@ exports[`fileTreePlugin > should work with default options 1`] = `
|
||||
<FileTreeNode type="file" filename="package.json" :level="0">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-node" /></template>
|
||||
</FileTreeNode></div>
|
||||
<div class="vp-file-tree"><p class="vp-file-tree-title">files</p><FileTreeNode expanded type="folder" filename="src" :level="0">
|
||||
<div class="vp-file-tree"><p class="vp-file-tree-title">files</p><VPCopyButton text="eJzT43o0Zc6jKQ1ApFBclAzkNSkoKCDEsooxhMpKU+FiU6BiycUgdTBekKuji6+rXm4KAKLWLVo=" encode aria-label="Copy" data-copied="Copied" /><FileTreeNode expanded type="folder" filename="src" :level="0">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-src" /></template><FileTreeNode expanded type="folder" filename="js" :level="1">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-folder" /></template><FileTreeNode type="file" filename="…" :level="2">
|
||||
|
||||
@ -122,7 +182,7 @@ exports[`fileTreePlugin > should work with default options 1`] = `
|
||||
<FileTreeNode type="file" filename="README.md" :level="0">
|
||||
<template #icon><VPIcon provider="iconify" name="flat-color-icons:info" /></template>
|
||||
</FileTreeNode></div>
|
||||
<div class="vp-file-tree"><FileTreeNode type="file" filename="docs" :level="0">
|
||||
<div class="vp-file-tree"><VPCopyButton text="eJzT43o0Zc6jKQ1ApJCSn1yMxC0uSgbymhQUFBBiiXpZICUQwSlQwSS9EpAgjBvk6uji66qXmwIAUH4sGA==" encode aria-label="Copy" data-copied="Copied" /><FileTreeNode type="file" filename="docs" :level="0">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
||||
</FileTreeNode>
|
||||
<FileTreeNode expanded type="folder" filename="src" :level="0">
|
||||
@ -136,7 +196,7 @@ exports[`fileTreePlugin > should work with default options 1`] = `
|
||||
<FileTreeNode type="file" filename="README.md" :level="0">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
||||
</FileTreeNode></div>
|
||||
<div class="vp-file-tree"><FileTreeNode type="file" filename="" :level="0">
|
||||
<div class="vp-file-tree"><VPCopyButton text="eJzT43o0Zc6jKQ1ApABkToExFYAAzgUAZ2oS9w==" encode aria-label="Copy" data-copied="Copied" /><FileTreeNode type="file" filename="" :level="0">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
||||
</FileTreeNode>
|
||||
<FileTreeNode expanded type="folder" filename="" :level="0">
|
||||
@ -144,7 +204,7 @@ exports[`fileTreePlugin > should work with default options 1`] = `
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
||||
</FileTreeNode>
|
||||
</FileTreeNode></div>
|
||||
<div class="vp-file-tree"><FileTreeNode expanded type="folder" filename="docs" :level="0">
|
||||
<div class="vp-file-tree"><VPCopyButton text="eJzT43o0Zc6jKQ1ApJCSn1wM5DYpKCggBBNTUlJT9HJT4BJToBJFqbn5ZakQGZji4qJkIA+moji/tCg5FQAkoS+X" encode aria-label="Copy" data-copied="Copied" /><FileTreeNode expanded type="folder" filename="docs" :level="0">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-docs" /></template><FileTreeNode type="file" diff="add" filename="added.md" :level="1">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-markdown" /></template>
|
||||
</FileTreeNode>
|
||||
@ -158,7 +218,7 @@ exports[`fileTreePlugin > should work with default options 1`] = `
|
||||
<FileTreeNode type="file" diff="remove" filename="source" :level="0">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
||||
</FileTreeNode></div>
|
||||
<div class="vp-file-tree"></div>
|
||||
<div class="vp-file-tree"><VPCopyButton text="eJzTAwAALwAv" encode aria-label="Copy" data-copied="Copied" /></div>
|
||||
"
|
||||
`;
|
||||
|
||||
@ -166,7 +226,7 @@ exports[`fileTreePlugin > should work with nesting content 1`] = `
|
||||
"<ul>
|
||||
<li>
|
||||
<p>item1</p>
|
||||
<div class="vp-file-tree"><FileTreeNode type="folder" filename="docs" :level="0">
|
||||
<div class="vp-file-tree"><VPCopyButton text="eJzT43o0Zc6jKQ1ApJCSn1yMxC0uSgbymhQUFBBiiXpZICUQwSlQwSS9EpAgjBvk6uji66qXmwIAUH4sGA==" encode aria-label="Copy" data-copied="Copied" /><FileTreeNode type="folder" filename="docs" :level="0">
|
||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-docs" /></template><FileTreeNode type="file" filename="…" :level="1">
|
||||
|
||||
</FileTreeNode>
|
||||
|
||||
@ -56,7 +56,9 @@ describe('fileTree > parseFileTreeNodeInfo', () => {
|
||||
})
|
||||
|
||||
function createMarkdown(options?: FileTreeOptions) {
|
||||
return new MarkdownIt().use(fileTreePlugin, options)
|
||||
const md = new MarkdownIt()
|
||||
fileTreePlugin(md, options, {})
|
||||
return md
|
||||
}
|
||||
|
||||
describe('fileTreePlugin', () => {
|
||||
|
||||
@ -68,6 +68,7 @@ function toggle(ev: MouseEvent) {
|
||||
|
||||
<style>
|
||||
.vp-file-tree {
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
padding: 16px;
|
||||
overflow: auto hidden;
|
||||
@ -87,6 +88,14 @@ function toggle(ev: MouseEvent) {
|
||||
transition: color var(--vp-t-color), border-color var(--vp-t-color);
|
||||
}
|
||||
|
||||
.vp-file-tree .vp-file-tree-title + .vp-copy-code-button {
|
||||
top: calc(45px + 1em);
|
||||
}
|
||||
|
||||
.vp-file-tree:hover .vp-copy-code-button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vp-file-tree .vp-file-tree-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { decodeData } from '@vuepress/helper/client'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { text, encode = false } = defineProps<{
|
||||
text: string
|
||||
encode?: boolean
|
||||
}>()
|
||||
|
||||
const content = computed(() => encode ? decodeData(text) : text)
|
||||
|
||||
const { copied, copy } = useClipboard()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button" class="vp-copy-code-button" :class="{ copied }"
|
||||
aria-label="Copy"
|
||||
data-copied="Copied"
|
||||
@click="copy(content)"
|
||||
/>
|
||||
</template>
|
||||
@ -95,8 +95,6 @@ export function createContainerSyntaxPlugin(
|
||||
if (state.src[pos] !== maker)
|
||||
return false
|
||||
|
||||
pos += markerMinLen
|
||||
|
||||
// 检查 marker 长度是否满足要求
|
||||
for (pos = start + 1; pos <= max; pos++) {
|
||||
if (state.src[pos] !== maker)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { Markdown } from 'vuepress/markdown'
|
||||
import type { FileTreeIconMode, FileTreeOptions } from '../../shared/index.js'
|
||||
import { removeEndingSlash } from 'vuepress/shared'
|
||||
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
|
||||
import type { CommonLocaleData, FileTreeIconMode, FileTreeOptions } from '../../shared/index.js'
|
||||
import { encodeData } from '@vuepress/helper'
|
||||
import { ensureLeadingSlash, removeEndingSlash, resolveLocalePath } from 'vuepress/shared'
|
||||
import { defaultFile, defaultFolder, getFileIcon } from '../fileIcons/index.js'
|
||||
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
||||
import { createContainerSyntaxPlugin } from './createContainer.js'
|
||||
@ -8,8 +9,7 @@ import { createContainerSyntaxPlugin } from './createContainer.js'
|
||||
/**
|
||||
* 文件树节点结构
|
||||
*/
|
||||
interface FileTreeNode {
|
||||
info: string
|
||||
interface FileTreeNode extends FileTreeNodeProps {
|
||||
level: number
|
||||
children: FileTreeNode[]
|
||||
}
|
||||
@ -41,7 +41,7 @@ export interface FileTreeNodeProps {
|
||||
* @returns 文件树节点数组
|
||||
*/
|
||||
export function parseFileTreeRawContent(content: string): FileTreeNode[] {
|
||||
const root: FileTreeNode = { info: '', level: -1, children: [] }
|
||||
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 // 去除行首空格/)
|
||||
@ -60,7 +60,7 @@ export function parseFileTreeRawContent(content: string): FileTreeNode[] {
|
||||
}
|
||||
|
||||
const parent = stack[stack.length - 1]
|
||||
const node: FileTreeNode = { info, level, children: [] }
|
||||
const node: FileTreeNode = { level, children: [], ...parseFileTreeNodeInfo(info) }
|
||||
parent.children.push(node)
|
||||
stack.push(node)
|
||||
}
|
||||
@ -124,7 +124,11 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
|
||||
* @param md markdown 实例
|
||||
* @param options 文件树渲染选项
|
||||
*/
|
||||
export function fileTreePlugin(md: Markdown, options: FileTreeOptions = {}): void {
|
||||
export function fileTreePlugin(
|
||||
md: Markdown,
|
||||
options: FileTreeOptions = {},
|
||||
locales: Record<string, CommonLocaleData>,
|
||||
): void {
|
||||
/**
|
||||
* 获取文件或文件夹的图标
|
||||
*/
|
||||
@ -140,13 +144,12 @@ export function fileTreePlugin(md: Markdown, options: FileTreeOptions = {}): voi
|
||||
*/
|
||||
const renderFileTree = (nodes: FileTreeNode[], meta: FileTreeAttrs): string =>
|
||||
nodes.map((node) => {
|
||||
const { info, level, children } = node
|
||||
const { filename, comment, focus, expanded, type, diff } = parseFileTreeNodeInfo(info)
|
||||
const { level, children, filename, comment, focus, expanded, type, diff } = node
|
||||
const isOmit = filename === '…' || filename === '...' /* fallback */
|
||||
|
||||
// 文件夹无子节点时补充省略号
|
||||
if (children.length === 0 && type === 'folder') {
|
||||
children.push({ info: '…', level: level + 1, children: [] })
|
||||
children.push({ level: level + 1, children: [], filename: '…', type: 'file' } as unknown as FileTreeNode)
|
||||
}
|
||||
|
||||
const nodeType = children.length > 0 ? 'folder' : type
|
||||
@ -173,13 +176,30 @@ ${renderedIcon}${renderedComment}${children.length > 0 ? renderFileTree(children
|
||||
return createContainerSyntaxPlugin(
|
||||
md,
|
||||
'file-tree',
|
||||
(tokens, index) => {
|
||||
(tokens, index, _, env: MarkdownEnv) => {
|
||||
const token = tokens[index]
|
||||
const nodes = parseFileTreeRawContent(token.content)
|
||||
const meta = token.meta as FileTreeAttrs
|
||||
const cmdText = fileTreeToCMDText(nodes).trim()
|
||||
const localePath = resolveLocalePath(locales, ensureLeadingSlash(env.filePathRelative || ''))
|
||||
const data = locales[localePath] ?? {}
|
||||
return `<div class="vp-file-tree">${
|
||||
meta.title ? `<p class="vp-file-tree-title">${meta.title}</p>` : ''
|
||||
}${renderFileTree(nodes, meta)}</div>\n`
|
||||
}<VPCopyButton text="${encodeData(cmdText)}" encode aria-label="${data.copy || 'Copy'}" data-copied="${data.copied || 'Copied'}" />${
|
||||
renderFileTree(nodes, meta)
|
||||
}</div>\n`
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
function fileTreeToCMDText(nodes: FileTreeNode[], prefix = ''): string {
|
||||
let content = prefix ? '' : '.\n'
|
||||
for (let i = 0, l = nodes.length; i < l; i++) {
|
||||
const { filename, children } = nodes[i]
|
||||
content += `${prefix + (i === l - 1 ? '└── ' : '├── ')}${filename}\n`
|
||||
const child = children.filter(n => n.filename !== '…')
|
||||
if (child.length)
|
||||
content += fileTreeToCMDText(child, prefix + (i === l - 1 ? ' ' : '│ '))
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import type { App } from 'vuepress'
|
||||
import type { Markdown } from 'vuepress/markdown'
|
||||
import type { MarkdownPowerPluginOptions } from '../../shared/index.js'
|
||||
import { isPlainObject } from '@vuepress/helper'
|
||||
import type { MDPowerLocaleData } from '../../shared/locale.js'
|
||||
import { type ExactLocaleConfig, isPlainObject } from '@vuepress/helper'
|
||||
import { findLocales } from '../utils/findLocales.js'
|
||||
import { alignPlugin } from './align.js'
|
||||
import { cardPlugin } from './card.js'
|
||||
import { chatPlugin } from './chat.js'
|
||||
@ -23,6 +25,7 @@ export async function containerPlugin(
|
||||
app: App,
|
||||
md: Markdown,
|
||||
options: MarkdownPowerPluginOptions,
|
||||
locales: ExactLocaleConfig<MDPowerLocaleData>,
|
||||
): Promise<void> {
|
||||
// ::: left / right / center / justify
|
||||
alignPlugin(md)
|
||||
@ -56,7 +59,7 @@ export async function containerPlugin(
|
||||
|
||||
if (options.fileTree) {
|
||||
// ::: file-tree
|
||||
fileTreePlugin(md, isPlainObject(options.fileTree) ? options.fileTree : {})
|
||||
fileTreePlugin(md, isPlainObject(options.fileTree) ? options.fileTree : {}, findLocales(locales, 'common'))
|
||||
}
|
||||
|
||||
if (options.codeTree) {
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||
|
||||
export const deLocale: MDPowerLocaleData = {
|
||||
common: {
|
||||
copy: 'Kopieren',
|
||||
copied: 'Kopiert',
|
||||
},
|
||||
encrypt: {
|
||||
hint: 'Der Inhalt ist verschlüsselt, bitte entsperren Sie ihn, um ihn anzuzeigen.',
|
||||
placeholder: 'Passwort eingeben',
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||
|
||||
export const enLocale: MDPowerLocaleData = {
|
||||
common: {
|
||||
copy: 'Copy',
|
||||
copied: 'Copied',
|
||||
},
|
||||
encrypt: {
|
||||
hint: 'The content is encrypted, please unlock to view.',
|
||||
placeholder: 'Enter password',
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||
|
||||
export const frLocale: MDPowerLocaleData = {
|
||||
common: {
|
||||
copy: 'Copier',
|
||||
copied: 'Copié',
|
||||
},
|
||||
encrypt: {
|
||||
hint: 'Le contenu est chiffré, veuillez déverrouiller pour afficher.',
|
||||
placeholder: 'Entrez le mot de passe',
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||
|
||||
export const jaLocale: MDPowerLocaleData = {
|
||||
common: {
|
||||
copy: 'コピー',
|
||||
copied: 'コピー済み',
|
||||
},
|
||||
encrypt: {
|
||||
hint: 'コンテンツは暗号化されています。閲覧するにはロックを解除してください。',
|
||||
placeholder: 'パスワードを入力',
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||
|
||||
export const koLocale: MDPowerLocaleData = {
|
||||
common: {
|
||||
copy: '복사',
|
||||
copied: '복사됨',
|
||||
},
|
||||
encrypt: {
|
||||
hint: '내용이 암호화되어 있습니다. 잠금 해제 후 확인하세요.',
|
||||
placeholder: '비밀번호 입력',
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||
|
||||
export const ruLocale: MDPowerLocaleData = {
|
||||
common: {
|
||||
copy: 'Копировать',
|
||||
copied: 'Скопировано',
|
||||
},
|
||||
encrypt: {
|
||||
hint: 'Контент зашифрован, разблокируйте для просмотра.',
|
||||
placeholder: 'Введите пароль',
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||
|
||||
export const zhTWLocale: MDPowerLocaleData = {
|
||||
common: {
|
||||
copy: '複製',
|
||||
copied: '已複製',
|
||||
},
|
||||
encrypt: {
|
||||
hint: '內容已加密,請解鎖後查看。',
|
||||
placeholder: '輸入密碼',
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||
|
||||
export const zhLocale: MDPowerLocaleData = {
|
||||
common: {
|
||||
copy: '复制',
|
||||
copied: '已复制',
|
||||
},
|
||||
encrypt: {
|
||||
hint: '内容已加密,请解锁后查看。',
|
||||
placeholder: '输入密码',
|
||||
|
||||
@ -2,6 +2,7 @@ import type { Plugin } from 'vuepress/core'
|
||||
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
|
||||
import { isPlainObject } from '@pengzhanbo/utils'
|
||||
import { addViteOptimizeDepsInclude } from '@vuepress/helper'
|
||||
import { getFullLocaleConfig } from '@vuepress/helper'
|
||||
import { extendsPageWithCodeTree } from './container/codeTree.js'
|
||||
import { containerPlugin } from './container/index.js'
|
||||
import { demoPlugin, demoWatcher, extendsPageWithDemo, waitDemoRender } from './demo/index.js'
|
||||
@ -11,80 +12,90 @@ import { imageSizePlugin } from './enhance/imageSize.js'
|
||||
import { linksPlugin } from './enhance/links.js'
|
||||
import { iconPlugin } from './icon/index.js'
|
||||
import { inlineSyntaxPlugin } from './inline/index.js'
|
||||
import { LOCALE_OPTIONS } from './locales/index.js'
|
||||
import { prepareConfigFile } from './prepareConfigFile.js'
|
||||
import { provideData } from './provideData.js'
|
||||
|
||||
export function markdownPowerPlugin(
|
||||
options: MarkdownPowerPluginOptions = {},
|
||||
): Plugin {
|
||||
return {
|
||||
name: 'vuepress-plugin-md-power',
|
||||
return (app) => {
|
||||
const locales = getFullLocaleConfig({
|
||||
app,
|
||||
name: 'vuepress-plugin-md-power',
|
||||
default: LOCALE_OPTIONS,
|
||||
config: options.locales,
|
||||
})
|
||||
|
||||
clientConfigFile: app => prepareConfigFile(app, options),
|
||||
return {
|
||||
name: 'vuepress-plugin-md-power',
|
||||
|
||||
define: app => provideData(app, options),
|
||||
clientConfigFile: app => prepareConfigFile(app, options),
|
||||
|
||||
alias: (_, isServer) => {
|
||||
if (!isServer) {
|
||||
return { ...options.encrypt ? { '/^vue$/': 'vue/dist/vue.esm-bundler.js' } : undefined }
|
||||
}
|
||||
return {}
|
||||
},
|
||||
define: provideData(options, locales),
|
||||
|
||||
extendsBundlerOptions(bundlerOptions, app) {
|
||||
if (options.repl) {
|
||||
addViteOptimizeDepsInclude(
|
||||
bundlerOptions,
|
||||
app,
|
||||
['shiki/core', 'shiki/wasm', 'shiki/engine/oniguruma'],
|
||||
)
|
||||
alias: (_, isServer) => {
|
||||
if (!isServer) {
|
||||
return { ...options.encrypt ? { '/^vue$/': 'vue/dist/vue.esm-bundler.js' } : undefined }
|
||||
}
|
||||
return {}
|
||||
},
|
||||
|
||||
if (options.repl.python)
|
||||
addViteOptimizeDepsInclude(bundlerOptions, app, ['pyodide'])
|
||||
}
|
||||
if (options.artPlayer) {
|
||||
addViteOptimizeDepsInclude(
|
||||
bundlerOptions,
|
||||
app,
|
||||
['artplayer', 'dashjs', 'hls.js', 'mpegts.js/dist/mpegts.js'],
|
||||
)
|
||||
}
|
||||
if (options.qrcode) {
|
||||
addViteOptimizeDepsInclude(bundlerOptions, app, ['qrcode'])
|
||||
}
|
||||
},
|
||||
extendsBundlerOptions(bundlerOptions, app) {
|
||||
if (options.repl) {
|
||||
addViteOptimizeDepsInclude(
|
||||
bundlerOptions,
|
||||
app,
|
||||
['shiki/core', 'shiki/wasm', 'shiki/engine/oniguruma'],
|
||||
)
|
||||
|
||||
extendsMarkdown: async (md, app) => {
|
||||
linksPlugin(md)
|
||||
docsTitlePlugin(md)
|
||||
embedSyntaxPlugin(md, options)
|
||||
inlineSyntaxPlugin(md, options)
|
||||
iconPlugin(md, options.icon ?? (isPlainObject(options.icons) ? options.icons : {}))
|
||||
if (options.repl.python)
|
||||
addViteOptimizeDepsInclude(bundlerOptions, app, ['pyodide'])
|
||||
}
|
||||
if (options.artPlayer) {
|
||||
addViteOptimizeDepsInclude(
|
||||
bundlerOptions,
|
||||
app,
|
||||
['artplayer', 'dashjs', 'hls.js', 'mpegts.js/dist/mpegts.js'],
|
||||
)
|
||||
}
|
||||
if (options.qrcode) {
|
||||
addViteOptimizeDepsInclude(bundlerOptions, app, ['qrcode'])
|
||||
}
|
||||
},
|
||||
|
||||
if (options.demo)
|
||||
demoPlugin(app, md)
|
||||
extendsMarkdown: async (md, app) => {
|
||||
linksPlugin(md)
|
||||
docsTitlePlugin(md)
|
||||
embedSyntaxPlugin(md, options)
|
||||
inlineSyntaxPlugin(md, options)
|
||||
iconPlugin(md, options.icon ?? (isPlainObject(options.icons) ? options.icons : {}))
|
||||
|
||||
await containerPlugin(app, md, options)
|
||||
await imageSizePlugin(app, md, options.imageSize)
|
||||
},
|
||||
if (options.demo)
|
||||
demoPlugin(app, md)
|
||||
|
||||
onPrepared: async () => {
|
||||
if (options.demo)
|
||||
await waitDemoRender()
|
||||
},
|
||||
await containerPlugin(app, md, options, locales)
|
||||
await imageSizePlugin(app, md, options.imageSize)
|
||||
},
|
||||
|
||||
onWatched(app, watchers) {
|
||||
if (options.demo) {
|
||||
demoWatcher(app, watchers)
|
||||
}
|
||||
},
|
||||
onPrepared: async () => {
|
||||
if (options.demo)
|
||||
await waitDemoRender()
|
||||
},
|
||||
|
||||
extendsPage: (page) => {
|
||||
if (options.demo)
|
||||
extendsPageWithDemo(page)
|
||||
onWatched(app, watchers) {
|
||||
if (options.demo) {
|
||||
demoWatcher(app, watchers)
|
||||
}
|
||||
},
|
||||
|
||||
if (options.codeTree)
|
||||
extendsPageWithCodeTree(page)
|
||||
},
|
||||
extendsPage: (page) => {
|
||||
if (options.demo)
|
||||
extendsPageWithDemo(page)
|
||||
|
||||
if (options.codeTree)
|
||||
extendsPageWithCodeTree(page)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,9 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp
|
||||
const imports = new Set<string>()
|
||||
const enhances = new Set<string>()
|
||||
|
||||
imports.add(`import VPCopyButton from '${CLIENT_FOLDER}components/VPCopyButton.vue'`)
|
||||
enhances.add(`app.component('VPCopyButton', VPCopyButton)`)
|
||||
|
||||
imports.add(`import Tabs from '${CLIENT_FOLDER}components/Tabs.vue'`)
|
||||
enhances.add(`app.component('Tabs', Tabs)`)
|
||||
|
||||
|
||||
@ -1,22 +1,17 @@
|
||||
import type { App, LocaleConfig } from 'vuepress'
|
||||
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
|
||||
import type { MDPowerLocaleData } from '../shared/locale.js'
|
||||
import { getFullLocaleConfig } from '@vuepress/helper'
|
||||
import type { ExactLocaleConfig } from '@vuepress/helper'
|
||||
import type { MarkdownPowerPluginOptions, MDPowerLocaleData } from '../shared/index.js'
|
||||
import { isPackageExists } from 'local-pkg'
|
||||
import { LOCALE_OPTIONS } from './locales/index.js'
|
||||
import { findLocales } from './utils/findLocales.js'
|
||||
|
||||
export function provideData(app: App, options: MarkdownPowerPluginOptions): Record<string, unknown> {
|
||||
export function provideData(
|
||||
options: MarkdownPowerPluginOptions,
|
||||
locales: ExactLocaleConfig<MDPowerLocaleData>,
|
||||
): Record<string, unknown> {
|
||||
const markdownOptions = {
|
||||
plot: options.plot,
|
||||
pdf: options.pdf,
|
||||
}
|
||||
|
||||
const locales = getFullLocaleConfig({
|
||||
app,
|
||||
name: 'vuepress-plugin-md-power',
|
||||
default: LOCALE_OPTIONS,
|
||||
config: options.locales,
|
||||
})
|
||||
const icon = options.icon ?? { provider: 'iconify' }
|
||||
|
||||
return {
|
||||
@ -29,16 +24,3 @@ export function provideData(app: App, options: MarkdownPowerPluginOptions): Reco
|
||||
__MD_POWER_ENCRYPT_LOCALES__: options.encrypt ? findLocales(locales, 'encrypt') : {},
|
||||
}
|
||||
}
|
||||
|
||||
function findLocales<
|
||||
T extends MDPowerLocaleData,
|
||||
K extends keyof T,
|
||||
>(locales: LocaleConfig<T>, key: K): Record<string, T[K]> {
|
||||
const res: Record<string, T[K]> = {}
|
||||
|
||||
for (const [locale, value] of Object.entries(locales)) {
|
||||
res[locale] = value[key] ?? {} as T[K]
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
15
plugins/plugin-md-power/src/node/utils/findLocales.ts
Normal file
15
plugins/plugin-md-power/src/node/utils/findLocales.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { LocaleConfig } from 'vuepress'
|
||||
import type { MDPowerLocaleData } from '../../shared/index.js'
|
||||
|
||||
export function findLocales<
|
||||
T extends MDPowerLocaleData,
|
||||
K extends keyof T,
|
||||
>(locales: LocaleConfig<T>, key: K): Record<string, T[K]> {
|
||||
const res: Record<string, T[K]> = {}
|
||||
|
||||
for (const [locale, value] of Object.entries(locales)) {
|
||||
res[locale] = value[key] ?? {} as T[K]
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
@ -7,6 +7,7 @@ export * from './env.js'
|
||||
export * from './fileTree.js'
|
||||
export * from './icon.js'
|
||||
export * from './jsfiddle.js'
|
||||
export * from './locale.js'
|
||||
export * from './npmTo.js'
|
||||
export * from './pdf.js'
|
||||
export * from './plot.js'
|
||||
|
||||
@ -2,5 +2,11 @@ import type { LocaleData } from 'vuepress'
|
||||
import type { EncryptSnippetLocale } from './encrypt'
|
||||
|
||||
export interface MDPowerLocaleData extends LocaleData {
|
||||
common?: CommonLocaleData
|
||||
encrypt?: EncryptSnippetLocale
|
||||
}
|
||||
|
||||
export interface CommonLocaleData extends LocaleData {
|
||||
copy?: string
|
||||
copied?: string
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user