fix(theme): keep the copy button out of code text selection

close #5120
pull/5464/head
ChisakaKanako 1 week ago
parent 19145a2fc7
commit 1fac4f18e1

@ -96,9 +96,9 @@ describe('node/markdown/plugins/containers', () => {
<p>content</p> <p>content</p>
</div> </div>
<details class="details custom-block"><summary>Click me to toggle the code</summary> <details class="details custom-block"><summary>Click me to toggle the code</summary>
<div class="language-js" dir="ltr"><button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">js</span><pre><code class="language-js">console.log('hi') <div class="language-js" dir="ltr"><pre><code class="language-js">console.log('hi')
</code></pre> </code></pre>
</div></details> <button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">js</span></div></details>
" "
`) `)
}) })
@ -335,11 +335,11 @@ describe('node/markdown/plugins/containers', () => {
].join('\n') ].join('\n')
expect(await render(src)).toMatchInlineSnapshot(` 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="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" dir="ltr"><button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">js</span><pre><code class="language-js">const a = 1 <div class="language-js active" dir="ltr"><pre><code class="language-js">const a = 1
</code></pre> </code></pre>
</div><div class="language-ts" dir="ltr"><button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">ts</span><pre><code class="language-ts">const a: number = 1 <button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">js</span></div><div class="language-ts" dir="ltr"><pre><code class="language-ts">const a: number = 1
</code></pre> </code></pre>
</div></div></div> <button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">ts</span></div></div></div>
" "
`) `)
}) })
@ -497,9 +497,9 @@ describe('node/markdown/plugins/containers (github alerts)', () => {
<ul> <ul>
<li>list item</li> <li>list item</li>
</ul> </ul>
<div class="language-js" dir="ltr"><button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">js</span><pre><code class="language-js">const a = 1 <div class="language-js" dir="ltr"><pre><code class="language-js">const a = 1
</code></pre> </code></pre>
</div></div> <button title="Copy code" data-copied="Copied" class="copy"></button><span class="lang">js</span></div></div>
" "
`) `)
}) })

@ -7,11 +7,33 @@ const ignoredNodes = ['.vp-copy-ignore', '.diff.remove'].join(', ')
export function useCopyCode() { export function useCopyCode() {
if (inBrowser) { if (inBrowser) {
const timeoutIdMap: WeakMap<HTMLElement, number> = new WeakMap() const timeoutIdMap: WeakMap<HTMLElement, number> = new WeakMap()
let selectingBlock: HTMLElement | null = null
const blockOf = (node: Node | null) =>
node?.parentElement?.closest<HTMLElement>('div[class*="language-"]') ??
null
document.addEventListener('selectionchange', () => {
const selection = document.getSelection()
const block =
selection && !selection.isCollapsed
? blockOf(selection.anchorNode) || blockOf(selection.focusNode)
: null
if (selectingBlock && selectingBlock !== block) {
selectingBlock.classList.remove('selecting')
}
if (block) {
block.classList.add('selecting')
}
selectingBlock = block
})
window.addEventListener('click', (e) => { window.addEventListener('click', (e) => {
const el = e.target as HTMLElement const el = e.target as HTMLElement
if (el.matches('div[class*="language-"] > button.copy')) { if (el.matches('div[class*="language-"] > button.copy')) {
const parent = el.parentElement const parent = el.parentElement
const sibling = el.nextElementSibling?.nextElementSibling // <pre> tag const sibling = parent?.querySelector('pre')
if (!parent || !sibling) { if (!parent || !sibling) {
return return
} }

@ -506,13 +506,16 @@
top: 0.75rem; top: 0.75rem;
inset-inline-end: 0.75rem; inset-inline-end: 0.75rem;
z-index: 3; z-index: 3;
pointer-events: none;
border: 1px solid var(--vp-code-copy-code-border-color); border: 1px solid var(--vp-code-copy-code-border-color);
border-radius: 0.25rem; border-radius: 0.25rem;
width: 2.5rem; width: 2.5rem;
height: 2.5rem; height: 2.5rem;
background-color: var(--vp-code-copy-code-bg); background-color: var(--vp-code-copy-code-bg);
opacity: 0; opacity: 0;
visibility: hidden;
cursor: pointer; cursor: pointer;
user-select: none;
background-image: var(--vp-icon-copy); background-image: var(--vp-icon-copy);
background-position: 50%; background-position: 50%;
background-size: 1.25rem; background-size: 1.25rem;
@ -520,12 +523,36 @@
transition: transition:
border-color 0.25s, border-color 0.25s,
background-color 0.25s, background-color 0.25s,
opacity 0.25s; opacity 0.25s,
visibility 0s 0.25s;
} }
.vp-doc [class*='language-']:hover > button.copy, .vp-doc [class*='language-']:hover > button.copy,
.vp-doc [class*='language-']:focus-within > button.copy,
.vp-doc [class*='language-'] > button.copy:focus { .vp-doc [class*='language-'] > button.copy:focus {
opacity: 1; opacity: 1;
visibility: visible;
pointer-events: auto;
transition:
border-color 0.25s,
background-color 0.25s,
opacity 0.25s,
visibility 0s;
}
.vp-doc [class*='language-'].selecting > button.copy:not(:focus),
.vp-doc [class*='language-'].selecting > span.lang {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.vp-doc [class*='language-'].selecting > button.copy:not(:focus) {
transition:
border-color 0.25s,
background-color 0.25s,
opacity 0.25s,
visibility 0s 0.25s;
} }
.vp-doc [class*='language-'] > button.copy:hover, .vp-doc [class*='language-'] > button.copy:hover,

@ -43,9 +43,9 @@ export function preWrapperPlugin(md: MarkdownItAsync, options: Options) {
return ( return (
`<div class="language-${lang}${active}" dir="ltr">` + `<div class="language-${lang}${active}" dir="ltr">` +
fence(...args) +
`<button title="${tooltipText}" data-copied="${copiedText}" class="copy"></button>` + `<button title="${tooltipText}" data-copied="${copiedText}" class="copy"></button>` +
`<span class="lang">${label}</span>` + `<span class="lang">${label}</span>` +
fence(...args) +
'</div>' '</div>'
) )
} }

Loading…
Cancel
Save