Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comment Tracker: Fix edited comment appears as read instead of unread #1773

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.rulers": [100]
}
176 changes: 126 additions & 50 deletions src/modules/Comments.js → src/modules/Comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,28 @@ import { Shared } from '../class/Shared';
import { Comment } from '../models/Comment';
import { common } from './Common';

const getUser = common.getUser.bind(common),
getValue = common.getValue.bind(common);
interface Comment2 {
actions?: HTMLElement;
author?: string;
bump?: string[];
code?: string;
comment?: HTMLElement;
displayState?: HTMLElement;
id?: string;
index?: number;
isOp?: boolean;
length?: number;
outerWrap?: HTMLElement;
parent?: HTMLElement;
permalink?: HTMLElement;
summary?: HTMLElement;
text?: string;
timestamp?: number;
type?: string;
words?: string[];
}

const getValue = common.getValue.bind(common);
class Comments extends Module {
constructor() {
super();
Expand All @@ -19,17 +39,23 @@ class Comments extends Module {
};
}

async comments_load(context, main, source, endless, mainEndless) {
comments_load = async (
context: HTMLElement,
main: boolean,
_source: never,
endless: number,
mainEndless: unknown
) => {
const commentsV2 = Comment.parseAll(context);
Scope.addData('current', 'commentsV2', commentsV2, endless);
for (const feature of Shared.esgst.commentV2Features) {
await feature(commentsV2, main);
}
let count, comments, i, n;
comments = await this.comments_get(context, document, main, endless);
let count = 0;
const comments = await this.comments_get(context, document, main, endless);
if (!comments.length) return;
Scope.addData('current', 'comments', comments, endless);
for (i = 0, n = comments.length; i < n; ++i) {
for (let i = 0, n = comments.length; i < n; ++i) {
comments[i].index = i;
}
for (const feature of Shared.esgst.commentFeatures) {
Expand Down Expand Up @@ -64,9 +90,14 @@ class Comments extends Module {
const breadcrumbs = context.querySelectorAll(
'.page__heading:not([data-esgst]) .page__heading__breadcrumbs'
);
count = breadcrumbs[1] || breadcrumbs[0];
if (count) {
count = parseInt(count.firstElementChild.textContent.replace(/,/g, '').match(/\d+/)[0]);
const countElement = breadcrumbs[1] || breadcrumbs[0];
if (countElement && countElement.firstElementChild) {
const reMatches = countElement.firstElementChild.textContent
.replace(/,/g, '')
.match(/\d+/);
if (reMatches) {
count = parseInt(reMatches[0]);
}
} else {
count = 0;
}
Expand Down Expand Up @@ -97,22 +128,30 @@ class Comments extends Module {
if (Settings.get('ged')) {
this.esgst.ged_addIcons(comments);
}
}
};

async comments_get(context, mainContext, main, endless) {
let comment, comments, i, matches, sourceLink;
comments = [];
matches = context.querySelectorAll(
comments_get = async (
context: HTMLElement,
mainContext: Document,
main: boolean,
endless: number
) => {
let comment: Comment2;
const comments: Comment2[] = [];
const matches: NodeListOf<HTMLElement> = context.querySelectorAll(
common.getSelectors(endless, [
`X:not(.comment--submit) > .comment__parent`,
'X:not(.comment--submit) > .comment__parent',
'X.comment__child',
'X.comment_inner',
])
);
sourceLink = mainContext.querySelector(
`.page__heading__breadcrumbs a[href*="/giveaway/"], .page__heading__breadcrumbs a[href*="/discussion/"], .page__heading__breadcrumbs a[href*="/ticket/"], .page_heading_breadcrumbs a[href*="/trade/"]`
const sourceLink: HTMLElement | null = mainContext.querySelector(
'.page__heading__breadcrumbs a[href*="/giveaway/"], .page__heading__breadcrumbs a[href*="/discussion/"], .page__heading__breadcrumbs a[href*="/ticket/"], .page_heading_breadcrumbs a[href*="/trade/"]'
);
for (i = matches.length - 1; i >= 0; --i) {
for (let i = matches.length - 1; i >= 0; --i) {
if (!sourceLink) {
continue;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make much sense here, because sourceLink is queried outside of the loop, so if it doesn't exist, we can return before the loop instead of evaluating it again for each match.

However, I'm not sure that it's a good idea to add this check, as I'm not sure that sourceLink will always exist. This might break the feature in some places. Better to make sourceLink accept HTMLElement | undefined.

comment = await this.comments_getInfo(
matches[i],
Scope.current?.sourceLink || sourceLink,
Expand All @@ -124,68 +163,105 @@ class Comments extends Module {
}
}
return comments;
}
};

async comments_getInfo(context, sourceLink, savedUsers, main) {
let matches, n, source;
const comment = {};
comments_getInfo = async (
context: HTMLElement,
sourceLink: HTMLElement,
_savedUsers: unknown,
main: boolean
) => {
let matches: NodeListOf<HTMLElement>,
n: number,
source: string | null | undefined,
element: HTMLElement | null,
reMatches: string[] | null,
attr: string | null;
const comment: Comment2 = {};
comment.comment = context;
comment.outerWrap = comment.comment;
comment.parent = comment.comment.parentElement;
const author = comment.comment.querySelector(`.comment__author, .author_name`);
element = comment.comment.parentElement;
if (element) {
comment.parent = element;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

element = comment.comment.parentElement would already receive either an HTMLElement or null, so there's no need to assign it under a conditional.

const author = comment.comment.querySelector('.comment__author, .author_name');
if (!author) {
return;
return {};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This messes with the logic, because of:

comment = await this.comments_getInfo(
    matches[i],
    Scope.current?.sourceLink || sourceLink,
    endless ? this.esgst.users : JSON.parse(getValue('users')),
    main
);
if (comment) {
    comments.push(comment);
}

So the empty object will be added to the array and might cause issues with features that rely on this array for actual comment objects.

Better to return null instead.

}
comment.author = author.textContent.trim();
comment.summary = comment.comment.querySelector('.comment__summary, .comment_body');
element = comment.comment.querySelector('.comment__summary, .comment_body');
if (element) {
comment.summary = element;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment.summary = comment.comment.querySelector('.comment__summary, .comment_body') would already receive either an HTMLElement or undefined, so there's no need to assign it under a conditional.

comment.isOp =
(comment.summary && !comment.summary.id) || (comment.parent && !comment.parent.id);
comment.displayState = comment.comment.querySelector(
`.comment__display-state, .comment_body_default`
);
element = comment.comment.querySelector('.comment__display-state, .comment_body_default');
if (element) {
comment.displayState = element;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment.displayState = comment.comment.querySelector('.comment__display-state, .comment_body_default') would already receive either an HTMLElement or undefined, so there's no need to assign it under a conditional.

comment.text = comment.displayState
? comment.displayState.textContent.trim().replace(/View\sattached\simage\./, '')
: '';
comment.bump = comment.text
reMatches = comment.text
.replace(/[^A-Za-z]/g, '')
.match(/^(havea|takea|thanksand|thankyou)?bump(ing|ity|o)?$/i);
if (reMatches) {
comment.bump = reMatches;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment.bump would already receive null if there are no matches, so there's no need to assign it under a conditional.

comment.length = comment.text.length;
comment.words = Array.from(new Set(comment.text.split(/\s/)));
comment.actions = comment.comment.querySelector(`.comment__actions, .action_list`);
matches = comment.actions.querySelectorAll(`[href*="/comment/"]`);
element = comment.comment.querySelector('.comment__actions, .action_list');
if (element) {
comment.actions = element;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment.actions = comment.comment.querySelector('.comment__actions, .action_list') would already receive either an HTMLElement or undefined, so there's no need to assign it under a conditional.

if (!comment.actions) {
return {};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to return null instead.

}
matches = comment.actions.querySelectorAll('[href*="/comment/"]');
n = matches.length;
if (n > 0) {
comment.permalink = matches[n - 1];
}
comment.id = comment.permalink
? comment.permalink.getAttribute('href').match(/\/comment\/(.+)/)[1]
: '';
comment.timestamp = parseInt(
comment.actions.querySelector(`[data-timestamp]`).getAttribute('data-timestamp')
);
comment.id = '';
if (comment.permalink) {
attr = comment.permalink.getAttribute('href');
if (attr) {
reMatches = attr.match(/\/comment\/(.+)/);
if (reMatches) {
comment.id = reMatches[1];
}
}
}
matches = comment.actions.querySelectorAll('[data-timestamp]');
n = matches.length;
if (n > 0) {
attr = matches[n - 1].getAttribute('data-timestamp');
if (attr) {
comment.timestamp = parseInt(attr);
}
}
if (!main || Shared.common.isCurrentPath('Messages')) {
if (this.esgst.sg) {
try {
source = comment.comment
.closest('.comments')
.previousElementSibling.firstElementChild.firstElementChild.getAttribute('href');
} catch (e) {
/**/
}
source = comment.comment
.closest('.comments')
?.previousElementSibling?.firstElementChild?.firstElementChild?.getAttribute('href');
} else {
source = comment.actions.querySelector(`[href*="/trade/"]`).getAttribute('href');
source = comment.actions.querySelector('[href*="/trade/"]')?.getAttribute('href');
}
}
if (!source && sourceLink) {
source = sourceLink.getAttribute('href');
}
if (source) {
source = source.match(/(giveaway|discussion|ticket|trade)\/(.+?)(\/.*)?$/);
comment.type = `${source[1]}s`;
comment.code = source[2];
reMatches = source.match(/(giveaway|discussion|ticket|trade)\/(.+?)(\/.*)?$/);
if (reMatches) {
comment.type = `${reMatches[1]}s`;
comment.code = reMatches[2];
}
return comment;
}
}
return {};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to return null instead.

};
}

const commentsModule = new Comments();
Expand Down