refactor(flatten-threads): migrate to the new DI architecture
Bug: twpowertools:176
Change-Id: I7a24fb504ce53697112f11128d2d5249a5a7c7e7
diff --git a/src/contentScripts/communityConsole/main.js b/src/contentScripts/communityConsole/main.js
index 890f3b0..3e914af 100644
--- a/src/contentScripts/communityConsole/main.js
+++ b/src/contentScripts/communityConsole/main.js
@@ -8,11 +8,10 @@
// #!if ['chromium', 'chromium_mv3'].includes(browser_target)
import {applyDragAndDropFixIfEnabled} from './dragAndDropFix.js';
// #!endif
-import {default as FlattenThreads, kMatchingSelectors as kFlattenThreadMatchingSelectors} from './flattenThreads/flattenThreads.js';
import {kRepliesSectionSelector} from './threadToolbar/constants.js';
import ThreadToolbar from './threadToolbar/threadToolbar.js';
-var mutationObserver, options, avatars, threadToolbar, flattenThreads;
+var mutationObserver, options, avatars, threadToolbar;
const watchedNodesSelectors = [
// Scrollable content (used for the intersection observer)
@@ -45,9 +44,6 @@
// Thread page reply section (for the thread page toolbar)
kRepliesSectionSelector,
-
- // Reply payload (for the flatten threads UI)
- ...kFlattenThreadMatchingSelectors,
];
function handleCandidateNode(node) {
@@ -94,31 +90,6 @@
if (threadToolbar.shouldInject(node)) {
threadToolbar.injectIfApplicable(node);
}
-
- // Inject parent reply quote
- if (flattenThreads.shouldInjectQuote(node)) {
- flattenThreads.injectQuoteIfApplicable(node);
- }
-
- // Inject reply button in non-nested view
- if (flattenThreads.shouldInjectReplyBtn(node)) {
- flattenThreads.injectReplyBtnIfApplicable(node);
- }
-
- // Delete additional info in the edit message box
- if (flattenThreads.isAdditionalInfoElement(node)) {
- flattenThreads.deleteAdditionalInfoElementIfApplicable(node);
- }
- }
-}
-
-function handleRemovedNode(mutation, node) {
- if (!('tagName' in node)) return;
-
- // Readd reply button when the Community Console removes it
- if (node.tagName == 'TWPT-FLATTEN-THREAD-REPLY-BUTTON') {
- flattenThreads.injectReplyBtn(
- mutation.target, JSON.parse(node.getAttribute('extraInfo')));
}
}
@@ -128,10 +99,6 @@
mutation.addedNodes.forEach(function(node) {
handleCandidateNode(node);
});
-
- mutation.removedNodes.forEach(function(node) {
- handleRemovedNode(mutation, node);
- });
}
});
}
@@ -147,7 +114,6 @@
// Initialize classes needed by the mutation observer
avatars = new AvatarsHandler();
threadToolbar = new ThreadToolbar();
- flattenThreads = new FlattenThreads();
// Before starting the mutation Observer, check whether we missed any
// mutations by manually checking whether some watched nodes already
@@ -202,8 +168,6 @@
injectStylesheet(chrome.runtime.getURL('css/thread_list_avatars.css'));
// Thread toolbar
injectStylesheet(chrome.runtime.getURL('css/thread_toolbar.css'));
- // Flatten threads
- injectStylesheet(chrome.runtime.getURL('css/flatten_threads.css'));
});
new XHRProxyKillSwitchHandler();
diff --git a/src/contentScripts/communityConsole/start.js b/src/contentScripts/communityConsole/start.js
index f890bd0..8cb89e8 100644
--- a/src/contentScripts/communityConsole/start.js
+++ b/src/contentScripts/communityConsole/start.js
@@ -1,15 +1,9 @@
import {injectStylesheet} from '../../common/contentScriptsUtils';
import {getOptions} from '../../common/options/optionsUtils.js';
-import FlattenThreadsReplyActionHandler from './flattenThreads/replyActionHandler.js';
-
getOptions(null).then(options => {
if (options.uispacing) {
injectStylesheet(chrome.runtime.getURL('css/ui_spacing/shared.css'));
injectStylesheet(chrome.runtime.getURL('css/ui_spacing/console.css'));
}
-
- const flattenThreadsReplyActionHandler =
- new FlattenThreadsReplyActionHandler(options);
- flattenThreadsReplyActionHandler.handleIfApplicable();
});
diff --git a/src/entryPoints/communityConsole/contentScripts/main.ts b/src/entryPoints/communityConsole/contentScripts/main.ts
index 35088ea..2274fd0 100644
--- a/src/entryPoints/communityConsole/contentScripts/main.ts
+++ b/src/entryPoints/communityConsole/contentScripts/main.ts
@@ -42,6 +42,12 @@
import InjectLitComponentsScript from '../../../presentation/standaloneScripts/litComponents/injectLitComponents.script';
import ApplyStartupDataModificationsOnMainScript from '../../../presentation/standaloneScripts/startupDataStorage/applyStartupDataModificationsOnMain.script';
import ThreadPageDesignWarningInjectHandler from '../../../features/threadPageDesignWarning/presentation/nodeWatcherHandlers/inject.handler';
+import FlattenThreadsAdditionalInfoHandler from '../../../features/flattenThreads/presentation/nodeWatcherHandlers/additionalInfo.handler';
+import FlattenThreadsQuoteHandler from '../../../features/flattenThreads/presentation/nodeWatcherHandlers/quote.handler';
+import FlattenThreadsReaddReplyBtnHandler from '../../../features/flattenThreads/presentation/nodeWatcherHandlers/readdReplyBtn.handler';
+import FlattenThreadsReplyBtnHandler from '../../../features/flattenThreads/presentation/nodeWatcherHandlers/replyBtn.handler';
+import FlattenThreads from '../../../features/flattenThreads/core/flattenThreads';
+import FlattenThreadsStylesScript from '../../../features/flattenThreads/presentation/scripts/styles.script';
const scriptRunner = createScriptRunner();
scriptRunner.run();
@@ -64,6 +70,7 @@
);
const ccInfiniteScroll = new CCInfiniteScroll();
+ const flattenThreads = new FlattenThreads();
return new ScriptRunner(
new SortedScriptsProviderAdapter(
@@ -133,6 +140,22 @@
new CCInfiniteScrollLoadMoreBtnHandler(ccInfiniteScroll),
],
[
+ 'flattenThreadsAdditionalInfo',
+ new FlattenThreadsAdditionalInfoHandler(flattenThreads),
+ ],
+ [
+ 'flattenThreadsQuote',
+ new FlattenThreadsQuoteHandler(flattenThreads),
+ ],
+ [
+ 'flattenThreadsReaddReplyBtn',
+ new FlattenThreadsReaddReplyBtnHandler(flattenThreads),
+ ],
+ [
+ 'flattenThreadsReplyBtn',
+ new FlattenThreadsReplyBtnHandler(flattenThreads),
+ ],
+ [
'threadPageDesignWarningInject',
new ThreadPageDesignWarningInjectHandler(threadPageDesignWarning),
],
@@ -151,6 +174,7 @@
new AutoRefreshStylesScript(),
new CCExtraInfoInjectScript(),
new CCExtraInfoStylesScript(),
+ new FlattenThreadsStylesScript(),
new WorkflowsImportStylesheetScript(),
// Standalone scripts
diff --git a/src/entryPoints/communityConsole/contentScripts/start.ts b/src/entryPoints/communityConsole/contentScripts/start.ts
index 72302ff..21ef427 100644
--- a/src/entryPoints/communityConsole/contentScripts/start.ts
+++ b/src/entryPoints/communityConsole/contentScripts/start.ts
@@ -24,6 +24,8 @@
import ApplyStartupDataModificationsOnStartScript from '../../../presentation/standaloneScripts/startupDataStorage/applyStartupDataModificationsOnStart.script';
import XHRInterceptorScript from '../../../presentation/standaloneScripts/xhrInterceptor/xhrInterceptor.script';
import ThreadPageDesignWarningSetUpScript from '../../../features/threadPageDesignWarning/presentation/scripts/setUp.script';
+import FlattenThreadsSetUpReplyActionHandlerScript from '../../../features/flattenThreads/presentation/scripts/setUpReplyActionHandler.script';
+import FlattenThreadsReplyActionHandler from '../../../features/flattenThreads/core/replyActionHandler';
const scriptRunner = createScriptRunner();
scriptRunner.run();
@@ -53,10 +55,11 @@
new CCDarkThemeInjectAutoDarkTheme(),
new CCDarkThemeInjectForcedDarkTheme(),
new CCExtraInfoSetUpScript(extraInfo),
- new InteropThreadPageSetupScript(),
- new ThreadPageDesignWarningSetUpScript(
- threadPageDesignWarning,
+ new FlattenThreadsSetUpReplyActionHandlerScript(
+ new FlattenThreadsReplyActionHandler(optionsProvider),
),
+ new InteropThreadPageSetupScript(),
+ new ThreadPageDesignWarningSetUpScript(threadPageDesignWarning),
new LoadDraftsSetupScript(optionsProvider, startupDataStorage),
new WorkflowsImportSetUpScript(workflowsImport),
diff --git a/src/contentScripts/communityConsole/flattenThreads/components/QuoteAuthor.js b/src/features/flattenThreads/core/components/QuoteAuthor.js
similarity index 100%
rename from src/contentScripts/communityConsole/flattenThreads/components/QuoteAuthor.js
rename to src/features/flattenThreads/core/components/QuoteAuthor.js
diff --git a/src/contentScripts/communityConsole/flattenThreads/components/ReplyButton.js b/src/features/flattenThreads/core/components/ReplyButton.js
similarity index 96%
rename from src/contentScripts/communityConsole/flattenThreads/components/ReplyButton.js
rename to src/features/flattenThreads/core/components/ReplyButton.js
index 8adf676..d54a981 100644
--- a/src/contentScripts/communityConsole/flattenThreads/components/ReplyButton.js
+++ b/src/features/flattenThreads/core/components/ReplyButton.js
@@ -5,7 +5,7 @@
import {I18nLitElement} from '../../../../common/litI18nUtils.js';
import {SHARED_MD3_STYLES} from '../../../../common/styles/md3.js';
-import {openReplyEditor} from '../../utils/common.js';
+import {openReplyEditor} from '../../../../contentScripts/communityConsole/utils/common.js';
import {getExtraInfoNodes} from '../flattenThreads.js';
export default class TwptFlattenThreadReplyButton extends I18nLitElement {
diff --git a/src/contentScripts/communityConsole/flattenThreads/components/index.js b/src/features/flattenThreads/core/components/index.js
similarity index 100%
rename from src/contentScripts/communityConsole/flattenThreads/components/index.js
rename to src/features/flattenThreads/core/components/index.js
diff --git a/src/contentScripts/communityConsole/flattenThreads/flattenThreads.js b/src/features/flattenThreads/core/flattenThreads.js
similarity index 86%
rename from src/contentScripts/communityConsole/flattenThreads/flattenThreads.js
rename to src/features/flattenThreads/core/flattenThreads.js
index 557a533..1e63e24 100644
--- a/src/contentScripts/communityConsole/flattenThreads/flattenThreads.js
+++ b/src/features/flattenThreads/core/flattenThreads.js
@@ -5,11 +5,6 @@
export const kReplyActionButtonsSelector =
'.scTailwindThreadMessageMessagecardcontent:not(.scTailwindThreadMessageMessagecardpromoted) sc-tailwind-thread-message-message-actions';
export const kAdditionalInfoSelector = '.ck-indent-9996300035194';
-export const kMatchingSelectors = [
- kReplyPayloadSelector,
- kReplyActionButtonsSelector,
- kAdditionalInfoSelector,
-];
export function getExtraInfoNodes(node) {
return node.querySelectorAll(kAdditionalInfoSelector);
@@ -54,10 +49,6 @@
if (extraInfo.isComment) this.injectQuote(node, extraInfo);
}
- shouldInjectQuote(node) {
- return node.matches(kReplyPayloadSelector);
- }
-
injectReplyBtnIfApplicable(node) {
// If we injected the additional information, it means the flatten threads
// feature is enabled and in actual use, so we should inject the reply
@@ -71,16 +62,8 @@
this.injectReplyBtn(node, extraInfo);
}
- shouldInjectReplyBtn(node) {
- return node.matches(kReplyActionButtonsSelector);
- }
-
deleteAdditionalInfoElementIfApplicable(node) {
if (!node.closest('sc-tailwind-shared-rich-text-editor')) return;
node.remove();
}
-
- isAdditionalInfoElement(node) {
- return node.matches(kAdditionalInfoSelector);
- }
}
diff --git a/src/contentScripts/communityConsole/flattenThreads/replyActionHandler.js b/src/features/flattenThreads/core/replyActionHandler.js
similarity index 78%
rename from src/contentScripts/communityConsole/flattenThreads/replyActionHandler.js
rename to src/features/flattenThreads/core/replyActionHandler.js
index 182f962..3e5d02d 100644
--- a/src/contentScripts/communityConsole/flattenThreads/replyActionHandler.js
+++ b/src/features/flattenThreads/core/replyActionHandler.js
@@ -1,19 +1,14 @@
import {waitFor} from 'poll-until-promise';
import {parseUrl} from '../../../common/commonUtils';
-import {getOptions} from '../../../common/options/optionsUtils';
const kOpenReplyEditorIntervalInMs = 500;
const kOpenReplyEditorTimeoutInMs = 10 * 1000;
// @TODO: Handle observing when the hash is added after the page has loaded.
export default class FlattenThreadsReplyActionHandler {
- /**
- * @param {Object} options Options object which at least includes the
- * |flattenthreads| and |flattenthreads_switch_enabled| options.
- */
- constructor(options = null) {
- this.options = options;
+ constructor(optionsProvider) {
+ this.optionsProvider = optionsProvider;
}
async handleIfApplicable() {
@@ -53,13 +48,7 @@
}
async isFeatureEnabled() {
- let options;
- if (this.options !== null) {
- options = this.options;
- } else {
- options =
- await getOptions(['flattenthreads', 'flattenthreads_switch_enabled']);
- }
+ const options = await this.optionsProvider.getOptionsValues();
return options['flattenthreads'] &&
options['flattenthreads_switch_enabled'];
}
diff --git a/src/features/flattenThreads/presentation/nodeWatcherHandlers/additionalInfo.handler.ts b/src/features/flattenThreads/presentation/nodeWatcherHandlers/additionalInfo.handler.ts
new file mode 100644
index 0000000..c03b5bd
--- /dev/null
+++ b/src/features/flattenThreads/presentation/nodeWatcherHandlers/additionalInfo.handler.ts
@@ -0,0 +1,18 @@
+import CssSelectorNodeWatcherHandler from '../../../../infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter';
+import { NodeMutation } from '../../../../presentation/nodeWatcher/NodeWatcherHandler';
+import FlattenThreads, {
+ kAdditionalInfoSelector,
+} from '../../core/flattenThreads';
+
+/** Delete additional info in the edit message box */
+export default class FlattenThreadsAdditionalInfoHandler extends CssSelectorNodeWatcherHandler {
+ cssSelector = kAdditionalInfoSelector;
+
+ constructor(private flattenThreads: FlattenThreads) {
+ super();
+ }
+
+ onMutatedNode({ node }: NodeMutation) {
+ this.flattenThreads.deleteAdditionalInfoElementIfApplicable(node);
+ }
+}
diff --git a/src/features/flattenThreads/presentation/nodeWatcherHandlers/quote.handler.ts b/src/features/flattenThreads/presentation/nodeWatcherHandlers/quote.handler.ts
new file mode 100644
index 0000000..c156049
--- /dev/null
+++ b/src/features/flattenThreads/presentation/nodeWatcherHandlers/quote.handler.ts
@@ -0,0 +1,18 @@
+import CssSelectorNodeWatcherHandler from '../../../../infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter';
+import { NodeMutation } from '../../../../presentation/nodeWatcher/NodeWatcherHandler';
+import FlattenThreads, {
+ kReplyPayloadSelector,
+} from '../../core/flattenThreads';
+
+/** Inject parent reply quote */
+export default class FlattenThreadsQuoteHandler extends CssSelectorNodeWatcherHandler {
+ cssSelector = kReplyPayloadSelector;
+
+ constructor(private flattenThreads: FlattenThreads) {
+ super();
+ }
+
+ onMutatedNode({ node }: NodeMutation) {
+ this.flattenThreads.injectQuoteIfApplicable(node);
+ }
+}
diff --git a/src/features/flattenThreads/presentation/nodeWatcherHandlers/readdReplyBtn.handler.ts b/src/features/flattenThreads/presentation/nodeWatcherHandlers/readdReplyBtn.handler.ts
new file mode 100644
index 0000000..2eb3d33
--- /dev/null
+++ b/src/features/flattenThreads/presentation/nodeWatcherHandlers/readdReplyBtn.handler.ts
@@ -0,0 +1,23 @@
+import CssSelectorNodeWatcherHandler from '../../../../infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter';
+import {
+ NodeMutation,
+ NodeMutationType,
+} from '../../../../presentation/nodeWatcher/NodeWatcherHandler';
+import FlattenThreads from '../../core/flattenThreads';
+
+/** Readd reply button when the Community Console removes it */
+export default class FlattenThreadsReaddReplyBtnHandler extends CssSelectorNodeWatcherHandler {
+ cssSelector = 'twpt-flatten-thread-reply-button';
+ mutationTypesProcessed = [NodeMutationType.RemovedNode];
+
+ constructor(private flattenThreads: FlattenThreads) {
+ super();
+ }
+
+ onMutatedNode({ node, mutationRecord }: NodeMutation<HTMLElement>) {
+ this.flattenThreads.injectReplyBtn(
+ mutationRecord.target,
+ JSON.parse(node.getAttribute('extraInfo')),
+ );
+ }
+}
diff --git a/src/features/flattenThreads/presentation/nodeWatcherHandlers/replyBtn.handler.ts b/src/features/flattenThreads/presentation/nodeWatcherHandlers/replyBtn.handler.ts
new file mode 100644
index 0000000..66eff17
--- /dev/null
+++ b/src/features/flattenThreads/presentation/nodeWatcherHandlers/replyBtn.handler.ts
@@ -0,0 +1,18 @@
+import CssSelectorNodeWatcherHandler from '../../../../infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter';
+import { NodeMutation } from '../../../../presentation/nodeWatcher/NodeWatcherHandler';
+import FlattenThreads, {
+ kReplyActionButtonsSelector,
+} from '../../core/flattenThreads';
+
+/** Inject reply button in non-nested view */
+export default class FlattenThreadsReplyBtnHandler extends CssSelectorNodeWatcherHandler {
+ cssSelector = kReplyActionButtonsSelector;
+
+ constructor(private flattenThreads: FlattenThreads) {
+ super();
+ }
+
+ onMutatedNode({ node }: NodeMutation) {
+ this.flattenThreads.injectReplyBtnIfApplicable(node);
+ }
+}
diff --git a/src/features/flattenThreads/presentation/scripts/setUpReplyActionHandler.script.ts b/src/features/flattenThreads/presentation/scripts/setUpReplyActionHandler.script.ts
new file mode 100644
index 0000000..b62140e
--- /dev/null
+++ b/src/features/flattenThreads/presentation/scripts/setUpReplyActionHandler.script.ts
@@ -0,0 +1,20 @@
+import Script from '../../../../common/architecture/scripts/Script';
+import InjectLitComponentsScript from '../../../../presentation/standaloneScripts/litComponents/injectLitComponents.script';
+import FlattenThreadsReplyActionHandler from '../../core/replyActionHandler';
+
+export default class FlattenThreadsSetUpReplyActionHandlerScript extends Script {
+ page: never;
+ environment: never;
+ runPhase: never;
+ runAfter = [InjectLitComponentsScript];
+
+ constructor(
+ private flattenThreadsReplyActionHandler: FlattenThreadsReplyActionHandler,
+ ) {
+ super();
+ }
+
+ execute() {
+ this.flattenThreadsReplyActionHandler.handleIfApplicable();
+ }
+}
diff --git a/src/features/flattenThreads/presentation/scripts/styles.script.ts b/src/features/flattenThreads/presentation/scripts/styles.script.ts
new file mode 100644
index 0000000..338adb2
--- /dev/null
+++ b/src/features/flattenThreads/presentation/scripts/styles.script.ts
@@ -0,0 +1,12 @@
+import Script from '../../../../common/architecture/scripts/Script';
+import { injectStylesheet } from '../../../../common/contentScriptsUtils';
+
+export default class FlattenThreadsStylesScript extends Script {
+ page: never;
+ environment: never;
+ runPhase: never;
+
+ execute() {
+ injectStylesheet(chrome.runtime.getURL('css/flatten_threads.css'));
+ }
+}
diff --git a/src/infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter.ts b/src/infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter.ts
index bf57020..5617130 100644
--- a/src/infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter.ts
+++ b/src/infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter.ts
@@ -28,5 +28,5 @@
return this.cssSelector;
}
- abstract onMutatedNode(nodeMutation: NodeMutation): void;
+ abstract onMutatedNode(nodeMutation: NodeMutation<HTMLElement>): void;
}
diff --git a/src/injections/litComponentsInject.js b/src/injections/litComponentsInject.js
index ce8ace8..7143bd5 100644
--- a/src/injections/litComponentsInject.js
+++ b/src/injections/litComponentsInject.js
@@ -4,7 +4,7 @@
// because `window.customElements` doesn't exist in content scripts.
import '../features/workflows/core/communityConsole/components/index.js';
import '../contentScripts/communityConsole/threadToolbar/components/index.js';
-import '../contentScripts/communityConsole/flattenThreads/components/index.js';
+import '../features/flattenThreads/core/components/index.js';
import '../contentScripts/communityConsole/updateHandler/banner/components/index.js';
import {injectStylesheet} from '../common/contentScriptsUtils';
diff --git a/src/presentation/nodeWatcher/NodeWatcherHandler.ts b/src/presentation/nodeWatcher/NodeWatcherHandler.ts
index b6c3a25..67801f4 100644
--- a/src/presentation/nodeWatcher/NodeWatcherHandler.ts
+++ b/src/presentation/nodeWatcher/NodeWatcherHandler.ts
@@ -13,11 +13,11 @@
RemovedNode,
}
-export interface NodeMutation {
+export interface NodeMutation<T extends Node = Node> {
/**
* Node being mutated.
*/
- node: Node;
+ node: T;
/**
* Which mutation has occurred to the node.
*/
diff --git a/src/xhrInterceptor/responseModifiers/flattenThread.js b/src/xhrInterceptor/responseModifiers/flattenThread.js
index 2eac64d..452b1fb 100644
--- a/src/xhrInterceptor/responseModifiers/flattenThread.js
+++ b/src/xhrInterceptor/responseModifiers/flattenThread.js
@@ -1,4 +1,4 @@
-import {kAdditionalInfoClass} from '../../contentScripts/communityConsole/flattenThreads/flattenThreads.js';
+import {kAdditionalInfoClass} from '../../features/flattenThreads/core/flattenThreads.js';
import GapModel from '../../models/Gap.js';
import MessageModel from '../../models/Message.js';
import StartupDataModel from '../../models/StartupData.js';