* 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": [
|
||||||
{
|
{
|
||||||
"children": [],
|
"children": [],
|
||||||
"info": "README.md",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "README.md",
|
||||||
|
"focus": false,
|
||||||
"level": 1,
|
"level": 1,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"children": [],
|
"children": [],
|
||||||
"info": "foo.md",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "foo.md",
|
||||||
|
"focus": false,
|
||||||
"level": 1,
|
"level": 1,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"info": "docs",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "docs",
|
||||||
|
"focus": false,
|
||||||
"level": 0,
|
"level": 0,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"children": [
|
"children": [
|
||||||
@ -26,52 +41,97 @@ exports[`fileTree > parseFileTreeRawContent > should work 1`] = `
|
|||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"children": [],
|
"children": [],
|
||||||
"info": "**Navbar.vue**",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "Navbar.vue",
|
||||||
|
"focus": true,
|
||||||
"level": 3,
|
"level": 3,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"info": "components",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "components",
|
||||||
|
"focus": false,
|
||||||
"level": 2,
|
"level": 2,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"children": [],
|
"children": [],
|
||||||
"info": "index.ts # comment",
|
"comment": "# comment",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "index.ts",
|
||||||
|
"focus": false,
|
||||||
"level": 2,
|
"level": 2,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"info": "client",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "client",
|
||||||
|
"focus": false,
|
||||||
"level": 1,
|
"level": 1,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"children": [],
|
"children": [],
|
||||||
"info": "index.ts",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "index.ts",
|
||||||
|
"focus": false,
|
||||||
"level": 2,
|
"level": 2,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"info": "node",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "node",
|
||||||
|
"focus": false,
|
||||||
"level": 1,
|
"level": 1,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"info": "src",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "src",
|
||||||
|
"focus": false,
|
||||||
"level": 0,
|
"level": 0,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"children": [],
|
"children": [],
|
||||||
"info": ".gitignore",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": ".gitignore",
|
||||||
|
"focus": false,
|
||||||
"level": 0,
|
"level": 0,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"children": [],
|
"children": [],
|
||||||
"info": "package.json",
|
"comment": "",
|
||||||
|
"diff": undefined,
|
||||||
|
"expanded": true,
|
||||||
|
"filename": "package.json",
|
||||||
|
"focus": false,
|
||||||
"level": 0,
|
"level": 0,
|
||||||
|
"type": "file",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`fileTreePlugin > should work with default options 1`] = `
|
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="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>
|
<template #icon><VPIcon provider="iconify" name="flat-color-icons:info" /></template>
|
||||||
</FileTreeNode>
|
</FileTreeNode>
|
||||||
@ -102,7 +162,7 @@ exports[`fileTreePlugin > should work with default options 1`] = `
|
|||||||
<FileTreeNode type="file" filename="package.json" :level="0">
|
<FileTreeNode type="file" filename="package.json" :level="0">
|
||||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-node" /></template>
|
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-node" /></template>
|
||||||
</FileTreeNode></div>
|
</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: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">
|
<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">
|
<FileTreeNode type="file" filename="README.md" :level="0">
|
||||||
<template #icon><VPIcon provider="iconify" name="flat-color-icons:info" /></template>
|
<template #icon><VPIcon provider="iconify" name="flat-color-icons:info" /></template>
|
||||||
</FileTreeNode></div>
|
</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>
|
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
||||||
</FileTreeNode>
|
</FileTreeNode>
|
||||||
<FileTreeNode expanded type="folder" filename="src" :level="0">
|
<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">
|
<FileTreeNode type="file" filename="README.md" :level="0">
|
||||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
||||||
</FileTreeNode></div>
|
</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>
|
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
||||||
</FileTreeNode>
|
</FileTreeNode>
|
||||||
<FileTreeNode expanded type="folder" filename="" :level="0">
|
<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>
|
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
||||||
</FileTreeNode>
|
</FileTreeNode>
|
||||||
</FileTreeNode></div>
|
</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: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>
|
<template #icon><VPIcon provider="iconify" name="vscode-icons:file-type-markdown" /></template>
|
||||||
</FileTreeNode>
|
</FileTreeNode>
|
||||||
@ -158,7 +218,7 @@ exports[`fileTreePlugin > should work with default options 1`] = `
|
|||||||
<FileTreeNode type="file" diff="remove" filename="source" :level="0">
|
<FileTreeNode type="file" diff="remove" filename="source" :level="0">
|
||||||
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
<template #icon><VPIcon provider="iconify" name="vscode-icons:default-file" /></template>
|
||||||
</FileTreeNode></div>
|
</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>
|
"<ul>
|
||||||
<li>
|
<li>
|
||||||
<p>item1</p>
|
<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">
|
<template #icon><VPIcon provider="iconify" name="vscode-icons:folder-type-docs" /></template><FileTreeNode type="file" filename="…" :level="1">
|
||||||
|
|
||||||
</FileTreeNode>
|
</FileTreeNode>
|
||||||
|
|||||||
@ -56,7 +56,9 @@ describe('fileTree > parseFileTreeNodeInfo', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function createMarkdown(options?: FileTreeOptions) {
|
function createMarkdown(options?: FileTreeOptions) {
|
||||||
return new MarkdownIt().use(fileTreePlugin, options)
|
const md = new MarkdownIt()
|
||||||
|
fileTreePlugin(md, options, {})
|
||||||
|
return md
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('fileTreePlugin', () => {
|
describe('fileTreePlugin', () => {
|
||||||
|
|||||||
@ -68,6 +68,7 @@ function toggle(ev: MouseEvent) {
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.vp-file-tree {
|
.vp-file-tree {
|
||||||
|
position: relative;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow: auto hidden;
|
overflow: auto hidden;
|
||||||
@ -87,6 +88,14 @@ function toggle(ev: MouseEvent) {
|
|||||||
transition: color var(--vp-t-color), border-color var(--vp-t-color);
|
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 {
|
.vp-file-tree .vp-file-tree-info {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
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)
|
if (state.src[pos] !== maker)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
pos += markerMinLen
|
|
||||||
|
|
||||||
// 检查 marker 长度是否满足要求
|
// 检查 marker 长度是否满足要求
|
||||||
for (pos = start + 1; pos <= max; pos++) {
|
for (pos = start + 1; pos <= max; pos++) {
|
||||||
if (state.src[pos] !== maker)
|
if (state.src[pos] !== maker)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { Markdown } from 'vuepress/markdown'
|
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
|
||||||
import type { FileTreeIconMode, FileTreeOptions } from '../../shared/index.js'
|
import type { CommonLocaleData, FileTreeIconMode, FileTreeOptions } from '../../shared/index.js'
|
||||||
import { removeEndingSlash } from 'vuepress/shared'
|
import { encodeData } from '@vuepress/helper'
|
||||||
|
import { ensureLeadingSlash, removeEndingSlash, resolveLocalePath } from 'vuepress/shared'
|
||||||
import { defaultFile, defaultFolder, getFileIcon } from '../fileIcons/index.js'
|
import { defaultFile, defaultFolder, getFileIcon } from '../fileIcons/index.js'
|
||||||
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
|
||||||
import { createContainerSyntaxPlugin } from './createContainer.js'
|
import { createContainerSyntaxPlugin } from './createContainer.js'
|
||||||
@ -8,8 +9,7 @@ import { createContainerSyntaxPlugin } from './createContainer.js'
|
|||||||
/**
|
/**
|
||||||
* 文件树节点结构
|
* 文件树节点结构
|
||||||
*/
|
*/
|
||||||
interface FileTreeNode {
|
interface FileTreeNode extends FileTreeNodeProps {
|
||||||
info: string
|
|
||||||
level: number
|
level: number
|
||||||
children: FileTreeNode[]
|
children: FileTreeNode[]
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ export interface FileTreeNodeProps {
|
|||||||
* @returns 文件树节点数组
|
* @returns 文件树节点数组
|
||||||
*/
|
*/
|
||||||
export function parseFileTreeRawContent(content: string): FileTreeNode[] {
|
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 stack: FileTreeNode[] = [root]
|
||||||
const lines = content.trimEnd().split('\n')
|
const lines = content.trimEnd().split('\n')
|
||||||
const spaceLength = lines[0].match(/^\s*/)?.[0].length ?? 0 // 去除行首空格/)
|
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 parent = stack[stack.length - 1]
|
||||||
const node: FileTreeNode = { info, level, children: [] }
|
const node: FileTreeNode = { level, children: [], ...parseFileTreeNodeInfo(info) }
|
||||||
parent.children.push(node)
|
parent.children.push(node)
|
||||||
stack.push(node)
|
stack.push(node)
|
||||||
}
|
}
|
||||||
@ -124,7 +124,11 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
|
|||||||
* @param md markdown 实例
|
* @param md markdown 实例
|
||||||
* @param options 文件树渲染选项
|
* @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 =>
|
const renderFileTree = (nodes: FileTreeNode[], meta: FileTreeAttrs): string =>
|
||||||
nodes.map((node) => {
|
nodes.map((node) => {
|
||||||
const { info, level, children } = node
|
const { level, children, filename, comment, focus, expanded, type, diff } = node
|
||||||
const { filename, comment, focus, expanded, type, diff } = parseFileTreeNodeInfo(info)
|
|
||||||
const isOmit = filename === '…' || filename === '...' /* fallback */
|
const isOmit = filename === '…' || filename === '...' /* fallback */
|
||||||
|
|
||||||
// 文件夹无子节点时补充省略号
|
// 文件夹无子节点时补充省略号
|
||||||
if (children.length === 0 && type === 'folder') {
|
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
|
const nodeType = children.length > 0 ? 'folder' : type
|
||||||
@ -173,13 +176,30 @@ ${renderedIcon}${renderedComment}${children.length > 0 ? renderFileTree(children
|
|||||||
return createContainerSyntaxPlugin(
|
return createContainerSyntaxPlugin(
|
||||||
md,
|
md,
|
||||||
'file-tree',
|
'file-tree',
|
||||||
(tokens, index) => {
|
(tokens, index, _, env: MarkdownEnv) => {
|
||||||
const token = tokens[index]
|
const token = tokens[index]
|
||||||
const nodes = parseFileTreeRawContent(token.content)
|
const nodes = parseFileTreeRawContent(token.content)
|
||||||
const meta = token.meta as FileTreeAttrs
|
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">${
|
return `<div class="vp-file-tree">${
|
||||||
meta.title ? `<p class="vp-file-tree-title">${meta.title}</p>` : ''
|
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 { App } from 'vuepress'
|
||||||
import type { Markdown } from 'vuepress/markdown'
|
import type { Markdown } from 'vuepress/markdown'
|
||||||
import type { MarkdownPowerPluginOptions } from '../../shared/index.js'
|
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 { alignPlugin } from './align.js'
|
||||||
import { cardPlugin } from './card.js'
|
import { cardPlugin } from './card.js'
|
||||||
import { chatPlugin } from './chat.js'
|
import { chatPlugin } from './chat.js'
|
||||||
@ -23,6 +25,7 @@ export async function containerPlugin(
|
|||||||
app: App,
|
app: App,
|
||||||
md: Markdown,
|
md: Markdown,
|
||||||
options: MarkdownPowerPluginOptions,
|
options: MarkdownPowerPluginOptions,
|
||||||
|
locales: ExactLocaleConfig<MDPowerLocaleData>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// ::: left / right / center / justify
|
// ::: left / right / center / justify
|
||||||
alignPlugin(md)
|
alignPlugin(md)
|
||||||
@ -56,7 +59,7 @@ export async function containerPlugin(
|
|||||||
|
|
||||||
if (options.fileTree) {
|
if (options.fileTree) {
|
||||||
// ::: file-tree
|
// ::: file-tree
|
||||||
fileTreePlugin(md, isPlainObject(options.fileTree) ? options.fileTree : {})
|
fileTreePlugin(md, isPlainObject(options.fileTree) ? options.fileTree : {}, findLocales(locales, 'common'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.codeTree) {
|
if (options.codeTree) {
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||||
|
|
||||||
export const deLocale: MDPowerLocaleData = {
|
export const deLocale: MDPowerLocaleData = {
|
||||||
|
common: {
|
||||||
|
copy: 'Kopieren',
|
||||||
|
copied: 'Kopiert',
|
||||||
|
},
|
||||||
encrypt: {
|
encrypt: {
|
||||||
hint: 'Der Inhalt ist verschlüsselt, bitte entsperren Sie ihn, um ihn anzuzeigen.',
|
hint: 'Der Inhalt ist verschlüsselt, bitte entsperren Sie ihn, um ihn anzuzeigen.',
|
||||||
placeholder: 'Passwort eingeben',
|
placeholder: 'Passwort eingeben',
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||||
|
|
||||||
export const enLocale: MDPowerLocaleData = {
|
export const enLocale: MDPowerLocaleData = {
|
||||||
|
common: {
|
||||||
|
copy: 'Copy',
|
||||||
|
copied: 'Copied',
|
||||||
|
},
|
||||||
encrypt: {
|
encrypt: {
|
||||||
hint: 'The content is encrypted, please unlock to view.',
|
hint: 'The content is encrypted, please unlock to view.',
|
||||||
placeholder: 'Enter password',
|
placeholder: 'Enter password',
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||||
|
|
||||||
export const frLocale: MDPowerLocaleData = {
|
export const frLocale: MDPowerLocaleData = {
|
||||||
|
common: {
|
||||||
|
copy: 'Copier',
|
||||||
|
copied: 'Copié',
|
||||||
|
},
|
||||||
encrypt: {
|
encrypt: {
|
||||||
hint: 'Le contenu est chiffré, veuillez déverrouiller pour afficher.',
|
hint: 'Le contenu est chiffré, veuillez déverrouiller pour afficher.',
|
||||||
placeholder: 'Entrez le mot de passe',
|
placeholder: 'Entrez le mot de passe',
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||||
|
|
||||||
export const jaLocale: MDPowerLocaleData = {
|
export const jaLocale: MDPowerLocaleData = {
|
||||||
|
common: {
|
||||||
|
copy: 'コピー',
|
||||||
|
copied: 'コピー済み',
|
||||||
|
},
|
||||||
encrypt: {
|
encrypt: {
|
||||||
hint: 'コンテンツは暗号化されています。閲覧するにはロックを解除してください。',
|
hint: 'コンテンツは暗号化されています。閲覧するにはロックを解除してください。',
|
||||||
placeholder: 'パスワードを入力',
|
placeholder: 'パスワードを入力',
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||||
|
|
||||||
export const koLocale: MDPowerLocaleData = {
|
export const koLocale: MDPowerLocaleData = {
|
||||||
|
common: {
|
||||||
|
copy: '복사',
|
||||||
|
copied: '복사됨',
|
||||||
|
},
|
||||||
encrypt: {
|
encrypt: {
|
||||||
hint: '내용이 암호화되어 있습니다. 잠금 해제 후 확인하세요.',
|
hint: '내용이 암호화되어 있습니다. 잠금 해제 후 확인하세요.',
|
||||||
placeholder: '비밀번호 입력',
|
placeholder: '비밀번호 입력',
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||||
|
|
||||||
export const ruLocale: MDPowerLocaleData = {
|
export const ruLocale: MDPowerLocaleData = {
|
||||||
|
common: {
|
||||||
|
copy: 'Копировать',
|
||||||
|
copied: 'Скопировано',
|
||||||
|
},
|
||||||
encrypt: {
|
encrypt: {
|
||||||
hint: 'Контент зашифрован, разблокируйте для просмотра.',
|
hint: 'Контент зашифрован, разблокируйте для просмотра.',
|
||||||
placeholder: 'Введите пароль',
|
placeholder: 'Введите пароль',
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||||
|
|
||||||
export const zhTWLocale: MDPowerLocaleData = {
|
export const zhTWLocale: MDPowerLocaleData = {
|
||||||
|
common: {
|
||||||
|
copy: '複製',
|
||||||
|
copied: '已複製',
|
||||||
|
},
|
||||||
encrypt: {
|
encrypt: {
|
||||||
hint: '內容已加密,請解鎖後查看。',
|
hint: '內容已加密,請解鎖後查看。',
|
||||||
placeholder: '輸入密碼',
|
placeholder: '輸入密碼',
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import type { MDPowerLocaleData } from '../../shared/locale'
|
import type { MDPowerLocaleData } from '../../shared/locale'
|
||||||
|
|
||||||
export const zhLocale: MDPowerLocaleData = {
|
export const zhLocale: MDPowerLocaleData = {
|
||||||
|
common: {
|
||||||
|
copy: '复制',
|
||||||
|
copied: '已复制',
|
||||||
|
},
|
||||||
encrypt: {
|
encrypt: {
|
||||||
hint: '内容已加密,请解锁后查看。',
|
hint: '内容已加密,请解锁后查看。',
|
||||||
placeholder: '输入密码',
|
placeholder: '输入密码',
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { Plugin } from 'vuepress/core'
|
|||||||
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
|
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
|
||||||
import { isPlainObject } from '@pengzhanbo/utils'
|
import { isPlainObject } from '@pengzhanbo/utils'
|
||||||
import { addViteOptimizeDepsInclude } from '@vuepress/helper'
|
import { addViteOptimizeDepsInclude } from '@vuepress/helper'
|
||||||
|
import { getFullLocaleConfig } from '@vuepress/helper'
|
||||||
import { extendsPageWithCodeTree } from './container/codeTree.js'
|
import { extendsPageWithCodeTree } from './container/codeTree.js'
|
||||||
import { containerPlugin } from './container/index.js'
|
import { containerPlugin } from './container/index.js'
|
||||||
import { demoPlugin, demoWatcher, extendsPageWithDemo, waitDemoRender } from './demo/index.js'
|
import { demoPlugin, demoWatcher, extendsPageWithDemo, waitDemoRender } from './demo/index.js'
|
||||||
@ -11,18 +12,27 @@ import { imageSizePlugin } from './enhance/imageSize.js'
|
|||||||
import { linksPlugin } from './enhance/links.js'
|
import { linksPlugin } from './enhance/links.js'
|
||||||
import { iconPlugin } from './icon/index.js'
|
import { iconPlugin } from './icon/index.js'
|
||||||
import { inlineSyntaxPlugin } from './inline/index.js'
|
import { inlineSyntaxPlugin } from './inline/index.js'
|
||||||
|
import { LOCALE_OPTIONS } from './locales/index.js'
|
||||||
import { prepareConfigFile } from './prepareConfigFile.js'
|
import { prepareConfigFile } from './prepareConfigFile.js'
|
||||||
import { provideData } from './provideData.js'
|
import { provideData } from './provideData.js'
|
||||||
|
|
||||||
export function markdownPowerPlugin(
|
export function markdownPowerPlugin(
|
||||||
options: MarkdownPowerPluginOptions = {},
|
options: MarkdownPowerPluginOptions = {},
|
||||||
): Plugin {
|
): Plugin {
|
||||||
|
return (app) => {
|
||||||
|
const locales = getFullLocaleConfig({
|
||||||
|
app,
|
||||||
|
name: 'vuepress-plugin-md-power',
|
||||||
|
default: LOCALE_OPTIONS,
|
||||||
|
config: options.locales,
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'vuepress-plugin-md-power',
|
name: 'vuepress-plugin-md-power',
|
||||||
|
|
||||||
clientConfigFile: app => prepareConfigFile(app, options),
|
clientConfigFile: app => prepareConfigFile(app, options),
|
||||||
|
|
||||||
define: app => provideData(app, options),
|
define: provideData(options, locales),
|
||||||
|
|
||||||
alias: (_, isServer) => {
|
alias: (_, isServer) => {
|
||||||
if (!isServer) {
|
if (!isServer) {
|
||||||
@ -64,7 +74,7 @@ export function markdownPowerPlugin(
|
|||||||
if (options.demo)
|
if (options.demo)
|
||||||
demoPlugin(app, md)
|
demoPlugin(app, md)
|
||||||
|
|
||||||
await containerPlugin(app, md, options)
|
await containerPlugin(app, md, options, locales)
|
||||||
await imageSizePlugin(app, md, options.imageSize)
|
await imageSizePlugin(app, md, options.imageSize)
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -88,3 +98,4 @@ export function markdownPowerPlugin(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -15,6 +15,9 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp
|
|||||||
const imports = new Set<string>()
|
const imports = new Set<string>()
|
||||||
const enhances = 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'`)
|
imports.add(`import Tabs from '${CLIENT_FOLDER}components/Tabs.vue'`)
|
||||||
enhances.add(`app.component('Tabs', Tabs)`)
|
enhances.add(`app.component('Tabs', Tabs)`)
|
||||||
|
|
||||||
|
|||||||
@ -1,22 +1,17 @@
|
|||||||
import type { App, LocaleConfig } from 'vuepress'
|
import type { ExactLocaleConfig } from '@vuepress/helper'
|
||||||
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
|
import type { MarkdownPowerPluginOptions, MDPowerLocaleData } from '../shared/index.js'
|
||||||
import type { MDPowerLocaleData } from '../shared/locale.js'
|
|
||||||
import { getFullLocaleConfig } from '@vuepress/helper'
|
|
||||||
import { isPackageExists } from 'local-pkg'
|
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 = {
|
const markdownOptions = {
|
||||||
plot: options.plot,
|
plot: options.plot,
|
||||||
pdf: options.pdf,
|
pdf: options.pdf,
|
||||||
}
|
}
|
||||||
|
|
||||||
const locales = getFullLocaleConfig({
|
|
||||||
app,
|
|
||||||
name: 'vuepress-plugin-md-power',
|
|
||||||
default: LOCALE_OPTIONS,
|
|
||||||
config: options.locales,
|
|
||||||
})
|
|
||||||
const icon = options.icon ?? { provider: 'iconify' }
|
const icon = options.icon ?? { provider: 'iconify' }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -29,16 +24,3 @@ export function provideData(app: App, options: MarkdownPowerPluginOptions): Reco
|
|||||||
__MD_POWER_ENCRYPT_LOCALES__: options.encrypt ? findLocales(locales, 'encrypt') : {},
|
__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 './fileTree.js'
|
||||||
export * from './icon.js'
|
export * from './icon.js'
|
||||||
export * from './jsfiddle.js'
|
export * from './jsfiddle.js'
|
||||||
|
export * from './locale.js'
|
||||||
export * from './npmTo.js'
|
export * from './npmTo.js'
|
||||||
export * from './pdf.js'
|
export * from './pdf.js'
|
||||||
export * from './plot.js'
|
export * from './plot.js'
|
||||||
|
|||||||
@ -2,5 +2,11 @@ import type { LocaleData } from 'vuepress'
|
|||||||
import type { EncryptSnippetLocale } from './encrypt'
|
import type { EncryptSnippetLocale } from './encrypt'
|
||||||
|
|
||||||
export interface MDPowerLocaleData extends LocaleData {
|
export interface MDPowerLocaleData extends LocaleData {
|
||||||
|
common?: CommonLocaleData
|
||||||
encrypt?: EncryptSnippetLocale
|
encrypt?: EncryptSnippetLocale
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CommonLocaleData extends LocaleData {
|
||||||
|
copy?: string
|
||||||
|
copied?: string
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user