forked from tobyxdd/bread
-
Notifications
You must be signed in to change notification settings - Fork 1
/
bread.user.js
123 lines (112 loc) · 4.05 KB
/
bread.user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// ==UserScript==
// @name Bread
// @match *://*/*
// @version 2.7.0
// @author Toby (v1.0.5), ltGuillaume
// @license MIT
// @description Bread (Bionic Reading) - Read text faster & easier
// @grant GM_addStyle
// @grant GM_getValue
// @run-at document-start
// ==/UserScript==
/* source: https://github.com/tobyxdd/bread (v1.0.5) */
/* jshint esversion: 6 */
let
minWordLength = GM_getValue('minWordLength') || 4, // Minimum word length
minTextLength = GM_getValue('minTextLength') || 20, // Minimum text length
boldRatio = GM_getValue('boldRatio') || 0.4, // Bold ratio (percentage of letters per word)
processDyn = GM_getValue('processDyn'), // Process dynamically loaded content, which may cause performance issues (True/False)
breadAllSites = GM_getValue('breadAllSites'), // Apply to all sites visited (True), or just the ones listed in breadSites (False)
breadSites = GM_getValue('breadSites') || { /* Configure sites bread will be triggered on
Apply to domains including "domain_part", restrict bread to a node via a "css_selector" (or false), apply custom CSS "custom_css"
"domain_part" : "css_selector",
"domain_part" : ["css_selector", "custom_css"],
*/
},
insertTextBefore = (text, node, bold) => {
if (bold) {
let span = document.createElement('span');
span.className = 'bread';
span.appendChild(document.createTextNode(text));
node.parentNode.insertBefore(span, node);
} else {
node.parentNode.insertBefore(document.createTextNode(text), node);
}
},
processNode = root => {
let walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode: function (node) {
return (
node.parentNode.nodeName !== 'INPUT' &&
node.parentNode.nodeName !== 'NOSCRIPT' &&
node.parentNode.nodeName !== 'SCRIPT' &&
node.parentNode.nodeName !== 'STYLE' &&
node.parentNode.nodeName !== 'TEXTAREA' &&
node.parentNode.nodeName !== 'TITLE' &&
(node.parentNode.nodeName === 'A' ||
node.parentNode.nodeName === 'EM' ||
node.parentNode.nodeName === 'STRONG' ||
node.nodeValue.length >= minTextLength)) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}
});
let node;
while (node = walker.nextNode()) {
let text = node.nodeValue;
let wStart = -1, wLen = 0, eng = null;
for (let i = 0; i <= text.length; i++) { // We use <= here because we want to include the last character in the loop
let cEng = i < text.length ? /[\p{Letter}\p{Mark}]/u.test(text[i]) : false;
if (i == text.length || eng !== cEng) {
// State flipped or end of string
if (eng && wLen >= minWordLength) {
let word = text.substring(wStart, wStart + wLen);
let numBold = Math.ceil(word.length * boldRatio);
let bt = word.substring(0, numBold), nt = word.substring(numBold);
insertTextBefore(bt, node, true);
insertTextBefore(nt, node, false);
} else if (wLen > 0) {
let word = text.substring(wStart, wStart + wLen);
insertTextBefore(word, node, false);
}
wStart = i;
wLen = 1;
eng = cEng;
} else {
wLen++;
}
}
node.nodeValue = ''; // Can't remove the node (otherwise the tree walker will break) so just set it to empty
}
};
GM_addStyle(`
span.bread {
display: contents !important;
font-weight: bolder !important;
}
`);
let breadNode = breadAllSites != false ? 'body' : false;
for (domain in breadSites) {
if (document.location.host.includes(domain)) {
if (Array.isArray(breadSites[domain])) {
GM_addStyle(breadSites[domain][1]);
breadSites[domain] = breadSites[domain][0];
}
breadNode = breadSites[domain] || 'body';
break;
}
}
if (breadNode) window.addEventListener('load', e => {
let node = document.querySelector(breadNode);
if (!node)
return console.log('Bread: cannot find node', breadNode);
if (processDyn != false) {
const observer = new MutationObserver(
mutations => mutations.forEach(
mutation => mutation.addedNodes.forEach(
addedNode => processNode(addedNode)
)
)
);
observer.observe(node, { subtree: true, childList: true });
}
processNode(node);
}, false);