Add infinite scroll support to CC interop threads
Bug: twpowertools:96
Change-Id: I90eeae09d8bdcb777c69fa4dc2bc0fe8c034cd09
diff --git a/src/contentScripts/communityConsole/infiniteScroll.js b/src/contentScripts/communityConsole/infiniteScroll.js
new file mode 100644
index 0000000..55dc0ae
--- /dev/null
+++ b/src/contentScripts/communityConsole/infiniteScroll.js
@@ -0,0 +1,78 @@
+import {getOptions, isOptionEnabled} from '../../common/optionsUtils.js';
+
+const kInteropLoadMoreClasses = {
+ 'scTailwindThreadMorebuttonload-all': 'threadall',
+ 'scTailwindThreadMorebuttonload-more': 'thread',
+};
+
+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();
+ }
+ });
+ }
+
+ observeLoadMoreBar(bar) {
+ if (this.intersectionObserver === null) {
+ console.warn(
+ '[infinitescroll] ' +
+ 'The intersectionObserver is not ready yet.');
+ return;
+ }
+
+ getOptions(['thread', 'threadall']).then(threadOptions => {
+ if (threadOptions.thread)
+ this.intersectionObserver.observe(
+ bar.querySelector('.load-more-button'));
+ if (threadOptions.threadall)
+ this.intersectionObserver.observe(
+ bar.querySelector('.load-all-button'));
+ });
+ }
+
+ observeLoadMoreInteropBtn(btn) {
+ if (this.intersectionObserver === null) {
+ console.warn(
+ '[infinitescroll] ' +
+ 'The intersectionObserver is not ready yet.');
+ return;
+ }
+
+ 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.intersectionObserver.observe(btn);
+ });
+ }
+};
diff --git a/src/contentScripts/communityConsole/main.js b/src/contentScripts/communityConsole/main.js
index 0ffff41..75de36f 100644
--- a/src/contentScripts/communityConsole/main.js
+++ b/src/contentScripts/communityConsole/main.js
@@ -8,19 +8,23 @@
// #!if ['chromium', 'chromium_mv3'].includes(browser_target)
import {applyDragAndDropFixIfEnabled} from './dragAndDropFix.js';
// #!endif
+import InfiniteScroll from './infiniteScroll.js';
import {unifiedProfilesFix} from './unifiedProfiles.js';
import Workflows from './workflows/workflows.js';
-var mutationObserver, intersectionObserver, intersectionOptions, options,
- avatars, workflows;
+var mutationObserver, options, avatars, infiniteScroll, workflows;
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)
+ // Scrollable content (used for the intersection observer)
+ '.scrollable-content',
+
+ // Load more bar and buttons
'.load-more-bar',
+ '.scTailwindThreadMorebuttonbutton',
// User profile card inside ec-unified-user
'ec-unified-user .scTailwindUser_profileUsercardmain',
@@ -63,20 +67,7 @@
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);
- }
- }
+ infiniteScroll.setUpIntersectionObserver(node, false);
// Inject the dark mode button
// TODO(avm99963): make this feature dynamic.
@@ -87,23 +78,18 @@
}
}
+ // 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')) {
- if (typeof intersectionObserver !== 'undefined') {
- getOptions(['thread', 'threadall']).then(threadOptions => {
- if (threadOptions.thread)
- intersectionObserver.observe(
- node.querySelector('.load-more-button'));
- if (threadOptions.threadall)
- intersectionObserver.observe(
- node.querySelector('.load-all-button'));
- });
- } else {
- console.warn(
- '[infinitescroll] ' +
- 'The intersectionObserver is not ready yet.');
- }
+ infiniteScroll.observeLoadMoreBar(node);
+ }
+ if (node.classList.contains('scTailwindThreadMorebuttonbutton')) {
+ infiniteScroll.observeLoadMoreInteropBtn(node);
}
// Show additional details in the profile view.
@@ -213,14 +199,6 @@
});
}
-function intersectionCallback(entries, observer) {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- entry.target.click();
- }
- });
-};
-
var observerOptions = {
childList: true,
subtree: true,
@@ -231,6 +209,7 @@
// Initialize classes needed by the mutation observer
avatars = new AvatarsHandler();
+ infiniteScroll = new InfiniteScroll();
workflows = new Workflows();
// autoRefresh is initialized in start.js