Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit af755ba

Browse files
authored
Merge pull request #5 from kip-13/sanitize-and-copy
Added the option to sanitize and copy text, and more
2 parents bc138e8 + 06b7c2e commit af755ba

File tree

10 files changed

+142
-40
lines changed

10 files changed

+142
-40
lines changed

manifest.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@
44
"manifest_version": 2,
55
"description": "Detects zero-width characters on a page, highlights the containing DOM element, and displays a warning at bottom-right of element.",
66
"homepage_url": "https://github.com/roymckenzie/detect-zero-width-characters-chrome-extension",
7+
"permissions": [
8+
"contextMenus",
9+
"clipboardWrite"
10+
],
11+
"background": {
12+
"scripts": [
13+
"src/constants.js",
14+
"src/utils.js",
15+
"src/background/background.js"
16+
]
17+
},
18+
"icons": {
19+
"16": "src/icon/16x16.png",
20+
"48": "src/icon/48x48.png",
21+
"128": "src/icon/128x128.png"
22+
},
723
"content_scripts": [
824
{
925
"matches": [
@@ -20,6 +36,7 @@
2036
"https://*/*"
2137
],
2238
"js": [
39+
"src/constants.js",
2340
"src/inject/inject.js"
2441
]
2542
}

src/background/background.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
(function() {
2+
let contextMenuOptionId, selectionText;
3+
4+
const sanitizeAndCopy = function() {
5+
copyTextToClipboard(sanitize(selectionText));
6+
};
7+
8+
const handleContextMenu = function(request) {
9+
if (request.shouldSanitizeSelection) {
10+
selectionText = request.selection;
11+
12+
if (!contextMenuOptionId) {
13+
contextMenuOptionId = chrome.contextMenus.create({
14+
"title" : "Sanitize and copy",
15+
"type" : "normal",
16+
"contexts" : ["selection"],
17+
"onclick" : sanitizeAndCopy
18+
});
19+
}
20+
} else {
21+
if (contextMenuOptionId) {
22+
chrome.contextMenus.remove(contextMenuOptionId);
23+
contextMenuOptionId = null;
24+
}
25+
}
26+
};
27+
28+
chrome.runtime.onMessage.addListener(handleContextMenu);
29+
})();

src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const zeroLengthCharacterCodes = [ 8203, 8204, 8205, 8288 ];

src/icon/128x128.png

4.13 KB
Loading

src/icon/16x16.png

1.2 KB
Loading

src/icon/32x32.png

1.51 KB
Loading

src/icon/48x48.png

2.27 KB
Loading

src/inject/inject.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
1616
padding: 2px 4px;
1717
position: absolute;
1818
right: 0;
19-
}
19+
}

src/inject/inject.js

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,88 @@
1-
chrome.extension.sendMessage({}, function(response) {
2-
var readyStateCheckInterval = setInterval(function() {
3-
if (document.readyState === "complete") {
4-
clearInterval(readyStateCheckInterval);
5-
6-
// Check Page
7-
checkPage();
8-
9-
// Check page again when any input field is changed
10-
const inputs = document.querySelectorAll('input');
11-
[...inputs].forEach( function( input ) {
12-
input.addEventListener( 'change', checkPage );
13-
});
1+
(function() {
2+
let elementsWithZLCC = [];
143

15-
}
16-
}, 10);
4+
const checkPage = function() {
5+
const allElements = document.getElementsByTagName('*');
6+
[...allElements].forEach( checkElement );
7+
}
178

18-
});
9+
// From: https://jsfiddle.net/tim333/np874wae/13/
10+
const checkElement = function( element ) {
11+
const text = textWithoutChildren( element );
1912

20-
const checkPage = function() {
21-
const allElements = document.getElementsByTagName('*');
22-
[...allElements].forEach( checkElement );
23-
}
13+
[...text].forEach( function( character ) {
14+
unicodeCode = character.codePointAt(0);
2415

25-
// From: https://jsfiddle.net/tim333/np874wae/13/
26-
const checkElement = function( element ) {
27-
const text = textWithoutChildren( element );
28-
const zeroLengthCharacterCodes = [ 8203, 8204, 8205, 8288 ];
16+
if (
17+
zeroLengthCharacterCodes.includes( unicodeCode )
18+
&& !elementsWithZLCC.includes(element)
19+
) {
20+
elementsWithZLCC.push(element)
21+
}
22+
});
2923

30-
[...text].forEach( function( character ) {
31-
unicodeCode = character.codePointAt(0);
32-
if ( zeroLengthCharacterCodes.includes( unicodeCode ) ) {
24+
elementsWithZLCC.forEach(function( element ) {
3325
element.classList.add('zero-length-characters');
26+
})
27+
}
28+
29+
// From: https://stackoverflow.com/a/9340862/535363
30+
const textWithoutChildren = function( element ) {
31+
let child = element.firstChild,
32+
texts = [];
33+
34+
while (child) {
35+
if (child.nodeType == 3) {
36+
texts.push(child.data);
37+
}
38+
child = child.nextSibling;
3439
}
40+
41+
return texts.join("");
42+
}
43+
44+
chrome.extension.sendMessage({}, function(response) {
45+
var readyStateCheckInterval = setInterval(function() {
46+
if (document.readyState === "complete") {
47+
clearInterval(readyStateCheckInterval);
48+
49+
// Check Page
50+
checkPage();
51+
52+
// Check page again when any input field is changed
53+
const inputs = document.querySelectorAll('input');
54+
55+
[...inputs].forEach( function( input ) {
56+
input.addEventListener( 'change', checkPage );
57+
});
58+
}
59+
}, 10);
3560
});
36-
}
3761

38-
// From: https://stackoverflow.com/a/9340862/535363
39-
const textWithoutChildren = function( element ) {
40-
child = element.firstChild,
41-
texts = [];
62+
document.body.addEventListener('mouseup', function (e) {
63+
const selection = window.getSelection();
64+
65+
const shouldSanitizeSelection = elementsWithZLCC
66+
.map(function(element) {
67+
return selection.containsNode(element, true);
68+
})
69+
.includes(true);
4270

43-
while (child) {
44-
if (child.nodeType == 3) {
45-
texts.push(child.data);
71+
try {
72+
chrome.runtime.sendMessage({
73+
"shouldSanitizeSelection": shouldSanitizeSelection,
74+
"selection": selection.toString()
75+
});
76+
} catch(e) {
77+
if (
78+
e.message.match(/Invocation of form runtime\.connect/) &&
79+
e.message.match(/doesn't match definition runtime\.connect/)
80+
) {
81+
console.error('Chrome extension has been reloaded. Please refresh the page');
82+
} else {
83+
throw(e);
4684
}
47-
child = child.nextSibling;
48-
}
85+
}
86+
});
4987

50-
return texts.join("");
51-
}
88+
})();

src/utils.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const sanitize = function(text) {
2+
return [...text].filter(function(char) {
3+
const unicodeCode = char.codePointAt(0);
4+
5+
return !zeroLengthCharacterCodes.includes(unicodeCode);
6+
}).join("");
7+
}
8+
9+
//https://stackoverflow.com/a/18455088/6591929
10+
const copyTextToClipboard = function (text) {
11+
const copyFrom = document.createElement("textarea");
12+
const body = document.body;
13+
copyFrom.textContent = text;
14+
body.appendChild(copyFrom);
15+
copyFrom.select();
16+
document.execCommand('copy');
17+
body.removeChild(copyFrom);
18+
}

0 commit comments

Comments
 (0)