Add flattenthreads experiment
This experiment allows users to flatten the replies in threads, so they
are shown linearly in a chronological way instead of nested.
When the option is enabled, a switch is added to the thread page which
lets the user switch between flattening replies and not flattening them.
Some UI is still missing (see the design document[1]).
[1]: https://docs.google.com/document/d/1P-HanTHxaOFF_FHh0uSv0GIhG1dxWTJTGoT6VPjjvY0/edit
Bug: twpowertools:153
Change-Id: I43f94442cadc12b752700f0e8d974522be621d3e
diff --git a/src/contentScripts/communityConsole/threadToolbar/components/index.js b/src/contentScripts/communityConsole/threadToolbar/components/index.js
new file mode 100644
index 0000000..65b2e31
--- /dev/null
+++ b/src/contentScripts/communityConsole/threadToolbar/components/index.js
@@ -0,0 +1,65 @@
+import '@material/web/formfield/formfield.js';
+import '@material/web/switch/switch.js';
+
+import {css, html, LitElement, nothing} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import {SHARED_MD3_STYLES} from '../../../../common/styles/md3.js';
+import {kEventFlattenThreadsUpdated} from '../constants.js';
+
+export default class TwptThreadToolbarInject extends LitElement {
+ static properties = {
+ options: {type: Object},
+ };
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ css`
+ :host {
+ display: flex;
+ flex-direction: row;
+ padding-top: 1.5rem;
+ padding-left: 0.25rem;
+ padding-right: 0.25rem;
+ padding-bottom: 0.5rem;
+ }
+ `,
+ ];
+
+ nestedViewRef = createRef();
+
+ constructor() {
+ super();
+ this.options = {};
+ }
+
+ renderFlattenRepliesSwitch() {
+ if (!this.options.flattenthreads) return nothing;
+
+ return html`
+ <md-formfield label="Nested view">
+ <md-switch ${ref(this.nestedViewRef)}
+ ?selected=${!this.options?.flattenthreads_switch_enabled}
+ @click=${this._flattenThreadsChanged}>
+ </md-formfield>
+ `;
+ }
+
+ render() {
+ return html`
+ ${this.renderFlattenRepliesSwitch()}
+ `;
+ }
+
+ _flattenThreadsChanged() {
+ const enabled = !this.nestedViewRef.value.selected;
+ const e = new CustomEvent(kEventFlattenThreadsUpdated, {
+ bubbles: true,
+ composed: true,
+ detail: {enabled},
+ });
+ this.dispatchEvent(e);
+ }
+}
+window.customElements.define(
+ 'twpt-thread-toolbar-inject', TwptThreadToolbarInject);
diff --git a/src/contentScripts/communityConsole/threadToolbar/constants.js b/src/contentScripts/communityConsole/threadToolbar/constants.js
new file mode 100644
index 0000000..756d880
--- /dev/null
+++ b/src/contentScripts/communityConsole/threadToolbar/constants.js
@@ -0,0 +1,5 @@
+export const kEventFlattenThreadsUpdated =
+ 'TWPTThreadToolbarFlattenThreadsUpdated';
+
+export const kRepliesSectionSelector =
+ 'ec-thread .scTailwindThreadThreadcontentreplies-section';
diff --git a/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js b/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js
new file mode 100644
index 0000000..6344e3c
--- /dev/null
+++ b/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js
@@ -0,0 +1,57 @@
+import {getOptions} from '../../../common/optionsUtils.js';
+import {softRefreshView} from '../utils/common.js';
+
+import * as consts from './constants.js';
+
+export default class ThreadToolbar {
+ constructor() {
+ this.getOptions().then(options => {
+ this.updateBodyClasses(options);
+ });
+ }
+
+ updateBodyClasses(options) {
+ if (this.shouldSeeToolbar(options))
+ document.body.classList.add('TWPT-threadtoolbar-shown');
+ else
+ document.body.classList.remove('TWPT-threadtoolbar-shown');
+
+ if (options.flattenthreads && options.flattenthreads_switch_enabled)
+ document.body.classList.add('TWPT-flattenthreads-enabled');
+ else
+ document.body.classList.remove('TWPT-flattenthreads-enabled');
+ }
+
+ shouldSeeToolbar(options) {
+ return Object.values(options).some(option => !!option);
+ }
+
+ getOptions() {
+ return getOptions(['flattenthreads', 'flattenthreads_switch_enabled']);
+ }
+
+ inject(node, options) {
+ const toolbar = document.createElement('twpt-thread-toolbar-inject');
+ toolbar.setAttribute('options', JSON.stringify(options));
+ toolbar.addEventListener(consts.kEventFlattenThreadsUpdated, e => {
+ const enabled = e.detail?.enabled;
+ if (typeof enabled != 'boolean') return;
+ chrome.storage.sync.set({flattenthreads_switch_enabled: enabled}, _ => {
+ softRefreshView();
+ });
+ });
+ node.parentElement.insertBefore(toolbar, node);
+ }
+
+ injectIfApplicable(node) {
+ this.getOptions().then(options => {
+ this.updateBodyClasses(options);
+ if (!this.shouldSeeToolbar(options)) return;
+ return this.inject(node, options);
+ });
+ }
+
+ shouldInject(node) {
+ return node.matches(consts.kRepliesSectionSelector);
+ }
+}