@shadowtube/tizentube-noanimations 1.15.2 → 1.15.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/myCustom-userScript-manified.js +490 -0
- package/dist/myCustom-userScript.js +452 -143
- package/dist/userScript.js +13087 -7774
- package/package.json +1 -1
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* myCustom-userScript.js
|
|
3
|
+
* Runs AFTER Reis's TizenTube userScript.js and overrides the parts it breaks.
|
|
4
|
+
*
|
|
5
|
+
* 1. Arrow-key seek (10 s) without showing the player UI (your original fix)
|
|
6
|
+
* 2. Restore "Not interested" / "Don't recommend channel" in the tile menu
|
|
7
|
+
* (Reis's script adds "Add to Queue" and can leave these two out)
|
|
8
|
+
* 3. Restore the subtitle / progress-bar animations
|
|
9
|
+
* (Reis's MutationObserver strips the `app-quality-root` class from <body>)
|
|
10
|
+
*
|
|
11
|
+
* Written in plain ES5 on purpose so it runs on older Tizen / Cobalt engines.
|
|
12
|
+
*/
|
|
13
|
+
(function () {
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
/* ------------------------------------------------------------------ *
|
|
17
|
+
* Settings
|
|
18
|
+
* ------------------------------------------------------------------ */
|
|
19
|
+
var SEEK_SECONDS = 10;
|
|
20
|
+
var KEEP_ADD_TO_QUEUE = false; // true = keep "Add to Queue" in the menu
|
|
21
|
+
var QUALITY_CLASS = 'app-quality-root';
|
|
22
|
+
var DEBUG = false; // true = on-screen report whenever a tile menu is fixed
|
|
23
|
+
// Reis puts the feedback items at the very end of the menu, where the list
|
|
24
|
+
// can be cut off. Move them to this position (0 = first, 2 = after
|
|
25
|
+
// "Play" and "Save to Watch Later").
|
|
26
|
+
var FEEDBACK_POSITION = 2;
|
|
27
|
+
|
|
28
|
+
if (window.__ttCustomInstalled) return;
|
|
29
|
+
window.__ttCustomInstalled = true;
|
|
30
|
+
|
|
31
|
+
/* ================================================================== *
|
|
32
|
+
* 1) ARROW-KEY SEEK (your original code, de-minified, logic unchanged)
|
|
33
|
+
* ================================================================== */
|
|
34
|
+
var handledKeys = {};
|
|
35
|
+
|
|
36
|
+
function isHidden(el) {
|
|
37
|
+
while (el && el !== document) {
|
|
38
|
+
var style = window.getComputedStyle(el);
|
|
39
|
+
if (
|
|
40
|
+
el.getAttribute('aria-hidden') === 'true' ||
|
|
41
|
+
(el.classList && el.classList.contains('zylon-hidden')) ||
|
|
42
|
+
style.display === 'none' ||
|
|
43
|
+
style.visibility === 'hidden'
|
|
44
|
+
) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
el = el.parentNode;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isArrowLeftRight(e) {
|
|
53
|
+
return (
|
|
54
|
+
e.key === 'ArrowLeft' ||
|
|
55
|
+
e.key === 'ArrowRight' ||
|
|
56
|
+
e.keyCode === 37 ||
|
|
57
|
+
e.keyCode === 39
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isTypingTarget(t) {
|
|
62
|
+
return (
|
|
63
|
+
t &&
|
|
64
|
+
(t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName || ''))
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Focus is on a video tile (home / browse grid) -> let YouTube navigate.
|
|
69
|
+
function focusIsOnTile(target) {
|
|
70
|
+
if (!target || !target.closest) return false;
|
|
71
|
+
var tile = target.closest('ytlr-tile-renderer');
|
|
72
|
+
return !!(tile && !isHidden(tile));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Player UI (progress bar, actions) already visible -> let YouTube handle it.
|
|
76
|
+
function playerUiVisible() {
|
|
77
|
+
var selectors = [
|
|
78
|
+
'ytlr-player-page',
|
|
79
|
+
'ytlr-player-actions-container',
|
|
80
|
+
'ytlr-progress-bar',
|
|
81
|
+
];
|
|
82
|
+
for (var i = 0; i < selectors.length; i++) {
|
|
83
|
+
var el = document.querySelector(selectors[i]);
|
|
84
|
+
if (!el || isHidden(el)) continue;
|
|
85
|
+
var cs = window.getComputedStyle(el);
|
|
86
|
+
if (
|
|
87
|
+
cs.visibility !== 'hidden' &&
|
|
88
|
+
parseFloat(cs.opacity) > 0 &&
|
|
89
|
+
el.getClientRects().length > 0
|
|
90
|
+
) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function seekBy(video, direction) {
|
|
98
|
+
var t = video.currentTime + direction * SEEK_SECONDS;
|
|
99
|
+
var d = video.duration;
|
|
100
|
+
if (t < 0) t = 0;
|
|
101
|
+
if (isFinite(d) && t > d) t = d;
|
|
102
|
+
video.currentTime = t;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function onArrowKey(e) {
|
|
106
|
+
if (!isArrowLeftRight(e)) return;
|
|
107
|
+
|
|
108
|
+
var code = e.keyCode || e.which;
|
|
109
|
+
var direction = code === 39 ? 1 : -1;
|
|
110
|
+
|
|
111
|
+
if (e.type === 'keyup') {
|
|
112
|
+
if (!handledKeys[code]) return;
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
e.stopImmediatePropagation();
|
|
115
|
+
delete handledKeys[code];
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
120
|
+
if (isTypingTarget(e.target)) return;
|
|
121
|
+
|
|
122
|
+
var video = document.querySelector('video');
|
|
123
|
+
if (!video || video.paused || video.ended) return;
|
|
124
|
+
|
|
125
|
+
if (!handledKeys[code]) {
|
|
126
|
+
if (focusIsOnTile(e.target)) return;
|
|
127
|
+
if (playerUiVisible()) return;
|
|
128
|
+
handledKeys[code] = true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
e.stopImmediatePropagation();
|
|
133
|
+
seekBy(video, direction);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
document.addEventListener('keydown', onArrowKey, true);
|
|
137
|
+
document.addEventListener('keyup', onArrowKey, true);
|
|
138
|
+
|
|
139
|
+
/* ================================================================== *
|
|
140
|
+
* 2) RESTORE "Not interested" / "Don't recommend channel"
|
|
141
|
+
*
|
|
142
|
+
* Reis hooks resolveCommand and appends an ADD_TO_QUEUE item to tile
|
|
143
|
+
* menus. We wrap resolveCommand again (on top of his hook) and, right
|
|
144
|
+
* before a menu is shown, make sure the two feedback items exist.
|
|
145
|
+
* ================================================================== */
|
|
146
|
+
function hasFeedbackToken(obj, depth) {
|
|
147
|
+
if (!obj || typeof obj !== 'object' || depth > 8) return false;
|
|
148
|
+
if (typeof obj.feedbackToken === 'string') return true;
|
|
149
|
+
for (var k in obj) {
|
|
150
|
+
if (
|
|
151
|
+
Object.prototype.hasOwnProperty.call(obj, k) &&
|
|
152
|
+
hasFeedbackToken(obj[k], depth + 1)
|
|
153
|
+
) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getCustomAction(item) {
|
|
161
|
+
var r = item && item.menuServiceItemRenderer;
|
|
162
|
+
var ep = r && r.serviceEndpoint;
|
|
163
|
+
var pe = ep && ep.playlistEditEndpoint;
|
|
164
|
+
return (ep && ep.customAction) || (pe && pe.customAction) || null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
var FEEDBACK_TEXT = /not interested|don.?t recommend/i;
|
|
168
|
+
|
|
169
|
+
function getItemText(item) {
|
|
170
|
+
var r =
|
|
171
|
+
item && (item.menuServiceItemRenderer || item.menuNavigationItemRenderer);
|
|
172
|
+
var t = r && r.text;
|
|
173
|
+
if (!t) return '';
|
|
174
|
+
if (typeof t.simpleText === 'string') return t.simpleText;
|
|
175
|
+
var out = '';
|
|
176
|
+
if (t.runs)
|
|
177
|
+
for (var i = 0; i < t.runs.length; i++) out += t.runs[i].text || '';
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Strict check: our/Reis's custom actions, or the English label.
|
|
182
|
+
function isFeedbackItem(item) {
|
|
183
|
+
var ca = getCustomAction(item);
|
|
184
|
+
if (
|
|
185
|
+
ca &&
|
|
186
|
+
(ca.action === 'NOT_INTERESTED' || ca.action === 'DONT_RECOMMEND_CHANNEL')
|
|
187
|
+
) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
return FEEDBACK_TEXT.test(getItemText(item));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Item carries a YouTube feedback token (native feedback entry, any language).
|
|
194
|
+
function isTokenItem(item) {
|
|
195
|
+
var r =
|
|
196
|
+
item && (item.menuServiceItemRenderer || item.menuNavigationItemRenderer);
|
|
197
|
+
return !!(r && hasFeedbackToken(r, 0));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function findDeep(node, test, depth) {
|
|
201
|
+
if (!node || typeof node !== 'object' || depth > 10) return null;
|
|
202
|
+
if (test(node)) return node;
|
|
203
|
+
for (var k in node) {
|
|
204
|
+
if (Object.prototype.hasOwnProperty.call(node, k)) {
|
|
205
|
+
var r = findDeep(node[k], test, depth + 1);
|
|
206
|
+
if (r) return r;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Native feedback items anywhere inside the tile's original data.
|
|
213
|
+
function getNativeFeedbackItems(tile) {
|
|
214
|
+
var out = [];
|
|
215
|
+
findDeep(
|
|
216
|
+
tile,
|
|
217
|
+
function (n) {
|
|
218
|
+
var items = n.menuRenderer && n.menuRenderer.items;
|
|
219
|
+
if (items && items.length) {
|
|
220
|
+
for (var i = 0; i < items.length; i++) {
|
|
221
|
+
if (isFeedbackItem(items[i]) || isTokenItem(items[i]))
|
|
222
|
+
out.push(items[i]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return false; // keep walking
|
|
226
|
+
},
|
|
227
|
+
0,
|
|
228
|
+
);
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Panel info (same data Reis's NOT_INTERESTED / DONT_RECOMMEND_CHANNEL use),
|
|
233
|
+
// searched at any depth of the tile copy.
|
|
234
|
+
function getPanelInfo(tile) {
|
|
235
|
+
var node = findDeep(
|
|
236
|
+
tile,
|
|
237
|
+
function (n) {
|
|
238
|
+
var ep = n.showEngagementPanelEndpoint;
|
|
239
|
+
return !!(
|
|
240
|
+
ep &&
|
|
241
|
+
ep.identifier &&
|
|
242
|
+
ep.identifier.tag &&
|
|
243
|
+
ep.globalConfiguration &&
|
|
244
|
+
ep.globalConfiguration.params
|
|
245
|
+
);
|
|
246
|
+
},
|
|
247
|
+
0,
|
|
248
|
+
);
|
|
249
|
+
if (!node) return null;
|
|
250
|
+
var ep = node.showEngagementPanelEndpoint;
|
|
251
|
+
return {
|
|
252
|
+
panelId: ep.identifier.tag,
|
|
253
|
+
params: ep.globalConfiguration.params,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function makeServiceItem(text, endpoint) {
|
|
258
|
+
return {
|
|
259
|
+
menuServiceItemRenderer: {
|
|
260
|
+
text: { runs: [{ text: text }] },
|
|
261
|
+
serviceEndpoint: endpoint,
|
|
262
|
+
trackingParams: null,
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function buildFeedbackItems(tile) {
|
|
268
|
+
// a) The tile's own native menu has them -> reuse.
|
|
269
|
+
var native = getNativeFeedbackItems(tile);
|
|
270
|
+
if (native.length) return native;
|
|
271
|
+
// b) Build them the way Reis's own handlers expect.
|
|
272
|
+
var panel = getPanelInfo(tile);
|
|
273
|
+
if (!panel) {
|
|
274
|
+
console.warn(
|
|
275
|
+
'myCustom: no feedback items and no panel info on tile',
|
|
276
|
+
tile,
|
|
277
|
+
);
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
return [
|
|
281
|
+
makeServiceItem('Not interested', {
|
|
282
|
+
clickTrackingParams: null,
|
|
283
|
+
customAction: { action: 'NOT_INTERESTED', parameters: panel },
|
|
284
|
+
}),
|
|
285
|
+
makeServiceItem("Don't recommend channel", {
|
|
286
|
+
clickTrackingParams: null,
|
|
287
|
+
customAction: { action: 'DONT_RECOMMEND_CHANNEL', parameters: panel },
|
|
288
|
+
}),
|
|
289
|
+
];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function dbg(lines) {
|
|
293
|
+
var text = lines.join('\n');
|
|
294
|
+
console.info('myCustom:\n' + text);
|
|
295
|
+
if (!DEBUG) return;
|
|
296
|
+
try {
|
|
297
|
+
var box = document.getElementById('__ttDbg');
|
|
298
|
+
if (!box) {
|
|
299
|
+
box = document.createElement('div');
|
|
300
|
+
box.id = '__ttDbg';
|
|
301
|
+
box.style.cssText =
|
|
302
|
+
'position:fixed;top:0;left:0;z-index:2147483647;max-width:60%;' +
|
|
303
|
+
'max-height:90%;overflow:hidden;padding:8px;background:rgba(0,0,0,.88);' +
|
|
304
|
+
'color:#0f0;font:14px/1.3 monospace;white-space:pre;pointer-events:none';
|
|
305
|
+
(document.body || document.documentElement).appendChild(box);
|
|
306
|
+
}
|
|
307
|
+
box.textContent = text;
|
|
308
|
+
clearTimeout(box.__t);
|
|
309
|
+
box.__t = setTimeout(function () {
|
|
310
|
+
if (box.parentNode) box.parentNode.removeChild(box);
|
|
311
|
+
}, 15000);
|
|
312
|
+
} catch (e) {}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// The menu renders items whose endpoint is wrapped in playlistEditEndpoint
|
|
316
|
+
// (Go To Channel, Add to Queue) but Reis adds Not interested / Don't
|
|
317
|
+
// recommend with a bare `customAction`, which is not displayed.
|
|
318
|
+
// Reis's handler accepts both shapes, so re-wrap them.
|
|
319
|
+
function normalizeFeedbackItem(item) {
|
|
320
|
+
var r = item && item.menuServiceItemRenderer;
|
|
321
|
+
var ep = r && r.serviceEndpoint;
|
|
322
|
+
if (!ep || !ep.customAction || ep.playlistEditEndpoint) return;
|
|
323
|
+
var a = ep.customAction.action;
|
|
324
|
+
if (a !== 'NOT_INTERESTED' && a !== 'DONT_RECOMMEND_CHANNEL') return;
|
|
325
|
+
ep.playlistEditEndpoint = { customAction: ep.customAction };
|
|
326
|
+
delete ep.customAction;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function describeItem(item, i) {
|
|
330
|
+
var ca = getCustomAction(item);
|
|
331
|
+
return (
|
|
332
|
+
i +
|
|
333
|
+
': ' +
|
|
334
|
+
getItemText(item) +
|
|
335
|
+
' | act=' +
|
|
336
|
+
(ca ? ca.action : '-') +
|
|
337
|
+
' | tok=' +
|
|
338
|
+
(isTokenItem(item) ? 1 : 0)
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function fixMenuItems(items) {
|
|
343
|
+
var queueIndex = -1;
|
|
344
|
+
var hasFeedback = false;
|
|
345
|
+
var tokenItems = 0;
|
|
346
|
+
var i;
|
|
347
|
+
for (i = 0; i < items.length; i++) {
|
|
348
|
+
var ca = getCustomAction(items[i]);
|
|
349
|
+
if (ca && ca.action === 'ADD_TO_QUEUE' && queueIndex === -1)
|
|
350
|
+
queueIndex = i;
|
|
351
|
+
if (isFeedbackItem(items[i])) hasFeedback = true;
|
|
352
|
+
if (isTokenItem(items[i])) tokenItems++;
|
|
353
|
+
}
|
|
354
|
+
if (queueIndex === -1) return; // not a menu Reis touched
|
|
355
|
+
if (tokenItems >= 2) hasFeedback = true; // native pair in another language
|
|
356
|
+
|
|
357
|
+
var report = ['BEFORE (' + items.length + ' items):'];
|
|
358
|
+
for (i = 0; i < items.length; i++) report.push(describeItem(items[i], i));
|
|
359
|
+
report.push(
|
|
360
|
+
'hasFeedback=' +
|
|
361
|
+
hasFeedback +
|
|
362
|
+
' tokenItems=' +
|
|
363
|
+
tokenItems +
|
|
364
|
+
' queueIndex=' +
|
|
365
|
+
queueIndex,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
if (!hasFeedback) {
|
|
369
|
+
var tile = getCustomAction(items[queueIndex]).parameters;
|
|
370
|
+
var restored = buildFeedbackItems(tile);
|
|
371
|
+
report.push(
|
|
372
|
+
'panelInfo=' +
|
|
373
|
+
(getPanelInfo(tile) ? 'found' : 'MISSING') +
|
|
374
|
+
' nativeFeedback=' +
|
|
375
|
+
getNativeFeedbackItems(tile).length +
|
|
376
|
+
' restored=' +
|
|
377
|
+
restored.length,
|
|
378
|
+
);
|
|
379
|
+
for (i = 0; i < restored.length; i++) {
|
|
380
|
+
items.splice(queueIndex + i, 0, restored[i]);
|
|
381
|
+
}
|
|
382
|
+
queueIndex += restored.length;
|
|
383
|
+
} else {
|
|
384
|
+
report.push('feedback items already in menu -> nothing added');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (!KEEP_ADD_TO_QUEUE) items.splice(queueIndex, 1);
|
|
388
|
+
|
|
389
|
+
// Move "Not interested" / "Don't recommend channel" near the top.
|
|
390
|
+
var fb = [];
|
|
391
|
+
var rest = [];
|
|
392
|
+
for (i = 0; i < items.length; i++) {
|
|
393
|
+
if (
|
|
394
|
+
isFeedbackItem(items[i]) ||
|
|
395
|
+
(tokenItems >= 2 && isTokenItem(items[i]))
|
|
396
|
+
) {
|
|
397
|
+
fb.push(items[i]);
|
|
398
|
+
} else {
|
|
399
|
+
rest.push(items[i]);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
for (i = 0; i < fb.length; i++) normalizeFeedbackItem(fb[i]);
|
|
403
|
+
var at = Math.min(FEEDBACK_POSITION, rest.length);
|
|
404
|
+
var ordered = rest.slice(0, at).concat(fb, rest.slice(at));
|
|
405
|
+
items.splice.apply(items, [0, items.length].concat(ordered));
|
|
406
|
+
|
|
407
|
+
report.push('AFTER (' + items.length + ' items):');
|
|
408
|
+
for (i = 0; i < items.length; i++) report.push(describeItem(items[i], i));
|
|
409
|
+
dbg(report);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function walkForMenus(node, depth) {
|
|
413
|
+
if (!node || typeof node !== 'object' || depth > 7) return;
|
|
414
|
+
var items = node.menuRenderer && node.menuRenderer.items;
|
|
415
|
+
if (items && items.length) fixMenuItems(items);
|
|
416
|
+
for (var k in node) {
|
|
417
|
+
if (Object.prototype.hasOwnProperty.call(node, k)) {
|
|
418
|
+
walkForMenus(node[k], depth + 1);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function hookResolveCommand() {
|
|
424
|
+
var yttv = window._yttv;
|
|
425
|
+
if (!yttv) return;
|
|
426
|
+
for (var key in yttv) {
|
|
427
|
+
var entry = yttv[key];
|
|
428
|
+
if (!entry || !entry.instance) continue;
|
|
429
|
+
var current = entry.instance.resolveCommand;
|
|
430
|
+
if (typeof current !== 'function' || current.__ttCustomHook) continue;
|
|
431
|
+
entry.instance.resolveCommand = (function (orig) {
|
|
432
|
+
var wrapped = function (command) {
|
|
433
|
+
try {
|
|
434
|
+
if (
|
|
435
|
+
command &&
|
|
436
|
+
(command.showMenuCommand ||
|
|
437
|
+
command.innertubeCommand ||
|
|
438
|
+
command.commandExecutorCommand ||
|
|
439
|
+
command.openPopupAction)
|
|
440
|
+
) {
|
|
441
|
+
walkForMenus(command, 0);
|
|
442
|
+
}
|
|
443
|
+
} catch (err) {
|
|
444
|
+
console.error('myCustom: menu fix failed', err);
|
|
445
|
+
}
|
|
446
|
+
return orig.apply(this, arguments);
|
|
447
|
+
};
|
|
448
|
+
wrapped.__ttCustomHook = true;
|
|
449
|
+
return wrapped;
|
|
450
|
+
})(current);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Reis (re)patches resolveCommand during startup, so keep re-checking.
|
|
455
|
+
setInterval(hookResolveCommand, 500);
|
|
456
|
+
|
|
457
|
+
/* ================================================================== *
|
|
458
|
+
* 3) RESTORE SUBTITLE / PROGRESS-BAR ANIMATIONS
|
|
459
|
+
*
|
|
460
|
+
* Reis installs a MutationObserver on <body> that removes the
|
|
461
|
+
* `app-quality-root` class every time body attributes change. Two
|
|
462
|
+
* observers fighting over the class would loop forever, so instead we
|
|
463
|
+
* make classList.remove() ignore that one class on <body>.
|
|
464
|
+
* ================================================================== */
|
|
465
|
+
function protectQualityClass() {
|
|
466
|
+
var body = document.body;
|
|
467
|
+
if (!body) return false;
|
|
468
|
+
if (body.__ttQualityGuard) return true;
|
|
469
|
+
body.__ttQualityGuard = true;
|
|
470
|
+
|
|
471
|
+
var list = body.classList;
|
|
472
|
+
var origRemove = list.remove;
|
|
473
|
+
list.remove = function () {
|
|
474
|
+
var args = [];
|
|
475
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
476
|
+
if (arguments[i] !== QUALITY_CLASS) args.push(arguments[i]);
|
|
477
|
+
}
|
|
478
|
+
if (args.length) return origRemove.apply(list, args);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
if (!list.contains(QUALITY_CLASS)) list.add(QUALITY_CLASS);
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (!protectQualityClass()) {
|
|
486
|
+
var bodyWait = setInterval(function () {
|
|
487
|
+
if (protectQualityClass()) clearInterval(bodyWait);
|
|
488
|
+
}, 100);
|
|
489
|
+
}
|
|
490
|
+
})();
|