diff --git a/plugins/plugin-md-power/src/client/components/CodeTabs.vue b/plugins/plugin-md-power/src/client/components/CodeTabs.vue index 5f1df5e3..95afd174 100644 --- a/plugins/plugin-md-power/src/client/components/CodeTabs.vue +++ b/plugins/plugin-md-power/src/client/components/CodeTabs.vue @@ -184,6 +184,10 @@ function onTabNavClick(index: number): void { transition: background var(--vp-t-color); } +.vp-code-tab-nav:focus-visible { + outline: none; +} + .vp-code-tab-nav.active { color: var(--vp-code-tab-active-text-color); background: transparent; diff --git a/plugins/plugin-md-power/src/node/container/codeTabs.ts b/plugins/plugin-md-power/src/node/container/codeTabs.ts index 10164a6f..9e52a45f 100644 --- a/plugins/plugin-md-power/src/node/container/codeTabs.ts +++ b/plugins/plugin-md-power/src/node/container/codeTabs.ts @@ -1,9 +1,32 @@ -import type { PluginSimple } from 'markdown-it' +import type { PluginWithOptions } from 'markdown-it' +import type { CodeTabsOptions } from '../../shared/index.js' import { tab } from '@mdit/plugin-tab' -import { getFileIconName } from '../fileIcons/index.js' +import { isPlainObject } from '@vuepress/helper' +import { definitions, getFileIconName, getFileIconTypeFromExtension } from '../fileIcons/index.js' import { stringifyProp } from '../utils/stringifyProp.js' -export const codeTabs: PluginSimple = (md) => { +export const codeTabs: PluginWithOptions = (md, options: CodeTabsOptions = {}) => { + const getIcon = (filename: string): string | undefined => { + if (options.icon === false) + return undefined + const { named, extensions } = isPlainObject(options.icon) ? options.icon : {} + if (named === false && definitions.named[filename]) + return undefined + if (extensions === false && getFileIconTypeFromExtension(filename)) { + return undefined + } + const hasNamed = named && named.length + const hasExt = extensions && extensions.length + if (hasNamed || hasExt) { + if (hasNamed && named.includes(filename)) + return definitions.named[filename] + if (hasExt && extensions.some(ext => filename.endsWith(ext))) + return getFileIconTypeFromExtension(filename) + return undefined + } + return getFileIconName(filename) + } + tab(md, { name: 'code-tabs', @@ -17,7 +40,7 @@ export const codeTabs: PluginSimple = (md) => { }) const titlesContent = titles.map((title, index) => { - const icon = getFileIconName(title) + const icon = getIcon(title) return `` }).join('') diff --git a/plugins/plugin-md-power/src/node/container/fileTree.ts b/plugins/plugin-md-power/src/node/container/fileTree.ts index df1964ec..f1755b75 100644 --- a/plugins/plugin-md-power/src/node/container/fileTree.ts +++ b/plugins/plugin-md-power/src/node/container/fileTree.ts @@ -1,8 +1,9 @@ import type { Markdown } from 'vuepress/markdown' +import type { FileTreeIconMode, FileTreeOptions } from '../../shared/index.js' import Token from 'markdown-it/lib/token.mjs' import container from 'markdown-it-container' import { removeEndingSlash, removeLeadingSlash } from 'vuepress/shared' -import { getFileIcon } from '../fileIcons/index.js' +import { defaultFile, defaultFolder, getFileIcon } from '../fileIcons/index.js' interface FileTreeNode { filename: string @@ -17,10 +18,22 @@ const closeType = `container_${type}_close` const componentName = 'FileTreeItem' const itemOpen = 'file_tree_item_open' const itemClose = 'file_tree_item_close' +const RE_SIMPLE_ICON = /:simple-icon\b/ +const RE_COLORED_ICON = /:colored-icon\b/ + +export function fileTreePlugin(md: Markdown, options: FileTreeOptions = {}) { + const getIcon = (filename: string, type: 'folder' | 'file', mode?: FileTreeIconMode): string => { + mode ||= options.icon || 'colored' + if (mode === 'simple') + return type === 'folder' ? defaultFolder : defaultFile + return getFileIcon(filename, type) + } -export function fileTreePlugin(md: Markdown) { const validate = (info: string): boolean => info.trim().startsWith(type) + const render = (tokens: Token[], idx: number): string => { + const mode = getFileIconMode(tokens[idx].info) + if (tokens[idx].nesting === 1) { const hasRes: number[] = [] // level stack for ( @@ -36,7 +49,7 @@ export function fileTreePlugin(md: Markdown) { hasRes.push(token.level) const [info, inline] = result const { filename, type, expanded, empty } = info - const icon = getFileIcon(filename, type) + const icon = getIcon(filename, type, mode) token.type = itemOpen token.tag = componentName @@ -56,9 +69,8 @@ export function fileTreePlugin(md: Markdown) { } } } - const info = tokens[idx].info.trim() - const title = info.slice(type.length).trim() + const title = resolveTitle(tokens[idx].info) return `
${title ? `

${title}

` : ''}` } else { @@ -69,6 +81,20 @@ export function fileTreePlugin(md: Markdown) { md.use(container, type, { validate, render }) } +function getFileIconMode(info: string): FileTreeIconMode | undefined { + if (RE_SIMPLE_ICON.test(info)) + return 'simple' + if (RE_COLORED_ICON.test(info)) + return 'colored' + return undefined +} + +function resolveTitle(info: string): string { + info = info.trim().slice(type.length).trim() + info = info.replace(RE_SIMPLE_ICON, '').replace(RE_COLORED_ICON, '') + return info.trim() +} + export function resolveTreeNodeInfo( tokens: Token[], current: Token, @@ -143,19 +169,21 @@ export function updateInlineToken(inline: Token, info: FileTreeNode, icon: strin if (token.content.includes(' ')) { const [first, ...other] = token.content.split(' ') const text = new Token('text', '', 0) - text.content = first + text.content = removeEndingSlash(first) tokens.push(text) const comment = new Token('text', '', 0) comment.content = other.join(' ') children.unshift(comment) } else { + token.content = removeEndingSlash(token.content) tokens.push(token) } if (!isStrongTag) break } else if (token.tag === 'strong') { + token.content = removeEndingSlash(token.content) tokens.push(token) if (token.nesting === 1) { isStrongTag = true diff --git a/plugins/plugin-md-power/src/node/container/index.ts b/plugins/plugin-md-power/src/node/container/index.ts index e186ac57..f76d98f4 100644 --- a/plugins/plugin-md-power/src/node/container/index.ts +++ b/plugins/plugin-md-power/src/node/container/index.ts @@ -1,6 +1,7 @@ import type { App } from 'vuepress' import type { Markdown } from 'vuepress/markdown' import type { MarkdownPowerPluginOptions } from '../../shared/index.js' +import { isPlainObject } from '@vuepress/helper' import { alignPlugin } from './align.js' import { codeTabs } from './codeTabs.js' import { fileTreePlugin } from './fileTree.js' @@ -17,7 +18,7 @@ export async function containerPlugin( // ::: tabs tabs(md) // ::: code-tabs - codeTabs(md) + codeTabs(md, options.codeTabs) if (options.repl) await langReplPlugin(app, md, options.repl) diff --git a/plugins/plugin-md-power/src/node/fileIcons/findIcon.ts b/plugins/plugin-md-power/src/node/fileIcons/findIcon.ts index 8cc28aef..d123ed60 100644 --- a/plugins/plugin-md-power/src/node/fileIcons/findIcon.ts +++ b/plugins/plugin-md-power/src/node/fileIcons/findIcon.ts @@ -30,7 +30,7 @@ export function getFileIconName(fileName: string, type: 'file' | 'folder' = 'fil return icon } -function getFileIconTypeFromExtension(fileName: string): string | undefined { +export function getFileIconTypeFromExtension(fileName: string): string | undefined { const firstDotIndex = fileName.indexOf('.') if (firstDotIndex === -1) return diff --git a/plugins/plugin-md-power/src/node/inline/index.ts b/plugins/plugin-md-power/src/node/inline/index.ts index 8b138fad..92e52d62 100644 --- a/plugins/plugin-md-power/src/node/inline/index.ts +++ b/plugins/plugin-md-power/src/node/inline/index.ts @@ -6,6 +6,7 @@ import { mark } from '@mdit/plugin-mark' import { sub } from '@mdit/plugin-sub' import { sup } from '@mdit/plugin-sup' import { tasklist } from '@mdit/plugin-tasklist' +import { isPlainObject } from '@vuepress/helper' import { iconsPlugin } from './icons.js' import { plotPlugin } from './plot.js'