blob: 8a1d37372fd228b947514292366bf7e138cb1aa8 [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
avm99963f5923962020-12-07 16:44:37 +010016function createExtBadge() {
17 var badge = document.createElement('div');
18 badge.classList.add('TWPT-badge');
19 badge.setAttribute(
20 'title', chrome.i18n.getMessage('inject_extension_badge_helper', [
21 chrome.i18n.getMessage('appName')
22 ]));
23
24 var badgeI = document.createElement('i');
25 badgeI.classList.add('material-icon-i', 'material-icons-extended');
26 badgeI.textContent = 'repeat';
27
28 badge.append(badgeI);
29 return badge;
avm99963af7860e2019-06-04 03:33:26 +020030}
31
avm99963943b8492020-08-31 23:40:43 +020032function addProfileHistoryLink(node, type, query) {
33 var urlpart = encodeURIComponent('query=' + query);
avm99963a2945b62020-11-27 00:32:02 +010034 var authuserpart =
35 (authuser == '0' ? '' : '?authuser=' + encodeURIComponent(authuser));
avm99963943b8492020-08-31 23:40:43 +020036 var container = document.createElement('div');
37 container.style.margin = '3px 0';
38
39 var link = document.createElement('a');
40 link.setAttribute(
avm99963a2945b62020-11-27 00:32:02 +010041 'href',
42 'https://support.google.com/s/community/search/' + urlpart +
43 authuserpart);
avm99963943b8492020-08-31 23:40:43 +020044 link.innerText = chrome.i18n.getMessage('inject_previousposts_' + type);
45
46 container.appendChild(link);
avm9996306167752020-09-08 00:50:36 +020047 node.appendChild(container);
avm99963943b8492020-08-31 23:40:43 +020048}
49
avm999638e0c1002020-12-03 16:54:20 +010050function applyDragAndDropFix(node) {
51 console.debug('Adding link drag&drop fix to ', node);
52 node.addEventListener('drop', e => {
53 if (e.dataTransfer.types.includes('text/uri-list')) {
54 e.stopImmediatePropagation();
55 console.debug('Stopping link drop event propagation.');
56 }
57 }, true);
58}
59
avm99963f5923962020-12-07 16:44:37 +010060function nodeIsReadToggleBtn(node) {
61 return ('tagName' in node) && node.tagName == 'MATERIAL-BUTTON' &&
62 node.getAttribute('debugid') !== null &&
63 (node.getAttribute('debugid') == 'mark-read-button' ||
64 node.getAttribute('debugid') == 'mark-unread-button') &&
65 ('parentNode' in node) && node.parentNode !== null &&
66 ('parentNode' in node.parentNode) &&
67 node.parentNode.querySelector('[debugid="batchlock"]') === null &&
68 node.parentNode.parentNode !== null &&
69 ('tagName' in node.parentNode.parentNode) &&
70 node.parentNode.parentNode.tagName == 'EC-BULK-ACTIONS';
71}
72
73function addBatchLockBtn(readToggle) {
74 var clone = readToggle.cloneNode(true);
75 clone.setAttribute('debugid', 'batchlock');
76 clone.classList.add('TWPT-btn--with-badge');
77 clone.setAttribute('title', chrome.i18n.getMessage('inject_lockbtn'));
78 clone.querySelector('material-icon').setAttribute('icon', 'lock');
79 clone.querySelector('i.material-icon-i').textContent = 'lock';
80
81 var badge = createExtBadge();
82 clone.append(badge);
83
84 clone.addEventListener('click', function() {
85 var modal = document.querySelector('.pane[pane-id="default-1"]');
86
87 var dialog = document.createElement('material-dialog');
88 dialog.setAttribute('role', 'dialog');
89 dialog.setAttribute('aria-modal', 'true');
90 dialog.classList.add('TWPT-dialog');
91
92 var header = document.createElement('header');
93 header.setAttribute('role', 'presentation');
94 header.classList.add('TWPT-dialog-header');
95
96 var title = document.createElement('div');
97 title.classList.add('TWPT-dialog-header--title', 'title');
98 title.textContent = chrome.i18n.getMessage('inject_lockbtn');
99
100 header.append(title);
101
102 var main = document.createElement('main');
103 main.setAttribute('role', 'presentation');
104 main.classList.add('TWPT-dialog-main');
105
106 var p = document.createElement('p');
107 p.textContent = chrome.i18n.getMessage('inject_lockdialog_desc');
108
109 main.append(p);
110
111 dialog.append(header, main);
112
113 var footers = [['lock', 'unlock', 'cancel'], ['reload', 'close']];
114
115 for (var i = 0; i < footers.length; ++i) {
116 var footer = document.createElement('footer');
117 footer.setAttribute('role', 'presentation');
118 footer.classList.add('TWPT-dialog-footer');
119 footer.setAttribute('data-footer-id', i);
120
121 if (i > 0) footer.classList.add('is-hidden');
122
123 footers[i].forEach(action => {
124 var btn = document.createElement('material-button');
125 btn.setAttribute('role', 'button');
126 btn.classList.add('TWPT-dialog-footer-btn');
127 if (i == 1) btn.classList.add('is-disabled');
128
129 switch (action) {
130 case 'lock':
131 case 'unlock':
132 btn.addEventListener('click', _ => {
133 if (btn.classList.contains('is-disabled')) return;
134 var message = {
135 action,
136 prefix: 'TWPT-batchlock',
137 };
138 window.postMessage(message, '*');
139 });
140 break;
141
142 case 'cancel':
143 case 'close':
144 btn.addEventListener('click', _ => {
145 if (btn.classList.contains('is-disabled')) return;
146 modal.classList.remove('visible');
147 modal.style.display = 'none';
148 removeChildNodes(modal);
149 });
150 break;
151
152 case 'reload':
153 btn.addEventListener('click', _ => {
154 if (btn.classList.contains('is-disabled')) return;
155 window.location.reload()
156 });
157 break;
158 }
159
160 var content = document.createElement('div');
161 content.classList.add('content', 'TWPT-dialog-footer-btn--content');
162 content.textContent =
163 chrome.i18n.getMessage('inject_lockdialog_btn_' + action);
164
165 btn.append(content);
166 footer.append(btn);
167 });
168
169 var clear = document.createElement('div');
170 clear.style.clear = 'both';
171
172 footer.append(clear);
173 dialog.append(footer);
174 }
175
176 removeChildNodes(modal);
177 modal.append(dialog);
178 modal.classList.add('visible', 'modal');
179 modal.style.display = 'flex';
180 });
181 readToggle.parentNode.insertBefore(
182 clone, (readToggle.nextSibling || readToggle));
183}
184
avm9996390cc2e32021-02-05 18:14:16 +0100185function injectPreviousPostsLinks(nameElement) {
186 var mainCardContent = getNParent(nameElement, 3);
187 if (mainCardContent === null) {
188 console.error(
189 '[previousposts] Couldn\'t find |.main-card-content| element.');
190 return;
avm99963490114d2021-02-05 16:12:20 +0100191 }
avm9996390cc2e32021-02-05 18:14:16 +0100192
193 var forumId = location.href.split('/forum/')[1].split('/')[0] || '0';
194
195 var name = escapeUsername(nameElement.textContent);
196 var query1 = encodeURIComponent(
197 '(creator:"' + name + '" | replier:"' + name + '") forum:' + forumId);
198 var query2 = encodeURIComponent(
199 '(creator:"' + name + '" | replier:"' + name + '") forum:any');
200
201 var container = document.createElement('div');
202 container.classList.add('TWPT-previous-posts');
203
204 var badge = createExtBadge();
205 container.appendChild(badge);
206
207 var linkContainer = document.createElement('div');
208 linkContainer.classList.add('TWPT-previous-posts--links');
209
210 addProfileHistoryLink(linkContainer, 'forum', query1);
211 addProfileHistoryLink(linkContainer, 'all', query2);
212
213 container.appendChild(linkContainer);
214
215 mainCardContent.appendChild(container);
avm99963490114d2021-02-05 16:12:20 +0100216}
217
218const watchedNodesSelectors = [
avm9996311707032021-02-05 19:11:25 +0100219 // App container (used to set up the intersection observer)
220 'ec-app',
221
avm99963490114d2021-02-05 16:12:20 +0100222 // Load more bar (for the "load more"/"load all" buttons)
223 '.load-more-bar',
224
avm9996390cc2e32021-02-05 18:14:16 +0100225 // Username span inside ec-user (user profile view)
226 'ec-user .main-card .header > .name > span',
avm99963490114d2021-02-05 16:12:20 +0100227
228 // Rich text editor
229 'ec-movable-dialog',
230 'ec-rich-text-editor',
231
232 // Read/unread bulk action in the list of thread, for the batch lock feature
233 'ec-bulk-actions material-button[debugid="mark-read-button"]',
234 'ec-bulk-actions material-button[debugid="mark-unread-button"]',
235];
236
237function handleCandidateNode(node) {
238 if (typeof node.classList !== 'undefined') {
avm9996311707032021-02-05 19:11:25 +0100239 // Set up the intersectionObserver
240 if (typeof intersectionObserver === 'undefined' && ('tagName' in node) &&
241 node.tagName == 'EC-APP') {
avm99963d24e2db2021-02-05 20:03:55 +0100242 var scrollableContent = node.querySelector('.scrollable-content');
243 if (scrollableContent !== null) {
244 intersectionOptions = {
245 root: scrollableContent,
246 rootMargin: '0px',
247 threshold: 1.0,
248 };
avm9996311707032021-02-05 19:11:25 +0100249
avm99963d24e2db2021-02-05 20:03:55 +0100250 intersectionObserver =
251 new IntersectionObserver(intersectionCallback, intersectionOptions);
252 }
avm99963490114d2021-02-05 16:12:20 +0100253 }
254
avm9996311707032021-02-05 19:11:25 +0100255 // Start the intersectionObserver for the "load more"/"load all" buttons
256 // inside a thread
257 if ((options.thread || options.threadall) &&
258 node.classList.contains('load-more-bar')) {
259 if (typeof intersectionObserver !== 'undefined') {
260 if (options.thread)
261 intersectionObserver.observe(node.querySelector('.load-more-button'));
262 if (options.threadall)
263 intersectionObserver.observe(node.querySelector('.load-all-button'));
264 } else {
265 console.warn(
266 '[infinitescroll] ' +
267 'The intersectionObserver is not ready yet.');
268 }
avm99963490114d2021-02-05 16:12:20 +0100269 }
270
271 // Show the "previous posts" links
272 // Here we're selecting the 'ec-user > div' element (unique child)
avm9996390cc2e32021-02-05 18:14:16 +0100273 if (options.history &&
274 node.matches('ec-user .main-card .header > .name > span')) {
avm99963490114d2021-02-05 16:12:20 +0100275 injectPreviousPostsLinks(node);
276 }
277
278 // Fix the drag&drop issue with the rich text editor
279 //
280 // We target both tags because in different contexts different
281 // elements containing the text editor get added to the DOM structure.
282 // Sometimes it's a EC-MOVABLE-DIALOG which already contains the
283 // EC-RICH-TEXT-EDITOR, and sometimes it's the EC-RICH-TEXT-EDITOR
284 // directly.
285 if (options.ccdragndropfix && ('tagName' in node) &&
286 (node.tagName == 'EC-MOVABLE-DIALOG' ||
287 node.tagName == 'EC-RICH-TEXT-EDITOR')) {
288 applyDragAndDropFix(node);
289 }
290
291 // Inject the batch lock button in the thread list
292 if (options.batchlock && nodeIsReadToggleBtn(node)) {
293 addBatchLockBtn(node);
294 }
295 }
296}
297
avm99963847ee632019-03-27 00:57:44 +0100298function mutationCallback(mutationList, observer) {
299 mutationList.forEach((mutation) => {
avm99963b69eb3d2020-08-20 02:03:44 +0200300 if (mutation.type == 'childList') {
301 mutation.addedNodes.forEach(function(node) {
avm99963490114d2021-02-05 16:12:20 +0100302 handleCandidateNode(node);
avm99963847ee632019-03-27 00:57:44 +0100303 });
304 }
305 });
306}
307
avm99963adf90862020-04-12 13:27:45 +0200308function intersectionCallback(entries, observer) {
avm99963847ee632019-03-27 00:57:44 +0100309 entries.forEach(entry => {
310 if (entry.isIntersecting) {
311 entry.target.click();
312 }
313 });
314};
315
316var observerOptions = {
317 childList: true,
avm99963b69eb3d2020-08-20 02:03:44 +0200318 subtree: true,
avm99963129fb502020-08-28 05:18:53 +0200319};
avm99963847ee632019-03-27 00:57:44 +0100320
avm99963129fb502020-08-28 05:18:53 +0200321chrome.storage.sync.get(null, function(items) {
322 options = items;
avm99963cbea3142019-03-28 00:48:15 +0100323
avm99963a2945b62020-11-27 00:32:02 +0100324 var startup =
325 JSON.parse(document.querySelector('html').getAttribute('data-startup'));
326 authuser = startup[2][1] || '0';
327
avm99963490114d2021-02-05 16:12:20 +0100328 // Before starting the mutation Observer, check whether we missed any
avm9996311707032021-02-05 19:11:25 +0100329 // mutations by manually checking whether some watched nodes already
330 // exist.
avm99963490114d2021-02-05 16:12:20 +0100331 var cssSelectors = watchedNodesSelectors.join(',');
332 document.querySelectorAll(cssSelectors)
333 .forEach(node => handleCandidateNode(node));
334
avm99963129fb502020-08-28 05:18:53 +0200335 mutationObserver = new MutationObserver(mutationCallback);
avm99963e4cac402020-12-03 16:10:58 +0100336 mutationObserver.observe(document.body, observerOptions);
avm99963cbea3142019-03-28 00:48:15 +0100337
avm99963129fb502020-08-28 05:18:53 +0200338 if (options.fixedtoolbar) {
339 injectStyles(
avm999630bc113a2020-09-07 13:02:11 +0200340 'ec-bulk-actions{position: sticky; top: 0; background: var(--TWPT-primary-background, #fff); z-index: 96;}');
avm99963129fb502020-08-28 05:18:53 +0200341 }
avm99963ae6a26d2020-04-12 14:03:51 +0200342
avm99963129fb502020-08-28 05:18:53 +0200343 if (options.increasecontrast) {
avm999630bc113a2020-09-07 13:02:11 +0200344 injectStyles(
avm99963a2a06442020-11-25 21:11:10 +0100345 '.thread-summary.read:not(.checked){background: var(--TWPT-thread-read-background, #ecedee)!important;}');
avm99963129fb502020-08-28 05:18:53 +0200346 }
avm999630f9503f2020-07-27 13:56:52 +0200347
avm99963129fb502020-08-28 05:18:53 +0200348 if (options.stickysidebarheaders) {
349 injectStyles(
avm999630bc113a2020-09-07 13:02:11 +0200350 'material-drawer .main-header{background: var(--TWPT-drawer-background, #fff)!important; position: sticky; top: 0; z-index: 1;}');
351 }
352
353 if (options.ccdarktheme && options.ccdarktheme_mode == 'switch') {
avm999630bc113a2020-09-07 13:02:11 +0200354 var darkThemeSwitch = document.createElement('material-button');
avm99963f5923962020-12-07 16:44:37 +0100355 darkThemeSwitch.classList.add('TWPT-dark-theme', 'TWPT-btn--with-badge');
avm999630bc113a2020-09-07 13:02:11 +0200356 darkThemeSwitch.setAttribute('button', '');
357 darkThemeSwitch.setAttribute(
358 'title', chrome.i18n.getMessage('inject_ccdarktheme_helper'));
359
360 darkThemeSwitch.addEventListener('click', e => {
361 chrome.storage.sync.get(null, currentOptions => {
362 currentOptions.ccdarktheme_switch_status =
363 !options.ccdarktheme_switch_status;
364 chrome.storage.sync.set(currentOptions, _ => {
365 location.reload();
366 });
367 });
368 });
369
370 var switchContent = document.createElement('div');
371 switchContent.classList.add('content');
372
373 var icon = document.createElement('material-icon');
374
375 var i = document.createElement('i');
376 i.classList.add('material-icon-i', 'material-icons-extended');
377 i.textContent = 'brightness_4';
378
379 icon.appendChild(i);
380 switchContent.appendChild(icon);
381 darkThemeSwitch.appendChild(switchContent);
382
avm99963f5923962020-12-07 16:44:37 +0100383 var badgeContent = createExtBadge();
avm9996306167752020-09-08 00:50:36 +0200384
avm9996306167752020-09-08 00:50:36 +0200385 darkThemeSwitch.appendChild(badgeContent);
386
avm999630bc113a2020-09-07 13:02:11 +0200387 var rightControl = document.querySelector('header .right-control');
388 rightControl.style.width =
389 (parseInt(window.getComputedStyle(rightControl).width) + 58) + 'px';
390 rightControl.insertAdjacentElement('afterbegin', darkThemeSwitch);
avm99963129fb502020-08-28 05:18:53 +0200391 }
avm99963129942f2020-09-08 02:07:18 +0200392
393 if (options.ccforcehidedrawer) {
394 var drawer = document.querySelector('material-drawer');
395 if (drawer !== null && drawer.classList.contains('mat-drawer-expanded')) {
396 document.querySelector('.material-drawer-button').click();
397 }
398 }
avm99963f5923962020-12-07 16:44:37 +0100399
400 if (options.batchlock) {
401 injectScript(chrome.runtime.getURL('injections/batchlock_inject.js'));
402 }
avm99963129fb502020-08-28 05:18:53 +0200403});