Skip to content

Commit 72d2809

Browse files
committed
feat(qoj): export rank list as markdown
1 parent 9f69e97 commit 72d2809

File tree

4 files changed

+158
-18
lines changed

4 files changed

+158
-18
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "CPAssistant.js",
3-
"version": "1.1.1",
3+
"version": "1.1.2",
44
"description": "",
55
"main": "index.js",
66
"license": "GPL-3.0-only",
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// import * as h from 'h';
2+
import Module from '../../../types/module';
3+
import Feature from '../../../types/feature';
4+
import { writeClipboard } from '../../../utils/browser';
5+
6+
interface RanklistProblem {
7+
id: string;
8+
accepted: number;
9+
attempted: number;
10+
link?: string;
11+
$el: HTMLElement;
12+
}
13+
14+
interface RanklistTeam {
15+
name: string;
16+
solved: number;
17+
penalty: number;
18+
status: Array<string>;
19+
selected: boolean;
20+
$el: HTMLElement;
21+
}
22+
23+
export default class RankListExport extends Feature {
24+
run() {
25+
this.on('/results/<name>', (args) => {
26+
const $ranklist = document.getElementsByClassName('standings')[0];
27+
28+
const problems = Array.from($ranklist.children[0].children[0].children)
29+
.slice(2, -3)
30+
.map(($el) => {
31+
const text = ($el as HTMLElement).innerText;
32+
const data = text.split(/[\s\/]+/);
33+
return {
34+
id: data[0],
35+
accepted: parseInt(data[1], 10),
36+
attempted: parseInt(data[2], 10),
37+
$el,
38+
} as RanklistProblem;
39+
});
40+
console.log(problems);
41+
42+
const teams = Array.from($ranklist.children[0].children)
43+
.slice(1, -2)
44+
.map(($el) => {
45+
const children = Array.from($el.children).map(($el) => ($el as HTMLElement).innerText.trim());
46+
47+
return {
48+
name: children[1],
49+
solved: parseInt(children[children.length - 3], 10),
50+
penalty: parseInt(children[children.length - 2], 10),
51+
status: children.slice(2, -3),
52+
selected: false,
53+
$el,
54+
} as RanklistTeam;
55+
});
56+
console.log(teams);
57+
58+
for (const team of teams) {
59+
team.$el.addEventListener('click', () => {
60+
this.log(team.name);
61+
team.selected = !team.selected;
62+
if (team.selected) {
63+
team.$el.style.color = 'red';
64+
} else {
65+
team.$el.style.color = '';
66+
}
67+
});
68+
}
69+
70+
const exportRank = (teams: Array<RanklistTeam>) => {
71+
const data: Array<Array<string>> = [[`[Team](https://qoj.ac/results/${args.name})`]];
72+
data[0].push('Solved');
73+
data[0].push('Penalty');
74+
data[0].push.apply(
75+
data[0],
76+
problems.map((problem) => `${problem.id}<br>${problem.accepted}`)
77+
);
78+
for (const team of teams) {
79+
const line = [team.name];
80+
line.push(team.solved.toString());
81+
line.push(team.penalty.toString());
82+
line.push.apply(
83+
line,
84+
team.status.map((source) => {
85+
const data = source.trim().split(/\n+/);
86+
if (data[0].startsWith('+')) {
87+
data[0] = '**' + data[0] + '**';
88+
return '[' + data.join('<br>') + '](#)';
89+
} else {
90+
return data.join('<br>');
91+
}
92+
})
93+
);
94+
data.push(line);
95+
}
96+
this.log(data);
97+
98+
let result = '| ';
99+
for (let i = 0; i < data[0].length; i++) {
100+
result += data[0][i] + ' | ';
101+
}
102+
result += '\n| ';
103+
for (let i = 0; i < data[0].length; i++) {
104+
result += ' --- |';
105+
}
106+
result += '\n';
107+
for (let i = 1; i < data.length; i++) {
108+
result += '| ';
109+
for (let j = 0; j < data[i].length; j++) {
110+
result += data[i][j] + ' | ';
111+
}
112+
result += '\n';
113+
}
114+
this.log('export rank', result);
115+
writeClipboard(result);
116+
};
117+
118+
const $buttonExport = document.createElement('button');
119+
$buttonExport.innerText = 'Export';
120+
$buttonExport.addEventListener('click', () => {
121+
exportRank(teams.filter((team) => team.selected));
122+
});
123+
const $buttonExportAll = document.createElement('button');
124+
$buttonExportAll.innerText = 'Export All';
125+
$buttonExportAll.addEventListener('click', () => {
126+
exportRank(teams);
127+
});
128+
document.body.children[0].appendChild($buttonExport);
129+
document.body.children[0].appendChild($buttonExportAll);
130+
});
131+
}
132+
133+
constructor(module: Module, name: string) {
134+
super(module, name);
135+
}
136+
}

