blob: dd118511a769c1d55cd63d974caa74a565d9f8bd [file] [log] [blame]
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +02001import {injectScript, injectStyles, injectStylesheet} from '../../common/contentScriptsUtils.js';
2
3import {autoRefresh} from './autoRefresh.js';
4import {avatars} from './avatars.js';
5import {addBatchLockBtn, nodeIsReadToggleBtn} from './batchLock.js';
6import {injectDarkModeButton, isDarkThemeOn} from './darkMode.js';
7import {applyDragAndDropFix} from './dragAndDropFix.js';
8import {markCurrentThreadAsRead} from './forceMarkAsRead.js';
9import {injectPreviousPostsLinks} from './profileHistoryLink.js';
10import {unifiedProfilesFix} from './unifiedProfiles.js';
11
12var mutationObserver, intersectionObserver, intersectionOptions, options;
13
14const watchedNodesSelectors = [
15 // App container (used to set up the intersection observer and inject the dark
16 // mode button)
17 'ec-app',
18
19 // Load more bar (for the "load more"/"load all" buttons)
20 '.load-more-bar',
21
22 // Username span/editor inside ec-user (user profile view)
23 'ec-user .main-card .header > .name > span',
24 'ec-user .main-card .header > .name > ec-display-name-editor',
25
26 // Rich text editor
27 'ec-movable-dialog',
28 'ec-rich-text-editor',
29
30 // Read/unread bulk action in the list of thread, for the batch lock feature
31 'ec-bulk-actions material-button[debugid="mark-read-button"]',
32 'ec-bulk-actions material-button[debugid="mark-unread-button"]',
33
34 // Thread list items (used to inject the avatars)
35 'li',
36
37 // Thread list (used for the autorefresh feature)
38 'ec-thread-list',
39
40 // Unified profile iframe
41 'iframe',
42
43 // Thread component
44 'ec-thread',
45];
46
47function handleCandidateNode(node) {
48 if (typeof node.classList !== 'undefined') {
49 if (('tagName' in node) && node.tagName == 'EC-APP') {
50 // Set up the intersectionObserver
51 if (typeof intersectionObserver === 'undefined') {
52 var scrollableContent = node.querySelector('.scrollable-content');
53 if (scrollableContent !== null) {
54 intersectionOptions = {
55 root: scrollableContent,
56 rootMargin: '0px',
57 threshold: 1.0,
58 };
59
60 intersectionObserver = new IntersectionObserver(
61 intersectionCallback, intersectionOptions);
62 }
63 }
64
65 // Inject the dark mode button
66 if (options.ccdarktheme && options.ccdarktheme_mode == 'switch') {
67 var rightControl = node.querySelector('header .right-control');
68 if (rightControl !== null)
69 injectDarkModeButton(rightControl, options.ccdarktheme_switch_status);
70 }
71 }
72
73 // Start the intersectionObserver for the "load more"/"load all" buttons
74 // inside a thread
75 if ((options.thread || options.threadall) &&
76 node.classList.contains('load-more-bar')) {
77 if (typeof intersectionObserver !== 'undefined') {
78 if (options.thread)
79 intersectionObserver.observe(node.querySelector('.load-more-button'));
80 if (options.threadall)
81 intersectionObserver.observe(node.querySelector('.load-all-button'));
82 } else {
83 console.warn(
84 '[infinitescroll] ' +
85 'The intersectionObserver is not ready yet.');
86 }
87 }
88
89 // Show the "previous posts" links
90 // Here we're selecting the 'ec-user > div' element (unique child)
91 if (options.history &&
92 (node.matches('ec-user .main-card .header > .name > span') ||
93 node.matches(
94 'ec-user .main-card .header > .name > ec-display-name-editor'))) {
95 injectPreviousPostsLinks(node);
96 }
97
98 // Fix the drag&drop issue with the rich text editor
99 //
100 // We target both tags because in different contexts different
101 // elements containing the text editor get added to the DOM structure.
102 // Sometimes it's a EC-MOVABLE-DIALOG which already contains the
103 // EC-RICH-TEXT-EDITOR, and sometimes it's the EC-RICH-TEXT-EDITOR
104 // directly.
105 if (options.ccdragndropfix && ('tagName' in node) &&
106 (node.tagName == 'EC-MOVABLE-DIALOG' ||
107 node.tagName == 'EC-RICH-TEXT-EDITOR')) {
108 applyDragAndDropFix(node);
109 }
110
111 // Inject the batch lock button in the thread list
112 if (options.batchlock && nodeIsReadToggleBtn(node)) {
113 addBatchLockBtn(node);
114 }
115
116 // Inject avatar links to threads in the thread list
117 if (options.threadlistavatars && ('tagName' in node) &&
118 (node.tagName == 'LI') &&
119 node.querySelector('ec-thread-summary') !== null) {
120 avatars.inject(node);
121 }
122
123 // Set up the autorefresh list feature
124 if (options.autorefreshlist && ('tagName' in node) &&
125 node.tagName == 'EC-THREAD-LIST') {
126 autoRefresh.setUp();
127 }
128
129 // Redirect unified profile iframe to dark version if applicable
130 if (node.tagName == 'IFRAME' && isDarkThemeOn(options) &&
131 unifiedProfilesFix.checkIframe(node)) {
132 unifiedProfilesFix.fixIframe(node);
133 }
134
135 // Force mark thread as read
136 if (options.forcemarkasread && node.tagName == 'EC-THREAD') {
137 markCurrentThreadAsRead();
138 }
139 }
140}
141
142function handleRemovedNode(node) {
143 // Remove snackbar when exiting thread list view
144 if (options.autorefreshlist && 'tagName' in node &&
145 node.tagName == 'EC-THREAD-LIST') {
146 autoRefresh.hideUpdatePrompt();
147 }
148}
149
150function mutationCallback(mutationList, observer) {
151 mutationList.forEach((mutation) => {
152 if (mutation.type == 'childList') {
153 mutation.addedNodes.forEach(function(node) {
154 handleCandidateNode(node);
155 });
156
157 mutation.removedNodes.forEach(function(node) {
158 handleRemovedNode(node);
159 });
160 }
161 });
162}
163
164function intersectionCallback(entries, observer) {
165 entries.forEach(entry => {
166 if (entry.isIntersecting) {
167 entry.target.click();
168 }
169 });
170};
171
172var observerOptions = {
173 childList: true,
174 subtree: true,
175};
176
177chrome.storage.sync.get(null, function(items) {
178 options = items;
179
180 // Before starting the mutation Observer, check whether we missed any
181 // mutations by manually checking whether some watched nodes already
182 // exist.
183 var cssSelectors = watchedNodesSelectors.join(',');
184 document.querySelectorAll(cssSelectors)
185 .forEach(node => handleCandidateNode(node));
186
187 mutationObserver = new MutationObserver(mutationCallback);
188 mutationObserver.observe(document.body, observerOptions);
189
190 if (options.fixedtoolbar) {
191 injectStyles(
192 'ec-bulk-actions{position: sticky; top: 0; background: var(--TWPT-primary-background, #fff); z-index: 96;}');
193 }
194
195 if (options.increasecontrast) {
196 injectStyles(
197 '.thread-summary.read:not(.checked){background: var(--TWPT-thread-read-background, #ecedee)!important;}');
198 }
199
200 if (options.stickysidebarheaders) {
201 injectStyles(
202 'material-drawer .main-header{background: var(--TWPT-drawer-background, #fff)!important; position: sticky; top: 0; z-index: 1;}');
203 }
204
205 if (options.enhancedannouncementsdot) {
206 injectStylesheet(
207 chrome.runtime.getURL('css/enhanced_announcements_dot.css'));
208 }
209
210 if (options.repositionexpandthread) {
211 injectStylesheet(
212 chrome.runtime.getURL('css/reposition_expand_thread.css'));
213 }
214
215 if (options.ccforcehidedrawer) {
216 var drawer = document.querySelector('material-drawer');
217 if (drawer !== null && drawer.classList.contains('mat-drawer-expanded')) {
218 document.querySelector('.material-drawer-button').click();
219 }
220 }
221
222 if (options.batchlock) {
223 injectScript(chrome.runtime.getURL('batchLockInject.bundle.js'));
224 injectStylesheet(chrome.runtime.getURL('css/batchlock_inject.css'));
225 }
226
227 if (options.threadlistavatars) {
228 injectStylesheet(
229 chrome.runtime.getURL('css/thread_list_avatars.css'));
230 }
231
232 if (options.autorefreshlist) {
233 injectStylesheet(chrome.runtime.getURL('css/autorefresh_list.css'));
234 }
235});