Skip to content

Commit

Permalink
feat: support copying code from codeblocks (#260)
Browse files Browse the repository at this point in the history
  • Loading branch information
nexmoe committed Mar 15, 2023
2 parents 44eda7c + 1b80664 commit d1e8c27
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 0 deletions.
4 changes: 4 additions & 0 deletions layout/post.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
<% } %>

<%- partial('_partial/_post/tag') %>

<% if (theme.function.copyCode) { %>
<%- js_auto_version('js/copy-codeblock') %>
<% } %>

<% if (page.comments){ %>
<div class="nexmoe-post-footer">
Expand Down
1 change: 1 addition & 0 deletions source/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ favicon:
function: # 功能开关,可选值(true,false)
globalToc: true # 开启该功能会自动开启文章 TOC(文章目录) 功能
wordCount: false # 是否开启文章字数统计 (true, false)
copyCode: true # 是否允许复制代码块

imageCDN: # 图片 CDN 功能
enable: false # 开启该功能
Expand Down
50 changes: 50 additions & 0 deletions source/css/_partial/copy-codeblock.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
article figure.highlight {
position: relative;

& > .codeblock-copy-wrapper {
position: absolute;
width: 30px;
height: 30px;
right: 12px;
top: 12px;
border-radius: 4px;
z-index: 10;
background-color: #1e1e20;
background-image: 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");
background-position: 50%;
background-size: 20px;
background-repeat: no-repeat;
cursor: pointer;
opacity: 0;
transition: opacity 0.25s;

&-copied {
opacity: 1;
border-radius: 0 4px 4px 0;
background-image: 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");

&::before {
position: relative;
left: -64px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px 0 0 4px;
width: 64px;
height: 30px;
padding-right: -15px;
text-align: center;
font-size: 12px;
font-weight: 500;
color: rgba(235, 235, 245, 0.6);
background-color: #1e1e20;
white-space: nowrap;
content: 'Copied';
}
}
}

&:hover > .codeblock-copy-wrapper {
opacity: 1;
}
}
1 change: 1 addition & 0 deletions source/css/style.styl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ $gutter = 24px;
@import '_partial/py.styl';
@import '_partial/searchbox.styl';
@import '_widget/hitokoto.styl';
@import '_partial/copy-codeblock.styl';

*, *:after, *::before {
box-sizing: border-box;
Expand Down
50 changes: 50 additions & 0 deletions source/js/copy-codeblock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
window.onload = () => {
const codeBlocks = document.querySelectorAll('figure.highlight');
if (!codeBlocks.length) return;

const addCopyButton = (codeBlock) => {
const copyWrapper = document.createElement('div');
copyWrapper.setAttribute('class', 'codeblock-copy-wrapper');

let copiedTimeout = null;

copyWrapper.addEventListener('click', (ev) => {
const highlightDom = ev.target.parentElement;
const code = highlightDom.querySelector('code');

let copiedCode = '';

(function traverseChildNodes(node) {
const childNodes = node.childNodes;
childNodes.forEach((child) => {
switch (child.nodeName) {
case '#text': // 文本节点
copiedCode += child.nodeValue;
break;
case 'BR': // <br />
copiedCode += '\n';
break;
default:
traverseChildNodes(child);
}
});
})(code);

navigator.clipboard.writeText(
/* 去掉最后的换行 */
copiedCode.slice(0, -1)
).then(() => {
if (!!copiedTimeout) clearTimeout(copiedTimeout);

copyWrapper.classList.add('codeblock-copy-wrapper-copied');
copiedTimeout = setTimeout(() => {
copyWrapper.classList.remove('codeblock-copy-wrapper-copied');
copiedTimeout = null;
}, 1500);
});
});
codeBlock.appendChild(copyWrapper);
};

codeBlocks.forEach(addCopyButton);
};

0 comments on commit d1e8c27

Please sign in to comment.