feat(bulk-report-replies): add feature with non-functional UI
This commit adds a new "bulk report replies" feature along with its UI.
The underlying logic will be implemented soon.
Bug: twpowertools:192
Change-Id: I6a6a636c7186485154c32dbbec001493b3b0458f
diff --git a/src/common/options/optionsPrototype.ts b/src/common/options/optionsPrototype.ts
index a5f7a93..fa38b1b 100644
--- a/src/common/options/optionsPrototype.ts
+++ b/src/common/options/optionsPrototype.ts
@@ -158,6 +158,11 @@
context: OptionContext.Experiments,
killSwitchType: KillSwitchType.Experiment,
},
+ bulkreportreplies: {
+ defaultValue: false,
+ context: OptionContext.Experiments,
+ killSwitchType: KillSwitchType.Experiment,
+ },
// Internal options
ccdarktheme_switch_status: {
@@ -170,6 +175,11 @@
context: OptionContext.Internal,
killSwitchType: KillSwitchType.Ignore,
},
+ bulkreportreplies_switch_enabled: {
+ defaultValue: true,
+ context: OptionContext.Internal,
+ killSwitchType: KillSwitchType.Ignore,
+ },
// Internal kill switches
killswitch_xhrproxy: {
diff --git a/src/contentScripts/communityConsole/threadToolbar/components/index.js b/src/contentScripts/communityConsole/threadToolbar/components/index.js
index 8e1bcd9..ae28a0b 100644
--- a/src/contentScripts/communityConsole/threadToolbar/components/index.js
+++ b/src/contentScripts/communityConsole/threadToolbar/components/index.js
@@ -1,15 +1,31 @@
import '@material/web/icon/icon.js';
+import '@material/web/iconbutton/icon-button.js';
+import '@material/web/menu/menu.js';
+import '@material/web/menu/menu-item.js';
import '@material/web/switch/switch.js';
import '../../../../common/components/FormField.js';
import consoleCommonStyles from '!!raw-loader!../../../../static/css/common/console.css';
import {msg} from '@lit/localize';
+import {Corner} from '@material/web/menu/menu.js';
import {css, html, nothing, unsafeCSS} from 'lit';
+import {map} from 'lit/directives/map.js';
import {createRef, ref} from 'lit/directives/ref.js';
import {I18nLitElement} from '../../../../common/litI18nUtils.js';
import {SHARED_MD3_STYLES} from '../../../../common/styles/md3.js';
-import {kEventFlattenThreadsUpdated} from '../constants.js';
+import {kEventOptionUpdated} from '../constants.js';
+
+const getOverflowMenuItems = (options) =>
+ [{
+ label: msg('Bulk report replies', {
+ 'desc':
+ 'Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.',
+ }),
+ isShown: options['bulkreportreplies'] === true,
+ option: 'bulkreportreplies_switch_enabled'
+ },
+].filter(item => item.isShown);
export default class TwptThreadToolbarInject extends I18nLitElement {
static properties = {
@@ -27,10 +43,18 @@
.toolbar {
display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ justify-content: space-between;
+ row-gap: 0.5rem;
+ padding: 0.5rem 0.25rem;
+ }
+
+ .toolbar-start, .toolbar-end {
+ display: flex;
flex-flow: row wrap;
align-items: center;
row-gap: 0.5rem;
- padding: 0.5rem 0.25rem;
}
.badge-container {
@@ -46,6 +70,7 @@
];
nestedViewRef = createRef();
+ overflowMenuRef = createRef();
constructor() {
super();
@@ -82,27 +107,75 @@
`;
}
+ renderOverflowMenu() {
+ const items = getOverflowMenuItems(this.options);
+
+ if (items.length === 0) return nothing;
+
+ return html`
+ <md-icon-button
+ id="thread-toolbar-overflow-menu-anchor"
+ @click="${this._toggleOverflowMenu}">
+ <md-icon>settings</md-icon>
+ </md-icon-button>
+ <md-menu ${ref(this.overflowMenuRef)}
+ anchor="thread-toolbar-overflow-menu-anchor"
+ anchor-corner="${Corner.END_END}"
+ menu-corner="${Corner.START_END}"
+ positioning="popover">
+ ${
+ map(items,
+ item => html`
+ <md-menu-item
+ @click="${
+ () => this._onOptionChanged(
+ item.option, !this.options[item.option], false)}">
+ <md-icon slot="start">${
+ this.options[item.option] ? 'check' : ''}</md-icon>
+ <span>
+ ${item.label}
+ </span>
+ </md-menu-item>
+ `)}
+ </md-menu>
+ `;
+ }
+
render() {
// NOTE: Keep this in sync!
- if (!this.options.flattenthreads) return nothing;
+ if (!this.options.flattenthreads && !this.options.bulkreportreplies)
+ return nothing;
return html`
<div class="toolbar">
- ${this.renderBadge()}
- ${this.renderFlattenRepliesSwitch()}
+ <div class="toolbar-start">
+ ${this.renderBadge()}
+ ${this.renderFlattenRepliesSwitch()}
+ </div>
+ <div class="toolbar-end">
+ ${this.renderOverflowMenu()}
+ </div>
</div>
`;
}
_flattenThreadsChanged() {
const enabled = !this.nestedViewRef.value.selected;
- const e = new CustomEvent(kEventFlattenThreadsUpdated, {
+ this._onOptionChanged('flattenthreads_switch_enabled', enabled, true);
+ }
+
+ _onOptionChanged(option, enabled, softRefreshView = false) {
+ const e = new CustomEvent(kEventOptionUpdated, {
bubbles: true,
composed: true,
- detail: {enabled},
+ detail: {option, enabled, softRefreshView},
});
this.dispatchEvent(e);
}
+
+ _toggleOverflowMenu() {
+ this.overflowMenuRef.value.open = !this.overflowMenuRef.value.open;
+ }
}
window.customElements.define(
'twpt-thread-toolbar-inject', TwptThreadToolbarInject);
diff --git a/src/contentScripts/communityConsole/threadToolbar/constants.js b/src/contentScripts/communityConsole/threadToolbar/constants.js
index 756d880..14f8c24 100644
--- a/src/contentScripts/communityConsole/threadToolbar/constants.js
+++ b/src/contentScripts/communityConsole/threadToolbar/constants.js
@@ -1,5 +1,5 @@
-export const kEventFlattenThreadsUpdated =
- 'TWPTThreadToolbarFlattenThreadsUpdated';
+export const kEventOptionUpdated =
+ 'TWPTThreadToolbarOptionUpdated';
export const kRepliesSectionSelector =
'ec-thread .scTailwindThreadThreadcontentreplies-section';
diff --git a/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js b/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js
index db11640..f13907e 100644
--- a/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js
+++ b/src/contentScripts/communityConsole/threadToolbar/threadToolbar.js
@@ -27,17 +27,30 @@
}
getOptions() {
- return getOptions(['flattenthreads', 'flattenthreads_switch_enabled']);
+ return getOptions([
+ 'flattenthreads',
+ 'flattenthreads_switch_enabled',
+ 'bulkreportreplies',
+ 'bulkreportreplies_switch_enabled',
+ ]);
}
inject(node, options) {
const toolbar = document.createElement('twpt-thread-toolbar-inject');
toolbar.setAttribute('options', JSON.stringify(options));
- toolbar.addEventListener(consts.kEventFlattenThreadsUpdated, e => {
+ toolbar.addEventListener(consts.kEventOptionUpdated, e => {
+ const option = e.detail?.option;
const enabled = e.detail?.enabled;
- if (typeof enabled != 'boolean') return;
- chrome.storage.sync.set({flattenthreads_switch_enabled: enabled}, _ => {
- softRefreshView();
+ if (typeof option != 'string' || typeof enabled != 'boolean') return;
+ chrome.storage.sync.set({[option]: enabled}, _ => {
+ options = {
+ ...options,
+ [option]: enabled,
+ };
+ toolbar.setAttribute('options', JSON.stringify(options));
+ if (e.detail?.softRefreshView) {
+ softRefreshView();
+ }
});
});
node.parentElement.insertBefore(toolbar, node);
diff --git a/src/entryPoints/communityConsole/contentScripts/main.ts b/src/entryPoints/communityConsole/contentScripts/main.ts
index 7eda953..008b1b6 100644
--- a/src/entryPoints/communityConsole/contentScripts/main.ts
+++ b/src/entryPoints/communityConsole/contentScripts/main.ts
@@ -53,6 +53,10 @@
import RepositionExpandThreadStylesScript from '../../../features/repositionExpandThread/presentation/scripts/styles.script';
import StickySidebarHeadersStylesScript from '../../../features/stickySidebarHeaders/presentation/scripts/styles.script';
import IncreaseContrastStylesScript from '../../../features/increaseContrast/presentation/scripts/styles.script';
+import BulkReportRepliesMessageCardHandler from '../../../features/bulkReportReplies/presentation/nodeWatcherHandlers/messageCard.handler';
+import BulkReportRepliesHandleBodyClassScript from '../../../features/bulkReportReplies/presentation/scripts/handleBodyClass.script';
+import { IsBulkReportRepliesFeatureEnabledServiceAdapter } from '../../../features/bulkReportReplies/services/isFeatureEnabled.service';
+import BulkReportRepliesStylesScript from '../../../features/bulkReportReplies/presentation/scripts/styles.script';
const scriptRunner = createScriptRunner();
scriptRunner.run();
@@ -92,6 +96,10 @@
'autoRefreshThreadListHide',
new AutoRefreshThreadListHideHandler(autoRefresh),
],
+ [
+ 'bulkReportRepliesMessageCard',
+ new BulkReportRepliesMessageCardHandler(optionsProvider),
+ ],
['ccDarkThemeEcApp', new CCDarkThemeEcAppHandler(optionsProvider)],
[
'ccDarkThemeReportDialog',
@@ -177,6 +185,11 @@
// Individual feature scripts
new AutoRefreshStylesScript(),
+ new BulkReportRepliesHandleBodyClassScript(
+ optionsProvider,
+ new IsBulkReportRepliesFeatureEnabledServiceAdapter(),
+ ),
+ new BulkReportRepliesStylesScript(),
new CCExtraInfoInjectScript(),
new CCExtraInfoStylesScript(),
new EnhancedAnnouncementsDotStylesScript(),
diff --git a/src/features/bulkReportReplies/presentation/consts.ts b/src/features/bulkReportReplies/presentation/consts.ts
new file mode 100644
index 0000000..674647f
--- /dev/null
+++ b/src/features/bulkReportReplies/presentation/consts.ts
@@ -0,0 +1,2 @@
+export const bulkReportRepliesEnabledClassName =
+ 'TWPT-bulkreportreplies-enabled';
diff --git a/src/features/bulkReportReplies/presentation/nodeWatcherHandlers/messageCard.handler.ts b/src/features/bulkReportReplies/presentation/nodeWatcherHandlers/messageCard.handler.ts
new file mode 100644
index 0000000..1084566
--- /dev/null
+++ b/src/features/bulkReportReplies/presentation/nodeWatcherHandlers/messageCard.handler.ts
@@ -0,0 +1,28 @@
+import CssSelectorNodeWatcherHandler from '../../../../infrastructure/presentation/nodeWatcher/handlers/CssSelectorHandler.adapter';
+import { NodeMutation } from '../../../../presentation/nodeWatcher/NodeWatcherHandler';
+import { OptionsProviderPort } from '../../../../services/options/OptionsProvider';
+import BulkReportControlsInjector from '../../ui/injectors/bulkReportControls.injector';
+
+/**
+ * Injects the bulk report reply controls.
+ */
+export default class BulkReportRepliesMessageCardHandler extends CssSelectorNodeWatcherHandler {
+ cssSelector =
+ ':is(.scTailwindThreadMessageMessagecardcontent:not(.scTailwindThreadMessageMessagecardpromoted), .scTailwindThreadMessageCommentcardcomment) sc-tailwind-thread-message-message-actions';
+
+ bulkReportControlsInjector = new BulkReportControlsInjector();
+
+ constructor(private optionsProvider: OptionsProviderPort) {
+ super();
+ }
+
+ async onMutatedNode(mutation: NodeMutation) {
+ if (!(mutation.node instanceof Element)) return;
+
+ const isFeatureEnabled =
+ await this.optionsProvider.isEnabled('bulkreportreplies');
+ if (!isFeatureEnabled) return;
+
+ this.bulkReportControlsInjector.inject(mutation.node);
+ }
+}
diff --git a/src/features/bulkReportReplies/presentation/scripts/handleBodyClass.script.ts b/src/features/bulkReportReplies/presentation/scripts/handleBodyClass.script.ts
new file mode 100644
index 0000000..4a56117
--- /dev/null
+++ b/src/features/bulkReportReplies/presentation/scripts/handleBodyClass.script.ts
@@ -0,0 +1,35 @@
+import Script from '../../../../common/architecture/scripts/Script';
+import { OptionsConfiguration } from '../../../../common/options/OptionsConfiguration';
+import { OptionsProviderPort } from '../../../../services/options/OptionsProvider';
+import { IsBulkReportRepliesFeatureEnabledService } from '../../services/isFeatureEnabled.service';
+import { bulkReportRepliesEnabledClassName } from '../consts';
+
+export default class BulkReportRepliesHandleBodyClassScript extends Script {
+ page: never;
+ environment: never;
+ runPhase: never;
+
+ constructor(
+ private optionsProvider: OptionsProviderPort,
+ private isFeatureEnabledService: IsBulkReportRepliesFeatureEnabledService,
+ ) {
+ super();
+ }
+
+ async execute() {
+ this.optionsProvider.addListener(
+ (_: OptionsConfiguration, currentConf: OptionsConfiguration) =>
+ this.updateBodyClass(currentConf),
+ );
+
+ this.updateBodyClass(await this.optionsProvider.getOptionsConfiguration());
+ }
+
+ private updateBodyClass(currentConf: OptionsConfiguration) {
+ if (this.isFeatureEnabledService.isEnabled(currentConf)) {
+ document.body?.classList.add(bulkReportRepliesEnabledClassName);
+ } else {
+ document.body?.classList.remove(bulkReportRepliesEnabledClassName);
+ }
+ }
+}
diff --git a/src/features/bulkReportReplies/presentation/scripts/styles.script.ts b/src/features/bulkReportReplies/presentation/scripts/styles.script.ts
new file mode 100644
index 0000000..fd358b4
--- /dev/null
+++ b/src/features/bulkReportReplies/presentation/scripts/styles.script.ts
@@ -0,0 +1,10 @@
+import StylesheetScript from '../../../../common/architecture/scripts/stylesheet/StylesheetScript';
+
+export default class BulkReportRepliesStylesScript extends StylesheetScript {
+ stylesheet = 'css/bulk_report_replies.css';
+ page: never;
+
+ async shouldBeInjected(): Promise<boolean> {
+ return true;
+ }
+}
diff --git a/src/features/bulkReportReplies/services/isFeatureEnabled.service.ts b/src/features/bulkReportReplies/services/isFeatureEnabled.service.ts
new file mode 100644
index 0000000..3c2455a
--- /dev/null
+++ b/src/features/bulkReportReplies/services/isFeatureEnabled.service.ts
@@ -0,0 +1,14 @@
+import { OptionsConfiguration } from '../../../common/options/OptionsConfiguration';
+
+export interface IsBulkReportRepliesFeatureEnabledService {
+ isEnabled(configuration: OptionsConfiguration): boolean;
+}
+
+export class IsBulkReportRepliesFeatureEnabledServiceAdapter {
+ isEnabled(configuration: OptionsConfiguration): boolean {
+ return (
+ configuration.isEnabled('bulkreportreplies') &&
+ configuration.isEnabled('bulkreportreplies_switch_enabled')
+ );
+ }
+}
diff --git a/src/features/bulkReportReplies/ui/components/BulkReportControls.ts b/src/features/bulkReportReplies/ui/components/BulkReportControls.ts
new file mode 100644
index 0000000..fee942e
--- /dev/null
+++ b/src/features/bulkReportReplies/ui/components/BulkReportControls.ts
@@ -0,0 +1,53 @@
+import '@material/web/chips/assist-chip.js';
+import '@material/web/chips/chip-set.js';
+import '@material/web/icon/icon.js';
+import { customElement, property } from 'lit/decorators.js';
+import { I18nLitElement } from '../../../../common/litI18nUtils';
+import { css, html } from 'lit';
+import { SHARED_MD3_STYLES } from '../../../../common/styles/md3';
+
+@customElement('bulk-report-controls')
+export default class BulkReportControls extends I18nLitElement {
+ @property({ type: String })
+ accessor messageId: string;
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ css`
+ :host {
+ display: flex;
+ align-items: center;
+ margin-left: auto;
+ }
+
+ md-assist-chip {
+ --md-assist-chip-leading-icon-color: var(--md-sys-color-error);
+ --md-assist-chip-focus-leading-icon-color: var(--md-sys-color-error);
+ --md-assist-chip-hover-leading-icon-color: var(--md-sys-color-error);
+ --md-assist-chip-pressed-leading-icon-color: var(--md-sys-color-error);
+ }
+ `,
+ ];
+
+ // TODO(https://iavm.xyz/b/twpowertools/192): Make the buttons work.
+ render() {
+ return html`
+ <md-chip-set aria-label="Report actions">
+ <md-assist-chip>
+ <md-icon slot="icon">block</md-icon>
+ Mark as off-topic
+ </md-assist-chip>
+ <md-assist-chip>
+ <md-icon slot="icon">error</md-icon>
+ Mark as abuse
+ </md-assist-chip>
+ </md-chip-set>
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'bulk-report-controls': BulkReportControls;
+ }
+}
diff --git a/src/features/bulkReportReplies/ui/components/index.ts b/src/features/bulkReportReplies/ui/components/index.ts
new file mode 100644
index 0000000..7203e3a
--- /dev/null
+++ b/src/features/bulkReportReplies/ui/components/index.ts
@@ -0,0 +1 @@
+export * from './BulkReportControls';
diff --git a/src/features/bulkReportReplies/ui/injectors/bulkReportControls.injector.ts b/src/features/bulkReportReplies/ui/injectors/bulkReportControls.injector.ts
new file mode 100644
index 0000000..18bdff7
--- /dev/null
+++ b/src/features/bulkReportReplies/ui/injectors/bulkReportControls.injector.ts
@@ -0,0 +1,7 @@
+export default class BulkReportControlsInjector {
+ inject(messageActions: Element) {
+ const controls = document.createElement('bulk-report-controls');
+ // TODO(https://iavm.xyz/b/twpowertools/192): Add message ID to the controls.
+ messageActions.append(controls);
+ }
+}
diff --git a/src/features/bulkReportReplies/ui/styles.css b/src/features/bulkReportReplies/ui/styles.css
new file mode 100644
index 0000000..7880703
--- /dev/null
+++ b/src/features/bulkReportReplies/ui/styles.css
@@ -0,0 +1,9 @@
+/**
+ * When the option is disabled or the feature is temporarily disabled via the
+ * toolbar, we hide bulk-report-controls.
+ */
+body:not(.TWPT-bulkreportreplies-enabled) {
+ bulk-report-controls {
+ display: none;
+ }
+}
diff --git a/src/injections/litComponentsInject.js b/src/injections/litComponentsInject.js
index 7143bd5..77813b5 100644
--- a/src/injections/litComponentsInject.js
+++ b/src/injections/litComponentsInject.js
@@ -6,6 +6,7 @@
import '../contentScripts/communityConsole/threadToolbar/components/index.js';
import '../features/flattenThreads/core/components/index.js';
import '../contentScripts/communityConsole/updateHandler/banner/components/index.js';
+import '../features/bulkReportReplies/ui/components/index';
import {injectStylesheet} from '../common/contentScriptsUtils';
diff --git a/src/lit-locales/source/ar.xlf b/src/lit-locales/source/ar.xlf
index 3325f80..dac395d 100644
--- a/src/lit-locales/source/ar.xlf
+++ b/src/lit-locales/source/ar.xlf
@@ -41,6 +41,10 @@
<target state="translated">إعادة التّحميل</target>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/ca.xlf b/src/lit-locales/source/ca.xlf
index 518dba7..b1f8e20 100644
--- a/src/lit-locales/source/ca.xlf
+++ b/src/lit-locales/source/ca.xlf
@@ -41,6 +41,10 @@
<target state="translated">Recarrega</target>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/de.xlf b/src/lit-locales/source/de.xlf
index c51a337..dd32051 100644
--- a/src/lit-locales/source/de.xlf
+++ b/src/lit-locales/source/de.xlf
@@ -41,6 +41,10 @@
<target state="translated">Neu laden</target>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/es.xlf b/src/lit-locales/source/es.xlf
index 8b92079..2bc52b5 100644
--- a/src/lit-locales/source/es.xlf
+++ b/src/lit-locales/source/es.xlf
@@ -41,6 +41,10 @@
<target state="translated">Recargar</target>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/fr.xlf b/src/lit-locales/source/fr.xlf
index 17819d7..02b1c83 100644
--- a/src/lit-locales/source/fr.xlf
+++ b/src/lit-locales/source/fr.xlf
@@ -41,6 +41,10 @@
<target state="translated">Actualiser</target>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/id.xlf b/src/lit-locales/source/id.xlf
index c706ab1..22d1a3b 100644
--- a/src/lit-locales/source/id.xlf
+++ b/src/lit-locales/source/id.xlf
@@ -33,6 +33,10 @@
<source>Reload</source>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/it.xlf b/src/lit-locales/source/it.xlf
index a25dc54..de573c6 100644
--- a/src/lit-locales/source/it.xlf
+++ b/src/lit-locales/source/it.xlf
@@ -35,6 +35,10 @@
<source>Reload</source>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/ja.xlf b/src/lit-locales/source/ja.xlf
index ea15fdd..4e7b21a 100644
--- a/src/lit-locales/source/ja.xlf
+++ b/src/lit-locales/source/ja.xlf
@@ -41,6 +41,10 @@
<target state="translated">再読み込み</target>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/ko.xlf b/src/lit-locales/source/ko.xlf
index d82bd54..b111b65 100644
--- a/src/lit-locales/source/ko.xlf
+++ b/src/lit-locales/source/ko.xlf
@@ -41,6 +41,10 @@
<target state="translated">새로고침</target>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/pl.xlf b/src/lit-locales/source/pl.xlf
index 85ff3ea..4c08fa9 100644
--- a/src/lit-locales/source/pl.xlf
+++ b/src/lit-locales/source/pl.xlf
@@ -33,6 +33,10 @@
<source>Reload</source>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/pt_BR.xlf b/src/lit-locales/source/pt_BR.xlf
index a69913f..ece9aad 100644
--- a/src/lit-locales/source/pt_BR.xlf
+++ b/src/lit-locales/source/pt_BR.xlf
@@ -33,6 +33,10 @@
<source>Reload</source>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/ru.xlf b/src/lit-locales/source/ru.xlf
index 20b92e2..db843df 100644
--- a/src/lit-locales/source/ru.xlf
+++ b/src/lit-locales/source/ru.xlf
@@ -41,6 +41,10 @@
<target state="translated">Перезагрузить</target>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/th.xlf b/src/lit-locales/source/th.xlf
index 2db0fdf..0ccee42 100644
--- a/src/lit-locales/source/th.xlf
+++ b/src/lit-locales/source/th.xlf
@@ -33,6 +33,10 @@
<source>Reload</source>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/tr.xlf b/src/lit-locales/source/tr.xlf
index 63b2570..4149755 100644
--- a/src/lit-locales/source/tr.xlf
+++ b/src/lit-locales/source/tr.xlf
@@ -33,6 +33,10 @@
<source>Reload</source>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/lit-locales/source/vi.xlf b/src/lit-locales/source/vi.xlf
index b82dae0..dc42ad3 100644
--- a/src/lit-locales/source/vi.xlf
+++ b/src/lit-locales/source/vi.xlf
@@ -33,6 +33,10 @@
<source>Reload</source>
<note from="lit-localize">Button which reloads the current page.</note>
</trans-unit>
+<trans-unit id="s9a0167b14ce5b9b1">
+ <source>Bulk report replies</source>
+ <note from="lit-localize">Option shown in the settings menu of the thread toolbar which enables the "bulk report replies" feature.</note>
+</trans-unit>
</body>
</file>
</xliff>
diff --git a/src/static/_locales/en/messages.json b/src/static/_locales/en/messages.json
index 4c655f7..d4f2571 100644
--- a/src/static/_locales/en/messages.json
+++ b/src/static/_locales/en/messages.json
@@ -183,6 +183,10 @@
"message": "Tentative fix for CC slowness and high memory usage (<a href='https://s.iavm.xyz/cc-running-slow-pekb'><code>pekb/269560789</code></a>).",
"description": "Feature checkbox in the options page"
},
+ "options_bulkreportreplies": {
+ "message": "Enable the ability to bulk report replies.",
+ "description": "Feature checkbox in the options page"
+ },
"options_save": {
"message": "Save",
"description": "Button in the options page to save the settings"
diff --git a/src/static/css/common/console.css b/src/static/css/common/console.css
index 68724b4..805e1e2 100644
--- a/src/static/css/common/console.css
+++ b/src/static/css/common/console.css
@@ -169,7 +169,7 @@
* that case, only users who have enabled one of the specified features will
* experience the breakage.
**/
-body.TWPT-flattenthreads-enabled {
+body:is(.TWPT-flattenthreads-enabled, .TWPT-bulkreportreplies-enabled) {
ec-thread sc-tailwind-thread-message-message-list sc-tailwind-thread-message-message-actions {
display: flex;
flex-direction: row;
diff --git a/src/static/options/experiments.html b/src/static/options/experiments.html
index 9a21fdf..7eed0fb 100644
--- a/src/static/options/experiments.html
+++ b/src/static/options/experiments.html
@@ -15,6 +15,7 @@
<div id="optional-permissions-warning" hidden data-i18n="optionalpermissionswarning_header"></div>
<div class="option"><input type="checkbox" id="workflows"> <label for="workflows" data-i18n="workflows"></label> <button id="manage-workflows" data-i18n="workflows_manage"></button></div>
<div class="option"><input type="checkbox" id="extrainfo"> <label for="extrainfo" data-i18n="extrainfo"></label></div>
+ <div class="option"><input type="checkbox" id="bulkreportreplies"> <label for="bulkreportreplies" data-i18n="bulkreportreplies"></label></div>
<div class="actions"><button id="save" data-i18n="save"></button></div>
</form>
<div id="save-indicator"></div>