diff --git a/plugins/plugin-md-power/src/node/container/codeTabs.ts b/plugins/plugin-md-power/src/node/container/codeTabs.ts index b369ce65..a16bda12 100644 --- a/plugins/plugin-md-power/src/node/container/codeTabs.ts +++ b/plugins/plugin-md-power/src/node/container/codeTabs.ts @@ -5,11 +5,17 @@ import { isPlainObject } from '@vuepress/helper' import { definitions, getFileIconName, getFileIconTypeFromExtension } from '../fileIcons/index.js' import { stringifyProp } from '../utils/stringifyProp.js' -export const codeTabs: PluginWithOptions = (md, options: CodeTabsOptions = {}) => { - const getIcon = (filename: string): string | void => { - if (options.icon === false) - return undefined - const { named, extensions } = isPlainObject(options.icon) ? options.icon : {} +export function createCodeTabIconGetter( + options: CodeTabsOptions = {}, +): (filename: string) => string | void { + const noop = () => undefined + + if (options.icon === false) + return noop + + const { named, extensions } = isPlainObject(options.icon) ? options.icon : {} + + return function getIcon(filename: string): string | void { if (named === false && definitions.named[filename]) return undefined if (extensions === false && getFileIconTypeFromExtension(filename)) { @@ -26,6 +32,10 @@ export const codeTabs: PluginWithOptions = (md, options: CodeTa } return getFileIconName(filename) } +} + +export const codeTabs: PluginWithOptions = (md, options: CodeTabsOptions = {}) => { + const getIcon = createCodeTabIconGetter(options) tab(md, { name: 'code-tabs', diff --git a/plugins/plugin-md-power/src/node/index.ts b/plugins/plugin-md-power/src/node/index.ts index 7b1a4de2..f3b2fb9f 100644 --- a/plugins/plugin-md-power/src/node/index.ts +++ b/plugins/plugin-md-power/src/node/index.ts @@ -1,3 +1,4 @@ export * from '../shared/index.js' +export { createCodeTabIconGetter } from './container/codeTabs.js' export { resolveImageSize } from './enhance/imageSize.js' export * from './plugin.js' diff --git a/theme/src/client/styles/code.css b/theme/src/client/styles/code.css index 6fd0f655..58e79577 100644 --- a/theme/src/client/styles/code.css +++ b/theme/src/client/styles/code.css @@ -42,7 +42,7 @@ html:not([data-theme="dark"]) .vp-code span { z-index: 3; font-size: 0.75rem; color: var(--vp-code-line-number-color); - content: attr(data-title); + content: attr(data-ext); transition: color var(--vp-t-color); } @@ -236,8 +236,9 @@ html:not([data-theme="dark"]) .vp-code span { content: "+"; } -/** - * Copy Code Button +/* + Copy Code Button + -------------------------------------------------------------------------- */ .vp-copy-code-button { --copy-code-c-text: var(--vp-code-block-color); @@ -280,3 +281,33 @@ html:not([data-theme="dark"]) .vp-code span { .vp-doc div[class*="language-"].has-collapsed-lines:not(.collapsed) .collapsed-lines:hover { --vp-collapsed-lines-bg: transparent; } + +/* + Code Block Title + --------------------------------------------------------------- + */ +:root { + --code-title-c-bg: var(--vp-code-block-bg); + --code-title-divider: var(--vp-c-divider); + --code-title-c-text: var(--vp-c-text-1); +} + +/* stylelint-disable-next-line no-descending-specificity */ +.vp-doc .code-block-title div[class*="language-"] { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.code-block-title .code-block-title-bar { + margin-bottom: -16px; +} + +.code-block-title .code-block-title-bar .title { + padding: 10px 12px; +} + +.code-block-title .code-block-title-bar .title .vp-icon { + width: 18px; + height: 18px; + margin-left: 0; +} diff --git a/theme/src/node/plugins/getPlugins.ts b/theme/src/node/plugins/getPlugins.ts index db037226..bd806b53 100644 --- a/theme/src/node/plugins/getPlugins.ts +++ b/theme/src/node/plugins/getPlugins.ts @@ -24,7 +24,7 @@ import { shikiPlugin } from '@vuepress/plugin-shiki' import { sitemapPlugin } from '@vuepress/plugin-sitemap' import { watermarkPlugin } from '@vuepress/plugin-watermark' import { mdEnhancePlugin } from 'vuepress-plugin-md-enhance' -import { markdownPowerPlugin } from 'vuepress-plugin-md-power' +import { createCodeTabIconGetter, markdownPowerPlugin } from 'vuepress-plugin-md-power' import { getThemeConfig } from '../loadConfig/index.js' export interface SetupPluginOptions { @@ -118,10 +118,16 @@ export function getPlugins({ const shikiTheme = shikiOptions && 'theme' in shikiOptions ? shikiOptions.theme : shikiOptions && 'themes' in shikiOptions ? shikiOptions.themes : { light: 'vitesse-light', dark: 'vitesse-dark' } if (shikiOptions !== false) { - const { twoslash, ...restShikiOptions } = isPlainObject(shikiOptions) ? shikiOptions : {} + const { twoslash, codeBlockTitle: _, langs = [], ...restShikiOptions } = isPlainObject(shikiOptions) ? shikiOptions : {} const twoslashOptions = twoslash === true ? {} : twoslash + const markdownPower = isPlainObject(pluginOptions.markdownPower) ? pluginOptions.markdownPower : {} + const getIcon = createCodeTabIconGetter(markdownPower.codeTabs) plugins.push(shikiPlugin({ // enable some default features + langs: [ + ...twoslash ? ['js', 'ts', 'vue'] : [], + ...langs, + ], notationDiff: true, notationErrorLevel: true, notationFocus: true, @@ -129,6 +135,15 @@ export function getPlugins({ notationWordHighlight: true, highlightLines: true, collapsedLines: false, + codeBlockTitle: (title, code) => { + const icon = getIcon(title) + return `
+
+ ${icon ? `` : ''}${title} +
+ ${code} +
` + }, twoslash: isPlainObject(twoslashOptions) ? { ...twoslashOptions, @@ -171,7 +186,6 @@ export function getPlugins({ if (pluginOptions.watermark) { plugins.push(watermarkPlugin({ - delay: 300, enabled: true, ...isPlainObject(pluginOptions.watermark) ? pluginOptions.watermark : {}, }))