blob: 5b80a38408fe98deaa899ebb4b50a1a496c6e2fd [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
avm99963f5923962020-12-07 16:44:37 +01009function createExtBadge() {
10 var badge = document.createElement('div');
11 badge.classList.add('TWPT-badge');
12 badge.setAttribute(
13 'title', chrome.i18n.getMessage('inject_extension_badge_helper', [
14 chrome.i18n.getMessage('appName')
15 ]));
16
17 var badgeI = document.createElement('i');
18 badgeI.classList.add('material-icon-i', 'material-icons-extended');
19 badgeI.textContent = 'repeat';
20
21 badge.append(badgeI);
22 return badge;
avm99963af7860e2019-06-04 03:33:26 +020023}
24
avm99963943b8492020-08-31 23:40:43 +020025function addProfileHistoryLink(node, type, query) {
26 var urlpart = encodeURIComponent('query=' + query);
avm99963a2945b62020-11-27 00:32:02 +010027 var authuserpart =
28 (authuser == '0' ? '' : '?authuser=' + encodeURIComponent(authuser));
avm99963943b8492020-08-31 23:40:43 +020029 var container = document.createElement('div');
30 container.style.margin = '3px 0';
31
32 var link = document.createElement('a');
33 link.setAttribute(
avm99963a2945b62020-11-27 00:32:02 +010034 'href',
35 'https://support.google.com/s/community/search/' + urlpart +
36 authuserpart);
avm99963943b8492020-08-31 23:40:43 +020037 link.innerText = chrome.i18n.getMessage('inject_previousposts_' + type);
38
39 container.appendChild(link);
avm9996306167752020-09-08 00:50:36 +020040 node.appendChild(container);
avm99963943b8492020-08-31 23:40:43 +020041}
42
avm999638e0c1002020-12-03 16:54:20 +010043function applyDragAndDropFix(node) {
44 console.debug('Adding link drag&drop fix to ', node);
45 node.addEventListener('drop', e => {
46 if (e.dataTransfer.types.includes('text/uri-list')) {
47 e.stopImmediatePropagation();
48 console.debug('Stopping link drop event propagation.');
49 }
50 }, true);
51}
52
avm99963f5923962020-12-07 16:44:37 +010053function nodeIsReadToggleBtn(node) {
54 return ('tagName' in node) && node.tagName == 'MATERIAL-BUTTON' &&
55 node.getAttribute('debugid') !== null &&
56 (node.getAttribute('debugid') == 'mark-read-button' ||
57 node.getAttribute('debugid') == 'mark-unread-button') &&
58 ('parentNode' in node) && node.parentNode !== null &&
59 ('parentNode' in node.parentNode) &&
60 node.parentNode.querySelector('[debugid="batchlock"]') === null &&
61 node.parentNode.parentNode !== null &&
62 ('tagName' in node.parentNode.parentNode) &&
63 node.parentNode.parentNode.tagName == 'EC-BULK-ACTIONS';
64}
65
66function addBatchLockBtn(readToggle) {
67 var clone = readToggle.cloneNode(true);
68 clone.setAttribute('debugid', 'batchlock');
69 clone.classList.add('TWPT-btn--with-badge');
70 clone.setAttribute('title', chrome.i18n.getMessage('inject_lockbtn'));
71 clone.querySelector('material-icon').setAttribute('icon', 'lock');
72 clone.querySelector('i.material-icon-i').textContent = 'lock';
73
74 var badge = createExtBadge();
75 clone.append(badge);
76
77 clone.addEventListener('click', function() {
78 var modal = document.querySelector('.pane[pane-id="default-1"]');
79
80 var dialog = document.createElement('material-dialog');
81 dialog.setAttribute('role', 'dialog');
82 dialog.setAttribute('aria-modal', 'true');
83 dialog.classList.add('TWPT-dialog');
84
85 var header = document.createElement('header');
86 header.setAttribute('role', 'presentation');
87 header.classList.add('TWPT-dialog-header');
88
89 var title = document.createElement('div');
90 title.classList.add('TWPT-dialog-header--title', 'title');
91 title.textContent = chrome.i18n.getMessage('inject_lockbtn');
92
93 header.append(title);
94
95 var main = document.createElement('main');
96 main.setAttribute('role', 'presentation');
97 main.classList.add('TWPT-dialog-main');
98
99 var p = document.createElement('p');
100 p.textContent = chrome.i18n.getMessage('inject_lockdialog_desc');
101
102 main.append(p);
103
104 dialog.append(header, main);
105
106 var footers = [['lock', 'unlock', 'cancel'], ['reload', 'close']];
107
108 for (var i = 0; i < footers.length; ++i) {
109 var footer = document.createElement('footer');
110 footer.setAttribute('role', 'presentation');
111 footer.classList.add('TWPT-dialog-footer');
112 footer.setAttribute('data-footer-id', i);
113
114 if (i > 0) footer.classList.add('is-hidden');
115
116 footers[i].forEach(action => {
117 var btn = document.createElement('material-button');
118 btn.setAttribute('role', 'button');
119 btn.classList.add('TWPT-dialog-footer-btn');
120 if (i == 1) btn.classList.add('is-disabled');
121
122 switch (action) {
123 case 'lock':
124 case 'unlock':
125 btn.addEventListener('click', _ => {
126 if (btn.classList.contains('is-disabled')) return;
127 var message = {
128 action,
129 prefix: 'TWPT-batchlock',
130 };
131 window.postMessage(message, '*');
132 });
133 break;
134
135 case 'cancel':
136 case 'close':
137 btn.addEventListener('click', _ => {
138 if (btn.classList.contains('is-disabled')) return;
139 modal.classList.remove('visible');
140 modal.style.display = 'none';
141 removeChildNodes(modal);
142 });
143 break;
144
145 case 'reload':
146 btn.addEventListener('click', _ => {
147 if (btn.classList.contains('is-disabled')) return;
148 window.location.reload()
149 });
150 break;
151 }
152
153 var content = document.createElement('div');
154 content.classList.add('content', 'TWPT-dialog-footer-btn--content');
155 content.textContent =
156 chrome.i18n.getMessage('inject_lockdialog_btn_' + action);
157
158 btn.append(content);
159 footer.append(btn);
160 });
161
162 var clear = document.createElement('div');
163 clear.style.clear = 'both';
164
165 footer.append(clear);
166 dialog.append(footer);
167 }
168
169 removeChildNodes(modal);
170 modal.append(dialog);
171 modal.classList.add('visible', 'modal');
172 modal.style.display = 'flex';
173 });
174 readToggle.parentNode.insertBefore(
175 clone, (readToggle.nextSibling || readToggle));
176}
177
avm99963490114d2021-02-05 16:12:20 +0100178function injectPreviousPostsLinks(node) {
179 var nameElement = node.querySelector('.name span');
180 if (nameElement !== null) {
181 var forumId = location.href.split('/forum/')[1].split('/')[0] || '0';
182
183 var name = escapeUsername(nameElement.textContent);
184 var query1 = encodeURIComponent(
185 '(creator:"' + name + '" | replier:"' + name + '") forum:' + forumId);
186 var query2 = encodeURIComponent(
187 '(creator:"' + name + '" | replier:"' + name + '") forum:any');
188
189 var container = document.createElement('div');
190 container.classList.add('TWPT-previous-posts');
191
192 var badge = createExtBadge();
193 container.appendChild(badge);
194
195 var linkContainer = document.createElement('div');
196 linkContainer.classList.add('TWPT-previous-posts--links');
197
198 addProfileHistoryLink(linkContainer, 'forum', query1);
199 addProfileHistoryLink(linkContainer, 'all', query2);
200
201 container.appendChild(linkContainer);
202
203 node.querySelector('.main-card-content').appendChild(container);
204 } else {
205 console.warn('[previousposts] Couldn\'t find the username element.');
206 }
207}
208
209const watchedNodesSelectors = [
210 // Load more bar (for the "load more"/"load all" buttons)
211 '.load-more-bar',
212
213 // Container inside ec-user (user profile view)
214 'ec-user > div',
215
216 // Rich text editor
217 'ec-movable-dialog',
218 'ec-rich-text-editor',
219
220 // Read/unread bulk action in the list of thread, for the batch lock feature
221 'ec-bulk-actions material-button[debugid="mark-read-button"]',
222 'ec-bulk-actions material-button[debugid="mark-unread-button"]',
223];
224
225function handleCandidateNode(node) {
226 if (typeof node.classList !== 'undefined') {
227 // Set up the intersectionObserver for the "load more" button inside a
228 // thread
229 if (options.thread && node.classList.contains('load-more-bar')) {
230 intersectionObserver.observe(node.querySelector('.load-more-button'));
231 }
232
233 // Set up the intersectionObserver for the "load all" button inside a thread
234 if (options.threadall && node.classList.contains('load-more-bar')) {
235 intersectionObserver.observe(node.querySelector('.load-all-button'));
236 }
237
238 // Show the "previous posts" links
239 // Here we're selecting the 'ec-user > div' element (unique child)
240 if (options.history && ('parentNode' in node) && node.parentNode !== null &&
241 ('tagName' in node.parentNode) &&
242 node.parentNode.tagName == 'EC-USER') {
243 injectPreviousPostsLinks(node);
244 }
245
246 // Fix the drag&drop issue with the rich text editor
247 //
248 // We target both tags because in different contexts different
249 // elements containing the text editor get added to the DOM structure.
250 // Sometimes it's a EC-MOVABLE-DIALOG which already contains the
251 // EC-RICH-TEXT-EDITOR, and sometimes it's the EC-RICH-TEXT-EDITOR
252 // directly.
253 if (options.ccdragndropfix && ('tagName' in node) &&
254 (node.tagName == 'EC-MOVABLE-DIALOG' ||
255 node.tagName == 'EC-RICH-TEXT-EDITOR')) {
256 applyDragAndDropFix(node);
257 }
258
259 // Inject the batch lock button in the thread list
260 if (options.batchlock && nodeIsReadToggleBtn(node)) {
261 addBatchLockBtn(node);
262 }
263 }
264}
265
avm99963847ee632019-03-27 00:57:44 +0100266function mutationCallback(mutationList, observer) {
267 mutationList.forEach((mutation) => {
avm99963b69eb3d2020-08-20 02:03:44 +0200268 if (mutation.type == 'childList') {
269 mutation.addedNodes.forEach(function(node) {
avm99963490114d2021-02-05 16:12:20 +0100270 handleCandidateNode(node);
avm99963847ee632019-03-27 00:57:44 +0100271 });
272 }
273 });
274}
275
avm99963adf90862020-04-12 13:27:45 +0200276function intersectionCallback(entries, observer) {
avm99963847ee632019-03-27 00:57:44 +0100277 entries.forEach(entry => {
278 if (entry.isIntersecting) {
279 entry.target.click();
280 }
281 });
282};
283
284var observerOptions = {
285 childList: true,
avm99963b69eb3d2020-08-20 02:03:44 +0200286 subtree: true,
avm99963847ee632019-03-27 00:57:44 +0100287}
288
avm99963129fb502020-08-28 05:18:53 +0200289var intersectionOptions = {
290 root: document.querySelector('.scrollable-content'),
291 rootMargin: '0px',
292 threshold: 1.0,
293};
avm99963847ee632019-03-27 00:57:44 +0100294
avm99963129fb502020-08-28 05:18:53 +0200295chrome.storage.sync.get(null, function(items) {
296 options = items;
avm99963cbea3142019-03-28 00:48:15 +0100297
avm99963a2945b62020-11-27 00:32:02 +0100298 var startup =
299 JSON.parse(document.querySelector('html').getAttribute('data-startup'));
300 authuser = startup[2][1] || '0';
301
avm99963490114d2021-02-05 16:12:20 +0100302 // Before starting the mutation Observer, check whether we missed any
303 // mutations by manually checking whether some watched nodes already exist.
304 var cssSelectors = watchedNodesSelectors.join(',');
305 document.querySelectorAll(cssSelectors)
306 .forEach(node => handleCandidateNode(node));
307
avm99963129fb502020-08-28 05:18:53 +0200308 mutationObserver = new MutationObserver(mutationCallback);
avm99963e4cac402020-12-03 16:10:58 +0100309 mutationObserver.observe(document.body, observerOptions);
avm99963cbea3142019-03-28 00:48:15 +0100310
avm99963129fb502020-08-28 05:18:53 +0200311 intersectionObserver =
312 new IntersectionObserver(intersectionCallback, intersectionOptions);
avm99963122dc9b2019-03-30 18:44:18 +0100313
avm99963129fb502020-08-28 05:18:53 +0200314 if (options.fixedtoolbar) {
315 injectStyles(
avm999630bc113a2020-09-07 13:02:11 +0200316 'ec-bulk-actions{position: sticky; top: 0; background: var(--TWPT-primary-background, #fff); z-index: 96;}');
avm99963129fb502020-08-28 05:18:53 +0200317 }
avm99963ae6a26d2020-04-12 14:03:51 +0200318
avm99963129fb502020-08-28 05:18:53 +0200319 if (options.increasecontrast) {
avm999630bc113a2020-09-07 13:02:11 +0200320 injectStyles(
avm99963a2a06442020-11-25 21:11:10 +0100321 '.thread-summary.read:not(.checked){background: var(--TWPT-thread-read-background, #ecedee)!important;}');
avm99963129fb502020-08-28 05:18:53 +0200322 }
avm999630f9503f2020-07-27 13:56:52 +0200323
avm99963129fb502020-08-28 05:18:53 +0200324 if (options.stickysidebarheaders) {
325 injectStyles(
avm999630bc113a2020-09-07 13:02:11 +0200326 'material-drawer .main-header{background: var(--TWPT-drawer-background, #fff)!important; position: sticky; top: 0; z-index: 1;}');
327 }
328
329 if (options.ccdarktheme && options.ccdarktheme_mode == 'switch') {
avm999630bc113a2020-09-07 13:02:11 +0200330 var darkThemeSwitch = document.createElement('material-button');
avm99963f5923962020-12-07 16:44:37 +0100331 darkThemeSwitch.classList.add('TWPT-dark-theme', 'TWPT-btn--with-badge');
avm999630bc113a2020-09-07 13:02:11 +0200332 darkThemeSwitch.setAttribute('button', '');
333 darkThemeSwitch.setAttribute(
334 'title', chrome.i18n.getMessage('inject_ccdarktheme_helper'));
335
336 darkThemeSwitch.addEventListener('click', e => {
337 chrome.storage.sync.get(null, currentOptions => {
338 currentOptions.ccdarktheme_switch_status =
339 !options.ccdarktheme_switch_status;
340 chrome.storage.sync.set(currentOptions, _ => {
341 location.reload();
342 });
343 });
344 });
345
346 var switchContent = document.createElement('div');
347 switchContent.classList.add('content');
348
349 var icon = document.createElement('material-icon');
350
351 var i = document.createElement('i');
352 i.classList.add('material-icon-i', 'material-icons-extended');
353 i.textContent = 'brightness_4';
354
355 icon.appendChild(i);
356 switchContent.appendChild(icon);
357 darkThemeSwitch.appendChild(switchContent);
358
avm99963f5923962020-12-07 16:44:37 +0100359 var badgeContent = createExtBadge();
avm9996306167752020-09-08 00:50:36 +0200360
avm9996306167752020-09-08 00:50:36 +0200361 darkThemeSwitch.appendChild(badgeContent);
362
avm999630bc113a2020-09-07 13:02:11 +0200363 var rightControl = document.querySelector('header .right-control');
364 rightControl.style.width =
365 (parseInt(window.getComputedStyle(rightControl).width) + 58) + 'px';
366 rightControl.insertAdjacentElement('afterbegin', darkThemeSwitch);
avm99963129fb502020-08-28 05:18:53 +0200367 }
avm99963129942f2020-09-08 02:07:18 +0200368
369 if (options.ccforcehidedrawer) {
370 var drawer = document.querySelector('material-drawer');
371 if (drawer !== null && drawer.classList.contains('mat-drawer-expanded')) {
372 document.querySelector('.material-drawer-button').click();
373 }
374 }
avm99963f5923962020-12-07 16:44:37 +0100375
376 if (options.batchlock) {
377 injectScript(chrome.runtime.getURL('injections/batchlock_inject.js'));
378 }
avm99963129fb502020-08-28 05:18:53 +0200379});