-
Notifications
You must be signed in to change notification settings - Fork 30
/
remove-class.js
188 lines (175 loc) · 5.57 KB
/
remove-class.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
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import {
hit,
logMessage,
observeDOMChanges,
parseFlags,
throttle,
} from '../helpers';
/* 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
*
* ```text
* 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
*
* ```adblock
* 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>
* ```
*
* 1. Removes with specified selector
*
* ```adblock
* 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>
* ```
*
* 1. Using flags
*
* ```adblock
* example.org#%#//scriptlet('remove-class', 'branding', 'div[class^="inner"]', 'asap complete')
* ```
*
* @added v1.1.1.
*/
/* 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) {
logMessage(source, `Invalid 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 = parseFlags(applying);
const run = () => {
removeClassHandler();
if (!flags.hasFlag(flags.STAY)) {
return;
}
// 'true' for observing attributes
// 'class' for observing only classes
observeDOMChanges(removeClassHandler, true, CLASS_ATTR_NAME);
};
if (flags.hasFlag(flags.ASAP)) {
// https://github.com/AdguardTeam/Scriptlets/issues/245
// Call removeClassHandler on DOM content loaded
// to ensure that target node is present on the page
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', removeClassHandler, { once: true });
} else {
removeClassHandler();
}
}
if (document.readyState !== 'complete' && flags.hasFlag(flags.COMPLETE)) {
window.addEventListener('load', run, { once: true });
} else if (flags.hasFlag(flags.STAY)) {
// Only call removeClassHandler for single 'stay' flag
if (!applying.includes(' ')) {
removeClassHandler();
}
observeDOMChanges(removeClassHandler, true, CLASS_ATTR_NAME);
}
}
export const removeClassNames = [
'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',
];
// eslint-disable-next-line prefer-destructuring
removeClass.primaryName = removeClassNames[0];
removeClass.injections = [
hit,
logMessage,
observeDOMChanges,
parseFlags,
// following helpers should be imported and injected
// because they are used by helpers above
throttle,
];