feat(markdown)!: group code copy button strings into one option

`markdown.codeCopyButton` now holds both the button tooltip
(`tooltipText`) and the text shown after copying (`copiedText`),
per-locale overridable via `locales.<index>.markdown.codeCopyButton`.
The copied text is emitted as a `data-copied` attribute on the button
and rendered by the theme with `content: attr(data-copied)`.

BREAKING CHANGE: `markdown.codeCopyButtonTitle` is now
`markdown.codeCopyButton.tooltipText`, and its default changed from
"Copy Code" to "Copy code". The `--vp-code-copy-copied-text-content`
CSS variable and its built-in per-language `:lang()` defaults are
removed - set `codeCopyButton.copiedText` (per locale) instead.

close #4431

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/5335/head
Divyansh Singh 3 days ago
parent 4e263772b4
commit 2fa0dedbb2

@ -91,7 +91,7 @@ describe('node/markdown/plugins/containers', () => {
<p>content</p>
</div>
<details class="details custom-block"><summary>Click me to toggle the code</summary>
<div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre><code class="language-js">console.log('hi')
<div class="language-js"><button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">js</span><pre><code class="language-js">console.log('hi')
</code></pre>
</div></details>
"
@ -273,9 +273,9 @@ describe('node/markdown/plugins/containers', () => {
].join('\n')
expect(await render(src)).toMatchInlineSnapshot(`
"<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-0" id="tab-1" checked><label data-title="config.js" for="tab-1">config.js</label><input type="radio" name="group-0" id="tab-2" ><label data-title="config.ts" for="tab-2">config.ts</label></div><div class="blocks">
<div class="language-js active"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre><code class="language-js">const a = 1
<div class="language-js active"><button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">js</span><pre><code class="language-js">const a = 1
</code></pre>
</div><div class="language-ts"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre><code class="language-ts">const a: number = 1
</div><div class="language-ts"><button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">ts</span><pre><code class="language-ts">const a: number = 1
</code></pre>
</div></div></div>
"
@ -435,7 +435,7 @@ describe('node/markdown/plugins/containers (github alerts)', () => {
<ul>
<li>list item</li>
</ul>
<div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre><code class="language-js">const a = 1
<div class="language-js"><button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">js</span><pre><code class="language-js">const a = 1
</code></pre>
</div></div>
"
@ -569,18 +569,24 @@ describe('node/markdown/plugins/containers (locales)', () => {
).rejects.toThrow('is not registered in the root markdown config')
})
test('resolves the code copy button title for the active locale', async () => {
test('resolves the code copy button strings for the active locale', async () => {
const src = '```js\nconst a = 1\n```'
const opts: MarkdownOptions = {
codeCopyButtonTitle: 'Copy Code',
locales: { zh: { codeCopyButtonTitle: '复制代码' } }
codeCopyButton: { copiedText: 'Copied!' },
locales: {
zh: {
codeCopyButton: { tooltipText: '复制代码', copiedText: '已复制' }
}
}
}
expect(await render(src, opts, { localeIndex: 'zh' })).toContain(
'title="复制代码"'
)
expect(await render(src, opts, { localeIndex: 'es' })).toContain(
'title="Copy Code"'
)
expect(await render(src, opts)).toContain('title="Copy Code"')
const zh = await render(src, opts, { localeIndex: 'zh' })
expect(zh).toContain('title="复制代码"')
expect(zh).toContain('data-copied="已复制"')
const es = await render(src, opts, { localeIndex: 'es' })
expect(es).toContain('title="Copy code"')
expect(es).toContain('data-copied="Copied!"')
const root = await render(src, opts)
expect(root).toContain('title="Copy code"')
expect(root).toContain('data-copied="Copied!"')
})
})

@ -45,7 +45,6 @@ export default defineConfig({
markdown: {
math: true,
codeCopyButtonTitle: 'Copy code',
codeTransformers: [
// We use `[!!code` and `@@include` in demo to prevent transformation,
// here we revert it back.

@ -59,7 +59,7 @@ Refer [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types
## Per-locale Markdown Strings
Strings baked into pages by the markdown renderer - the default titles of [custom containers](./markdown#custom-containers) and [GitHub-flavored alerts](./markdown#github-flavored-alerts), and the tooltip of the code copy button - can be overridden per locale with the `markdown` key of a locale entry:
Strings baked into pages by the markdown renderer - the default titles of [custom containers](./markdown#custom-containers) and [GitHub-flavored alerts](./markdown#github-flavored-alerts), and the strings of the code copy button - can be overridden per locale with the `markdown` key of a locale entry:
```ts [docs/.vitepress/config.ts]
import { defineConfig } from 'vitepress'
@ -76,7 +76,7 @@ export default defineConfig({
warningLabel: '警告'
// ...the other labels, and titles of `customContainers`
},
codeCopyButtonTitle: '复制代码'
codeCopyButton: { tooltipText: '复制代码', copiedText: '已复制' }
}
}
}

@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = {
importantLabel: 'IMPORTANTE',
cautionLabel: 'PRECAUCIÓN'
},
codeCopyButtonTitle: 'Copiar código'
codeCopyButton: { tooltipText: 'Copiar código', copiedText: 'Copiado' }
}
export default defineAdditionalConfig({

@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = {
importantLabel: 'مهم',
cautionLabel: 'احتیاط'
},
codeCopyButtonTitle: 'کپی کد'
codeCopyButton: { tooltipText: 'کپی کد', copiedText: 'کپی شد' }
}
export default defineAdditionalConfig({

@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = {
importantLabel: '重要',
cautionLabel: '注意'
},
codeCopyButtonTitle: 'コードをコピー'
codeCopyButton: { tooltipText: 'コードをコピー', copiedText: 'コピー完了' }
}
export default defineAdditionalConfig({

@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = {
importantLabel: '중요',
cautionLabel: '주의'
},
codeCopyButtonTitle: '코드 복사'
codeCopyButton: { tooltipText: '코드 복사', copiedText: '복사됨' }
}
export default defineAdditionalConfig({

@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = {
importantLabel: 'IMPORTANTE',
cautionLabel: 'CUIDADO'
},
codeCopyButtonTitle: 'Copiar código'
codeCopyButton: { tooltipText: 'Copiar código', copiedText: 'Copiado' }
}
export default defineAdditionalConfig({

@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = {
importantLabel: 'ВАЖНО',
cautionLabel: 'ВНИМАНИЕ'
},
codeCopyButtonTitle: 'Скопировать код'
codeCopyButton: { tooltipText: 'Скопировать код', copiedText: 'Скопировано' }
}
export default defineAdditionalConfig({

@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = {
importantLabel: '重要',
cautionLabel: '小心'
},
codeCopyButtonTitle: '复制代码'
codeCopyButton: { tooltipText: '复制代码', copiedText: '已复制' }
}
export default defineAdditionalConfig({

@ -518,7 +518,7 @@
color: var(--vp-code-copy-code-active-text);
background-color: var(--vp-code-copy-code-hover-bg);
white-space: nowrap;
content: var(--vp-code-copy-copied-text-content);
content: attr(data-copied);
}
.vp-doc [class*='language-'] > span.lang {

@ -365,7 +365,6 @@
--vp-code-copy-code-hover-border-color: var(--vp-c-divider);
--vp-code-copy-code-hover-bg: var(--vp-c-bg);
--vp-code-copy-code-active-text: var(--vp-c-text-2);
--vp-code-copy-copied-text-content: 'Copied';
--vp-code-tab-divider: var(--vp-code-block-divider-color);
--vp-code-tab-text-color: var(--vp-c-text-2);
@ -375,26 +374,6 @@
--vp-code-tab-active-bar-color: var(--vp-c-brand-1);
}
:lang(es),
:lang(pt) {
--vp-code-copy-copied-text-content: 'Copiado';
}
:lang(fa) {
--vp-code-copy-copied-text-content: 'کپی شد';
}
:lang(ja) {
--vp-code-copy-copied-text-content: 'コピー完了';
}
:lang(ko) {
--vp-code-copy-copied-text-content: '복사됨';
}
:lang(ru) {
--vp-code-copy-copied-text-content: 'Скопировано';
}
:lang(zh) {
--vp-code-copy-copied-text-content: '已复制';
}
/**
* Component: Button
* -------------------------------------------------------------------------- */

@ -35,7 +35,12 @@ import mditCjkFriendly from 'markdown-it-cjk-friendly'
import path from 'node:path'
import type { BuiltinLanguage, BuiltinTheme, Highlighter } from 'shiki'
import type { Logger } from 'vite'
import type { Awaitable, LocaleConfig, MarkdownLocaleOptions } from '../shared'
import type {
Awaitable,
CodeCopyButtonOptions,
LocaleConfig,
MarkdownLocaleOptions
} from '../shared'
import {
containerPlugin,
gitHubAlertsPlugin,
@ -176,10 +181,11 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
*/
preWrapper?: boolean
/**
* The tooltip text for the copy button in code blocks.
* @default 'Copy Code'
* Strings for the copy button in code blocks: `tooltipText` is the
* button's tooltip and `copiedText` is shown next to it after copying.
* @default { tooltipText: 'Copy code', copiedText: 'Copied' }
*/
codeCopyButtonTitle?: string
codeCopyButton?: CodeCopyButtonOptions
/**
* Custom language labels for display.
* Overrides the default language label shown in code blocks.
@ -349,7 +355,10 @@ export async function createMarkdownRenderer(
publicDir ??= path.resolve(srcDir, 'public')
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
const codeCopyButtonTitle = options.codeCopyButtonTitle || 'Copy Code'
const codeCopyButton = {
tooltipText: options.codeCopyButton?.tooltipText || 'Copy code',
copiedText: options.codeCopyButton?.copiedText || 'Copied'
}
const [highlight, dispose] = options.highlight
? [options.highlight, () => {}]
@ -372,7 +381,7 @@ export async function createMarkdownRenderer(
// VitePress customizations
if (options.preWrapper !== false) {
preWrapperPlugin(md, {
codeCopyButtonTitle,
codeCopyButton,
languageLabel: options.languageLabel,
locales: options.locales
})

@ -2,10 +2,10 @@ import type { MarkdownItAsync } from 'markdown-it-async'
import type { MarkdownEnv, MarkdownLocaleOptions } from '../../shared'
export interface Options {
codeCopyButtonTitle: string
codeCopyButton: { tooltipText: string; copiedText: string }
languageLabel?: Record<string, string>
/**
* Per-locale overrides for the copy button title, keyed by locale index.
* Per-locale overrides for the copy button strings, keyed by locale index.
*/
locales?: Record<string, MarkdownLocaleOptions | undefined>
}
@ -31,13 +31,18 @@ export function preWrapperPlugin(md: MarkdownItAsync, options: Options) {
const label = langLabel[lang.toLowerCase()] || lang.replace(/_/g, ' ')
const { localeIndex } = (env ?? {}) as MarkdownEnv
const codeCopyButtonTitle =
(localeIndex && options.locales?.[localeIndex]?.codeCopyButtonTitle) ||
options.codeCopyButtonTitle
const localeButton = localeIndex
? options.locales?.[localeIndex]?.codeCopyButton
: undefined
const tooltipText =
localeButton?.tooltipText || options.codeCopyButton.tooltipText
// rendered by the theme via `content: attr(data-copied)`
const copiedText =
localeButton?.copiedText || options.codeCopyButton.copiedText
return (
`<div class="language-${lang}${active}">` +
`<button title="${codeCopyButtonTitle}" class="copy"></button>` +
`<button title="${tooltipText}" data-copied="${copiedText}" class="copy"></button>` +
`<span class="lang">${label}</span>` +
fence(...args) +
'</div>'

@ -10,6 +10,7 @@ export type {
AdditionalConfigDict,
AdditionalConfigLoader,
Awaitable,
CodeCopyButtonOptions,
ContainerOptions,
DefaultTheme,
HeadConfig,

17
types/shared.d.ts vendored

@ -230,6 +230,19 @@ export interface ContainerOptions {
customContainers?: Record<string, string>
}
export interface CodeCopyButtonOptions {
/**
* The tooltip (`title` attribute) of the copy button in code blocks.
* @default 'Copy code'
*/
tooltipText?: string
/**
* The text shown next to the copy button after copying.
* @default 'Copied'
*/
copiedText?: string
}
/**
* Build-time markdown strings that can be overridden per locale. Set them
* under `locales.<index>.markdown` in the site config; values fall back to
@ -242,9 +255,9 @@ export interface MarkdownLocaleOptions {
*/
container?: ContainerOptions
/**
* Locale-specific tooltip text for the copy button in code blocks.
* Locale-specific strings for the copy button in code blocks.
*/
codeCopyButtonTitle?: string
codeCopyButton?: CodeCopyButtonOptions
}
export type LocaleConfig<ThemeConfig = any> = Record<

Loading…
Cancel
Save