Refactor extension to webpack
This change is the biggest in the history of the project. The entire
project has been refactored so it is built with webpack.
This involves:
- Creating webpack and npm config files.
- Fixing some bugs in the code due to the fact that webpack uses strict
mode.
- Merging some pieces of code which were shared throughout the codebase
(not exhaustive, more work should be done in this direction).
- Splitting the console_inject.js file into separate files (it had 1000+
lines).
- Adapting all the build-related files (Makefile, bash scripts, etc.)
- Changing the docs to explain the new build process.
- Changing the Zuul playbook/roles to adapt to the new build process.
Change-Id: I16476d47825461c3a318b3f1a1eddb06b2df2e89
diff --git a/src/contentScripts/communityConsole/main.js b/src/contentScripts/communityConsole/main.js
new file mode 100644
index 0000000..dd11851
--- /dev/null
+++ b/src/contentScripts/communityConsole/main.js
@@ -0,0 +1,235 @@
+import {injectScript, injectStyles, injectStylesheet} from '../../common/contentScriptsUtils.js';
+
+import {autoRefresh} from './autoRefresh.js';
+import {avatars} from './avatars.js';
+import {addBatchLockBtn, nodeIsReadToggleBtn} from './batchLock.js';
+import {injectDarkModeButton, isDarkThemeOn} from './darkMode.js';
+import {applyDragAndDropFix} from './dragAndDropFix.js';
+import {markCurrentThreadAsRead} from './forceMarkAsRead.js';
+import {injectPreviousPostsLinks} from './profileHistoryLink.js';
+import {unifiedProfilesFix} from './unifiedProfiles.js';
+
+var mutationObserver, intersectionObserver, intersectionOptions, options;
+
+const watchedNodesSelectors = [
+ // App container (used to set up the intersection observer and inject the dark
+ // mode button)
+ 'ec-app',
+
+ // Load more bar (for the "load more"/"load all" buttons)
+ '.load-more-bar',
+
+ // Username span/editor inside ec-user (user profile view)
+ 'ec-user .main-card .header > .name > span',
+ 'ec-user .main-card .header > .name > ec-display-name-editor',
+
+ // Rich text editor
+ 'ec-movable-dialog',
+ 'ec-rich-text-editor',
+
+ // Read/unread bulk action in the list of thread, for the batch lock feature
+ 'ec-bulk-actions material-button[debugid="mark-read-button"]',
+ 'ec-bulk-actions material-button[debugid="mark-unread-button"]',
+
+ // Thread list items (used to inject the avatars)
+ 'li',
+
+ // Thread list (used for the autorefresh feature)
+ 'ec-thread-list',
+
+ // Unified profile iframe
+ 'iframe',
+
+ // Thread component
+ 'ec-thread',
+];
+
+function handleCandidateNode(node) {
+ if (typeof node.classList !== 'undefined') {
+ if (('tagName' in node) && node.tagName == 'EC-APP') {
+ // Set up the intersectionObserver
+ if (typeof intersectionObserver === 'undefined') {
+ var scrollableContent = node.querySelector('.scrollable-content');
+ if (scrollableContent !== null) {
+ intersectionOptions = {
+ root: scrollableContent,
+ rootMargin: '0px',
+ threshold: 1.0,
+ };
+
+ intersectionObserver = new IntersectionObserver(
+ intersectionCallback, intersectionOptions);
+ }
+ }
+
+ // Inject the dark mode button
+ if (options.ccdarktheme && options.ccdarktheme_mode == 'switch') {
+ var rightControl = node.querySelector('header .right-control');
+ if (rightControl !== null)
+ injectDarkModeButton(rightControl, options.ccdarktheme_switch_status);
+ }
+ }
+
+ // Start the intersectionObserver for the "load more"/"load all" buttons
+ // inside a thread
+ if ((options.thread || options.threadall) &&
+ node.classList.contains('load-more-bar')) {
+ if (typeof intersectionObserver !== 'undefined') {
+ if (options.thread)
+ intersectionObserver.observe(node.querySelector('.load-more-button'));
+ if (options.threadall)
+ intersectionObserver.observe(node.querySelector('.load-all-button'));
+ } else {
+ console.warn(
+ '[infinitescroll] ' +
+ 'The intersectionObserver is not ready yet.');
+ }
+ }
+
+ // Show the "previous posts" links
+ // Here we're selecting the 'ec-user > div' element (unique child)
+ if (options.history &&
+ (node.matches('ec-user .main-card .header > .name > span') ||
+ node.matches(
+ 'ec-user .main-card .header > .name > ec-display-name-editor'))) {
+ injectPreviousPostsLinks(node);
+ }
+
+ // Fix the drag&drop issue with the rich text editor
+ //
+ // We target both tags because in different contexts different
+ // elements containing the text editor get added to the DOM structure.
+ // Sometimes it's a EC-MOVABLE-DIALOG which already contains the
+ // EC-RICH-TEXT-EDITOR, and sometimes it's the EC-RICH-TEXT-EDITOR
+ // directly.
+ if (options.ccdragndropfix && ('tagName' in node) &&
+ (node.tagName == 'EC-MOVABLE-DIALOG' ||
+ node.tagName == 'EC-RICH-TEXT-EDITOR')) {
+ applyDragAndDropFix(node);
+ }
+
+ // Inject the batch lock button in the thread list
+ if (options.batchlock && nodeIsReadToggleBtn(node)) {
+ addBatchLockBtn(node);
+ }
+
+ // Inject avatar links to threads in the thread list
+ if (options.threadlistavatars && ('tagName' in node) &&
+ (node.tagName == 'LI') &&
+ node.querySelector('ec-thread-summary') !== null) {
+ avatars.inject(node);
+ }
+
+ // Set up the autorefresh list feature
+ if (options.autorefreshlist && ('tagName' in node) &&
+ node.tagName == 'EC-THREAD-LIST') {
+ autoRefresh.setUp();
+ }
+
+ // Redirect unified profile iframe to dark version if applicable
+ if (node.tagName == 'IFRAME' && isDarkThemeOn(options) &&
+ unifiedProfilesFix.checkIframe(node)) {
+ unifiedProfilesFix.fixIframe(node);
+ }
+
+ // Force mark thread as read
+ if (options.forcemarkasread && node.tagName == 'EC-THREAD') {
+ markCurrentThreadAsRead();
+ }
+ }
+}
+
+function handleRemovedNode(node) {
+ // Remove snackbar when exiting thread list view
+ if (options.autorefreshlist && 'tagName' in node &&
+ node.tagName == 'EC-THREAD-LIST') {
+ autoRefresh.hideUpdatePrompt();
+ }
+}
+
+function mutationCallback(mutationList, observer) {
+ mutationList.forEach((mutation) => {
+ if (mutation.type == 'childList') {
+ mutation.addedNodes.forEach(function(node) {
+ handleCandidateNode(node);
+ });
+
+ mutation.removedNodes.forEach(function(node) {
+ handleRemovedNode(node);
+ });
+ }
+ });
+}
+
+function intersectionCallback(entries, observer) {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ entry.target.click();
+ }
+ });
+};
+
+var observerOptions = {
+ childList: true,
+ subtree: true,
+};
+
+chrome.storage.sync.get(null, function(items) {
+ options = items;
+
+ // Before starting the mutation Observer, check whether we missed any
+ // mutations by manually checking whether some watched nodes already
+ // exist.
+ var cssSelectors = watchedNodesSelectors.join(',');
+ document.querySelectorAll(cssSelectors)
+ .forEach(node => handleCandidateNode(node));
+
+ mutationObserver = new MutationObserver(mutationCallback);
+ mutationObserver.observe(document.body, observerOptions);
+
+ if (options.fixedtoolbar) {
+ injectStyles(
+ 'ec-bulk-actions{position: sticky; top: 0; background: var(--TWPT-primary-background, #fff); z-index: 96;}');
+ }
+
+ if (options.increasecontrast) {
+ injectStyles(
+ '.thread-summary.read:not(.checked){background: var(--TWPT-thread-read-background, #ecedee)!important;}');
+ }
+
+ if (options.stickysidebarheaders) {
+ injectStyles(
+ 'material-drawer .main-header{background: var(--TWPT-drawer-background, #fff)!important; position: sticky; top: 0; z-index: 1;}');
+ }
+
+ if (options.enhancedannouncementsdot) {
+ injectStylesheet(
+ chrome.runtime.getURL('css/enhanced_announcements_dot.css'));
+ }
+
+ if (options.repositionexpandthread) {
+ injectStylesheet(
+ chrome.runtime.getURL('css/reposition_expand_thread.css'));
+ }
+
+ if (options.ccforcehidedrawer) {
+ var drawer = document.querySelector('material-drawer');
+ if (drawer !== null && drawer.classList.contains('mat-drawer-expanded')) {
+ document.querySelector('.material-drawer-button').click();
+ }
+ }
+
+ if (options.batchlock) {
+ injectScript(chrome.runtime.getURL('batchLockInject.bundle.js'));
+ injectStylesheet(chrome.runtime.getURL('css/batchlock_inject.css'));
+ }
+
+ if (options.threadlistavatars) {
+ injectStylesheet(
+ chrome.runtime.getURL('css/thread_list_avatars.css'));
+ }
+
+ if (options.autorefreshlist) {
+ injectStylesheet(chrome.runtime.getURL('css/autorefresh_list.css'));
+ }
+});