81 lines
3.3 KiB
JavaScript
81 lines
3.3 KiB
JavaScript
import { useLocaleConfig, wait } from '@vuepress/helper/client';
|
|
import { useClipboard, useEventListener, useMediaQuery } from '@vueuse/core';
|
|
import { computed, nextTick, watch } from 'vue';
|
|
import { usePageData } from 'vuepress/client';
|
|
import '../styles/copy-code.css';
|
|
const SHELL_RE = /language-(shellscript|shell|bash|sh|zsh)/;
|
|
export const useCopyCode = ({ delay = 500, duration = 2000, locales, selector, showInMobile, ignoreSelector = [], transform, }) => {
|
|
if (__VUEPRESS_SSR__)
|
|
return;
|
|
/**
|
|
* On small-screen devices, the copy button is not displayed by default in order to prevent
|
|
* it from obstructing content, as the `:hover` effect can be triggered by `touch` events.
|
|
*/
|
|
const is419 = useMediaQuery('(max-width: 419px)');
|
|
const enabled = computed(() => !is419.value || showInMobile);
|
|
const locale = useLocaleConfig(locales);
|
|
const page = usePageData();
|
|
const insertCopyButton = (codeBlockElement) => {
|
|
if (codeBlockElement.hasAttribute('copy-code-registered'))
|
|
return;
|
|
const copyElement = document.createElement('button');
|
|
copyElement.type = 'button';
|
|
copyElement.classList.add('vp-copy-code-button');
|
|
copyElement.setAttribute('aria-label', locale.value.copy);
|
|
copyElement.setAttribute('data-copied', locale.value.copied);
|
|
codeBlockElement.parentElement?.insertBefore(copyElement, codeBlockElement);
|
|
codeBlockElement.setAttribute('copy-code-registered', '');
|
|
};
|
|
const appendCopyButton = async () => {
|
|
document.body.classList.toggle('copy-code-disabled', !enabled.value);
|
|
if (!enabled.value)
|
|
return;
|
|
await nextTick();
|
|
await wait(delay);
|
|
document
|
|
.querySelectorAll(selector.join(','))
|
|
.forEach(insertCopyButton);
|
|
};
|
|
watch(() => [page.value.path, enabled.value], appendCopyButton, {
|
|
immediate: true,
|
|
});
|
|
const { copy } = useClipboard({ legacy: true });
|
|
const timeoutIdMap = new WeakMap();
|
|
const copyContent = (codeContainer, codeContent, button) => {
|
|
const clone = codeContent.cloneNode(true);
|
|
if (ignoreSelector.length) {
|
|
clone
|
|
.querySelectorAll(ignoreSelector.join(','))
|
|
.forEach((node) => node.remove());
|
|
}
|
|
if (transform)
|
|
transform(clone);
|
|
let text = clone.textContent || '';
|
|
if (SHELL_RE.test(codeContainer.className))
|
|
text = text.replace(/^ *(\$|>) /gm, '');
|
|
copy(text).then(() => {
|
|
if (duration <= 0)
|
|
return;
|
|
button.classList.add('copied');
|
|
clearTimeout(timeoutIdMap.get(button));
|
|
const timeoutId = setTimeout(() => {
|
|
button.classList.remove('copied');
|
|
button.blur();
|
|
timeoutIdMap.delete(button);
|
|
}, duration);
|
|
timeoutIdMap.set(button, timeoutId);
|
|
});
|
|
};
|
|
useEventListener('click', (event) => {
|
|
const el = event.target;
|
|
if (enabled.value &&
|
|
el.matches('div[class*="language-"] > button.vp-copy-code-button')) {
|
|
const codeContainer = el.parentElement;
|
|
const preBlock = el.nextElementSibling;
|
|
if (!codeContainer || !preBlock)
|
|
return;
|
|
copyContent(codeContainer, preBlock, el);
|
|
}
|
|
});
|
|
};
|