feat(bulk-report-replies): implement actual reporting logic
This commit introduces logic to actually send the report, and change the
state of the chips so they reflect the status of the request.
Bug: twpowertools:192
Change-Id: I6a6a636ca4b1a15bf9bd0580a9985bd4b28156af
diff --git a/src/entryPoints/communityConsole/contentScripts/main.ts b/src/entryPoints/communityConsole/contentScripts/main.ts
index 8ac5c82..e34e99c 100644
--- a/src/entryPoints/communityConsole/contentScripts/main.ts
+++ b/src/entryPoints/communityConsole/contentScripts/main.ts
@@ -63,6 +63,8 @@
import ThreadToolbar from '../../../features/threadToolbar/core/threadToolbar';
import { BulkReportControlsInjectorAdapter } from '../../../features/bulkReportReplies/infrastructure/ui/injectors/bulkReportControls.injector.adapter';
import { MessageInfoRepositoryAdapter } from '../../../features/bulkReportReplies/infrastructure/repositories/messageInfo/messageInfo.repository.adapter';
+import { ReportOffTopicRepositoryAdapter } from '../../../features/bulkReportReplies/infrastructure/repositories/api/reportOffTopic/reportOffTopic.repository.adapter';
+import { ReportAbuseRepositoryAdapter } from '../../../features/bulkReportReplies/infrastructure/repositories/api/reportAbuse/reportAbuse.repository.adapter';
const scriptRunner = createScriptRunner();
scriptRunner.run();
@@ -107,7 +109,11 @@
'bulkReportRepliesMessageCard',
new BulkReportRepliesMessageCardHandler(
optionsProvider,
- new BulkReportControlsInjectorAdapter(new MessageInfoRepositoryAdapter()),
+ new BulkReportControlsInjectorAdapter(
+ new MessageInfoRepositoryAdapter(),
+ new ReportOffTopicRepositoryAdapter(),
+ new ReportAbuseRepositoryAdapter(),
+ ),
),
],
['ccDarkThemeEcApp', new CCDarkThemeEcAppHandler(optionsProvider)],
diff --git a/src/features/bulkReportReplies/domain/reportStatus.ts b/src/features/bulkReportReplies/domain/reportStatus.ts
new file mode 100644
index 0000000..d05c003
--- /dev/null
+++ b/src/features/bulkReportReplies/domain/reportStatus.ts
@@ -0,0 +1,15 @@
+/** Status of a specific type of report. */
+export const ReportStatusValues = {
+ /**
+ * This report hasn't yet been initiated or it has completed with an error and
+ * is ready to be retried.
+ */
+ Idle: 'idle',
+ /* The user initiated the report and the request hasn't completed. */
+ Processing: 'processing',
+ /* The reporting request has completed successfully. */
+ Done: 'done',
+} as const;
+
+export type ReportStatus =
+ (typeof ReportStatusValues)[keyof typeof ReportStatusValues];
diff --git a/src/features/bulkReportReplies/domain/reportType.ts b/src/features/bulkReportReplies/domain/reportType.ts
new file mode 100644
index 0000000..acc87a5
--- /dev/null
+++ b/src/features/bulkReportReplies/domain/reportType.ts
@@ -0,0 +1,7 @@
+export const ReportTypeValues = {
+ OffTopic: 'off-topic',
+ Abuse: 'abuse',
+} as const;
+
+export type ReportType =
+ (typeof ReportTypeValues)[keyof typeof ReportTypeValues];
diff --git a/src/features/bulkReportReplies/infrastructure/repositories/api/reportAbuse/reportAbuse.repository.adapter.ts b/src/features/bulkReportReplies/infrastructure/repositories/api/reportAbuse/reportAbuse.repository.adapter.ts
new file mode 100644
index 0000000..1d05dfd
--- /dev/null
+++ b/src/features/bulkReportReplies/infrastructure/repositories/api/reportAbuse/reportAbuse.repository.adapter.ts
@@ -0,0 +1,19 @@
+import { CCApi } from '../../../../../../common/api';
+import {
+ MessageInfo,
+ ReportAbuseReposioryPort,
+} from '../../../ui/injectors/bulkReportControls.injector.adapter';
+
+export class ReportAbuseRepositoryAdapter implements ReportAbuseReposioryPort {
+ async report(messageInfo: MessageInfo): Promise<void> {
+ return await CCApi(
+ 'UserFlag',
+ {
+ 1: messageInfo.forumId,
+ 3: messageInfo.threadId,
+ 4: messageInfo.messageId,
+ },
+ /* authenticated = */ true,
+ );
+ }
+}
diff --git a/src/features/bulkReportReplies/infrastructure/repositories/api/reportOffTopic/reportOffTopic.repository.adapter.ts b/src/features/bulkReportReplies/infrastructure/repositories/api/reportOffTopic/reportOffTopic.repository.adapter.ts
new file mode 100644
index 0000000..a629f7e
--- /dev/null
+++ b/src/features/bulkReportReplies/infrastructure/repositories/api/reportOffTopic/reportOffTopic.repository.adapter.ts
@@ -0,0 +1,19 @@
+import { CCApi } from '../../../../../../common/api';
+import {
+ MessageInfo,
+ ReportOffTopicRepositoryPort,
+} from '../../../ui/injectors/bulkReportControls.injector.adapter';
+
+export class ReportOffTopicRepositoryAdapter implements ReportOffTopicRepositoryPort {
+ async report(messageInfo: MessageInfo): Promise<void> {
+ return await CCApi(
+ 'SetOffTopic',
+ {
+ 1: messageInfo.forumId,
+ 2: messageInfo.threadId,
+ 3: messageInfo.messageId,
+ },
+ /* authenticated = */ true,
+ );
+ }
+}
diff --git a/src/features/bulkReportReplies/infrastructure/ui/injectors/bulkReportControls.injector.adapter.ts b/src/features/bulkReportReplies/infrastructure/ui/injectors/bulkReportControls.injector.adapter.ts
index e7035be..d19e0bd 100644
--- a/src/features/bulkReportReplies/infrastructure/ui/injectors/bulkReportControls.injector.adapter.ts
+++ b/src/features/bulkReportReplies/infrastructure/ui/injectors/bulkReportControls.injector.adapter.ts
@@ -1,3 +1,6 @@
+import { ReportTypeValues } from '../../../domain/reportType';
+import BulkReportControls from '../../../ui/components/BulkReportControls';
+import { kEventReportReply } from '../../../ui/events';
import { BulkReportControlsInjectorPort } from '../../../ui/injectors/bulkReportControls.injector.port';
export interface MessageInfo {
@@ -10,10 +13,22 @@
getInfo(elementInsideMessage: Element): MessageInfo;
}
+export interface ReportOffTopicRepositoryPort {
+ report(messageInfo: MessageInfo): Promise<void>;
+}
+
+export interface ReportAbuseReposioryPort {
+ report(messageInfo: MessageInfo): Promise<void>;
+}
+
export class BulkReportControlsInjectorAdapter
implements BulkReportControlsInjectorPort
{
- constructor(private messageInfoRepository: MessageInfoRepositoryPort) {}
+ constructor(
+ private messageInfoRepository: MessageInfoRepositoryPort,
+ private reportOffTopicRepository: ReportOffTopicRepositoryPort,
+ private reportAbuseRepository: ReportAbuseReposioryPort,
+ ) {}
inject(messageActions: Element) {
const { forumId, threadId, messageId } =
@@ -24,5 +39,60 @@
controls.setAttribute('threadId', threadId);
controls.setAttribute('messageId', messageId);
messageActions.append(controls);
+
+ this.addEventHandlers(controls);
+ }
+
+ private addEventHandlers(controls: BulkReportControls) {
+ controls.addEventListener(
+ kEventReportReply,
+ async (e: WindowEventMap[typeof kEventReportReply]) => {
+ const { detail } = e;
+
+ let statusProperty: string | undefined;
+ switch (detail.type) {
+ case ReportTypeValues.OffTopic:
+ statusProperty = 'offTopicStatus';
+ break;
+
+ case ReportTypeValues.Abuse:
+ statusProperty = 'abuseStatus';
+ break;
+ }
+ controls.setAttribute(statusProperty, 'processing');
+
+ const messageInfo = {
+ forumId: detail.forumId,
+ threadId: detail.threadId,
+ messageId: detail.messageId,
+ };
+
+ try {
+ switch (detail.type) {
+ case ReportTypeValues.OffTopic:
+ await this.reportOffTopicRepository.report(messageInfo);
+ break;
+
+ case ReportTypeValues.Abuse:
+ await this.reportAbuseRepository.report(messageInfo);
+ break;
+ }
+ controls.setAttribute(statusProperty, 'done');
+ } catch (error) {
+ console.error(
+ `[bulk-report-controls] Error reporting message ${JSON.stringify(messageInfo)} (${detail.type}):`,
+ error,
+ );
+ controls.setAttribute(statusProperty, 'idle');
+ // TODO: Create a snackbar instead of showing an alert.
+ alert(
+ `An error occured while reporting the message:\n${error}` +
+ (!navigator.onLine
+ ? "\n\nYou don't seem to be connected to the Internet."
+ : ''),
+ );
+ }
+ },
+ );
}
}
diff --git a/src/features/bulkReportReplies/ui/components/BulkReportControls.ts b/src/features/bulkReportReplies/ui/components/BulkReportControls.ts
index 50269fd..1ed77c8 100644
--- a/src/features/bulkReportReplies/ui/components/BulkReportControls.ts
+++ b/src/features/bulkReportReplies/ui/components/BulkReportControls.ts
@@ -5,6 +5,17 @@
import { I18nLitElement } from '../../../../common/litI18nUtils';
import { css, html } from 'lit';
import { SHARED_MD3_STYLES } from '../../../../common/styles/md3';
+import { map } from 'lit/directives/map.js';
+import { ReportStatus, ReportStatusValues } from '../../domain/reportStatus';
+import { ReportType, ReportTypeValues } from '../../domain/reportType';
+import { kEventReportReply } from '../events';
+
+interface ReportButton {
+ type: ReportType;
+ icon: string;
+ labels: Record<ReportStatus, string>;
+ status: ReportStatus;
+}
@customElement('bulk-report-controls')
export default class BulkReportControls extends I18nLitElement {
@@ -17,6 +28,12 @@
@property({ type: String })
accessor messageId: string;
+ @property({ type: String })
+ accessor offTopicStatus: ReportStatus = ReportStatusValues.Idle;
+
+ @property({ type: String })
+ accessor abuseStatus: ReportStatus = ReportStatusValues.Idle;
+
static styles = [
SHARED_MD3_STYLES,
css`
@@ -35,21 +52,89 @@
`,
];
- // 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>
+ ${this.renderButtons()}
</md-chip-set>
`;
}
+
+ private renderButtons() {
+ const buttons = this.getButtons();
+ const hasNonIdleButton = buttons.some(
+ (btn) => btn.status !== ReportStatusValues.Idle,
+ );
+
+ return map(this.getButtons(), (btn) =>
+ this.renderButton(btn, hasNonIdleButton),
+ );
+ }
+
+ private renderButton(button: ReportButton, hasNonIdleButton: boolean) {
+ let icon = button.icon;
+ switch (button.status) {
+ case ReportStatusValues.Processing:
+ icon = 'pending';
+ break;
+
+ case ReportStatusValues.Done:
+ icon = 'check';
+ break;
+ }
+
+ return html`
+ <md-assist-chip
+ ?disabled=${hasNonIdleButton}
+ @click=${() => this.sendReport(button.type)}
+ >
+ <md-icon slot="icon">${icon}</md-icon>
+ ${button.labels[button.status]}
+ </md-assist-chip>
+ `;
+ }
+
+ private getButtons(): ReportButton[] {
+ return [
+ {
+ type: ReportTypeValues.OffTopic,
+ icon: 'block',
+ labels: {
+ [ReportStatusValues.Idle]: 'Mark as off-topic',
+ [ReportStatusValues.Processing]: 'Marking as off-topic…',
+ [ReportStatusValues.Done]: 'Marked as off-topic',
+ },
+ status: this.offTopicStatus,
+ },
+ {
+ type: ReportTypeValues.Abuse,
+ icon: 'error',
+ labels: {
+ [ReportStatusValues.Idle]: 'Mark as abuse',
+ [ReportStatusValues.Processing]: 'Marking as abuse…',
+ [ReportStatusValues.Done]: 'Marked as abuse',
+ },
+ status: this.abuseStatus,
+ },
+ ];
+ }
+
+ private sendReport(type: ReportType) {
+ const e: WindowEventMap[typeof kEventReportReply] = new CustomEvent(
+ kEventReportReply,
+ {
+ bubbles: false,
+ composed: false,
+ detail: {
+ forumId: this.forumId,
+ threadId: this.threadId,
+ messageId: this.messageId,
+ type,
+ },
+ },
+ );
+ this.dispatchEvent(e);
+ }
}
declare global {
diff --git a/src/features/bulkReportReplies/ui/events.ts b/src/features/bulkReportReplies/ui/events.ts
new file mode 100644
index 0000000..2ff5954
--- /dev/null
+++ b/src/features/bulkReportReplies/ui/events.ts
@@ -0,0 +1,14 @@
+import { ReportType } from "../domain/reportType";
+
+export const kEventReportReply = 'twpt-bulk-report-replies-report-reply';
+
+declare global {
+ interface WindowEventMap {
+ [kEventReportReply]: CustomEvent<{
+ forumId: string;
+ threadId: string;
+ messageId: string;
+ type: ReportType;
+ }>;
+ }
+}