forked from AdguardTeam/Scriptlets
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathremove-class.js
More file actions
170 lines (155 loc) · 5.38 KB
/
remove-class.js
File metadata and controls
170 lines (155 loc) · 5.38 KB
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { hit, observeDOMChanges } from '../helpers/index';
/* eslint-disable max-len */
/**
* @scriptlet remove-class
*
* @description
* Removes the specified classes from DOM nodes. This scriptlet runs once after the page loads
* and after that periodically in order to DOM tree changes.
*
* Related UBO scriptlet:
* https://github.com/gorhill/uBlock/wiki/Resources-Library#remove-classjs-
*
* **Syntax**
* ```
* example.org#%#//scriptlet('remove-class', classes[, selector, applying])
* ```
*
* - `classes` — required, class or list of classes separated by '|'
* - `selector` — optional, CSS selector, specifies DOM nodes from which the classes will be removed.
* If there is no `selector`, each class of `classes` independently will be removed from all nodes which has one
* - `applying` — optional, one or more space-separated flags that describe the way scriptlet apply, defaults to 'asap stay'; possible flags:
* - `asap` — runs as fast as possible **once**
* - `complete` — runs **once** after the whole page has been loaded
* - `stay` — as fast as possible **and** stays on the page observing possible DOM changes
*
* **Examples**
* 1. Removes by classes
* ```
* example.org#%#//scriptlet('remove-class', 'example|test')
* ```
*
* ```html
* <!-- before -->
* <div id="first" class="nice test">Some text</div>
* <div id="second" class="rare example for test">Some text</div>
* <div id="third" class="testing better example">Some text</div>
*
* <!-- after -->
* <div id="first" class="nice">Some text</div>
* <div id="second" class="rare for">Some text</div>
* <div id="third" class="testing better">Some text</div>
* ```
*
* 2. Removes with specified selector
* ```
* example.org#%#//scriptlet('remove-class', 'branding', 'div[class^="inner"]')
* ```
*
* ```html
* <!-- before -->
* <div class="wrapper true branding">
* <div class="inner bad branding">Some text</div>
* </div>
*
* <!-- after -->
* <div class="wrapper true branding">
* <div class="inner bad">Some text</div>
* </div>
* ```
*
* 3. Using flags
* ```
* example.org#%#//scriptlet('remove-class', 'branding', 'div[class^="inner"]', 'asap complete')
* ```
*/
/* eslint-enable max-len */
export function removeClass(source, classNames, selector, applying = 'asap stay') {
if (!classNames) {
return;
}
classNames = classNames.split(/\s*\|\s*/);
let selectors = [];
if (!selector) {
selectors = classNames.map((className) => {
return `.${className}`;
});
}
const removeClassHandler = () => {
const nodes = new Set();
if (selector) {
let foundNodes = [];
try {
foundNodes = [].slice.call(document.querySelectorAll(selector));
} catch (e) {
// eslint-disable-next-line no-console
console.log(`Invalid remove-class selector arg: '${selector}'`);
}
foundNodes.forEach((n) => nodes.add(n));
} else if (selectors.length > 0) {
selectors.forEach((s) => {
const elements = document.querySelectorAll(s);
for (let i = 0; i < elements.length; i += 1) {
const element = elements[i];
nodes.add(element);
}
});
}
let removed = false;
nodes.forEach((node) => {
classNames.forEach((className) => {
if (node.classList.contains(className)) {
node.classList.remove(className);
removed = true;
}
});
});
if (removed) {
hit(source);
}
};
const CLASS_ATTR_NAME = ['class'];
const FLAGS_DIVIDER = ' ';
const ASAP_FLAG = 'asap';
const COMPLETE_FLAG = 'complete';
const STAY_FLAG = 'stay';
const VALID_FLAGS = [STAY_FLAG, ASAP_FLAG, COMPLETE_FLAG];
/* eslint-disable no-restricted-properties */
const passedFlags = applying.trim()
.split(FLAGS_DIVIDER)
.filter((f) => VALID_FLAGS.indexOf(f) !== -1);
const run = () => {
removeClassHandler();
if (!passedFlags.indexOf(STAY_FLAG) !== -1) {
return;
}
// 'true' for observing attributes
// 'class' for observing only classes
observeDOMChanges(removeClassHandler, true, CLASS_ATTR_NAME);
};
if (passedFlags.indexOf(ASAP_FLAG) !== -1) {
removeClassHandler();
}
if (document.readyState !== 'complete' && passedFlags.indexOf(COMPLETE_FLAG) !== -1) {
window.addEventListener('load', run, { once: true });
} else if (passedFlags.indexOf(STAY_FLAG) !== -1) {
// Do not call removeClassHandler() twice for 'asap stay' flag
if (passedFlags.length === 1) {
removeClassHandler();
}
// 'true' for observing attributes
// 'class' for observing only classes
observeDOMChanges(removeClassHandler, true, CLASS_ATTR_NAME);
}
}
removeClass.names = [
'remove-class',
// aliases are needed for matching the related scriptlet converted into our syntax
'remove-class.js',
'ubo-remove-class.js',
'rc.js',
'ubo-rc.js',
'ubo-remove-class',
'ubo-rc',
];
removeClass.injections = [hit, observeDOMChanges];