Skip to content

Commit

Permalink
feat: code copied button
Browse files Browse the repository at this point in the history
  • Loading branch information
callqh committed Oct 4, 2022
1 parent a02c7a6 commit 52298a7
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/node/plugin-mdx/rehypePlugins/preWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ export const rehypePluginPreWrapper: Plugin<[], import('hast').Root> = () => {
node.properties = node.properties || {};
node.properties.className = codeClassName;
node.children = [
{
type: 'element',
tagName: 'button',
properties: {
className: 'copy'
},
children: [
{
type: 'text',
value: ''
}
]
},
{
type: 'element',
tagName: 'span',
Expand Down
7 changes: 7 additions & 0 deletions src/theme-default/layout/DocLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { DocFooter } from '../../components/DocFooter/index';
import { Content, usePageData } from 'island/client';
import { useLocaleSiteData } from '../../logic';
import { useLocation } from 'react-router-dom';
import { setupCopyCodeButton } from '../../logic';
import { useEffect } from 'react';

export function DocLayout() {
const { toc: headers = [], siteData, pagePath } = usePageData();
Expand All @@ -20,6 +22,11 @@ export function DocLayout() {
localesData?.outlineTitle || themeConfig?.outlineTitle || 'ON THIS PAGE';

const hasAside = headers.length > 0 && themeConfig.outline !== false;

useEffect(() => {
setupCopyCodeButton();
}, []);

return (
<div p="t-0 x-6 b-24 sm:6">
{hasSidebar ? <SideBar /> : null}
Expand Down
76 changes: 76 additions & 0 deletions src/theme-default/logic/copyCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { inBrowser } from '../../shared/utils';

export function setupCopyCodeButton() {
if (inBrowser()) {
const timeoutIdMap: Map<HTMLElement, NodeJS.Timeout> = new Map();
window.addEventListener('click', (e) => {
const el = e.target as HTMLElement;

if (el.matches('div[class*="language-"] > button.copy')) {
const parent = el.parentElement;
const sibling = el.nextElementSibling
?.nextElementSibling as HTMLPreElement | null;
if (!parent || !sibling) {
return;
}

const { innerText: text = '' } = sibling;

copyToClipboard(text).then(() => {
el.classList.add('copied');
clearTimeout(timeoutIdMap.get(el));
const timeoutId = setTimeout(() => {
el.classList.remove('copied');
el.blur();
timeoutIdMap.delete(el);
}, 2000);
timeoutIdMap.set(el, timeoutId);
});
}
});
}
}

async function copyToClipboard(text: string) {
try {
return navigator.clipboard.writeText(text);
} catch {
const element = document.createElement('textarea');
const previouslyFocusedElement = document.activeElement;

element.value = text;

// Prevent keyboard from showing on mobile
element.setAttribute('readonly', '');

element.style.contain = 'strict';
element.style.position = 'absolute';
element.style.left = '-9999px';
element.style.fontSize = '12pt'; // Prevent zooming on iOS

const selection = document.getSelection();
const originalRange = selection
? selection.rangeCount > 0 && selection.getRangeAt(0)
: null;

document.body.appendChild(element);
element.select();

// Explicit selection workaround for iOS
element.selectionStart = 0;
element.selectionEnd = text.length;

document.execCommand('copy');
document.body.removeChild(element);

if (originalRange) {
selection!.removeAllRanges(); // originalRange can't be truthy when selection is falsy
selection!.addRange(originalRange);
}

// Get the focus back on the previously focused element, if any
if (previouslyFocusedElement) {
(previouslyFocusedElement as HTMLElement).focus();
}
}
}
1 change: 1 addition & 0 deletions src/theme-default/logic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ export { useEditLink } from './useEditLink';
export { useSidebarData } from './useSidebarData';
export { useLocaleSiteData } from './useLocaleSiteData';
export { setupEffects, bindingAsideScroll } from './sideEffects';
export { setupCopyCodeButton } from './copyCode';
9 changes: 9 additions & 0 deletions src/theme-default/styles/vars.css
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@
--island-code-copy-code-active-text: var(--island-c-text-dark-2);
}

/**
* Icons
* -------------------------------------------------------------------------- */

:root {
--island-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");
--island-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E");
}

.dark {
--island-code-block-bg: var(--island-c-bg-alt);
}
Expand Down

0 comments on commit 52298a7

Please sign in to comment.