blob: 8d79277ac2aad8798d497356e55bb9a61f34815f [file] [log] [blame]
avm99963a2945b62020-11-27 00:32:02 +01001var mutationObserver, intersectionObserver, options, authuser;
avm99963cbea3142019-03-28 00:48:15 +01002
avm99963f5923962020-12-07 16:44:37 +01003function removeChildNodes(node) {
4 while (node.firstChild) {
5 node.removeChild(node.firstChild);
avm99963af7860e2019-06-04 03:33:26 +02006 }
avm99963f5923962020-12-07 16:44:37 +01007}
avm99963af7860e2019-06-04 03:33:26 +02008
avm9996390cc2e32021-02-05 18:14:16 +01009function getNParent(node, n) {
10 if (n <= 0) return node;
11 if (!('parentNode' in node)) return null;
12 return getNParent(node.parentNode, n - 1);
13}
14
avm99963f5923962020-12-07 16:44:37 +010015function createExtBadge() {
16 var badge = document.createElement('div');
17 badge.classList.add('TWPT-badge');
18 badge.setAttribute(
19 'title', chrome.i18n.getMessage('inject_extension_badge_helper', [
20 chrome.i18n.getMessage('appName')
21 ]));
22
23 var badgeI = document.createElement('i');
24 badgeI.classList.add('material-icon-i', 'material-icons-extended');
25 badgeI.textContent = 'repeat';
26
27 badge.append(badgeI);
28 return badge;
avm99963af7860e2019-06-04 03:33:26 +020029}
30
avm99963943b8492020-08-31 23:40:43 +020031function addProfileHistoryLink(node, type, query) {
32 var urlpart = encodeURIComponent('query=' + query);
avm99963a2945b62020-11-27 00:32:02 +010033 var authuserpart =
34 (authuser == '0' ? '' : '?authuser=' + encodeURIComponent(authuser));
avm99963943b8492020-08-31 23:40:43 +020035 var container = document.createElement('div');
36 container.style.margin = '3px 0';
37
38 var link = document.createElement('a');
39 link.setAttribute(
avm99963a2945b62020-11-27 00:32:02 +010040 'href',
41 'https://support.google.com/s/community/search/' + urlpart +
42 authuserpart);
avm99963943b8492020-08-31 23:40:43 +020043 link.innerText = chrome.i18n.getMessage('inject_previousposts_' + type);
44
45 container.appendChild(link);
avm9996306167752020-09-08 00:50:36 +020046 node.appendChild(container);
avm99963943b8492020-08-31 23:40:43 +020047}
48
avm999638e0c1002020-12-03 16:54:20 +010049function applyDragAndDropFix(node) {
50 console.debug('Adding link drag&drop fix to ', node);
51 node.addEventListener('drop', e => {
52 if (e.dataTransfer.types.includes('text/uri-list')) {
53 e.stopImmediatePropagation();
54 console.debug('Stopping link drop event propagation.');
55 }
56 }, true);
57}
58
avm99963f5923962020-12-07 16:44:37 +010059function nodeIsReadToggleBtn(node) {
60 return ('tagName' in node) && node.tagName == 'MATERIAL-BUTTON' &&
61 node.getAttribute('debugid') !== null &&
62 (node.getAttribute('debugid') == 'mark-read-button' ||
63 node.getAttribute('debugid') == 'mark-unread-button') &&
64 ('parentNode' in node) && node.parentNode !== null &&
65 ('parentNode' in node.parentNode) &&
66 node.parentNode.querySelector('[debugid="batchlock"]') === null &&
67 node.parentNode.parentNode !== null &&
68 ('tagName' in node.parentNode.parentNode) &&
69 node.parentNode.parentNode.tagName == 'EC-BULK-ACTIONS';
70}
71
72function addBatchLockBtn(readToggle) {
73 var clone = readToggle.cloneNode(true);
74 clone.setAttribute('debugid', 'batchlock');
75 clone.classList.add('TWPT-btn--with-badge');
76 clone.setAttribute('title', chrome.i18n.getMessage('inject_lockbtn'));
77 clone.querySelector('material-icon').setAttribute('icon', 'lock');
78 clone.querySelector('i.material-icon-i').textContent = 'lock';
79
80 var badge = createExtBadge();
81 clone.append(badge);
82
83 clone.addEventListener('click', function() {
84 var modal = document.querySelector('.pane[pane-id="default-1"]');
85
86 var dialog = document.createElement('material-dialog');
87 dialog.setAttribute('role', 'dialog');
88 dialog.setAttribute('aria-modal', 'true');
89 dialog.classList.add('TWPT-dialog');
90
91 var header = document.createElement('header');
92 header.setAttribute('role', 'presentation');
93 header.classList.add('TWPT-dialog-header');
94
95 var title = document.createElement('div');
96 title.classList.add('TWPT-dialog-header--title', 'title');
97 title.textContent = chrome.i18n.getMessage('inject_lockbtn');
98
99 header.append(title);
100
101 var main = document.createElement('main');
102 main.setAttribute('role', 'presentation');
103 main.classList.add('TWPT-dialog-main');
104
105 var p = document.createElement('p');
106 p.textContent = chrome.i18n.getMessage('inject_lockdialog_desc');
107
108 main.append(p);
109
110 dialog.append(header, main);
111
112 var footers = [['lock', 'unlock', 'cancel'], ['reload', 'close']];
113
114 for (var i = 0; i < footers.length; ++i) {
115 var footer = document.createElement('footer');
116 footer.setAttribute('role', 'presentation');
117 footer.classList.add('TWPT-dialog-footer');
118 footer.setAttribute('data-footer-id', i);
119
120 if (i > 0) footer.classList.add('is-hidden');
121
122 footers[i].forEach(action => {
123 var btn = document.createElement('material-button');
124 btn.setAttribute('role', 'button');
125 btn.classList.add('TWPT-dialog-footer-btn');
126 if (i == 1) btn.classList.add('is-disabled');
127
128 switch (action) {
129 case 'lock':
130 case 'unlock':
131 btn.addEventListener('click', _ => {
132 if (btn.classList.contains('is-disabled')) return;
133 var message = {
134 action,
135 prefix: 'TWPT-batchlock',
136 };
137 window.postMessage(message, '*');
138 });
139 break;
140
141 case 'cancel':
142 case 'close':
143 btn.addEventListener('click', _ => {
144 if (btn.classList.contains('is-disabled')) return;
145 modal.classList.remove('visible');
146 modal.style.display = 'none';
147 removeChildNodes(modal);
148 });
149 break;
150
151 case 'reload':
152 btn.addEventListener('click', _ => {
153 if (btn.classList.contains('is-disabled')) return;
154 window.location.reload()
155 });
156 break;
157 }
158
159 var content = document.createElement('div');
160 content.classList.add('content', 'TWPT-dialog-footer-btn--content');
161 content.textContent =
162 chrome.i18n.getMessage('inject_lockdialog_btn_' + action);
163
164 btn.append(content);
165 footer.append(btn);
166 });
167
168 var clear = document.createElement('div');
169 clear.style.clear = 'both';
170
171 footer.append(clear);
172 dialog.append(footer);
173 }
174
175 removeChildNodes(modal);
176 modal.append(dialog);
177 modal.classList.add('visible', 'modal');
178 modal.style.display = 'flex';
179 });
180 readToggle.parentNode.insertBefore(
181 clone, (readToggle.nextSibling || readToggle));
182}
183
avm9996390cc2e32021-02-05 18:14:16 +0100184function injectPreviousPostsLinks(nameElement) {
185 var mainCardContent = getNParent(nameElement, 3);
186 if (mainCardContent === null) {
187 console.error(
188 '[previousposts] Couldn\'t find |.main-card-content| element.');
189 return;
avm99963490114d2021-02-05 16:12:20 +0100190 }
avm9996390cc2e32021-02-05 18:14:16 +0100191
192 var forumId = location.href.split('/forum/')[1].split('/')[0] || '0';
193
194 var name = escapeUsername(nameElement.textContent);
195 var query1 = encodeURIComponent(
196 '(creator:"' + name + '" | replier:"' + name + '") forum:' + forumId);
197 var query2 = encodeURIComponent(
198 '(creator:"' + name + '" | replier:"' + name + '") forum:any');
199
200 var container = document.createElement('div');
201 container.classList.add('TWPT-previous-posts');
202
203 var badge = createExtBadge();
204 container.appendChild(badge);
205
206 var linkContainer = document.createElement('div');
207 linkContainer.classList.add('TWPT-previous-posts--links');
208
209 addProfileHistoryLink(linkContainer, 'forum', query1);
210 addProfileHistoryLink(linkContainer, 'all', query2);
211
212 container.appendChild(linkContainer);
213
214 mainCardContent.appendChild(container);
avm99963490114d2021-02-05 16:12:20 +0100215}
216
217const watchedNodesSelectors = [
218 // Load more bar (for the "load more"/"load all" buttons)
219 '.load-more-bar',
220
avm9996390cc2e32021-02-05 18:14:16 +0100221 // Username span inside ec-user (user profile view)
222 'ec-user .main-card .header > .name > span',
avm99963490114d2021-02-05 16:12:20 +0100223
224 // Rich text editor
225 'ec-movable-dialog',
226 'ec-rich-text-editor',
227
228 // Read/unread bulk action in the list of thread, for the batch lock feature
229 'ec-bulk-actions material-button[debugid="mark-read-button"]',
230 'ec-bulk-actions material-button[debugid="mark-unread-button"]',
231];
232
233function handleCandidateNode(node) {
234 if (typeof node.classList !== 'undefined') {
235 // Set up the intersectionObserver for the "load more" button inside a
236 // thread
237 if (options.thread && node.classList.contains('load-more-bar')) {
238 intersectionObserver.observe(node.querySelector('.load-more-button'));
239 }
240
241 // Set up the intersectionObserver for the "load all" button inside a thread
242 if (options.threadall && node.classList.contains('load-more-bar')) {
243 intersectionObserver.observe(node.querySelector('.load-all-button'));
244 }
245
246 // Show the "previous posts" links
247 // Here we're selecting the 'ec-user > div' element (unique child)
avm9996390cc2e32021-02-05 18:14:16 +0100248 if (options.history &&
249 node.matches('ec-user .main-card .header > .name > span')) {
avm99963490114d2021-02-05 16:12:20 +0100250 injectPreviousPostsLinks(node);
251 }
252
253 // Fix the drag&drop issue with the rich text editor
254 //
255 // We target both tags because in different contexts different
256 // elements containing the text editor get added to the DOM structure.
257 // Sometimes it's a EC-MOVABLE-DIALOG which already contains the
258 // EC-RICH-TEXT-EDITOR, and sometimes it's the EC-RICH-TEXT-EDITOR
259 // directly.
260 if (options.ccdragndropfix && ('tagName' in node) &&
261 (node.tagName == 'EC-MOVABLE-DIALOG' ||
262 node.tagName == 'EC-RICH-TEXT-EDITOR')) {
263 applyDragAndDropFix(node);
264 }
265
266 // Inject the batch lock button in the thread list
267 if (options.batchlock && nodeIsReadToggleBtn(node)) {
268 addBatchLockBtn(node);
269 }
270 }
271}
272
avm99963847ee632019-03-27 00:57:44 +0100273function mutationCallback(mutationList, observer) {
274 mutationList.forEach((mutation) => {
avm99963b69eb3d2020-08-20 02:03:44 +0200275 if (mutation.type == 'childList') {
276 mutation.addedNodes.forEach(function(node) {
avm99963490114d2021-02-05 16:12:20 +0100277 handleCandidateNode(node);
avm99963847ee632019-03-27 00:57:44 +0100278 });
279 }
280 });
281}
282
avm99963adf90862020-04-12 13:27:45 +0200283function intersectionCallback(entries, observer) {
avm99963847ee632019-03-27 00:57:44 +0100284 entries.forEach(entry => {
285 if (entry.isIntersecting) {
286 entry.target.click();
287 }
288 });
289};
290
291var observerOptions = {
292 childList: true,
avm99963b69eb3d2020-08-20 02:03:44 +0200293 subtree: true,
avm99963847ee632019-03-27 00:57:44 +0100294}
295
avm99963129fb502020-08-28 05:18:53 +0200296var intersectionOptions = {
297 root: document.querySelector('.scrollable-content'),
298 rootMargin: '0px',
299 threshold: 1.0,
300};
avm99963847ee632019-03-27 00:57:44 +0100301
avm99963129fb502020-08-28 05:18:53 +0200302chrome.storage.sync.get(null, function(items) {
303 options = items;
avm99963cbea3142019-03-28 00:48:15 +0100304
avm99963a2945b62020-11-27 00:32:02 +0100305 var startup =
306 JSON.parse(document.querySelector('html').getAttribute('data-startup'));
307 authuser = startup[2][1] || '0';
308
avm99963490114d2021-02-05 16:12:20 +0100309 // Before starting the mutation Observer, check whether we missed any
310 // mutations by manually checking whether some watched nodes already exist.
311 var cssSelectors = watchedNodesSelectors.join(',');
312 document.querySelectorAll(cssSelectors)
313 .forEach(node => handleCandidateNode(node));
314
avm99963129fb502020-08-28 05:18:53 +0200315 mutationObserver = new MutationObserver(mutationCallback);
avm99963e4cac402020-12-03 16:10:58 +0100316 mutationObserver.observe(document.body, observerOptions);
avm99963cbea3142019-03-28 00:48:15 +0100317
avm99963129fb502020-08-28 05:18:53 +0200318 intersectionObserver =
319 new IntersectionObserver(intersectionCallback, intersectionOptions);
avm99963122dc9b2019-03-30 18:44:18 +0100320
avm99963129fb502020-08-28 05:18:53 +0200321 if (options.fixedtoolbar) {
322 injectStyles(
avm999630bc113a2020-09-07 13:02:11 +0200323 'ec-bulk-actions{position: sticky; top: 0; background: var(--TWPT-primary-background, #fff); z-index: 96;}');
avm99963129fb502020-08-28 05:18:53 +0200324 }
avm99963ae6a26d2020-04-12 14:03:51 +0200325
avm99963129fb502020-08-28 05:18:53 +0200326 if (options.increasecontrast) {
avm999630bc113a2020-09-07 13:02:11 +0200327 injectStyles(
avm99963a2a06442020-11-25 21:11:10 +0100328 '.thread-summary.read:not(.checked){background: var(--TWPT-thread-read-background, #ecedee)!important;}');
avm99963129fb502020-08-28 05:18:53 +0200329 }
avm999630f9503f2020-07-27 13:56:52 +0200330
avm99963129fb502020-08-28 05:18:53 +0200331 if (options.stickysidebarheaders) {
332 injectStyles(
avm999630bc113a2020-09-07 13:02:11 +0200333 'material-drawer .main-header{background: var(--TWPT-drawer-background, #fff)!important; position: sticky; top: 0; z-index: 1;}');
334 }
335
336 if (options.ccdarktheme && options.ccdarktheme_mode == 'switch') {
avm999630bc113a2020-09-07 13:02:11 +0200337 var darkThemeSwitch = document.createElement('material-button');
avm99963f5923962020-12-07 16:44:37 +0100338 darkThemeSwitch.classList.add('TWPT-dark-theme', 'TWPT-btn--with-badge');
avm999630bc113a2020-09-07 13:02:11 +0200339 darkThemeSwitch.setAttribute('button', '');
340 darkThemeSwitch.setAttribute(
341 'title', chrome.i18n.getMessage('inject_ccdarktheme_helper'));
342
343 darkThemeSwitch.addEventListener('click', e => {
344 chrome.storage.sync.get(null, currentOptions => {
345 currentOptions.ccdarktheme_switch_status =
346 !options.ccdarktheme_switch_status;
347 chrome.storage.sync.set(currentOptions, _ => {
348 location.reload();
349 });
350 });
351 });
352
353 var switchContent = document.createElement('div');
354 switchContent.classList.add('content');
355
356 var icon = document.createElement('material-icon');
357
358 var i = document.createElement('i');
359 i.classList.add('material-icon-i', 'material-icons-extended');
360 i.textContent = 'brightness_4';
361
362 icon.appendChild(i);
363 switchContent.appendChild(icon);
364 darkThemeSwitch.appendChild(switchContent);
365
avm99963f5923962020-12-07 16:44:37 +0100366 var badgeContent = createExtBadge();
avm9996306167752020-09-08 00:50:36 +0200367
avm9996306167752020-09-08 00:50:36 +0200368 darkThemeSwitch.appendChild(badgeContent);
369
avm999630bc113a2020-09-07 13:02:11 +0200370 var rightControl = document.querySelector('header .right-control');
371 rightControl.style.width =
372 (parseInt(window.getComputedStyle(rightControl).width) + 58) + 'px';
373 rightControl.insertAdjacentElement('afterbegin', darkThemeSwitch);
avm99963129fb502020-08-28 05:18:53 +0200374 }
avm99963129942f2020-09-08 02:07:18 +0200375
376 if (options.ccforcehidedrawer) {
377 var drawer = document.querySelector('material-drawer');
378 if (drawer !== null && drawer.classList.contains('mat-drawer-expanded')) {
379 document.querySelector('.material-drawer-button').click();
380 }
381 }
avm99963f5923962020-12-07 16:44:37 +0100382
383 if (options.batchlock) {
384 injectScript(chrome.runtime.getURL('injections/batchlock_inject.js'));
385 }
avm99963129fb502020-08-28 05:18:53 +0200386});