Skip to content

Commit 4655911

Browse files
committed
feat: add copy button.
1 parent bbfb4de commit 4655911

File tree

5 files changed

+171
-24
lines changed

5 files changed

+171
-24
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"dependencies": {
5050
"@babel/runtime": "7.15.4",
5151
"@mapbox/rehype-prism": "0.8.0",
52+
"@uiw/copy-to-clipboard": "1.0.12",
5253
"rehype-autolink-headings": "6.1.0",
5354
"rehype-raw": "6.1.0",
5455
"rehype-attr": "2.0.6",

src/index.tsx

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,11 @@ import rehypeAttrs from 'rehype-attr';
99
// @ts-ignore
1010
import rehypePrism from '@mapbox/rehype-prism';
1111
import rehypeRewrite from 'rehype-rewrite';
12+
import { octiconLink } from './nodes/octiconLink';
13+
import { copyElement } from './nodes/copy';
1214
import './styles/markdown.less';
1315
import './styles/markdowncolor.less';
1416

15-
const octiconLink: Element = {
16-
type: 'element',
17-
tagName: 'svg',
18-
properties: {
19-
class: 'octicon octicon-link',
20-
viewBox: '0 0 16 16',
21-
version: '1.1',
22-
width: '16',
23-
height: '16',
24-
ariaHidden: 'true',
25-
},
26-
children: [
27-
{
28-
type: 'element',
29-
tagName: 'path',
30-
children: [],
31-
properties: {
32-
fillRule: 'evenodd',
33-
d: 'M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z',
34-
},
35-
},
36-
],
37-
};
38-
3917
const rehypeRewriteHandle = (node: ElementContent, index: number | null, parent: Root | Element | null) => {
4018
if (node.type === 'element' && parent && parent.type === 'root' && /h(1|2|3|4|5|6)/.test(node.tagName)) {
4119
const child = node.children && (node.children[0] as Element);
@@ -44,6 +22,21 @@ const rehypeRewriteHandle = (node: ElementContent, index: number | null, parent:
4422
child.children = [octiconLink];
4523
}
4624
}
25+
if (node.type === 'element' && node.tagName === 'pre') {
26+
const code = getCodeStr(node.children);
27+
node.children.unshift(copyElement(code));
28+
}
29+
};
30+
31+
const getCodeStr = (data: ElementContent[] = [], code: string = '') => {
32+
data.forEach((node) => {
33+
if (node.type === 'text') {
34+
code += node.value;
35+
} else if (node.type === 'element' && node.children && Array.isArray(node.children)) {
36+
code += getCodeStr(node.children);
37+
}
38+
});
39+
return code;
4740
};
4841

4942
export interface MarkdownPreviewProps extends Omit<Options, 'children'> {

src/nodes/copy.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Element } from 'hast';
2+
import copyTextToClipboard from '@uiw/copy-to-clipboard';
3+
4+
export function copyElement(str: string = 'test'): Element {
5+
return {
6+
type: 'element',
7+
tagName: 'div',
8+
properties: {
9+
// @ts-ignore
10+
onClick: ({ target }) => {
11+
target.classList.add('active');
12+
copyTextToClipboard(target.dataset.code as string, function () {
13+
setTimeout(() => {
14+
target.classList.remove('active');
15+
}, 2000);
16+
});
17+
},
18+
'data-code': str,
19+
class: 'copied',
20+
},
21+
children: [
22+
{
23+
type: 'element',
24+
tagName: 'svg',
25+
properties: {
26+
className: 'octicon-copy',
27+
ariaHidden: 'true',
28+
viewBox: '0 0 16 16',
29+
fill: 'currentColor',
30+
height: 12,
31+
width: 12,
32+
},
33+
children: [
34+
{
35+
type: 'element',
36+
tagName: 'path',
37+
properties: {
38+
fillRule: 'evenodd',
39+
d: 'M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25v-7.5z',
40+
},
41+
children: [],
42+
},
43+
{
44+
type: 'element',
45+
tagName: 'path',
46+
properties: {
47+
fillRule: 'evenodd',
48+
d: 'M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25v-7.5zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25h-7.5z',
49+
},
50+
children: [],
51+
},
52+
],
53+
},
54+
{
55+
type: 'element',
56+
tagName: 'svg',
57+
properties: {
58+
className: 'octicon-check',
59+
ariaHidden: 'true',
60+
viewBox: '0 0 16 16',
61+
fill: 'currentColor',
62+
height: 12,
63+
width: 12,
64+
},
65+
children: [
66+
{
67+
type: 'element',
68+
tagName: 'path',
69+
properties: {
70+
fillRule: 'evenodd',
71+
d: 'M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z',
72+
},
73+
children: [],
74+
},
75+
],
76+
},
77+
],
78+
};
79+
}

src/nodes/octiconLink.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Element } from 'hast';
2+
3+
export const octiconLink: Element = {
4+
type: 'element',
5+
tagName: 'svg',
6+
properties: {
7+
className: 'octicon octicon-link',
8+
viewBox: '0 0 16 16',
9+
version: '1.1',
10+
width: '16',
11+
height: '16',
12+
ariaHidden: 'true',
13+
},
14+
children: [
15+
{
16+
type: 'element',
17+
tagName: 'path',
18+
children: [],
19+
properties: {
20+
fillRule: 'evenodd',
21+
d: 'M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z',
22+
},
23+
},
24+
],
25+
};

src/styles/markdown.less

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,55 @@
99
> :last-child {
1010
margin-bottom: 0 !important;
1111
}
12+
13+
pre[class*='language-'] {
14+
&:hover .copied {
15+
visibility: visible;
16+
}
17+
.copied {
18+
visibility: hidden;
19+
display: flex;
20+
position: absolute;
21+
cursor: pointer;
22+
color: #a5afbb;
23+
top: 6px;
24+
right: 6px;
25+
border-radius: 5px;
26+
background: #e3e3e3;
27+
padding: 6px;
28+
font-size: 12px;
29+
transition: all 0.3s;
30+
&::before {
31+
content: ' ';
32+
position: absolute;
33+
width: 100%;
34+
height: 100%;
35+
top: 0;
36+
left: 0;
37+
}
38+
.octicon-copy {
39+
display: block;
40+
}
41+
.octicon-check {
42+
display: none;
43+
}
44+
&.active {
45+
.octicon-copy {
46+
display: none;
47+
}
48+
.octicon-check {
49+
display: block;
50+
}
51+
}
52+
&:hover,
53+
&:active,
54+
&.active {
55+
background: #2e9b33;
56+
color: #fff;
57+
}
58+
}
59+
}
60+
1261
code[class*='language-'],
1362
pre[class*='language-'] {
1463
color: black;

0 commit comments

Comments
 (0)