From 2fa0dedbb241e901569c2e20690e8e5917f4bd26 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Sat, 25 Jul 2026 15:53:49 +0530 Subject: [PATCH] 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..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 --- .../node/markdown/plugins/containers.test.ts | 34 +++++++++++-------- docs/.vitepress/config.ts | 1 - docs/en/guide/i18n.md | 4 +-- docs/es/config.ts | 2 +- docs/fa/config.ts | 2 +- docs/ja/config.ts | 2 +- docs/ko/config.ts | 2 +- docs/pt/config.ts | 2 +- docs/ru/config.ts | 2 +- docs/zh/config.ts | 2 +- .../styles/components/vp-doc.css | 2 +- src/client/theme-default/styles/vars.css | 21 ------------ src/node/markdown/markdown.ts | 21 ++++++++---- src/node/markdown/plugins/preWrapper.ts | 17 ++++++---- src/shared/shared.ts | 1 + types/shared.d.ts | 17 ++++++++-- 16 files changed, 72 insertions(+), 60 deletions(-) diff --git a/__tests__/unit/node/markdown/plugins/containers.test.ts b/__tests__/unit/node/markdown/plugins/containers.test.ts index ee71e2925..f3005c9b8 100644 --- a/__tests__/unit/node/markdown/plugins/containers.test.ts +++ b/__tests__/unit/node/markdown/plugins/containers.test.ts @@ -91,7 +91,7 @@ describe('node/markdown/plugins/containers', () => {

content

Click me to toggle the code -
js
console.log('hi')
+      
js
console.log('hi')
       
" @@ -273,9 +273,9 @@ describe('node/markdown/plugins/containers', () => { ].join('\n') expect(await render(src)).toMatchInlineSnapshot(` "
-
js
const a = 1
+      
js
const a = 1
       
-
ts
const a: number = 1
+      
ts
const a: number = 1
       
" @@ -435,7 +435,7 @@ describe('node/markdown/plugins/containers (github alerts)', () => {
  • list item
-
js
const a = 1
+      
js
const a = 1
       
" @@ -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!"') }) }) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 9f671b12c..1be8a3177 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -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. diff --git a/docs/en/guide/i18n.md b/docs/en/guide/i18n.md index ac3fd22f4..683ed01c2 100644 --- a/docs/en/guide/i18n.md +++ b/docs/en/guide/i18n.md @@ -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: '已复制' } } } } diff --git a/docs/es/config.ts b/docs/es/config.ts index b0207abb2..b380083f5 100644 --- a/docs/es/config.ts +++ b/docs/es/config.ts @@ -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({ diff --git a/docs/fa/config.ts b/docs/fa/config.ts index f61cafed3..60b1cd222 100644 --- a/docs/fa/config.ts +++ b/docs/fa/config.ts @@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = { importantLabel: 'مهم', cautionLabel: 'احتیاط' }, - codeCopyButtonTitle: 'کپی کد' + codeCopyButton: { tooltipText: 'کپی کد', copiedText: 'کپی شد' } } export default defineAdditionalConfig({ diff --git a/docs/ja/config.ts b/docs/ja/config.ts index fa22a8d41..bf3c3e6f4 100644 --- a/docs/ja/config.ts +++ b/docs/ja/config.ts @@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = { importantLabel: '重要', cautionLabel: '注意' }, - codeCopyButtonTitle: 'コードをコピー' + codeCopyButton: { tooltipText: 'コードをコピー', copiedText: 'コピー完了' } } export default defineAdditionalConfig({ diff --git a/docs/ko/config.ts b/docs/ko/config.ts index 1dae4c440..1ffb1bf4d 100644 --- a/docs/ko/config.ts +++ b/docs/ko/config.ts @@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = { importantLabel: '중요', cautionLabel: '주의' }, - codeCopyButtonTitle: '코드 복사' + codeCopyButton: { tooltipText: '코드 복사', copiedText: '복사됨' } } export default defineAdditionalConfig({ diff --git a/docs/pt/config.ts b/docs/pt/config.ts index 44d9f61e1..0d617abd9 100644 --- a/docs/pt/config.ts +++ b/docs/pt/config.ts @@ -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({ diff --git a/docs/ru/config.ts b/docs/ru/config.ts index c862f6a57..f61d4ac22 100644 --- a/docs/ru/config.ts +++ b/docs/ru/config.ts @@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = { importantLabel: 'ВАЖНО', cautionLabel: 'ВНИМАНИЕ' }, - codeCopyButtonTitle: 'Скопировать код' + codeCopyButton: { tooltipText: 'Скопировать код', copiedText: 'Скопировано' } } export default defineAdditionalConfig({ diff --git a/docs/zh/config.ts b/docs/zh/config.ts index 90a97a616..0f79d49ee 100644 --- a/docs/zh/config.ts +++ b/docs/zh/config.ts @@ -19,7 +19,7 @@ export const markdown: MarkdownLocaleOptions = { importantLabel: '重要', cautionLabel: '小心' }, - codeCopyButtonTitle: '复制代码' + codeCopyButton: { tooltipText: '复制代码', copiedText: '已复制' } } export default defineAdditionalConfig({ diff --git a/src/client/theme-default/styles/components/vp-doc.css b/src/client/theme-default/styles/components/vp-doc.css index fe9aa3dfd..4c069ac43 100644 --- a/src/client/theme-default/styles/components/vp-doc.css +++ b/src/client/theme-default/styles/components/vp-doc.css @@ -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 { diff --git a/src/client/theme-default/styles/vars.css b/src/client/theme-default/styles/vars.css index 228b98646..fe13ebf50 100644 --- a/src/client/theme-default/styles/vars.css +++ b/src/client/theme-default/styles/vars.css @@ -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 * -------------------------------------------------------------------------- */ diff --git a/src/node/markdown/markdown.ts b/src/node/markdown/markdown.ts index dc7f8dd79..18a42fe2f 100644 --- a/src/node/markdown/markdown.ts +++ b/src/node/markdown/markdown.ts @@ -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 }) diff --git a/src/node/markdown/plugins/preWrapper.ts b/src/node/markdown/plugins/preWrapper.ts index b27aa1d8b..270143364 100644 --- a/src/node/markdown/plugins/preWrapper.ts +++ b/src/node/markdown/plugins/preWrapper.ts @@ -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 /** - * 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 } @@ -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 ( `
` + - `` + + `` + `${label}` + fence(...args) + '
' diff --git a/src/shared/shared.ts b/src/shared/shared.ts index ac5810d80..797fbb9f0 100644 --- a/src/shared/shared.ts +++ b/src/shared/shared.ts @@ -10,6 +10,7 @@ export type { AdditionalConfigDict, AdditionalConfigLoader, Awaitable, + CodeCopyButtonOptions, ContainerOptions, DefaultTheme, HeadConfig, diff --git a/types/shared.d.ts b/types/shared.d.ts index 4dda66fc4..4eb2ba6b8 100644 --- a/types/shared.d.ts +++ b/types/shared.d.ts @@ -230,6 +230,19 @@ export interface ContainerOptions { customContainers?: Record } +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..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 = Record<