blob: c8fe46204f076424c07bb53985be256a845dc2b9 [file] [log] [blame]
avm9996311707032021-02-05 19:11:25 +01001var mutationObserver, intersectionObserver, intersectionOptions, options,
2 authuser;
avm99963cbea3142019-03-28 00:48:15 +01003
avm99963f5923962020-12-07 16:44:37 +01004function removeChildNodes(node) {
5 while (node.firstChild) {
6 node.removeChild(node.firstChild);
avm99963af7860e2019-06-04 03:33:26 +02007 }
avm99963f5923962020-12-07 16:44:37 +01008}
avm99963af7860e2019-06-04 03:33:26 +02009
avm9996390cc2e32021-02-05 18:14:16 +010010function getNParent(node, n) {
11 if (n <= 0) return node;
12 if (!('parentNode' in node)) return null;
13 return getNParent(node.parentNode, n - 1);
14}
15
avm999633eae4522021-04-22 01:14:27 +020016function parseUrl(url) {
17 var forum_a = url.match(/forum\/([0-9]+)/i);
18 var thread_a = url.match(/thread\/([0-9]+)/i);
19
20 if (forum_a === null || thread_a === null) {
21 return false;
22 }
23
24 return {
25 'forum': forum_a[1],
26 'thread': thread_a[1],
27 };
28}
29
avm99963f5923962020-12-07 16:44:37 +010030function createExtBadge() {
31 var badge = document.createElement('div');
32 badge.classList.add('TWPT-badge');
33 badge.setAttribute(
34 'title', chrome.i18n.getMessage('inject_extension_badge_helper', [
35 chrome.i18n.getMessage('appName')
36 ]));
37
38 var badgeI = document.createElement('i');
39 badgeI.classList.add('material-icon-i', 'material-icons-extended');
40 badgeI.textContent = 'repeat';
41
42 badge.append(badgeI);
43 return badge;
avm99963af7860e2019-06-04 03:33:26 +020044}
45
avm99963943b8492020-08-31 23:40:43 +020046function addProfileHistoryLink(node, type, query) {
47 var urlpart = encodeURIComponent('query=' + query);
avm99963a2945b62020-11-27 00:32:02 +010048 var authuserpart =
49 (authuser == '0' ? '' : '?authuser=' + encodeURIComponent(authuser));
avm99963943b8492020-08-31 23:40:43 +020050 var container = document.createElement('div');
51 container.style.margin = '3px 0';
52
53 var link = document.createElement('a');
54 link.setAttribute(
avm99963a2945b62020-11-27 00:32:02 +010055 'href',
56 'https://support.google.com/s/community/search/' + urlpart +
57 authuserpart);
avm99963943b8492020-08-31 23:40:43 +020058 link.innerText = chrome.i18n.getMessage('inject_previousposts_' + type);
59
60 container.appendChild(link);
avm9996306167752020-09-08 00:50:36 +020061 node.appendChild(container);
avm99963943b8492020-08-31 23:40:43 +020062}
63
avm999638e0c1002020-12-03 16:54:20 +010064function applyDragAndDropFix(node) {
65 console.debug('Adding link drag&drop fix to ', node);
66 node.addEventListener('drop', e => {
67 if (e.dataTransfer.types.includes('text/uri-list')) {
68 e.stopImmediatePropagation();
69 console.debug('Stopping link drop event propagation.');
70 }
71 }, true);
72}
73
avm99963f5923962020-12-07 16:44:37 +010074function nodeIsReadToggleBtn(node) {
75 return ('tagName' in node) && node.tagName == 'MATERIAL-BUTTON' &&
76 node.getAttribute('debugid') !== null &&
77 (node.getAttribute('debugid') == 'mark-read-button' ||
78 node.getAttribute('debugid') == 'mark-unread-button') &&
79 ('parentNode' in node) && node.parentNode !== null &&
80 ('parentNode' in node.parentNode) &&
81 node.parentNode.querySelector('[debugid="batchlock"]') === null &&
82 node.parentNode.parentNode !== null &&
83 ('tagName' in node.parentNode.parentNode) &&
84 node.parentNode.parentNode.tagName == 'EC-BULK-ACTIONS';
85}
86
avm9996328fddc62021-02-05 20:33:48 +010087function injectDarkModeButton(rightControl) {
88 var darkThemeSwitch = document.createElement('material-button');
89 darkThemeSwitch.classList.add('TWPT-dark-theme', 'TWPT-btn--with-badge');
90 darkThemeSwitch.setAttribute('button', '');
91 darkThemeSwitch.setAttribute(
92 'title', chrome.i18n.getMessage('inject_ccdarktheme_helper'));
93
94 darkThemeSwitch.addEventListener('click', e => {
95 chrome.storage.sync.get(null, currentOptions => {
96 currentOptions.ccdarktheme_switch_status =
97 !options.ccdarktheme_switch_status;
98 chrome.storage.sync.set(currentOptions, _ => {
99 location.reload();
100 });
101 });
102 });
103
104 var switchContent = document.createElement('div');
105 switchContent.classList.add('content');
106
107 var icon = document.createElement('material-icon');
108
109 var i = document.createElement('i');
110 i.classList.add('material-icon-i', 'material-icons-extended');
111 i.textContent = 'brightness_4';
112
113 icon.appendChild(i);
114 switchContent.appendChild(icon);
115 darkThemeSwitch.appendChild(switchContent);
116
117 var badgeContent = createExtBadge();
118
119 darkThemeSwitch.appendChild(badgeContent);
120
121 rightControl.style.width =
122 (parseInt(window.getComputedStyle(rightControl).width) + 58) + 'px';
123 rightControl.insertAdjacentElement('afterbegin', darkThemeSwitch);
124}
125
avm99963f5923962020-12-07 16:44:37 +0100126function addBatchLockBtn(readToggle) {
127 var clone = readToggle.cloneNode(true);
128 clone.setAttribute('debugid', 'batchlock');
129 clone.classList.add('TWPT-btn--with-badge');
130 clone.setAttribute('title', chrome.i18n.getMessage('inject_lockbtn'));
131 clone.querySelector('material-icon').setAttribute('icon', 'lock');
132 clone.querySelector('i.material-icon-i').textContent = 'lock';
133
134 var badge = createExtBadge();
135 clone.append(badge);
136
137 clone.addEventListener('click', function() {
138 var modal = document.querySelector('.pane[pane-id="default-1"]');
139
140 var dialog = document.createElement('material-dialog');
141 dialog.setAttribute('role', 'dialog');
142 dialog.setAttribute('aria-modal', 'true');
143 dialog.classList.add('TWPT-dialog');
144
145 var header = document.createElement('header');
146 header.setAttribute('role', 'presentation');
147 header.classList.add('TWPT-dialog-header');
148
149 var title = document.createElement('div');
150 title.classList.add('TWPT-dialog-header--title', 'title');
151 title.textContent = chrome.i18n.getMessage('inject_lockbtn');
152
153 header.append(title);
154
155 var main = document.createElement('main');
156 main.setAttribute('role', 'presentation');
157 main.classList.add('TWPT-dialog-main');
158
159 var p = document.createElement('p');
160 p.textContent = chrome.i18n.getMessage('inject_lockdialog_desc');
161
162 main.append(p);
163
164 dialog.append(header, main);
165
166 var footers = [['lock', 'unlock', 'cancel'], ['reload', 'close']];
167
168 for (var i = 0; i < footers.length; ++i) {
169 var footer = document.createElement('footer');
170 footer.setAttribute('role', 'presentation');
171 footer.classList.add('TWPT-dialog-footer');
172 footer.setAttribute('data-footer-id', i);
173
174 if (i > 0) footer.classList.add('is-hidden');
175
176 footers[i].forEach(action => {
177 var btn = document.createElement('material-button');
178 btn.setAttribute('role', 'button');
179 btn.classList.add('TWPT-dialog-footer-btn');
180 if (i == 1) btn.classList.add('is-disabled');
181
182 switch (action) {
183 case 'lock':
184 case 'unlock':
185 btn.addEventListener('click', _ => {
186 if (btn.classList.contains('is-disabled')) return;
187 var message = {
188 action,
189 prefix: 'TWPT-batchlock',
190 };
191 window.postMessage(message, '*');
192 });
193 break;
194
195 case 'cancel':
196 case 'close':
197 btn.addEventListener('click', _ => {
198 if (btn.classList.contains('is-disabled')) return;
199 modal.classList.remove('visible');
200 modal.style.display = 'none';
201 removeChildNodes(modal);
202 });
203 break;
204
205 case 'reload':
206 btn.addEventListener('click', _ => {
207 if (btn.classList.contains('is-disabled')) return;
208 window.location.reload()
209 });
210 break;
211 }
212
213 var content = document.createElement('div');
214 content.classList.add('content', 'TWPT-dialog-footer-btn--content');
215 content.textContent =
216 chrome.i18n.getMessage('inject_lockdialog_btn_' + action);
217
218 btn.append(content);
219 footer.append(btn);
220 });
221
222 var clear = document.createElement('div');
223 clear.style.clear = 'both';
224
225 footer.append(clear);
226 dialog.append(footer);
227 }
228
229 removeChildNodes(modal);
230 modal.append(dialog);
231 modal.classList.add('visible', 'modal');
232 modal.style.display = 'flex';
233 });
234 readToggle.parentNode.insertBefore(
235 clone, (readToggle.nextSibling || readToggle));
236}
237
avm999633eae4522021-04-22 01:14:27 +0200238// TODO(avm99963): This is a prototype. DON'T FORGET TO ADD ERROR HANDLING.
239function injectAvatars(node) {
240 var header = node.querySelector(
241 'ec-thread-summary .main-header .panel-description a.header');
242 if (header === null) return;
243
244 var link = parseUrl(header.href);
245 if (link === false) return;
246
247 var APIRequestUrl = 'https://support.google.com/s/community/api/ViewThread' +
248 (authuser == '0' ? '' : '?authuser=' + encodeURIComponent(authuser));
249
250 fetch(APIRequestUrl, {
251 'headers': {
252 'content-type': 'text/plain; charset=utf-8',
253 },
254 'body': JSON.stringify({
255 1: link.forum,
256 2: link.thread,
257 3: {
258 1: {2: 15},
259 3: true,
260 5: true,
261 10: true,
262 16: true,
263 18: true,
264 }
265 }),
266 'method': 'POST',
267 'mode': 'cors',
268 'credentials': 'omit',
269 })
270 .then(res => {
271 if (res.status == 200 || res.status == 400) {
272 return res.json().then(data => ({
273 status: res.status,
274 body: data,
275 }));
276 } else {
277 throw new Error('Status code ' + res.status + ' was not expected.');
278 }
279 })
280 .then(res => {
281 if (res.status == 400) {
282 throw new Error(
283 res.body[4] ||
284 ('Response status: 400. Error code: ' + res.body[2]));
285 }
286
287 return res.body;
288 })
289 .then(data => {
290 if (!('1' in data) || !('8' in data['1'])) return false;
291
292 var messages = data['1']['8'];
293 if (messages == 0) return;
294
295 var avatarUrls = [];
296
297 if (!('3' in data['1'])) return false;
298 for (var m of data['1']['3']) {
299 if (!('3' in m) || !('1' in m['3']) || !('2' in m['3']['1']))
300 continue;
301
302 var url = m['3']['1']['2'];
303
304 if (!avatarUrls.includes(url)) avatarUrls.push(url);
305
306 if (avatarUrls.length == 3) break;
307 }
308
309 var avatarsContainer = document.createElement('div');
310 avatarsContainer.classList.add('TWPT-avatars');
311
312 var count = Math.floor(Math.random() * 4);
313
314 for (var i = 0; i < avatarUrls.length; ++i) {
315 var avatar = document.createElement('div');
316 avatar.classList.add('TWPT-avatar');
avm99963a007d492021-05-02 12:32:03 +0200317 avatar.style.backgroundImage = 'url(\'' + avatarUrls[i] + '\')';
avm999633eae4522021-04-22 01:14:27 +0200318 avatarsContainer.appendChild(avatar);
319 }
320
321 header.appendChild(avatarsContainer);
322 });
323}
324
avm99963a007d492021-05-02 12:32:03 +0200325var autoRefresh = {
326 isLookingForUpdates: false,
327 isUpdatePromptShown: false,
328 lastTimestamp: null,
329 filter: null,
330 path: null,
331 snackbar: null,
332 interval: null,
333 firstCallTimeout: null,
334 intervalMs: 3 * 60 * 1000, // 3 minutes
335 firstCallDelayMs: 3 * 1000, // 3 seconds
336 getStartupData() {
337 return JSON.parse(
338 document.querySelector('html').getAttribute('data-startup'));
339 },
340 isOrderedByTimestampDescending() {
341 var startup = this.getStartupData();
342 // Returns orderOptions.by == TIMESTAMP && orderOptions.desc == true
343 return (
344 startup?.[1]?.[1]?.[3]?.[14]?.[1] == 1 &&
345 startup?.[1]?.[1]?.[3]?.[14]?.[2] == true);
346 },
347 getCustomFilter(path) {
348 var searchRegex = /^\/s\/community\/search\/([^\/]*)/;
349 var matches = path.match(searchRegex);
350 if (matches !== null && matches.length > 1) {
351 var search = decodeURIComponent(matches[1]);
352 var params = new URLSearchParams(search);
353 return params.get('query') || '';
354 }
355
356 return '';
357 },
358 filterHasOverride(filter, override) {
359 var escapedOverride = override.replace(/([^\w\d\s])/gi, '\\$1');
360 var regex = new RegExp('[^a-zA-Z0-9]?' + escapedOverride + ':');
361 return regex.test(filter);
362 },
363 getFilter(path) {
364 var query = this.getCustomFilter(path);
365
366 // Note: This logic has been copied and adapted from the
367 // _buildQuery$1$threadId function in the Community Console
368 var conditions = '';
369 var startup = this.getStartupData();
370
371 // TODO(avm99963): if the selected forums are changed without reloading the
372 // page, this will get the old selected forums. Fix this.
373 var forums = startup?.[1]?.[1]?.[3]?.[8] ?? [];
374 if (!this.filterHasOverride(query, 'forum') && forums !== null &&
375 forums.length > 0)
376 conditions += ' forum:(' + forums.join(' | ') + ')';
377
378 var langs = startup?.[1]?.[1]?.[3]?.[5] ?? [];
379 if (!this.filterHasOverride(query, 'lang') && langs !== null &&
380 langs.length > 0)
381 conditions += ' lang:(' + langs.map(l => '"' + l + '"').join(' | ') + ')';
382
383 if (query.length !== 0 && conditions.length !== 0)
384 return '(' + query + ')' + conditions;
385 return query + conditions;
386 },
387 getLastTimestamp() {
388 var APIRequestUrl = 'https://support.google.com/s/community/api/ViewForum' +
389 (authuser == '0' ? '' : '?authuser=' + encodeURIComponent(authuser));
390
391 return fetch(APIRequestUrl, {
392 'headers': {
393 'content-type': 'text/plain; charset=utf-8',
394 },
395 'body': JSON.stringify({
396 1: '0', // TODO: Change, when only a forum is selected, it
397 // should be set here
398 2: {
399 1: {
400 2: 2,
401 },
402 2: {
403 1: 1,
404 2: true,
405 },
406 12: this.filter,
407 },
408 }),
409 'method': 'POST',
410 'mode': 'cors',
411 'credentials': 'include',
412 })
413 .then(res => {
414 if (res.status == 200 || res.status == 400) {
415 return res.json().then(data => ({
416 status: res.status,
417 body: data,
418 }));
419 } else {
420 throw new Error('Status code ' + res.status + ' was not expected.');
421 }
422 })
423 .then(res => {
424 if (res.status == 400) {
425 throw new Error(
426 res.body[4] ||
427 ('Response status: 400. Error code: ' + res.body[2]));
428 }
429
430 return res.body;
431 })
432 .then(body => {
433 var timestamp = body?.[1]?.[2]?.[0]?.[2]?.[17];
434 if (timestamp === undefined)
435 throw new Error(
436 'Unexpected body of response (' +
437 (body?.[1]?.[2]?.[0] === undefined ?
438 'no threads were returned' :
439 'the timestamp value is not present in the first thread') +
440 ').');
441
442 return timestamp;
443 });
444 // TODO(avm99963): Add retry mechanism (sometimes thread lists are empty,
445 // but when loading the next page the thread appears).
446 //
447 // NOTE(avm99963): It seems like loading the first 2 threads instead of only
448 // the first one fixes this (empty lists are now rarely returned).
449 },
450 unregister() {
451 console.debug('autorefresh_list: unregistering');
452
453 if (!this.isLookingForUpdates) return;
454
455 window.clearTimeout(this.firstCallTimeout);
456 window.clearInterval(this.interval);
457 this.isUpdatePromptShown = false;
458 this.isLookingForUpdates = false;
459 },
460 showUpdatePrompt() {
461 this.snackbar.classList.remove('TWPT-hidden');
462 document.title = '[!!!] ' + document.title.replace('[!!!] ', '');
463 this.isUpdatePromptShown = true;
464 },
465 hideUpdatePrompt() {
466 this.snackbar.classList.add('TWPT-hidden');
467 document.title = document.title.replace('[!!!] ', '');
468 this.isUpdatePromptShown = false;
469 },
470 injectUpdatePrompt() {
471 var pane = document.createElement('div');
472 pane.classList.add('TWPT-pane-for-snackbar');
473
474 var snackbar = document.createElement('material-snackbar-panel');
475 snackbar.classList.add('TWPT-snackbar');
476 snackbar.classList.add('TWPT-hidden');
477
478 var ac = document.createElement('div');
479 ac.classList.add('TWPT-animation-container');
480
481 var nb = document.createElement('div');
482 nb.classList.add('TWPT-notification-bar');
483
484 var ft = document.createElement('focus-trap');
485
486 var content = document.createElement('div');
487 content.classList.add('TWPT-focus-content-wrapper');
488
489 var badge = createExtBadge();
490
491 var message = document.createElement('div');
492 message.classList.add('TWPT-message');
493 message.textContent =
494 chrome.i18n.getMessage('inject_autorefresh_list_snackbar_message');
495
496 var action = document.createElement('div');
497 action.classList.add('TWPT-action');
498 action.textContent =
499 chrome.i18n.getMessage('inject_autorefresh_list_snackbar_action');
500
501 action.addEventListener('click', e => {
502 this.hideUpdatePrompt();
503 document.querySelector('.app-title-button').click();
504 });
505
506 content.append(badge, message, action);
507 ft.append(content);
508 nb.append(ft);
509 ac.append(nb);
510 snackbar.append(ac);
511 pane.append(snackbar);
512 document.getElementById('default-acx-overlay-container').append(pane);
513 this.snackbar = snackbar;
514 },
515 checkUpdate() {
516 if (location.pathname != this.path) {
517 this.unregister();
518 return;
519 }
520
521 if (this.isUpdatePromptShown) return;
522
523 console.debug('Checking for update at: ', new Date());
524
525 this.getLastTimestamp()
526 .then(timestamp => {
527 if (timestamp != this.lastTimestamp) this.showUpdatePrompt();
528 })
529 .catch(
530 err => console.error(
531 'Coudln\'t get last timestamp (while updating): ', err));
532 },
533 firstCall() {
534 console.debug(
535 'autorefresh_list: now performing first call to finish setup (filter: [' +
536 this.filter + '])');
537
538 if (location.pathname != this.path) {
539 this.unregister();
540 return;
541 }
542
543 this.getLastTimestamp()
544 .then(timestamp => {
545 this.lastTimestamp = timestamp;
546 var checkUpdateCallback = this.checkUpdate.bind(this);
547 this.interval =
548 window.setInterval(checkUpdateCallback, this.intervalMs);
549 })
550 .catch(
551 err => console.error(
552 'Couldn\'t get last timestamp (while setting up): ', err));
553 },
554 setUp() {
555 if (!this.isOrderedByTimestampDescending()) return;
556
557 this.unregister();
558
559 console.debug('autorefresh_list: starting set up...');
560
561 if (this.snackbar === null) this.injectUpdatePrompt();
562 this.isLookingForUpdates = true;
563 this.path = location.pathname;
564 this.filter = this.getFilter(this.path);
565
566 var firstCall = this.firstCall.bind(this);
567 this.firstCallTimeout = window.setTimeout(firstCall, this.firstCallDelayMs);
568 },
569};
570
Adrià Vilanova Martínezc6aacfa2021-06-09 14:16:11 +0200571function isDarkThemeOn() {
572 if (!options.ccdarktheme) return false;
573
574 if (options.ccdarktheme_mode == 'switch')
575 return options.ccdarktheme_switch_status;
576
577 return window.matchMedia &&
578 window.matchMedia('(prefers-color-scheme: dark)').matches;
579}
580
581var unifiedProfilesFix = {
582 checkIframe(iframe) {
583 var srcRegex = /support.*\.google\.com\/profile\//;
Adrià Vilanova Martínezc6aacfa2021-06-09 14:16:11 +0200584 return srcRegex.test(iframe.src ?? '');
585 },
586 fixIframe(iframe) {
587 console.info('[unifiedProfilesFix] Fixing unified profiles iframe');
588 var url = new URL(iframe.src);
589 url.searchParams.set('dark', 1);
590 iframe.src = url.href;
591 },
592};
593
avm9996390cc2e32021-02-05 18:14:16 +0100594function injectPreviousPostsLinks(nameElement) {
595 var mainCardContent = getNParent(nameElement, 3);
596 if (mainCardContent === null) {
597 console.error(
598 '[previousposts] Couldn\'t find |.main-card-content| element.');
599 return;
avm99963490114d2021-02-05 16:12:20 +0100600 }
avm9996390cc2e32021-02-05 18:14:16 +0100601
602 var forumId = location.href.split('/forum/')[1].split('/')[0] || '0';
603
avm99963193233a2021-05-29 15:49:29 +0200604 var nameTag =
605 (nameElement.tagName == 'EC-DISPLAY-NAME-EDITOR' ?
606 nameElement.querySelector('.top-section > span') ?? nameElement :
607 nameElement);
608 var name = escapeUsername(nameTag.textContent);
avm9996390cc2e32021-02-05 18:14:16 +0100609 var query1 = encodeURIComponent(
610 '(creator:"' + name + '" | replier:"' + name + '") forum:' + forumId);
611 var query2 = encodeURIComponent(
612 '(creator:"' + name + '" | replier:"' + name + '") forum:any');
613
614 var container = document.createElement('div');
615 container.classList.add('TWPT-previous-posts');
616
617 var badge = createExtBadge();
618 container.appendChild(badge);
619
620 var linkContainer = document.createElement('div');
621 linkContainer.classList.add('TWPT-previous-posts--links');
622
623 addProfileHistoryLink(linkContainer, 'forum', query1);
624 addProfileHistoryLink(linkContainer, 'all', query2);
625
626 container.appendChild(linkContainer);
627
628 mainCardContent.appendChild(container);
avm99963490114d2021-02-05 16:12:20 +0100629}
630
631const watchedNodesSelectors = [
avm9996328fddc62021-02-05 20:33:48 +0100632 // App container (used to set up the intersection observer and inject the dark
633 // mode button)
avm9996311707032021-02-05 19:11:25 +0100634 'ec-app',
635
avm99963490114d2021-02-05 16:12:20 +0100636 // Load more bar (for the "load more"/"load all" buttons)
637 '.load-more-bar',
638
avm99963b3e89862021-05-15 19:58:21 +0200639 // Username span/editor inside ec-user (user profile view)
avm9996390cc2e32021-02-05 18:14:16 +0100640 'ec-user .main-card .header > .name > span',
avm99963b3e89862021-05-15 19:58:21 +0200641 'ec-user .main-card .header > .name > ec-display-name-editor',
avm99963490114d2021-02-05 16:12:20 +0100642
643 // Rich text editor
644 'ec-movable-dialog',
645 'ec-rich-text-editor',
646
647 // Read/unread bulk action in the list of thread, for the batch lock feature
648 'ec-bulk-actions material-button[debugid="mark-read-button"]',
649 'ec-bulk-actions material-button[debugid="mark-unread-button"]',
avm999633eae4522021-04-22 01:14:27 +0200650
651 // Thread list items (used to inject the avatars)
652 'li',
avm99963a007d492021-05-02 12:32:03 +0200653
654 // Thread list (used for the autorefresh feature)
655 'ec-thread-list',
Adrià Vilanova Martínezc6aacfa2021-06-09 14:16:11 +0200656
657 // Unified profile iframe
658 'iframe',
avm99963490114d2021-02-05 16:12:20 +0100659];
660
661function handleCandidateNode(node) {
662 if (typeof node.classList !== 'undefined') {
avm9996328fddc62021-02-05 20:33:48 +0100663 if (('tagName' in node) && node.tagName == 'EC-APP') {
664 // Set up the intersectionObserver
665 if (typeof intersectionObserver === 'undefined') {
666 var scrollableContent = node.querySelector('.scrollable-content');
667 if (scrollableContent !== null) {
668 intersectionOptions = {
669 root: scrollableContent,
670 rootMargin: '0px',
671 threshold: 1.0,
672 };
avm9996311707032021-02-05 19:11:25 +0100673
avm9996328fddc62021-02-05 20:33:48 +0100674 intersectionObserver = new IntersectionObserver(
675 intersectionCallback, intersectionOptions);
676 }
677 }
678
679 // Inject the dark mode button
680 if (options.ccdarktheme && options.ccdarktheme_mode == 'switch') {
681 var rightControl = node.querySelector('header .right-control');
682 if (rightControl !== null) injectDarkModeButton(rightControl);
avm99963d24e2db2021-02-05 20:03:55 +0100683 }
avm99963490114d2021-02-05 16:12:20 +0100684 }
685
avm9996311707032021-02-05 19:11:25 +0100686 // Start the intersectionObserver for the "load more"/"load all" buttons
687 // inside a thread
688 if ((options.thread || options.threadall) &&
689 node.classList.contains('load-more-bar')) {
690 if (typeof intersectionObserver !== 'undefined') {
691 if (options.thread)
692 intersectionObserver.observe(node.querySelector('.load-more-button'));
693 if (options.threadall)
694 intersectionObserver.observe(node.querySelector('.load-all-button'));
695 } else {
696 console.warn(
697 '[infinitescroll] ' +
698 'The intersectionObserver is not ready yet.');
699 }
avm99963490114d2021-02-05 16:12:20 +0100700 }
701
702 // Show the "previous posts" links
703 // Here we're selecting the 'ec-user > div' element (unique child)
avm9996390cc2e32021-02-05 18:14:16 +0100704 if (options.history &&
avm99963b3e89862021-05-15 19:58:21 +0200705 (node.matches('ec-user .main-card .header > .name > span') ||
706 node.matches(
707 'ec-user .main-card .header > .name > ec-display-name-editor'))) {
avm99963490114d2021-02-05 16:12:20 +0100708 injectPreviousPostsLinks(node);
709 }
710
711 // Fix the drag&drop issue with the rich text editor
712 //
713 // We target both tags because in different contexts different
714 // elements containing the text editor get added to the DOM structure.
715 // Sometimes it's a EC-MOVABLE-DIALOG which already contains the
716 // EC-RICH-TEXT-EDITOR, and sometimes it's the EC-RICH-TEXT-EDITOR
717 // directly.
718 if (options.ccdragndropfix && ('tagName' in node) &&
719 (node.tagName == 'EC-MOVABLE-DIALOG' ||
720 node.tagName == 'EC-RICH-TEXT-EDITOR')) {
721 applyDragAndDropFix(node);
722 }
723
724 // Inject the batch lock button in the thread list
725 if (options.batchlock && nodeIsReadToggleBtn(node)) {
726 addBatchLockBtn(node);
727 }
avm999633eae4522021-04-22 01:14:27 +0200728
729 // Inject avatar links to threads in the thread list
730 if (options.threadlistavatars && ('tagName' in node) &&
731 (node.tagName == 'LI') &&
732 node.querySelector('ec-thread-summary') !== null) {
733 injectAvatars(node);
734 }
avm99963a007d492021-05-02 12:32:03 +0200735
736 // Set up the autorefresh list feature
737 if (options.autorefreshlist && ('tagName' in node) &&
738 node.tagName == 'EC-THREAD-LIST') {
739 autoRefresh.setUp();
740 }
Adrià Vilanova Martínezc6aacfa2021-06-09 14:16:11 +0200741
742 // Redirect unified profile iframe to dark version if applicable
743 if (node.tagName == 'IFRAME' && isDarkThemeOn() &&
744 unifiedProfilesFix.checkIframe(node)) {
745 unifiedProfilesFix.fixIframe(node);
746 }
avm99963a007d492021-05-02 12:32:03 +0200747 }
748}
749
750function handleRemovedNode(node) {
751 // Remove snackbar when exiting thread list view
752 if ('tagName' in node && node.tagName == 'EC-THREAD-LIST') {
753 autoRefresh.hideUpdatePrompt();
avm99963490114d2021-02-05 16:12:20 +0100754 }
755}
756
avm99963847ee632019-03-27 00:57:44 +0100757function mutationCallback(mutationList, observer) {
758 mutationList.forEach((mutation) => {
avm99963b69eb3d2020-08-20 02:03:44 +0200759 if (mutation.type == 'childList') {
760 mutation.addedNodes.forEach(function(node) {
avm99963490114d2021-02-05 16:12:20 +0100761 handleCandidateNode(node);
avm99963847ee632019-03-27 00:57:44 +0100762 });
avm99963a007d492021-05-02 12:32:03 +0200763
764 mutation.removedNodes.forEach(function(node) {
765 handleRemovedNode(node);
766 });
avm99963847ee632019-03-27 00:57:44 +0100767 }
768 });
769}
770
avm99963adf90862020-04-12 13:27:45 +0200771function intersectionCallback(entries, observer) {
avm99963847ee632019-03-27 00:57:44 +0100772 entries.forEach(entry => {
773 if (entry.isIntersecting) {
774 entry.target.click();
775 }
776 });
777};
778
779var observerOptions = {
780 childList: true,
avm99963b69eb3d2020-08-20 02:03:44 +0200781 subtree: true,
avm99963129fb502020-08-28 05:18:53 +0200782};
avm99963847ee632019-03-27 00:57:44 +0100783
avm99963129fb502020-08-28 05:18:53 +0200784chrome.storage.sync.get(null, function(items) {
785 options = items;
avm99963cbea3142019-03-28 00:48:15 +0100786
avm99963a2945b62020-11-27 00:32:02 +0100787 var startup =
788 JSON.parse(document.querySelector('html').getAttribute('data-startup'));
789 authuser = startup[2][1] || '0';
790
avm99963490114d2021-02-05 16:12:20 +0100791 // Before starting the mutation Observer, check whether we missed any
avm9996311707032021-02-05 19:11:25 +0100792 // mutations by manually checking whether some watched nodes already
793 // exist.
avm99963490114d2021-02-05 16:12:20 +0100794 var cssSelectors = watchedNodesSelectors.join(',');
795 document.querySelectorAll(cssSelectors)
796 .forEach(node => handleCandidateNode(node));
797
avm99963129fb502020-08-28 05:18:53 +0200798 mutationObserver = new MutationObserver(mutationCallback);
avm99963e4cac402020-12-03 16:10:58 +0100799 mutationObserver.observe(document.body, observerOptions);
avm99963cbea3142019-03-28 00:48:15 +0100800
avm99963129fb502020-08-28 05:18:53 +0200801 if (options.fixedtoolbar) {
802 injectStyles(
avm999630bc113a2020-09-07 13:02:11 +0200803 'ec-bulk-actions{position: sticky; top: 0; background: var(--TWPT-primary-background, #fff); z-index: 96;}');
avm99963129fb502020-08-28 05:18:53 +0200804 }
avm99963ae6a26d2020-04-12 14:03:51 +0200805
avm99963129fb502020-08-28 05:18:53 +0200806 if (options.increasecontrast) {
avm999630bc113a2020-09-07 13:02:11 +0200807 injectStyles(
avm99963a2a06442020-11-25 21:11:10 +0100808 '.thread-summary.read:not(.checked){background: var(--TWPT-thread-read-background, #ecedee)!important;}');
avm99963129fb502020-08-28 05:18:53 +0200809 }
avm999630f9503f2020-07-27 13:56:52 +0200810
avm99963129fb502020-08-28 05:18:53 +0200811 if (options.stickysidebarheaders) {
812 injectStyles(
avm999630bc113a2020-09-07 13:02:11 +0200813 'material-drawer .main-header{background: var(--TWPT-drawer-background, #fff)!important; position: sticky; top: 0; z-index: 1;}');
814 }
815
avm99963698d3762021-02-16 01:19:54 +0100816 if (options.enhancedannouncementsdot) {
817 injectStylesheet(
818 chrome.runtime.getURL('injections/enhanced_announcements_dot.css'));
819 }
820
avm99963d98126f2021-02-17 10:44:36 +0100821 if (options.repositionexpandthread) {
822 injectStylesheet(
823 chrome.runtime.getURL('injections/reposition_expand_thread.css'));
824 }
825
avm99963129942f2020-09-08 02:07:18 +0200826 if (options.ccforcehidedrawer) {
827 var drawer = document.querySelector('material-drawer');
828 if (drawer !== null && drawer.classList.contains('mat-drawer-expanded')) {
829 document.querySelector('.material-drawer-button').click();
830 }
831 }
avm99963f5923962020-12-07 16:44:37 +0100832
833 if (options.batchlock) {
834 injectScript(chrome.runtime.getURL('injections/batchlock_inject.js'));
835 }
avm999633eae4522021-04-22 01:14:27 +0200836
837 if (options.threadlistavatars) {
838 injectStylesheet(
839 chrome.runtime.getURL('injections/thread_list_avatars.css'));
840 }
avm99963a007d492021-05-02 12:32:03 +0200841
842 if (options.autorefreshlist) {
843 injectStylesheet(chrome.runtime.getURL('injections/autorefresh_list.css'));
844 }
avm99963129fb502020-08-28 05:18:53 +0200845});