refactor: convert CCInfiniteScroll class to Typescript
Change-Id: I4ec59b96a71b1a23a16f3d5dd923e9d8fd3f2638
diff --git a/src/features/infiniteScroll/core/ccInfiniteScroll.js b/src/features/infiniteScroll/core/ccInfiniteScroll.js
deleted file mode 100644
index 4dde91c..0000000
--- a/src/features/infiniteScroll/core/ccInfiniteScroll.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import {getOptions, isOptionEnabled} from '../../../common/options/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 CCInfiniteScroll {
- 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/features/infiniteScroll/core/ccInfiniteScroll.ts b/src/features/infiniteScroll/core/ccInfiniteScroll.ts
new file mode 100644
index 0000000..84c3d63
--- /dev/null
+++ b/src/features/infiniteScroll/core/ccInfiniteScroll.ts
@@ -0,0 +1,96 @@
+import {
+ getOptions,
+ isOptionEnabled,
+} from '../../../common/options/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 CCInfiniteScroll {
+ private intersectionObserver: IntersectionObserver = null;
+
+ setUpIntersectionObserver(node: Element, isScrollableContent: boolean) {
+ if (this.intersectionObserver === null) {
+ const scrollableContent = isScrollableContent
+ ? node
+ : node.querySelector('.scrollable-content');
+ if (scrollableContent !== null) {
+ const intersectionOptions = {
+ root: scrollableContent,
+ rootMargin: '0px',
+ threshold: 1.0,
+ };
+ this.intersectionObserver = new IntersectionObserver(
+ this.intersectionCallback,
+ intersectionOptions,
+ );
+ }
+ }
+ }
+
+ intersectionCallback(entries: IntersectionObserverEntry[]) {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ const target = entry.target;
+ if (!(target instanceof HTMLElement)) return;
+ console.debug('[infinitescroll] Clicking button: ', target);
+ target.click();
+ }
+ });
+ }
+
+ isPotentiallyArtificialScroll(): boolean {
+ return window.location.href.includes('/message/');
+ }
+
+ observeWithPotentialDelay(node: Element) {
+ 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: Element) {
+ 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: Element) {
+ const parentNode = btn.parentNode;
+ if (!(parentNode instanceof Element)) return;
+ const parentClasses = parentNode?.classList;
+ let feature: string = 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/features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollLoadMoreBar.handler.ts b/src/features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollLoadMoreBar.handler.ts
index da4f5e6..e9f04ff 100644
--- a/src/features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollLoadMoreBar.handler.ts
+++ b/src/features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollLoadMoreBar.handler.ts
@@ -6,6 +6,13 @@
cssSelector = '.load-more-bar';
onMutatedNode({ node }: NodeMutation) {
+ if (!(node instanceof Element)) {
+ console.error(
+ '[CCInfiniteScrollLoadMoreBarHandler] Node is not an Element: ',
+ node,
+ );
+ return;
+ }
this.options.ccInfiniteScroll.observeLoadMoreBar(node);
}
}
diff --git a/src/features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollLoadMoreBtn.handler.ts b/src/features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollLoadMoreBtn.handler.ts
index ee54aff..8bf6fd2 100644
--- a/src/features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollLoadMoreBtn.handler.ts
+++ b/src/features/infiniteScroll/nodeWatcherHandlers/ccInfiniteScrollLoadMoreBtn.handler.ts
@@ -7,6 +7,13 @@
'.scTailwindThreadMorebuttonbutton, .scTailwindThreadMessagegapbutton';
onMutatedNode({ node }: NodeMutation) {
+ if (!(node instanceof Element)) {
+ console.error(
+ '[CCInfiniteScrollLoadMoreBtnHandler] Node is not an Element: ',
+ node,
+ );
+ return;
+ }
this.options.ccInfiniteScroll.observeLoadMoreInteropBtn(node);
}
}