refactor: migrate infinite scroll feature to a new architecture

This CL introduces a new architecture for the features source code.

Bug: twpowertools:176
Change-Id: I9abc4df2fb67f9bb0c9114aaffc6916d34f1b7ff
diff --git a/src/contentScripts/communityConsole/infiniteScroll.js b/src/contentScripts/communityConsole/infiniteScroll.js
deleted file mode 100644
index 447974e..0000000
--- a/src/contentScripts/communityConsole/infiniteScroll.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import {getOptions, isOptionEnabled} from '../../common/optionsUtils.js';
-
-const kInteropLoadMoreClasses = {
-  // New (interop) UI without nested replies
-  'scTailwindThreadMorebuttonload-all': 'threadall',
-  'scTailwindThreadMorebuttonload-more': 'thread',
-
-  // New (interop) UI with nested replies
-  'scTailwindThreadMessagegapload-all': 'threadall',
-  'scTailwindThreadMessagegapload-more': 'thread',
-};
-const kArtificialScrollingDelay = 3500;
-
-export default class InfiniteScroll {
-  constructor() {
-    this.intersectionObserver = null;
-  }
-
-  setUpIntersectionObserver(node, isScrollableContent) {
-    if (this.intersectionObserver === null) {
-      var scrollableContent = isScrollableContent ?
-          node :
-          node.querySelector('.scrollable-content');
-      if (scrollableContent !== null) {
-        let intersectionOptions = {
-          root: scrollableContent,
-          rootMargin: '0px',
-          threshold: 1.0,
-        };
-        this.intersectionObserver = new IntersectionObserver(
-            this.intersectionCallback, intersectionOptions);
-      }
-    }
-  }
-
-  intersectionCallback(entries, observer) {
-    entries.forEach(entry => {
-      if (entry.isIntersecting) {
-        console.debug('[infinitescroll] Clicking button: ', entry.target);
-        entry.target.click();
-      }
-    });
-  }
-
-  isPotentiallyArtificialScroll() {
-    return window.location.href.includes('/message/');
-  }
-
-  observeWithPotentialDelay(node) {
-    if (this.intersectionObserver === null) {
-      console.warn(
-          '[infinitescroll] ' +
-          'The intersectionObserver is not ready yet.');
-      return;
-    }
-
-    if (this.isPotentiallyArtificialScroll()) {
-      window.setTimeout(
-          () => {this.intersectionObserver.observe(node)},
-          kArtificialScrollingDelay);
-    } else {
-      this.intersectionObserver.observe(node);
-    }
-  }
-
-  observeLoadMoreBar(bar) {
-    getOptions(['thread', 'threadall']).then(threadOptions => {
-      if (threadOptions.thread)
-        this.observeWithPotentialDelay(bar.querySelector('.load-more-button'));
-      if (threadOptions.threadall)
-        this.observeWithPotentialDelay(bar.querySelector('.load-all-button'));
-    });
-  }
-
-  observeLoadMoreInteropBtn(btn) {
-    let parentClasses = btn.parentNode?.classList;
-    let feature = null;
-    for (const [c, f] of Object.entries(kInteropLoadMoreClasses)) {
-      if (parentClasses?.contains?.(c)) {
-        feature = f;
-        break;
-      }
-    }
-    if (feature === null) return;
-    isOptionEnabled(feature).then(isEnabled => {
-      if (isEnabled) this.observeWithPotentialDelay(btn);
-    });
-  }
-};
diff --git a/src/contentScripts/communityConsole/main.js b/src/contentScripts/communityConsole/main.js
index 0200394..ea9cb8c 100644
--- a/src/contentScripts/communityConsole/main.js
+++ b/src/contentScripts/communityConsole/main.js
@@ -12,12 +12,11 @@
 import {applyDragAndDropFixIfEnabled} from './dragAndDropFix.js';
 // #!endif
 import {default as FlattenThreads, kMatchingSelectors as kFlattenThreadMatchingSelectors} from './flattenThreads/flattenThreads.js';
-import InfiniteScroll from './infiniteScroll.js';
 import {kRepliesSectionSelector} from './threadToolbar/constants.js';
 import ThreadToolbar from './threadToolbar/threadToolbar.js';
 import Workflows from './workflows/workflows.js';
 
-var mutationObserver, options, avatars, infiniteScroll, workflows,
+var mutationObserver, options, avatars, workflows,
     threadToolbar, flattenThreads, reportDialogColorThemeFix;
 
 const watchedNodesSelectors = [
@@ -88,8 +87,6 @@
 function handleCandidateNode(node) {
   if (typeof node.classList !== 'undefined') {
     if (('tagName' in node) && node.tagName == 'EC-APP') {
-      infiniteScroll.setUpIntersectionObserver(node, false);
-
       // Inject the dark mode button
       // TODO(avm99963): make this feature dynamic.
       if (options.ccdarktheme && options.ccdarktheme_mode == 'switch') {
@@ -100,21 +97,6 @@
       }
     }
 
-    // To set up infinite scroll
-    if (node.classList.contains('scrollable-content')) {
-      infiniteScroll.setUpIntersectionObserver(node, true);
-    }
-
-    // Start the intersectionObserver for the "load more"/"load all" buttons
-    // inside a thread if the option is currently enabled.
-    if (node.classList.contains('load-more-bar')) {
-      infiniteScroll.observeLoadMoreBar(node);
-    }
-    if (node.classList.contains('scTailwindThreadMorebuttonbutton') ||
-        node.classList.contains('scTailwindThreadMessagegapbutton')) {
-      infiniteScroll.observeLoadMoreInteropBtn(node);
-    }
-
     // Show additional details in the profile view.
     if (node.matches('ec-unified-user .scTailwindUser_profileUsercardmain')) {
       window.TWPTExtraInfo.injectAbuseChipsAtProfileIfEnabled(node);
@@ -284,7 +266,6 @@
 
   // Initialize classes needed by the mutation observer
   avatars = new AvatarsHandler();
-  infiniteScroll = new InfiniteScroll();
   workflows = new Workflows();
   threadToolbar = new ThreadToolbar();
   flattenThreads = new FlattenThreads();