Skip to content

Commit

Permalink
refactor(MarkRights): split modules; rewrite code (#1648)
Browse files Browse the repository at this point in the history
* refactor(MarkRights): split modules; rewrite code
  • Loading branch information
WaitSpringQW authored Jan 8, 2025
1 parent 54722a9 commit 9c01a79
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 253 deletions.
228 changes: 118 additions & 110 deletions dist/MarkRights/MarkRights.js

Large diffs are not rendered by default.

205 changes: 62 additions & 143 deletions src/MarkRights/modules/core.ts
Original file line number Diff line number Diff line change
@@ -1,166 +1,85 @@
import {queryGlobalUserGroups, queryUserGroups} from './query';
import type {UserRights} from './types';
import {getMessage} from './i18n';
import {uniqueArray} from 'ext.gadget.Util';
import {appendUserRightsMark} from './util/appendUserRightsMark';
import {generateUserLinks} from './util/generateUserLinks';

const getUsername = (url: string): string => {
if (!url) {
return '';
}
const markUserRights = ($content: JQuery): void => {
const {users, $userLinks} = generateUserLinks($content);

const username: string | null = mw.util.getParamValue('title', url);
const promises: (() => Promise<void>)[] = [];

const decode = (string: string, replace: (_string: string) => string): string => {
return decodeURIComponent(
((): string => {
try {
return decodeURIComponent(replace(string));
} catch {
return replace(string).replace(/%(?!\d+)/g, '%25');
}
})()
);
};

if (username) {
return decode(username, (string: string): string => {
return string.replace('User:', '').replace(/_/g, ' ');
});
}
const usernameMatch: RegExpMatchArray | null = url.match(/\/wiki\/User:(.+?)$/);
if (usernameMatch?.[1]) {
return decode(usernameMatch[1], (string: string): string => {
return string.replace(/_/g, ' ');
});
}
return '';
};

const appendUserRightsMark = (
$userLinks: JQuery<HTMLElement>,
{userGroupMap, globalUserGroupMap}: {userGroupMap: Map<string, string[]>; globalUserGroupMap: Map<string, string[]>}
): void => {
for (const element of $userLinks) {
const $element: JQuery = $(element);
if ($element.parents('li').find('.gadgets-markrights').length) {
continue;
}
if ($element.siblings('.gadgets-markrights').length) {
for (let i = 0; i < users.length; i++) {
const ususers = users.splice(0, 25);
if (!ususers.length) {
continue;
}
const username: string = getUsername($element.attr('href') ?? '');
if (!username) {
continue;
}
const groups = userGroupMap.get(username) ?? [];
const globalGroups = globalUserGroupMap.get(username) ?? [];
if (!groups) {
continue;
}
const $sups: JQuery = $('<span>').addClass('gadgets-markrights');
for (const group of uniqueArray([...groups, ...globalGroups])) {
const className: string = `gadgets-markrights__${group}`;
if ($sups.find('sup').hasClass(className)) {
continue;
}
$sups.append(
// The following classes are used here:
// * gadget-markrights__qiuwen
// * gadget-markrights__steward
// * gadget-markrights__checkuser
// * gadget-markrights__suppress
// * gadget-markrights__sysop
// * gadget-markrights__interface-admin
// * gadget-markrights__templateeditor
// * gadget-markrights__transwiki
// * gadget-markrights__patroller
// * gadget-markrights__autoreviewer
// * gadget-markrights__senioreditor
// * gadget-markrights__eventsponsor
// * gadget-markrights__massmessage-sender
// * gadget-markrights__confirmed
// * gadget-markrights__autoconfirmed
// * gadget-markrights__bot
// * gadget-markrights__flood
// * gadget-markrights__ipblock-exempt
// * gadget-markrights__rnrsverify-exempt
$('<sup>')
.addClass(className)
.attr({
alt: getMessage(group as UserRights),
title: getMessage(group as UserRights),
})
);
}
$element.after($sups);
}
};

const markUserRights = async ($content: JQuery): Promise<void> => {
const $userLinks: JQuery = $content.find('a.mw-userlink:not(.mw-anonuserlink)');
let users: string[] = [];
const queue: Array<typeof users> = [];
const userGroupMap: Map<string, string[]> = new Map();
const globalUserGroupMap: Map<string, string[]> = new Map();
promises[promises.length] = async (): Promise<void> => {
const userGroups: Record<string, string[]> = {};

for (const element of $userLinks) {
const $element: JQuery = $(element);
if ($element.parents('li').find('.gadgets-markrights').length) {
continue;
}
if ($element.siblings('.gadgets-markrights').length) {
continue;
}
const {textContent} = element;
const userLinkText: string | undefined = textContent?.toString();
if (userLinkText) {
users[users.length] = userLinkText; // Replace `[].push()` to avoid polyfilling core-js
}
try {
const response = await queryUserGroups(ususers);
const {users: queryUsers} = response['query'] as {
users: {groups: string[]; name: string}[];
};

// 用户名列表去重
users = uniqueArray(users); // Replace `[...new Set()]` to avoid polyfilling core-js
for (const user of queryUsers) {
if (!user?.name || !user?.groups) {
continue;
}

if (users.length === 25) {
queue[queue.length] = users; // Replace `[].push()` to avoid polyfilling core-js
users = [];
}
}

if (users.length > 0) {
queue[queue.length] = users; // Replace `[].push()` to avoid polyfilling core-js
}
const {name, groups} = user;

for (const ususers of queue) {
try {
const queryUserResponse = await queryUserGroups(ususers);
const {users: queryUsers} = queryUserResponse['query'] as {
users: {groups: string[]; name: string}[];
};
userGroups[name] ??= [];

for (const user of queryUsers) {
if (!user || !user.groups) {
continue;
userGroups[name] = [
...userGroups[name],
...groups.filter((element) => {
return element !== '*';
}),
];
}
userGroupMap.set(
user.name,
user.groups.filter((element) => {
return element !== '*';
})
);
} catch (error: unknown) {
console.error('[MarkRights] Ajax error:', error);
}

appendUserRightsMark($userLinks, userGroups);
};

promises[promises.length] = async (): Promise<void> => {
const userGroups: Record<string, string[]> = {};

for (const user of ususers) {
const queryGlobalUserInfoResponse = await queryGlobalUserGroups(user);
if (queryGlobalUserInfoResponse['query'] && queryGlobalUserInfoResponse['query'].globaluserinfo) {
const {groups: globalgroups}: {groups: string[]} = queryGlobalUserInfoResponse['query']
.globaluserinfo as {groups: string[]};
globalUserGroupMap.set(user, globalgroups);
try {
const response = await queryGlobalUserGroups(user);
const {globaluserinfo} = response['query'] as {
globaluserinfo: {groups: string[]};
};

if (!globaluserinfo?.groups) {
continue;
}

const {groups} = globaluserinfo;

userGroups[user] ??= [];

userGroups[user] = [...userGroups[user], ...groups];
} catch (error: unknown) {
console.error('[MarkRights] Ajax error:', error);
}
}

appendUserRightsMark($userLinks, {userGroupMap, globalUserGroupMap});
} catch {}
appendUserRightsMark($userLinks, userGroups);
};
}

void (async () => {
for (const promise of promises) {
try {
await promise();
} catch {}
}
})();
};

export {markUserRights};
47 changes: 47 additions & 0 deletions src/MarkRights/modules/util/appendUserRightsMark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type {UserRights} from '../types';
import {getMessage} from '../i18n';
import {getUserName} from './getUserName';
import {uniqueArray} from 'ext.gadget.Util';

const appendUserRightsMark = ($userLinks: JQuery<HTMLAnchorElement>[], userGroups: Record<string, string[]>): void => {
for (const $element of $userLinks) {
const username: string = getUserName($element.attr('href') ?? '');
if (!username) {
continue;
}

userGroups ??= {};
const groups = userGroups[username] ?? [];

let $sups: JQuery;
if ($element.parents('li').find('.gadgets-markrights').length) {
$sups = $element.siblings('.gadgets-markrights').eq(0);
} else if ($element.siblings('.gadgets-markrights').length) {
$sups = $element.parents('li').find('.gadgets-markrights').eq(0);
} else {
$sups = $('<span>').addClass('gadgets-markrights');
$element.after($sups);
}

for (const group of uniqueArray(groups)) {
const className: string = `gadgets-markrights__${group}`;
if ($sups.find('sup').hasClass(className)) {
continue;
}

$sups.append(
// The following classes are used here:
// * see ../types.d.ts
// * for more information
$('<sup>')
.addClass(className)
.attr({
alt: getMessage(group as UserRights),
title: getMessage(group as UserRights),
})
);
}
}
};

export {appendUserRightsMark};
30 changes: 30 additions & 0 deletions src/MarkRights/modules/util/generateUserLinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const generateUserLinks = ($content: JQuery): {users: string[]; $userLinks: JQuery<HTMLAnchorElement>[]} => {
const users: string[] = [];
const $userLinks: JQuery<HTMLAnchorElement>[] = [];
const $elements = $content.find<HTMLAnchorElement>('a.mw-userlink:not(.mw-anonuserlink)');

for (const element of $elements) {
const $element: JQuery<HTMLAnchorElement> = $(element);
if (
$element.parents('li').find('.gadgets-markrights').length ||
$element.siblings('.gadgets-markrights').length
) {
continue;
}

$userLinks[$userLinks.length] = $element;

const {textContent} = element;
const user: string | undefined = textContent?.toString();

if (!user) {
continue;
}

users[users.length] = user;
}

return {users, $userLinks};
};

export {generateUserLinks};
37 changes: 37 additions & 0 deletions src/MarkRights/modules/util/getUserName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const getUserName = (url: string): string => {
if (!url) {
return '';
}

const username: string | null = mw.util.getParamValue('title', url);

const decode = (string: string, replace: (_string: string) => string): string => {
return decodeURIComponent(
((): string => {
try {
return decodeURIComponent(replace(string));
} catch {
return replace(string).replace(/%(?!\d+)/g, '%25');
}
})()
);
};

if (username) {
return decode(username, (string: string): string => {
return string.replace('User:', '').replace(/_/g, ' ');
});
}

const usernameMatch: RegExpMatchArray | null = url.match(/\/wiki\/User:(.+?)$/);

if (usernameMatch?.[1]) {
return decode(usernameMatch[1], (string: string): string => {
return string.replace(/_/g, ' ');
});
}

return '';
};

export {getUserName};

0 comments on commit 9c01a79

Please sign in to comment.