fix(extra-info): show extra info in the RCE thread page
The extra info feature worked with the old thread pages. This CL brings
support to the new RCE thread pages.
Bug: twpowertools:93
Change-Id: I47e4235afa4f7ec441f5a92edfcc28b1cb5f0419
diff --git a/src/contentScripts/communityConsole/extraInfo/handlers/thread.js b/src/contentScripts/communityConsole/extraInfo/handlers/thread.js
new file mode 100644
index 0000000..ba7b906
--- /dev/null
+++ b/src/contentScripts/communityConsole/extraInfo/handlers/thread.js
@@ -0,0 +1,60 @@
+import {parseUrl} from '../../../../common/commonUtils.js';
+import ThreadModel from '../../../../models/Thread.js';
+import {kViewThreadResponse} from '../consts.js';
+import MessageExtraInfoService from '../services/message.js';
+
+import ResponseEventBasedInfoHandler from './basedOnResponseEvent.js';
+
+const kIntervalInMs = 500;
+const kTimeoutInMs = 10 * 1000;
+const kCurrentInfoExpiresInMs = kTimeoutInMs * 1.5;
+
+export default class ThreadInfoHandler extends ResponseEventBasedInfoHandler {
+ constructor() {
+ super();
+
+ this.thread = undefined;
+ }
+
+ getEvent() {
+ return kViewThreadResponse;
+ }
+
+ getWaitForCurrentInfoOptions() {
+ return {
+ interval: kIntervalInMs,
+ timeout: kTimeoutInMs,
+ };
+ }
+
+ async isInfoCurrent(injectionDetails) {
+ this.thread = new ThreadModel(this.info.body?.[1]);
+
+ const currentPage = this.parseThreadUrl();
+ const isCurrentThread =
+ Date.now() - this.info.timestamp < kCurrentInfoExpiresInMs &&
+ this.thread.getId() == currentPage.thread &&
+ this.thread.getForumId() == currentPage.forum;
+
+ const isMessageNode = injectionDetails.isMessageNode;
+ const messageNode = injectionDetails.messageNode;
+
+ return isCurrentThread &&
+ (!isMessageNode || this.currentThreadContainsMessage(messageNode));
+ }
+
+ parseThreadUrl() {
+ const currentPage = parseUrl(location.href);
+ if (currentPage === false)
+ throw new Error(`couldn't parse current URL: ${location.href}`);
+
+ return currentPage;
+ }
+
+ currentThreadContainsMessage(messageNode) {
+ const messageId = MessageExtraInfoService.getMessageIdFromNode(messageNode);
+ const message = MessageExtraInfoService.getMessageFromThreadModel(
+ messageId, this.thread);
+ return message !== undefined;
+ }
+}
diff --git a/src/contentScripts/communityConsole/extraInfo/index.js b/src/contentScripts/communityConsole/extraInfo/index.js
index 21e7546..1d2b024 100644
--- a/src/contentScripts/communityConsole/extraInfo/index.js
+++ b/src/contentScripts/communityConsole/extraInfo/index.js
@@ -1,53 +1,35 @@
-import {MDCTooltip} from '@material/tooltip';
-import {waitFor} from 'poll-until-promise';
-
-import {parseUrl} from '../../../common/commonUtils.js';
import OptionsWatcher from '../../../common/optionsWatcher.js';
-import {kViewThreadResponse} from './consts.js';
import ProfileInfoHandler from './handlers/profile.js';
+import ThreadInfoHandler from './handlers/thread.js';
import ThreadListInfoHandler from './handlers/threadList.js';
import ExpandedThreadListExtraInfoInjection from './injections/expandedThreadList.js';
import ProfileAbuseExtraInfoInjection from './injections/profileAbuse.js';
import ProfilePerForumStatsExtraInfoInjection from './injections/profilePerForumStats.js';
import ThreadListExtraInfoInjection from './injections/threadList.js';
-import ThreadExtraInfoService from './services/thread.js';
+import ThreadMessageExtraInfoInjection from './injections/threadMessage.js';
+import ThreadQuestionExtraInfoInjection from './injections/threadQuestion.js';
export default class ExtraInfo {
constructor() {
- this.optionsWatcher = new OptionsWatcher(['extrainfo', 'perforumstats']);
+ const optionsWatcher = new OptionsWatcher(['extrainfo', 'perforumstats']);
const profileInfoHandler = new ProfileInfoHandler();
+ const threadInfoHandler = new ThreadInfoHandler();
const threadListInfoHandler = new ThreadListInfoHandler();
- this.profileAbuse = new ProfileAbuseExtraInfoInjection(
- profileInfoHandler, this.optionsWatcher);
+ this.profileAbuse =
+ new ProfileAbuseExtraInfoInjection(profileInfoHandler, optionsWatcher);
this.profilePerForumStats = new ProfilePerForumStatsExtraInfoInjection(
- profileInfoHandler, this.optionsWatcher);
+ profileInfoHandler, optionsWatcher);
+ this.threadQuestion =
+ new ThreadQuestionExtraInfoInjection(threadInfoHandler, optionsWatcher);
+ this.threadMessage =
+ new ThreadMessageExtraInfoInjection(threadInfoHandler, optionsWatcher);
this.expandedThreadList = new ExpandedThreadListExtraInfoInjection(
- threadListInfoHandler, this.optionsWatcher);
- this.threadList = new ThreadListExtraInfoInjection(
- threadListInfoHandler, this.optionsWatcher);
-
- this.lastThread = {
- body: {},
- id: -1,
- timestamp: 0,
- };
-
- this.setUpHandlers();
- }
-
- setUpHandlers() {
- window.addEventListener(kViewThreadResponse, e => {
- if (e.detail.id < this.lastThread.id) return;
-
- this.lastThread = {
- body: e.detail.body,
- id: e.detail.id,
- timestamp: Date.now(),
- };
- });
+ threadListInfoHandler, optionsWatcher);
+ this.threadList =
+ new ThreadListExtraInfoInjection(threadListInfoHandler, optionsWatcher);
}
injectAbuseChipsAtProfileIfEnabled(card) {
@@ -69,124 +51,11 @@
this.profilePerForumStats.injectIfEnabled({chart});
}
- // Whether |feature| is enabled
- isEnabled(feature) {
- return this.optionsWatcher.isEnabled(feature);
- }
-
- /**
- * Thread view functionality
- */
- injectAtQuestion(stateChips) {
- let currentPage = parseUrl(location.href);
- if (currentPage === false) {
- console.error('extraInfo: couldn\'t parse current URL:', location.href);
- return;
- }
-
- waitFor(
- () => {
- let now = Date.now();
- let threadInfo = this.lastThread.body['1']?.['2']?.['1'];
- if (now - this.lastThread.timestamp < 30 * 1000 &&
- threadInfo?.['1'] == currentPage.thread &&
- threadInfo?.['3'] == currentPage.forum)
- return Promise.resolve(this.lastThread);
- return Promise.reject(
- new Error('Didn\'t receive thread information'));
- },
- {
- interval: 500,
- timeout: 30 * 1000,
- })
- .then(thread => {
- const [info, tooltips] =
- ThreadExtraInfoService.getThreadChips(thread.body?.['1']);
- this.addExtraInfoElement(info, stateChips, false);
- for (const tooltip of tooltips) new MDCTooltip(tooltip);
- })
- .catch(err => {
- console.error(
- 'extraInfo: error while injecting question extra info: ', err);
- });
- }
-
injectAtQuestionIfEnabled(stateChips) {
- this.isEnabled('extrainfo').then(isEnabled => {
- if (isEnabled) return this.injectAtQuestion(stateChips);
- });
+ this.threadQuestion.injectIfEnabled({stateChips, isMessageNode: false});
}
- injectAtMessage(messageNode) {
- let currentPage = parseUrl(location.href);
- if (currentPage === false) {
- console.error('extraInfo: couldn\'t parse current URL:', location.href);
- return;
- }
-
- let footer = messageNode.querySelector('.footer-fill');
- if (!footer) {
- console.error('extraInfo: message doesn\'t have a footer:', messageNode);
- return;
- }
-
- const [type, index] =
- this.getMessageInfo(this.lastThread.body, messageNode);
- if (index == -1) {
- console.error('extraInfo: this.getMessageInfo() returned index -1.');
- return;
- }
-
- waitFor(
- () => {
- let now = Date.now();
- let threadInfo = this.lastThread.body['1']?.['2']?.['1'];
- if (now - this.lastThread.timestamp < 30 * 1000 &&
- threadInfo?.['1'] == currentPage.thread &&
- threadInfo?.['3'] == currentPage.forum) {
- const message = this.getMessageByTypeAndIndex(
- this.lastThread.body, type, index);
- if (message) return Promise.resolve(message);
- }
-
- return Promise.reject(new Error(
- 'Didn\'t receive thread information (type: ' + type +
- ', index: ' + index + ')'));
- },
- {
- interval: 1000,
- timeout: 30 * 1000,
- })
- .then(message => {
- let info = [];
-
- const endPendingStateTimestampMicros = message['1']?.['17'];
- const [pendingStateInfo, pendingTooltip] =
- this.getPendingStateInfo(endPendingStateTimestampMicros);
- if (pendingStateInfo) info.push(pendingStateInfo);
-
- const itemMetadata = message['1']?.['5'];
- const mdInfo = ThreadExtraInfoService.getMetadataInfo(itemMetadata);
- info.push(...mdInfo);
-
- const liveReviewStatus = message['1']?.['36'];
- const [liveReviewInfo, liveReviewTooltip] =
- this.getLiveReviewStatusChip(liveReviewStatus);
- if (liveReviewInfo) info.push(liveReviewInfo);
-
- this.addExtraInfoElement(info, footer, true);
- if (pendingTooltip) new MDCTooltip(pendingTooltip);
- if (liveReviewTooltip) new MDCTooltip(liveReviewTooltip);
- })
- .catch(err => {
- console.error(
- 'extraInfo: error while injecting message extra info: ', err);
- });
- }
-
- injectAtMessageIfEnabled(message) {
- this.isEnabled('extrainfo').then(isEnabled => {
- if (isEnabled) return this.injectAtMessage(message);
- });
+ injectAtMessageIfEnabled(messageNode) {
+ this.threadMessage.injectIfEnabled({messageNode, isMessageNode: true});
}
}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/threadMessage.js b/src/contentScripts/communityConsole/extraInfo/injections/threadMessage.js
new file mode 100644
index 0000000..e203e9e
--- /dev/null
+++ b/src/contentScripts/communityConsole/extraInfo/injections/threadMessage.js
@@ -0,0 +1,40 @@
+import {MDCTooltip} from '@material/tooltip';
+
+import ThreadModel from '../../../../models/Thread.js';
+import MessageExtraInfoService from '../services/message.js';
+
+import BaseExtraInfoInjection from './base.js';
+
+export default class ThreadMessageExtraInfoInjection extends
+ BaseExtraInfoInjection {
+ inject(threadInfo, injectionDetails) {
+ const messageNode = injectionDetails.messageNode;
+ const message = this.#getMessage(threadInfo, messageNode);
+ const [chips, tooltips] = MessageExtraInfoService.getMessageChips(message);
+ this.#injectChips(chips, messageNode);
+ for (const tooltip of tooltips) new MDCTooltip(tooltip);
+ }
+
+ #getMessage(threadInfo, messageNode) {
+ const thread = new ThreadModel(threadInfo.body?.[1]);
+ const messageId = MessageExtraInfoService.getMessageIdFromNode(messageNode);
+ return MessageExtraInfoService.getMessageFromThreadModel(messageId, thread);
+ }
+
+ #injectChips(chips, messageNode) {
+ const interactionsElement = messageNode.querySelector(
+ '.scTailwindThreadMessageMessageinteractionsroot');
+ if (interactionsElement === null)
+ throw new Error(`Couldn't find interactions element.`);
+
+ this.#indicateInteractionsElementIsNonEmpty(interactionsElement);
+
+ this.addExtraInfoChips(
+ chips, interactionsElement, /* withContainer = */ true);
+ }
+
+ #indicateInteractionsElementIsNonEmpty(interactionsElement) {
+ interactionsElement.classList.add(
+ 'scTailwindThreadMessageMessageinteractionsinteractions');
+ }
+}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/threadQuestion.js b/src/contentScripts/communityConsole/extraInfo/injections/threadQuestion.js
new file mode 100644
index 0000000..1efe6db
--- /dev/null
+++ b/src/contentScripts/communityConsole/extraInfo/injections/threadQuestion.js
@@ -0,0 +1,24 @@
+import {MDCTooltip} from '@material/tooltip';
+
+import ThreadModel from '../../../../models/Thread.js';
+import ThreadExtraInfoService from '../services/thread.js';
+
+import BaseExtraInfoInjection from './base.js';
+
+export default class ThreadQuestionExtraInfoInjection extends
+ BaseExtraInfoInjection {
+ inject(threadInfo, injectionDetails) {
+ const [chips, tooltips] =
+ ThreadExtraInfoService.getThreadChips(threadInfo.body?.['1']);
+ this.#injectChips(chips, injectionDetails.stateChips);
+ for (const tooltip of tooltips) new MDCTooltip(tooltip);
+ }
+
+ #injectChips(chips, stateChipsElement) {
+ const stateChipsContainer = stateChipsElement.querySelector(
+ '.scTailwindThreadQuestionStatechipsroot');
+ const container = stateChipsContainer ?? stateChipsElement;
+ const shouldCreateContainer = stateChipsContainer === null;
+ this.addExtraInfoChips(chips, container, shouldCreateContainer);
+ }
+}
diff --git a/src/contentScripts/communityConsole/extraInfo/services/message.js b/src/contentScripts/communityConsole/extraInfo/services/message.js
new file mode 100644
index 0000000..3b43403
--- /dev/null
+++ b/src/contentScripts/communityConsole/extraInfo/services/message.js
@@ -0,0 +1,56 @@
+import MessageModel from '../../../../models/Message.js';
+
+import StatesExtraInfoService from './states.js';
+
+export default class MessageExtraInfoService {
+ static getMessageIdFromNode(messageNode) {
+ const id =
+ messageNode.querySelector('.scTailwindThreadMessageMessagecardcontent')
+ ?.getAttribute?.('data-stats-id');
+ if (id === undefined)
+ throw new Error(`Couldn't retrieve message id from node.`);
+ return id;
+ }
+
+ static getMessageFromThreadModel(messageId, threadModel) {
+ for (const messageOrGap of threadModel.getMessageOrGapModels()) {
+ if (!(messageOrGap instanceof MessageModel)) continue;
+ if (messageOrGap.getId() == messageId) {
+ return messageOrGap;
+ } else {
+ for (const subMessageOrGap of messageOrGap.getCommentsAndGaps()) {
+ if (!(subMessageOrGap instanceof MessageModel)) continue;
+ if (subMessageOrGap.getId() == messageId) {
+ return subMessageOrGap;
+ }
+ }
+ }
+ }
+
+ throw new Error(`Couldn't find message ${messageId} in thread.`);
+ }
+
+ static getMessageChips(messageModel) {
+ const chips = [];
+ const tooltips = [];
+
+ const endPendingStateTimestampMicros =
+ messageModel.getEndPendingStateTimestampMicros();
+ const [pendingStateChip, pendingStateTooltip] =
+ StatesExtraInfoService.getPendingStateChip(
+ endPendingStateTimestampMicros);
+ if (pendingStateChip) chips.push(pendingStateChip);
+ if (pendingStateTooltip) tooltips.push(pendingStateTooltip);
+
+ const itemMetadata = messageModel.data?.[1]?.[5];
+ chips.push(...StatesExtraInfoService.getMetadataChips(itemMetadata));
+
+ const liveReviewStatus = messageModel.data?.[1]?.[36];
+ const [liveReviewChip, liveReviewTooltip] =
+ StatesExtraInfoService.getLiveReviewStatusChip(liveReviewStatus);
+ if (liveReviewChip) chips.push(liveReviewChip);
+ if (liveReviewTooltip) tooltips.push(liveReviewTooltip);
+
+ return [chips, tooltips];
+ }
+}
diff --git a/src/contentScripts/communityConsole/extraInfo/services/states.js b/src/contentScripts/communityConsole/extraInfo/services/states.js
new file mode 100644
index 0000000..6904e3e
--- /dev/null
+++ b/src/contentScripts/communityConsole/extraInfo/services/states.js
@@ -0,0 +1,106 @@
+import {createPlainTooltip} from '../../../../common/tooltip.js';
+import {kItemMetadataState, kItemMetadataStateI18n} from '../consts.js';
+
+export default class StatesExtraInfoService {
+ static getPendingStateChip(endPendingStateTimestampMicros) {
+ const endPendingStateTimestamp =
+ Math.floor(endPendingStateTimestampMicros / 1e3);
+ const now = Date.now();
+ if (!endPendingStateTimestampMicros || endPendingStateTimestamp < now)
+ return [null, null];
+
+ const span = document.createElement('span');
+ span.textContent =
+ chrome.i18n.getMessage('inject_extrainfo_message_pendingstate');
+
+ const date = new Date(endPendingStateTimestamp).toLocaleString();
+ const pendingTooltip = createPlainTooltip(
+ span,
+ chrome.i18n.getMessage(
+ 'inject_extrainfo_message_pendingstate_tooltip', [date]),
+ false);
+ return [span, pendingTooltip];
+ }
+
+ static getLiveReviewStatusChip(liveReviewStatus) {
+ const verdict = liveReviewStatus?.['1'];
+ if (!verdict) return [null, null];
+
+ const [label, labelClass] = this.getLiveReviewStatusLabel(verdict);
+ if (!label || !labelClass) return [null, null];
+
+ const reviewedBy = liveReviewStatus?.['2'];
+ const timestamp = liveReviewStatus?.['3'];
+ const date = (new Date(Math.floor(timestamp / 1e3))).toLocaleString();
+
+ let a = document.createElement('a');
+ a.href = 'https://support.google.com/s/community/user/' + reviewedBy;
+ a.classList.add(labelClass);
+ a.textContent = chrome.i18n.getMessage(
+ 'inject_extrainfo_message_livereviewverdict',
+ [chrome.i18n.getMessage(
+ 'inject_extrainfo_message_livereviewverdict_' + label)]);
+ let liveReviewTooltip = createPlainTooltip(a, date, false);
+ return [a, liveReviewTooltip];
+ }
+
+ static getLiveReviewStatusLabel(verdict) {
+ let label, labelClass;
+ switch (verdict) {
+ case 1: // LIVE_REVIEW_RELEVANT
+ label = 'relevant';
+ labelClass = 'TWPT-extrainfo-good';
+ break;
+
+ case 2: // LIVE_REVIEW_OFF_TOPIC
+ label = 'offtopic';
+ labelClass = 'TWPT-extrainfo-bad';
+ break;
+
+ case 3: // LIVE_REVIEW_ABUSE
+ label = 'abuse';
+ labelClass = 'TWPT-extrainfo-bad';
+ break;
+
+ default:
+ return [null, null];
+ }
+ return [label, labelClass];
+ }
+
+ static getMetadataChips(itemMetadata) {
+ return [
+ this.getStateChip(itemMetadata),
+ this.getShadowBlockChip(itemMetadata),
+ ].filter(chip => chip !== null);
+ }
+
+ static getStateChip(itemMetadata) {
+ const state = itemMetadata?.['1'];
+ if (!state || state == 1) return null;
+
+ const stateI18nKey =
+ 'inject_extrainfo_message_state_' + kItemMetadataStateI18n[state];
+ const stateLocalized = chrome.i18n.getMessage(stateI18nKey) ?? state;
+
+ const span = document.createElement('span');
+ span.textContent = chrome.i18n.getMessage(
+ 'inject_extrainfo_message_state', [stateLocalized]);
+ span.title = kItemMetadataState[state] ?? state;
+ return span;
+ }
+
+ static getShadowBlockChip(itemMetadata) {
+ const shadowBlockInfo = itemMetadata?.['10'];
+ const blockedTimestampMicros = shadowBlockInfo?.['2'];
+ if (!blockedTimestampMicros) return null;
+
+ const isBlocked = shadowBlockInfo?.['1'];
+ let span = document.createElement('span');
+ span.textContent = chrome.i18n.getMessage(
+ 'inject_extrainfo_message_shadowblock' +
+ (isBlocked ? 'active' : 'notactive'));
+ if (isBlocked) span.classList.add('TWPT-extrainfo-bad');
+ return span;
+ }
+}
diff --git a/src/contentScripts/communityConsole/extraInfo/services/thread.js b/src/contentScripts/communityConsole/extraInfo/services/thread.js
index 357ed3d..c36dfa5 100644
--- a/src/contentScripts/communityConsole/extraInfo/services/thread.js
+++ b/src/contentScripts/communityConsole/extraInfo/services/thread.js
@@ -1,5 +1,4 @@
-import {createPlainTooltip} from '../../../../common/tooltip.js';
-import {kItemMetadataState, kItemMetadataStateI18n} from '../consts.js';
+import StatesExtraInfoService from './states.js';
export default class ThreadExtraInfoService {
/**
@@ -11,42 +10,27 @@
let chips = [];
let tooltips = [];
- const [pendingStateInfo, pendingTooltip] = this.getPendingStateChip(thread);
+ const endPendingStateTimestampMicros = thread?.['2']?.['39'];
+ const [pendingStateInfo, pendingTooltip] =
+ StatesExtraInfoService.getPendingStateChip(
+ endPendingStateTimestampMicros);
if (pendingStateInfo) chips.push(pendingStateInfo);
if (pendingTooltip) tooltips.push(pendingTooltip);
chips.push(...this.getTrendingChips(thread));
- chips.push(...this.getMetadataChips(thread));
+ const itemMetadata = thread?.['2']?.['12'];
+ chips.push(...StatesExtraInfoService.getMetadataChips(itemMetadata));
+
+ const liveReviewStatus = thread?.['2']?.['38'];
const [liveReviewInfo, liveReviewTooltip] =
- this.getLiveReviewStatusChip(thread);
+ StatesExtraInfoService.getLiveReviewStatusChip(liveReviewStatus);
if (liveReviewInfo) chips.push(liveReviewInfo);
if (liveReviewTooltip) tooltips.push(liveReviewTooltip);
return [chips, tooltips];
}
- static getPendingStateChip(thread) {
- const endPendingStateTimestampMicros = thread?.['2']?.['39'];
- const endPendingStateTimestamp =
- Math.floor(endPendingStateTimestampMicros / 1e3);
- const now = Date.now();
- if (!endPendingStateTimestampMicros || endPendingStateTimestamp < now)
- return [null, null];
-
- const span = document.createElement('span');
- span.textContent =
- chrome.i18n.getMessage('inject_extrainfo_message_pendingstate');
-
- const date = new Date(endPendingStateTimestamp).toLocaleString();
- const pendingTooltip = createPlainTooltip(
- span,
- chrome.i18n.getMessage(
- 'inject_extrainfo_message_pendingstate_tooltip', [date]),
- false);
- return [span, pendingTooltip];
- }
-
static getTrendingChips(thread) {
const chips = [];
@@ -62,91 +46,6 @@
return chips;
}
- static getMetadataChips(thread) {
- const itemMetadata = thread?.['2']?.['12'];
-
- return [
- this.getStateChip(itemMetadata),
- this.getShadowBlockChip(itemMetadata),
- ].filter(chip => chip !== null);
- }
-
- static getLiveReviewStatusChip(thread) {
- const liveReviewStatus = thread?.['2']?.['38'];
- const verdict = liveReviewStatus?.['1'];
- if (!verdict) return [null, null];
-
- const [label, labelClass] = this.getLiveReviewStatusLabel(verdict);
- if (!label || !labelClass) return [null, null];
-
- const reviewedBy = liveReviewStatus?.['2'];
- const timestamp = liveReviewStatus?.['3'];
- const date = (new Date(Math.floor(timestamp / 1e3))).toLocaleString();
-
- let a = document.createElement('a');
- a.href = 'https://support.google.com/s/community/user/' + reviewedBy;
- a.classList.add(labelClass);
- a.textContent = chrome.i18n.getMessage(
- 'inject_extrainfo_message_livereviewverdict',
- [chrome.i18n.getMessage(
- 'inject_extrainfo_message_livereviewverdict_' + label)]);
- let liveReviewTooltip = createPlainTooltip(a, date, false);
- return [a, liveReviewTooltip];
- }
-
- static getStateChip(itemMetadata) {
- const state = itemMetadata?.['1'];
- if (!state || state == 1) return null;
-
- const stateI18nKey =
- 'inject_extrainfo_message_state_' + kItemMetadataStateI18n[state];
- const stateLocalized = chrome.i18n.getMessage(stateI18nKey) ?? state;
-
- const span = document.createElement('span');
- span.textContent = chrome.i18n.getMessage(
- 'inject_extrainfo_message_state', [stateLocalized]);
- span.title = kItemMetadataState[state] ?? state;
- return span;
- }
-
- static getLiveReviewStatusLabel(verdict) {
- let label, labelClass;
- switch (verdict) {
- case 1: // LIVE_REVIEW_RELEVANT
- label = 'relevant';
- labelClass = 'TWPT-extrainfo-good';
- break;
-
- case 2: // LIVE_REVIEW_OFF_TOPIC
- label = 'offtopic';
- labelClass = 'TWPT-extrainfo-bad';
- break;
-
- case 3: // LIVE_REVIEW_ABUSE
- label = 'abuse';
- labelClass = 'TWPT-extrainfo-bad';
- break;
-
- default:
- return [null, null];
- }
- return [label, labelClass];
- }
-
- static getShadowBlockChip(itemMetadata) {
- const shadowBlockInfo = itemMetadata?.['10'];
- const blockedTimestampMicros = shadowBlockInfo?.['2'];
- if (!blockedTimestampMicros) return null;
-
- const isBlocked = shadowBlockInfo?.['1'];
- let span = document.createElement('span');
- span.textContent = chrome.i18n.getMessage(
- 'inject_extrainfo_message_shadowblock' +
- (isBlocked ? 'active' : 'notactive'));
- if (isBlocked) span.classList.add('TWPT-extrainfo-bad');
- return span;
- }
-
static getThreadFromThreadList(threadList, currentThreadInfo) {
return threadList?.find?.(thread => {
const threadInfo = thread?.['2']?.['1'];