src/modules/qoj/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import config from '../../config';
22
import App from '../../app';
33
import Module from '../../types/module';
4+
import RankListExport from './features/RanklistExport';
45

56
export default class ModuleQOJ extends Module {
67
run() {}
78

89
constructor(app: App) {
910
super(app, 'qoj', config.match.qoj);
11+
12+
this.register(new RankListExport(this, 'ranklist-export'));
1013
}
1114
}

src/types/feature.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ export default class Feature {
3333
}
3434
}
3535

36-
on(match: string | Array<string>, func: (args: Dict<string>, params: Dict<string>) => void): boolean {
37-
if (match instanceof Array) {
36+
on(matcher: string | Array<string>, func: (args: Dict<string>, params: Dict<string>) => void): boolean {
37+
if (matcher instanceof Array) {
3838
let ok = false;
39-
for (const singleMatch of match) {
39+
for (const singleMatch of matcher) {
4040
ok = this.on(singleMatch, func);
4141
if (ok) {
4242
return ok;
@@ -46,30 +46,31 @@ export default class Feature {
4646
}
4747

4848
const args: Dict<string> = {};
49-
const matchParts = match.slice(1).split('/');
50-
const pathParts = location.pathname.slice(1).split('/');
51-
if (matchParts[matchParts.length - 1] == '') {
52-
--matchParts.length;
49+
const matcherParts = matcher.slice(1).split('/');
50+
const currentPaths = location.pathname.slice(1).split('/');
51+
52+
if (matcherParts[matcherParts.length - 1] == '') {
53+
--matcherParts.length;
5354
}
54-
if (pathParts[pathParts.length - 1] == '') {
55-
--pathParts.length;
55+
if (currentPaths[currentPaths.length - 1] == '') {
56+
--currentPaths.length;
5657
}
5758

58-
if (pathParts.length < matchParts.length) {
59+
if (currentPaths.length < matcherParts.length) {
5960
return false;
6061
}
61-
if (pathParts.length > matchParts.length && matchParts[matchParts.length - 1] !== '*') {
62+
if (currentPaths.length > matcherParts.length && matcherParts[matcherParts.length - 1] !== '*') {
6263
return false;
6364
}
6465

65-
for (let i = 0; i < matchParts.length; i++) {
66-
if (matchParts[i] === '*') {
66+
for (let i = 0; i < matcherParts.length; i++) {
67+
if (matcherParts[i] === '*') {
6768
break;
68-
} else if (matchParts[i].startsWith('<') && matchParts[i].endsWith('>')) {
69-
const key = matchParts[i].slice(1, matchParts[i].length - 1);
70-
args[key] = pathParts[i];
69+
} else if (matcherParts[i].startsWith('<') && matcherParts[i].endsWith('>')) {
70+
const key = matcherParts[i].slice(1, matcherParts[i].length - 1);
71+
args[key] = currentPaths[i];
7172
} else {
72-
if (matchParts[i] != pathParts[i]) {
73+
if (matcherParts[i] != currentPaths[i]) {
7374
return false;
7475
}
7576
}

0 commit comments

Comments
 (0)