Skip to content

Commit 268416f

Browse files
committed
Fix copy/paste behavior when using Firefox or Edge (#51)
1 parent 3bd0195 commit 268416f

File tree

1 file changed

+112
-5
lines changed

1 file changed

+112
-5
lines changed

src/highlightjs-line-numbers.js

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,113 @@
2121
w.console.error('highlight.js not detected!');
2222
}
2323

24+
function isHljsLnCodeDescendant(domElt) {
25+
var curElt = domElt;
26+
while (curElt) {
27+
if (curElt.className && curElt.className.indexOf('hljs-ln-code') !== -1) {
28+
return true;
29+
}
30+
curElt = curElt.parentNode;
31+
}
32+
return false;
33+
}
34+
35+
function getHljsLnTable(hljsLnDomElt) {
36+
var curElt = hljsLnDomElt;
37+
while (curElt.nodeName !== 'TABLE') {
38+
curElt = curElt.parentNode;
39+
}
40+
return curElt;
41+
}
42+
43+
// Function to workaround a copy issue with Microsoft Edge.
44+
// Due to hljs-ln wrapping the lines of code inside a <table> element,
45+
// itself wrapped inside a <pre> element, window.getSelection().toString()
46+
// does not contain any line breaks. So we need to get them back using the
47+
// rendered code in the DOM as reference.
48+
function edgeGetSelectedCodeLines(selection) {
49+
// current selected text without line breaks
50+
var selectionText = selection.toString();
51+
52+
// get the <td> element wrapping the first line of selected code
53+
var tdAnchor = selection.anchorNode;
54+
while (tdAnchor.nodeName !== 'TD') {
55+
tdAnchor = tdAnchor.parentNode;
56+
}
57+
58+
// get the <td> element wrapping the last line of selected code
59+
var tdFocus = selection.focusNode;
60+
while (tdFocus.nodeName !== 'TD') {
61+
tdFocus = tdFocus.parentNode;
62+
}
63+
64+
// extract line numbers
65+
var firstLineNumber = parseInt(tdAnchor.dataset.lineNumber);
66+
var lastLineNumber = parseInt(tdFocus.dataset.lineNumber);
67+
68+
// multi-lines copied case
69+
if (firstLineNumber != lastLineNumber) {
70+
71+
var firstLineText = tdAnchor.textContent;
72+
var lastLineText = tdFocus.textContent;
73+
74+
// if the selection was made backward, swap values
75+
if (firstLineNumber > lastLineNumber) {
76+
var tmp = firstLineNumber;
77+
firstLineNumber = lastLineNumber;
78+
lastLineNumber = tmp;
79+
tmp = firstLineText;
80+
firstLineText = lastLineText;
81+
lastLineText = tmp;
82+
}
83+
84+
// discard not copied characters in first line
85+
while (selectionText.indexOf(firstLineText) !== 0) {
86+
firstLineText = firstLineText.slice(1);
87+
}
88+
89+
// discard not copied characters in last line
90+
while (selectionText.lastIndexOf(lastLineText) === -1) {
91+
lastLineText = lastLineText.slice(0, -1);
92+
}
93+
94+
// reconstruct and return the real copied text
95+
var selectedText = firstLineText;
96+
var hljsLnTable = getHljsLnTable(tdAnchor);
97+
for (var i = firstLineNumber + 1 ; i < lastLineNumber ; ++i) {
98+
var codeLineSel = format('.{0}[{1}="{2}"]', [CODE_BLOCK_NAME, DATA_ATTR_NAME, i]);
99+
var codeLineElt = hljsLnTable.querySelector(codeLineSel);
100+
selectedText += '\n' + codeLineElt.textContent;
101+
}
102+
selectedText += '\n' + lastLineText;
103+
return selectedText;
104+
// single copied line case
105+
} else {
106+
return selectionText;
107+
}
108+
}
109+
110+
// ensure consistent code copy/paste behavior across all browsers
111+
// (see https://github.com/wcoder/highlightjs-line-numbers.js/issues/51)
112+
document.addEventListener('copy', function(e) {
113+
// get current selection
114+
var selection = window.getSelection();
115+
// override behavior when one wants to copy line of codes
116+
if (isHljsLnCodeDescendant(selection.anchorNode)) {
117+
var selectionText;
118+
// workaround an issue with Microsoft Edge as copied line breaks
119+
// are removed otherwise from the selection string
120+
if (window.navigator.userAgent.indexOf("Edge") !== -1) {
121+
selectionText = edgeGetSelectedCodeLines(selection);
122+
} else {
123+
// other browsers can directly use the selection string
124+
selectionText = selection.toString();
125+
}
126+
e.clipboardData.setData('text/plain', selectionText);
127+
e.preventDefault();
128+
}
129+
});
130+
24131
function addStyles () {
25132
var css = d.createElement('style');
26133
css.type = 'text/css';
@@ -106,16 +213,16 @@
106213
for (var i = 0, l = lines.length; i < l; i++) {
107214
html += format(
108215
'<tr>' +
109-
'<td class="{0}">' +
110-
'<div class="{1} {2}" {3}="{5}"></div>' +
216+
'<td class="{0} {1}" {3}="{5}">' +
217+
'<div class="{2}" {3}="{5}"></div>' +
111218
'</td>' +
112-
'<td class="{4}">' +
113-
'<div class="{1}">{6}</div>' +
219+
'<td class="{0} {4}" {3}="{5}">' +
220+
'{6}' +
114221
'</td>' +
115222
'</tr>',
116223
[
117-
NUMBERS_BLOCK_NAME,
118224
LINE_NAME,
225+
NUMBERS_BLOCK_NAME,
119226
NUMBER_LINE_NAME,
120227
DATA_ATTR_NAME,
121228
CODE_BLOCK_NAME,

0 commit comments

Comments
 (0)