blob: 21e75464023db43222009cbda14b05e82abf35f8 [file] [log] [blame]
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 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';
export default class ExtraInfo {
constructor() {
this.optionsWatcher = new OptionsWatcher(['extrainfo', 'perforumstats']);
const profileInfoHandler = new ProfileInfoHandler();
const threadListInfoHandler = new ThreadListInfoHandler();
this.profileAbuse = new ProfileAbuseExtraInfoInjection(
profileInfoHandler, this.optionsWatcher);
this.profilePerForumStats = new ProfilePerForumStatsExtraInfoInjection(
profileInfoHandler, this.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(),
};
});
}
injectAbuseChipsAtProfileIfEnabled(card) {
this.profileAbuse.injectIfEnabled({card});
}
injectAtThreadListIfEnabled(li) {
const injectionDetails = this.threadList.getInjectionDetails(li);
this.threadList.injectIfEnabled(injectionDetails);
}
injectAtExpandedThreadListIfEnabled(toolbelt) {
const injectionDetails =
this.expandedThreadList.getInjectionDetails(toolbelt);
this.expandedThreadList.injectIfEnabled(injectionDetails);
}
injectPerForumStatsIfEnabled(chart) {
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);
});
}
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);
});
}
}