Browse Source

Merge branch 'master' of https://github.com/syzoj/syzoj

master
Pisces000221 7 years ago
parent
commit
9ef02439c2
  1. 11
      app.js
  2. 4
      config-example.json
  3. 4
      models/article.js
  4. 17
      models/contest.js
  5. 149
      models/contest_player.js
  6. 101
      models/custom_test.js
  7. 63
      models/judge_state.js
  8. 64
      models/problem.js
  9. 1
      models/user.js
  10. 18
      models/waiting_judge.js
  11. 77
      modules/api.js
  12. 108
      modules/api_v2.js
  13. 40
      modules/contest.js
  14. 65
      modules/discussion.js
  15. 109
      modules/problem.js
  16. 62
      modules/submission.js
  17. 6
      modules/user.js
  18. 2
      package.json
  19. 2
      static/libs/semantic-ui/semantic.min.css
  20. 1
      static/mathjax.css
  21. 34
      static/script.js
  22. 6
      static/style.css
  23. 72
      utility.js
  24. 20
      views/article.ejs
  25. 4
      views/article_edit.ejs
  26. 8
      views/contest_ranklist.ejs
  27. 1
      views/contest_submissions.ejs
  28. 30
      views/custom_test_content.ejs
  29. 36
      views/discussion.ejs
  30. 6
      views/footer.ejs
  31. 6
      views/header.ejs
  32. 4
      views/help.ejs
  33. 2
      views/index.ejs
  34. 3
      views/login.ejs
  35. 147
      views/problem.ejs
  36. 2
      views/problem_data.ejs
  37. 2
      views/problem_edit.ejs
  38. 94
      views/problem_manage.ejs
  39. 8
      views/problem_testcases.ejs
  40. 2
      views/ranklist.ejs
  41. 3
      views/sign_up.ejs
  42. 2
      views/statistics.ejs
  43. 102
      views/submission_content.ejs
  44. 24
      views/submissions.ejs
  45. 41
      views/submissions_item.ejs
  46. 14
      views/user.ejs
  47. 9
      views/user_edit.ejs
  48. 11
      views/util.ejs

11
app.js

@ -60,6 +60,17 @@ global.syzoj = {
let multer = require('multer');
app.multer = multer({ dest: syzoj.utils.resolvePath(syzoj.config.upload_dir, 'tmp') });
// Trick to bypass CSRF for APIv2
app.use((() => {
let router = new Express.Router();
app.apiRouter = router;
require('./modules/api_v2');
return router;
})());
let csurf = require('csurf');
app.use(csurf({ cookie: true }));
await this.connectDatabase();
this.loadHooks();
this.loadModules();

4
config-example.json

@ -31,7 +31,9 @@
"data_size": 209715200,
"testdata": 209715200,
"submit_code": 102400,
"submit_answer": 10485760
"submit_answer": 10485760,
"custom_test_input": 20971520,
"testdata_filecount": 5
},
"page": {
"problem": 50,

4
models/article.js

@ -28,9 +28,10 @@ let model = db.define('article', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
title: { type: Sequelize.STRING(80) },
content: { type: Sequelize.TEXT },
content: { type: Sequelize.TEXT('medium') },
user_id: { type: Sequelize.INTEGER },
problem_id: { type: Sequelize.INTEGER },
public_time: { type: Sequelize.INTEGER },
update_time: { type: Sequelize.INTEGER },
@ -61,6 +62,7 @@ class Article extends Model {
content: '',
user_id: 0,
problem_id: 0,
public_time: 0,
update_time: 0,

17
models/contest.js

@ -123,9 +123,8 @@ class Contest extends Model {
let problems = await this.getProblems();
if (!problems.includes(judge_state.problem_id)) throw new ErrorMessage('当前比赛中无此题目。');
let player;
await syzoj.utils.lock(['Contest::newSubmission', 'create_player', judge_state.user_id], async () => {
player = await ContestPlayer.findInContest({
await syzoj.utils.lock(['Contest::newSubmission', judge_state.user_id], async () => {
let player = await ContestPlayer.findInContest({
contest_id: this.id,
user_id: judge_state.user_id
});
@ -136,14 +135,14 @@ class Contest extends Model {
user_id: judge_state.user_id
});
}
});
await player.updateScore(judge_state);
await player.save();
await player.updateScore(judge_state);
await player.save();
await this.loadRelationships();
await this.ranklist.updatePlayer(this, player);
await this.ranklist.save();
await this.loadRelationships();
await this.ranklist.updatePlayer(this, player);
await this.ranklist.save();
});
}
async isRunning(now) {

149
models/contest_player.js

@ -69,99 +69,100 @@ class ContestPlayer extends Model {
}
async updateScore(judge_state) {
await syzoj.utils.lock(['ContestPlayer::updateScore', this.id], async () => {
await this.loadRelationships();
if (this.contest.type === 'ioi') {
if (!judge_state.pending) {
if (!this.score_details[judge_state.problem_id]) {
this.score_details[judge_state.problem_id] = {
score: judge_state.score,
judge_id: judge_state.id,
submissions: {}
};
}
this.score_details[judge_state.problem_id].submissions[judge_state.id] = {
judge_id: judge_state.id,
await this.loadRelationships();
if (this.contest.type === 'ioi') {
if (!judge_state.pending) {
if (!this.score_details[judge_state.problem_id]) {
this.score_details[judge_state.problem_id] = {
score: judge_state.score,
time: judge_state.submit_time
judge_id: judge_state.id,
submissions: {}
};
}
let arr = Object.values(this.score_details[judge_state.problem_id].submissions);
arr.sort((a, b) => a.time - b.time);
let maxScoreSubmission = null;
for (let x of arr) {
if (!maxScoreSubmission || x.score >= maxScoreSubmission.score && maxScoreSubmission.score < 100) {
maxScoreSubmission = x;
}
}
this.score_details[judge_state.problem_id].submissions[judge_state.id] = {
judge_id: judge_state.id,
score: judge_state.score,
time: judge_state.submit_time
};
this.score_details[judge_state.problem_id].judge_id = maxScoreSubmission.judge_id;
this.score_details[judge_state.problem_id].score = maxScoreSubmission.score;
this.score_details[judge_state.problem_id].time = maxScoreSubmission.time;
let arr = Object.values(this.score_details[judge_state.problem_id].submissions);
arr.sort((a, b) => a.time - b.time);
this.score = 0;
for (let x in this.score_details) {
this.score += this.score_details[x].score;
let maxScoreSubmission = null;
for (let x of arr) {
if (!maxScoreSubmission || x.score >= maxScoreSubmission.score && maxScoreSubmission.score < 100) {
maxScoreSubmission = x;
}
}
} else if (this.contest.type === 'noi') {
this.score_details[judge_state.problem_id] = {
score: judge_state.score,
judge_id: judge_state.id
};
this.score_details[judge_state.problem_id].judge_id = maxScoreSubmission.judge_id;
this.score_details[judge_state.problem_id].score = maxScoreSubmission.score;
this.score_details[judge_state.problem_id].time = maxScoreSubmission.time;
this.score = 0;
for (let x in this.score_details) {
this.score += this.score_details[x].score;
}
} else if (this.contest.type === 'acm') {
if (!judge_state.pending) {
if (!this.score_details[judge_state.problem_id]) {
this.score_details[judge_state.problem_id] = {
accepted: false,
unacceptedCount: 0,
acceptedTime: 0,
judge_id: 0,
submissions: {}
};
}
}
} else if (this.contest.type === 'noi') {
if (this.score_details[judge_state.problem_id] && this.score_details[judge_state.problem_id].judge_id > judge_state.id) return;
this.score_details[judge_state.problem_id].submissions[judge_state.id] = {
judge_id: judge_state.id,
accepted: judge_state.status === 'Accepted',
compiled: judge_state.status !== 'Compile Error',
time: judge_state.submit_time
this.score_details[judge_state.problem_id] = {
score: judge_state.score,
judge_id: judge_state.id
};
this.score = 0;
for (let x in this.score_details) {
this.score += this.score_details[x].score;
}
} else if (this.contest.type === 'acm') {
if (!judge_state.pending) {
if (!this.score_details[judge_state.problem_id]) {
this.score_details[judge_state.problem_id] = {
accepted: false,
unacceptedCount: 0,
acceptedTime: 0,
judge_id: 0,
submissions: {}
};
}
let arr = Object.values(this.score_details[judge_state.problem_id].submissions);
arr.sort((a, b) => a.time - b.time);
this.score_details[judge_state.problem_id].unacceptedCount = 0;
this.score_details[judge_state.problem_id].judge_id = 0;
this.score_details[judge_state.problem_id].accepted = 0;
for (let x of arr) {
if (x.accepted) {
this.score_details[judge_state.problem_id].accepted = true;
this.score_details[judge_state.problem_id].acceptedTime = x.time;
this.score_details[judge_state.problem_id].judge_id = x.judge_id;
break;
} else if (x.compiled) {
this.score_details[judge_state.problem_id].unacceptedCount++;
}
}
this.score_details[judge_state.problem_id].submissions[judge_state.id] = {
judge_id: judge_state.id,
accepted: judge_state.status === 'Accepted',
compiled: judge_state.status !== 'Compile Error',
time: judge_state.submit_time
};
if (!this.score_details[judge_state.problem_id].accepted) {
this.score_details[judge_state.problem_id].judge_id = arr[arr.length - 1].judge_id;
let arr = Object.values(this.score_details[judge_state.problem_id].submissions);
arr.sort((a, b) => a.time - b.time);
this.score_details[judge_state.problem_id].unacceptedCount = 0;
this.score_details[judge_state.problem_id].judge_id = 0;
this.score_details[judge_state.problem_id].accepted = 0;
for (let x of arr) {
if (x.accepted) {
this.score_details[judge_state.problem_id].accepted = true;
this.score_details[judge_state.problem_id].acceptedTime = x.time;
this.score_details[judge_state.problem_id].judge_id = x.judge_id;
break;
} else if (x.compiled) {
this.score_details[judge_state.problem_id].unacceptedCount++;
}
}
this.score = 0;
for (let x in this.score_details) {
if (this.score_details[x].accepted) this.score++;
}
if (!this.score_details[judge_state.problem_id].accepted) {
this.score_details[judge_state.problem_id].judge_id = arr[arr.length - 1].judge_id;
}
this.score = 0;
for (let x in this.score_details) {
if (this.score_details[x].accepted) this.score++;
}
}
});
}
}
getModel() { return model; }

101
models/custom_test.js

@ -0,0 +1,101 @@
/*
* This file is part of SYZOJ.
*
* Copyright (c) 2016 Menci <huanghaorui301@gmail.com>
*
* SYZOJ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* SYZOJ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with SYZOJ. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
let Sequelize = require('sequelize');
let db = syzoj.db;
let User = syzoj.model('user');
let Problem = syzoj.model('problem');
let model = db.define('custom_test', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
input_filepath: { type: Sequelize.TEXT },
code: { type: Sequelize.TEXT('medium') },
language: { type: Sequelize.STRING(20) },
status: { type: Sequelize.STRING(50) },
time: { type: Sequelize.INTEGER },
pending: { type: Sequelize.BOOLEAN },
memory: { type: Sequelize.INTEGER },
result: { type: Sequelize.TEXT('medium'), json: true },
user_id: { type: Sequelize.INTEGER },
problem_id: { type: Sequelize.INTEGER },
submit_time: { type: Sequelize.INTEGER }
}, {
timestamps: false,
tableName: 'custom_test',
indexes: [
{
fields: ['status'],
},
{
fields: ['user_id'],
},
{
fields: ['problem_id'],
}
]
});
let Model = require('./common');
class CustomTest extends Model {
static async create(val) {
return CustomTest.fromRecord(CustomTest.model.build(Object.assign({
input_filepath: '',
code: '',
language: '',
user_id: 0,
problem_id: 0,
submit_time: parseInt((new Date()).getTime() / 1000),
pending: true,
time: 0,
memory: 0,
status: 'Waiting',
}, val)));
}
async loadRelationships() {
this.user = await User.fromID(this.user_id);
this.problem = await Problem.fromID(this.problem_id);
}
async updateResult(result) {
this.pending = result.pending;
this.status = result.status;
this.time = result.time_used;
this.memory = result.memory_used;
this.result = result;
}
getModel() { return model; }
}
CustomTest.model = model;
module.exports = CustomTest;

63
models/judge_state.js

@ -48,9 +48,8 @@ let model = db.define('judge_state', {
submit_time: { type: Sequelize.INTEGER },
/*
* "type" indicate it's contest's submission(type = 1) or normal submission(type = 0)
* type = 2: this is a test submission
* if it's contest's submission (type = 1), the type_info is contest_id
* use this way represent because it's easy to expand
* use this way represent because it's easy to expand // Menci:这锅我不背,是 Chenyao 留下来的坑。
*/
type: { type: Sequelize.INTEGER },
type_info: { type: Sequelize.INTEGER }
@ -105,7 +104,7 @@ class JudgeState extends Model {
await this.loadRelationships();
if (user && user.id === this.problem.user_id) return true;
else if (this.type === 0 || this.type == 2) return this.problem.is_public || (user && (await user.hasPrivilege('manage_problem')));
else if (this.type === 0) return this.problem.is_public || (user && (await user.hasPrivilege('manage_problem')));
else if (this.type === 1) {
let contest = await Contest.fromID(this.type_info);
if (await contest.isRunning()) {
@ -120,7 +119,7 @@ class JudgeState extends Model {
await this.loadRelationships();
if (user && user.id === this.problem.user_id) return true;
else if (this.type === 0 || this.type === 2) return this.problem.is_public || (user && (await user.hasPrivilege('manage_problem')));
else if (this.type === 0) return this.problem.is_public || (user && (await user.hasPrivilege('manage_problem')));
else if (this.type === 1) {
let contest = await Contest.fromID(this.type_info);
if (await contest.isRunning()) {
@ -135,7 +134,7 @@ class JudgeState extends Model {
await this.loadRelationships();
if (user && user.id === this.problem.user_id) return true;
else if (this.type === 0 || this.type === 2) return this.problem.is_public || (user && (await user.hasPrivilege('manage_problem')));
else if (this.type === 0) return this.problem.is_public || (user && (await user.hasPrivilege('manage_problem')));
else if (this.type === 1) {
let contest = await Contest.fromID(this.type_info);
if (await contest.isRunning()) {
@ -150,7 +149,7 @@ class JudgeState extends Model {
await this.loadRelationships();
if (user && user.id === this.problem.user_id) return true;
else if (this.type === 0 || this.type === 2) return this.problem.is_public || (user && (await user.hasPrivilege('manage_problem')));
else if (this.type === 0) return this.problem.is_public || (user && (await user.hasPrivilege('manage_problem')));
else if (this.type === 1) {
let contest = await Contest.fromID(this.type_info);
if (await contest.isRunning()) {
@ -174,30 +173,28 @@ class JudgeState extends Model {
}
async updateRelatedInfo(newSubmission) {
if (this.type === 0 || this.type === 2) {
if (newSubmission) {
await this.loadRelationships();
await this.user.refreshSubmitInfo();
this.problem.submit_num++;
await this.user.save();
await this.problem.save();
} else if (this.status === 'Accepted') {
await this.loadRelationships();
await this.user.refreshSubmitInfo();
this.problem.ac_num++;
await this.user.save();
await this.problem.save();
}
} else if (this.type === 1) {
let contest = await Contest.fromID(this.type_info);
await contest.newSubmission(this);
} else if (this.type === 2) {
if (newSubmission || this.status === 'Accepted') {
await this.loadRelationships();
await this.user.refreshSubmitInfo();
await this.user.save();
}
}
await syzoj.utils.lock(['JudgeState::updateRelatedInfo', 'problem', this.problem_id], async () => {
await syzoj.utils.lock(['JudgeState::updateRelatedInfo', 'user', this.user_id], async () => {
if (this.type === 0) {
if (newSubmission) {
await this.loadRelationships();
await this.user.refreshSubmitInfo();
this.problem.submit_num++;
await this.user.save();
await this.problem.save();
} else if (this.status === 'Accepted') {
await this.loadRelationships();
await this.user.refreshSubmitInfo();
this.problem.ac_num++;
await this.user.save();
await this.problem.save();
}
} else if (this.type === 1) {
let contest = await Contest.fromID(this.type_info);
await contest.newSubmission(this);
}
});
});
}
async rejudge() {
@ -219,7 +216,9 @@ class JudgeState extends Model {
let WaitingJudge = syzoj.model('waiting_judge');
let waiting_judge = await WaitingJudge.create({
judge_id: this.id
judge_id: this.id,
priority: 2,
type: 'submission'
});
await waiting_judge.save();
@ -229,7 +228,7 @@ class JudgeState extends Model {
await this.user.save();
}
if (this.type === 0 || this.type === 2) {
if (this.type === 0) {
if (oldStatus === 'Accepted') {
this.problem.ac_num--;
await this.problem.save();

64
models/problem.js

@ -322,10 +322,12 @@ class Problem extends Model {
await syzoj.utils.lock(['Problem::Testdata', this.id], async () => {
let p7zip = new (require('node-7z'));
let unzipSize = 0;
let unzipSize = 0, unzipCount;
await p7zip.list(path).progress(files => {
unzipCount = files.length;
for (let file of files) unzipSize += file.size;
});
if (!noLimit && unzipCount > syzoj.config.limit.testdata_filecount) throw new ErrorMessage('数据包中的文件太多。');
if (!noLimit && unzipSize > syzoj.config.limit.testdata) throw new ErrorMessage('数据包太大。');
let dir = this.getTestdataPath();
@ -344,13 +346,17 @@ class Problem extends Model {
let fs = Promise.promisifyAll(require('fs-extra')), path = require('path');
await fs.ensureDirAsync(dir);
let oldSize = 0;
let list = await this.listTestdata();
let oldSize = 0, list = await this.listTestdata(), replace = false, oldCount = 0;
if (list) {
for (let file of list.files) if (file.filename !== filename) oldSize += file.size;
oldCount = list.files.length;
for (let file of list.files) {
if (file.filename !== filename) oldSize += file.size;
else replace = true;
}
}
if (!noLimit && oldSize + size > syzoj.config.limit.testdata) throw new ErrorMessage('数据包太大。');
if (!noLimit && oldCount + !replace > syzoj.config.limit.testdata_filecount) throw new ErrorMessage('数据包中的文件太多。');
await fs.moveAsync(filepath, path.join(dir, filename), { overwrite: true });
await fs.removeAsync(dir + '.zip');
@ -374,8 +380,9 @@ class Problem extends Model {
let p7zip = new (require('node-7z'));
let list = await this.listTestdata(), path = require('path');
await p7zip.add(dir + '.zip', list.files.map(file => path.join(dir, file.filename)));
let list = await this.listTestdata(), path = require('path'), pathlist = list.files.map(file => path.join(dir, file.filename));
if (!pathlist.length) throw new ErrorMessage('无测试数据。');
await p7zip.add(dir + '.zip', pathlist);
});
}
@ -448,13 +455,15 @@ class Problem extends Model {
if (this.memory_limit <= 0) return 'Invalid memory limit';
if (this.memory_limit > syzoj.config.limit.memory_limit) return 'Memory limit too large';
let filenameRE = /^[\w \-\+\.]*$/;
if (this.file_io_input_name && !filenameRE.test(this.file_io_input_name)) return 'Invalid input file name';
if (this.file_io_output_name && !filenameRE.test(this.file_io_output_name)) return 'Invalid output file name';
if (this.type === 'traditional') {
let filenameRE = /^[\w \-\+\.]*$/;
if (this.file_io_input_name && !filenameRE.test(this.file_io_input_name)) return 'Invalid input file name';
if (this.file_io_output_name && !filenameRE.test(this.file_io_output_name)) return 'Invalid output file name';
if (this.file_io) {
if (!this.file_io_input_name) return 'No input file name';
if (!this.file_io_output_name) return 'No output file name';
if (this.file_io) {
if (!this.file_io_input_name) return 'No input file name';
if (!this.file_io_output_name) return 'No output file name';
}
}
return null;
@ -599,6 +608,7 @@ class Problem extends Model {
await db.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id);
await db.query('UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
await db.query('UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
await db.query('UPDATE `article` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
let Contest = syzoj.model('contest');
let contests = await Contest.all();
@ -637,6 +647,36 @@ class Problem extends Model {
await this.save();
}
async delete() {
let fs = Promise.promisifyAll(require('fs-extra'));
let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = oldTestdataDir + '.zip';
await fs.removeAsync(oldTestdataDir);
await fs.removeAsync(oldTestdataZip);
let JudgeState = syzoj.model('judge_state');
let submissions = await JudgeState.query(null, { problem_id: this.id }), submitCnt = {}, acUsers = new Set();
for (let sm of submissions) {
if (sm.status === 'Accepted') acUsers.add(sm.user_id);
if (!submitCnt[sm.user_id]) {
submitCnt[sm.user_id] = 1;
} else {
submitCnt[sm.user_id]++;
}
}
for (let u in submitCnt) {
let user = await User.fromID(u);
user.submit_num -= submitCnt[u];
if (acUsers.has(parseInt(u))) user.ac_num--;
await user.save();
}
await db.query('DELETE FROM `problem` WHERE `id` = ' + this.id);
await db.query('DELETE FROM `judge_state` WHERE `problem_id` = ' + this.id);
await db.query('DELETE FROM `problem_tag_map` WHERE `problem_id` = ' + this.id);
await db.query('DELETE FROM `article` WHERE `problem_id` = ' + this.id);
}
getModel() { return model; }
}

1
models/user.js

@ -37,6 +37,7 @@ let model = db.define('user', {
is_admin: { type: Sequelize.BOOLEAN },
is_show: { type: Sequelize.BOOLEAN },
public_email: { type: Sequelize.BOOLEAN },
sex: { type: Sequelize.INTEGER }
}, {

18
models/waiting_judge.js

@ -23,10 +23,19 @@ let Sequelize = require('sequelize');
let db = syzoj.db;
let JudgeState = syzoj.model('judge_state');
let CustomTest = syzoj.model('custom_test');
let model = db.define('waiting_judge', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
judge_id: { type: Sequelize.INTEGER }
judge_id: { type: Sequelize.INTEGER },
// Smaller is higher
priority: { type: Sequelize.INTEGER },
type: {
type: Sequelize.ENUM,
values: ['submission', 'custom-test']
}
}, {
timestamps: false,
tableName: 'waiting_judge',
@ -41,10 +50,15 @@ let Model = require('./common');
class WaitingJudge extends Model {
static async create(val) {
return WaitingJudge.fromRecord(WaitingJudge.model.build(Object.assign({
judge_id: 0
judge_id: 0,
priority: 0
}, val)));
}
async getCustomTest() {
return CustomTest.fromID(this.judge_id);
}
async getJudgeState() {
return JudgeState.fromID(this.judge_id);
}

77
modules/api.js

@ -21,8 +21,6 @@
let User = syzoj.model('user');
let Problem = syzoj.model('problem');
let WaitingJudge = syzoj.model('waiting_judge');
let JudgeState = syzoj.model('judge_state');
let File = syzoj.model('file');
function setLoginCookie(username, password, res) {
@ -96,7 +94,8 @@ app.post('/api/sign_up', async (req, res) => {
user = await User.create({
username: req.body.username,
password: req.body.password,
email: req.body.email
email: req.body.email,
public_email: true
});
await user.save();
@ -136,7 +135,8 @@ app.get('/api/sign_up/:token', async (req, res) => {
user = await User.create({
username: obj.username,
password: obj.password,
email: obj.email
email: obj.email,
public_email: true
});
await user.save();
@ -163,75 +163,6 @@ app.post('/api/markdown', async (req, res) => {
}
});
// APIs for judge client
app.get('/api/waiting_judge', async (req, res) => {
try {
if (req.query.session_id !== syzoj.config.judge_token) return res.status(404).send({ err: 'Permission denied' });
let judge_state;
await syzoj.utils.lock('/api/waiting_judge', async () => {
let waiting_judge = await WaitingJudge.findOne();
if (!waiting_judge) {
return;
}
judge_state = await waiting_judge.getJudgeState();
await judge_state.loadRelationships();
await judge_state.problem.loadRelationships();
await waiting_judge.destroy();
});
if (judge_state) {
await judge_state.loadRelationships();
await judge_state.problem.loadRelationships();
if (judge_state.problem.type === 'submit-answer') {
res.send({
have_task: 1,
judge_id: judge_state.id,
answer_file: judge_state.code,
testdata: judge_state.problem.id,
problem_type: judge_state.problem.type
});
} else {
res.send({
have_task: 1,
judge_id: judge_state.id,
code: judge_state.code,
language: judge_state.language,
testdata: judge_state.problem.id,
time_limit: judge_state.problem.time_limit,
memory_limit: judge_state.problem.memory_limit,
file_io: judge_state.problem.file_io,
file_io_input_name: judge_state.problem.file_io_input_name,
file_io_output_name: judge_state.problem.file_io_output_name,
problem_type: judge_state.problem.type
});
}
} else {
res.send({ have_task: 0 });
}
} catch (e) {
res.status(500).send(e);
}
});
app.post('/api/update_judge/:id', async (req, res) => {
try {
if (req.query.session_id !== syzoj.config.judge_token) return res.status(404).send({ err: 'Permission denied' });
let judge_state = await JudgeState.fromID(req.params.id);
await judge_state.updateResult(JSON.parse(req.body.result));
await judge_state.save();
await judge_state.updateRelatedInfo();
res.send({ return: 0 });
} catch (e) {
syzoj.log(e);
res.status(500).send(e);
}
});
app.get('/static/uploads/answer/:md5', async (req, res) => {
try {
res.sendFile(File.resolvePath('answer', req.params.md5));

108
modules/api_v2.js

@ -19,11 +19,10 @@
'use strict';
let Problem = syzoj.model('problem');
let ProblemTag = syzoj.model('problem_tag');
app.get('/api/v2/search/problems/:keyword*?', async (req, res) => {
try {
let Problem = syzoj.model('problem');
let keyword = req.params.keyword || '';
let problems = await Problem.query(null, {
title: { like: `%${req.params.keyword}%` }
@ -54,6 +53,9 @@ app.get('/api/v2/search/problems/:keyword*?', async (req, res) => {
app.get('/api/v2/search/tags/:keyword*?', async (req, res) => {
try {
let Problem = syzoj.model('problem');
let ProblemTag = syzoj.model('problem_tag');
let keyword = req.params.keyword || '';
let tags = await ProblemTag.query(null, {
name: { like: `%${req.params.keyword}%` }
@ -69,7 +71,7 @@ app.get('/api/v2/search/tags/:keyword*?', async (req, res) => {
}
});
app.post('/api/v2/markdown', async (req, res) => {
app.apiRouter.post('/api/v2/markdown', async (req, res) => {
try {
let s = await syzoj.utils.markdown(req.body.s.toString(), null, req.body.noReplaceUI === 'true');
res.send(s);
@ -78,3 +80,101 @@ app.post('/api/v2/markdown', async (req, res) => {
res.send(e);
}
});
// APIs for judge client
app.apiRouter.post('/api/v2/judge/peek', async (req, res) => {
try {
if (req.query.session_id !== syzoj.config.judge_token) return res.status(404).send({ err: 'Permission denied' });
let WaitingJudge = syzoj.model('waiting_judge');
let JudgeState = syzoj.model('judge_state');
let judge_state, custom_test;
await syzoj.utils.lock('/api/v2/judge/peek', async () => {
let waiting_judge = await WaitingJudge.findOne({ order: [['priority', 'ASC'], ['id', 'ASC']] });
if (!waiting_judge) {
return;
}
if (waiting_judge.type === 'submission') {
judge_state = await waiting_judge.getJudgeState();
await judge_state.loadRelationships();
} else {
custom_test = await waiting_judge.getCustomTest();
await custom_test.loadRelationships();
}
await waiting_judge.destroy();
});
if (judge_state) {
if (judge_state.problem.type === 'submit-answer') {
res.send({
have_task: 1,
judge_id: judge_state.id,
answer_file: judge_state.code,
testdata: judge_state.problem.id,
problem_type: judge_state.problem.type,
type: 'submission'
});
} else {
res.send({
have_task: 1,
judge_id: judge_state.id,
code: judge_state.code,
language: judge_state.language,
testdata: judge_state.problem.id,
time_limit: judge_state.problem.time_limit,
memory_limit: judge_state.problem.memory_limit,
file_io: judge_state.problem.file_io,
file_io_input_name: judge_state.problem.file_io_input_name,
file_io_output_name: judge_state.problem.file_io_output_name,
problem_type: judge_state.problem.type,
type: 'submission'
});
}
} else if (custom_test) {
res.send({
have_task: 1,
judge_id: custom_test.id,
code: custom_test.code,
language: custom_test.language,
input_file: (await require('fs-extra').readFileAsync(custom_test.input_filepath)).toString(),
time_limit: custom_test.problem.time_limit,
memory_limit: custom_test.problem.memory_limit,
file_io: custom_test.problem.file_io,
file_io_input_name: custom_test.problem.file_io_input_name,
file_io_output_name: custom_test.problem.file_io_output_name,
problem_type: custom_test.problem.type,
type: 'custom_test'
});
} else {
res.send({ have_task: 0 });
}
} catch (e) {
res.status(500).send(e);
}
});
app.apiRouter.post('/api/v2/judge/update/:id', async (req, res) => {
try {
if (req.query.session_id !== syzoj.config.judge_token) return res.status(404).send({ err: 'Permission denied' });
if (req.body.type === 'custom-test') {
let CustomTest = syzoj.model('custom_test');
let custom_test = CustomTest.fromID(req.params.id);
await custom_test.updateResult(JSON.parse(req.body.result));
await custom_test.save();
} else if (req.body.type === 'submission') {
let JudgeState = syzoj.model('judge_state');
let judge_state = await JudgeState.fromID(req.params.id);
await judge_state.updateResult(JSON.parse(req.body.result));
await judge_state.save();
await judge_state.updateRelatedInfo();
}
res.send({ return: 0 });
} catch (e) {
syzoj.log(e);
res.status(500).send(e);
}
});

40
modules/contest.js

@ -329,6 +329,7 @@ app.get('/contest/:id/:pid', async (req, res) => {
try {
let contest_id = parseInt(req.params.id);
let contest = await Contest.fromID(contest_id);
if (!contest) throw new ErrorMessage('无此比赛。');
let problems_id = await contest.getProblems();
@ -354,6 +355,8 @@ app.get('/contest/:id/:pid', async (req, res) => {
let state = await problem.getJudgeState(res.locals.user, false);
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath(), problem.type === 'submit-answer');
await problem.loadRelationships();
res.render('problem', {
pid: pid,
contest: contest,
@ -369,3 +372,40 @@ app.get('/contest/:id/:pid', async (req, res) => {
});
}
});
app.get('/contest/:id/:pid/download/additional_file', async (req, res) => {
try {
let id = parseInt(req.params.id);
let contest = await Contest.fromID(id);
if (!contest) throw new ErrorMessage('无此比赛。');
let problems_id = await contest.getProblems();
let pid = parseInt(req.params.pid);
if (!pid || pid < 1 || pid > problems_id.length) throw new ErrorMessage('无此题目。');
let problem_id = problems_id[pid - 1];
let problem = await Problem.fromID(problem_id);
contest.ended = await contest.isEnded();
if (!(await contest.isRunning() || contest.ended)) {
if (await problem.isAllowedUseBy(res.locals.user)) {
return res.redirect(syzoj.utils.makeUrl(['problem', problem_id, 'download', 'additional_file']));
}
throw new ErrorMessage('比赛尚未开始。');
}
await problem.loadRelationships();
if (!problem.additional_file) throw new ErrorMessage('无附加文件。');
console.log(`additional_file_${id}_${pid}.zip`);
res.download(problem.additional_file.getPath(), `additional_file_${id}_${pid}.zip`);
} catch (e) {
syzoj.log(e);
res.status(404);
res.render('error', {
err: e
});
}
});

65
modules/discussion.js

@ -19,20 +19,51 @@
'use strict';
let Problem = syzoj.model('problem');
let Article = syzoj.model('article');
let ArticleComment = syzoj.model('article-comment');
let User = syzoj.model('user');
app.get('/discussion', async (req, res) => {
try {
let paginate = syzoj.utils.paginate(await Article.count(), req.query.page, syzoj.config.page.discussion);
let articles = await Article.query(paginate, null, [['public_time', 'desc']]);
let where = { problem_id: null };
let paginate = syzoj.utils.paginate(await Article.count(where), req.query.page, syzoj.config.page.discussion);
let articles = await Article.query(paginate, where, [['public_time', 'desc']]);
for (let article of articles) await article.loadRelationships();
res.render('discussion', {
articles: articles,
paginate: paginate
paginate: paginate,
problem: null
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/problem/:pid/discussion', async (req, res) => {
try {
let pid = parseInt(req.params.pid);
let problem = await Problem.fromID(pid);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) {
throw new ErrorMessage('您没有权限进行此操作。');
}
let where = { problem_id: pid };
let paginate = syzoj.utils.paginate(await Article.count(where), req.query.page, syzoj.config.page.discussion);
let articles = await Article.query(paginate, where, [['public_time', 'desc']]);
for (let article of articles) await article.loadRelationships();
res.render('discussion', {
articles: articles,
paginate: paginate,
problem: problem
});
} catch (e) {
syzoj.log(e);
@ -54,8 +85,8 @@ app.get('/article/:id', async (req, res) => {
article.content = await syzoj.utils.markdown(article.content);
let where = { article_id: id };
let paginate = syzoj.utils.paginate(await ArticleComment.count(where), req.query.page, syzoj.config.page.article_comment);
let commentsCount = await ArticleComment.count(where);
let paginate = syzoj.utils.paginate(commentsCount, req.query.page, syzoj.config.page.article_comment);
let comments = await ArticleComment.query(paginate, where, [['public_time', 'desc']]);
@ -65,10 +96,20 @@ app.get('/article/:id', async (req, res) => {
await comment.loadRelationships();
}
let problem = null;
if (article.problem_id) {
problem = await Problem.fromID(article.problem_id);
if (!await problem.isAllowedUseBy(res.locals.user)) {
throw new ErrorMessage('您没有权限进行此操作。');
}
}
res.render('article', {
article: article,
comments: comments,
paginate: paginate
paginate: paginate,
problem: problem,
commentsCount: commentsCount
});
} catch (e) {
syzoj.log(e);
@ -116,6 +157,14 @@ app.post('/article/:id/edit', async (req, res) => {
article = await Article.create();
article.user_id = res.locals.user.id;
article.public_time = article.sort_time = time;
if (req.query.problem_id) {
let problem = await Problem.fromID(req.query.problem_id);
if (!problem) throw new ErrorMessage('无此题目。');
article.problem_id = problem.id;
} else {
article.problem_id = null;
}
} else {
if (!await article.isAllowedEditBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
}
@ -137,7 +186,7 @@ app.post('/article/:id/edit', async (req, res) => {
}
});
app.get('/article/:id/delete', async (req, res) => {
app.post('/article/:id/delete', async (req, res) => {
try {
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });
@ -192,7 +241,7 @@ app.post('/article/:id/comment', async (req, res) => {
}
});
app.get('/article/:article_id/comment/:id/delete', async (req, res) => {
app.post('/article/:article_id/comment/:id/delete', async (req, res) => {
try {
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': req.originalUrl }) });

109
modules/problem.js

@ -21,10 +21,12 @@
let Problem = syzoj.model('problem');
let JudgeState = syzoj.model('judge_state');
let CustomTest = syzoj.model('custom_test');
let WaitingJudge = syzoj.model('waiting_judge');
let Contest = syzoj.model('contest');
let ProblemTag = syzoj.model('problem_tag');
let ProblemTagMap = syzoj.model('problem_tag_map');
let Article = syzoj.model('article');
app.get('/problems', async (req, res) => {
try {
@ -102,7 +104,7 @@ app.get('/problems/search', async (req, res) => {
}
}
let order = [syzoj.db.literal('`id` = ' + id + ' DESC')];
let order = [syzoj.db.literal('`id` = ' + id + ' DESC'), ['id', 'ASC']];
let paginate = syzoj.utils.paginate(await Problem.count(where), req.query.page, syzoj.config.page.problem);
let problems = await Problem.query(paginate, where, order);
@ -204,11 +206,14 @@ app.get('/problem/:id', async (req, res) => {
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath(), problem.type === 'submit-answer');
let discussionCount = await Article.count({ problem_id: id });
res.render('problem', {
problem: problem,
state: state,
lastLanguage: res.locals.user ? await res.locals.user.getLastSubmitLanguage() : null,
testcases: testcases
testcases: testcases,
discussionCount: discussionCount
});
} catch (e) {
syzoj.log(e);
@ -489,12 +494,10 @@ app.post('/problem/:id/manage', app.multer.fields([{ name: 'testdata', maxCount:
problem.time_limit = req.body.time_limit;
problem.memory_limit = req.body.memory_limit;
problem.file_io = req.body.io_method === 'file-io';
problem.file_io_input_name = req.body.file_io_input_name;
problem.file_io_output_name = req.body.file_io_output_name;
if (req.body.type === 'interaction') {
throw new ErrorMessage('暂不支持该题目类型。');
if (req.body.type === 'traditional') {
problem.file_io = req.body.io_method === 'file-io';
problem.file_io_input_name = req.body.file_io_input_name;
problem.file_io_output_name = req.body.file_io_output_name;
}
if (problem.type === 'submit-answer' && req.body.type !== 'submit-answer' || problem.type !== 'submit-answer' && req.body.type === 'submit-answer') {
@ -549,11 +552,11 @@ async function setPublic(req, res, is_public) {
}
}
app.get('/problem/:id/public', async (req, res) => {
app.post('/problem/:id/public', async (req, res) => {
await setPublic(req, res, true);
});
app.get('/problem/:id/dis_public', async (req, res) => {
app.post('/problem/:id/dis_public', async (req, res) => {
await setPublic(req, res, false);
});
@ -627,13 +630,15 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
await judge_state.save();
} else {
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
judge_state.type = problem.is_public ? 0 : 2;
judge_state.type = 0;
await judge_state.save();
}
await judge_state.updateRelatedInfo(true);
let waiting_judge = await WaitingJudge.create({
judge_id: judge_state.id
judge_id: judge_state.id,
priority: 1,
type: 'submission'
});
await waiting_judge.save();
@ -647,6 +652,25 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
}
});
app.post('/problem/:id/delete', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!problem.isAllowedManageBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
await problem.delete();
res.redirect(syzoj.utils.makeUrl(['problem']));
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/problem/:id/testdata', async (req, res) => {
try {
let id = parseInt(req.params.id);
@ -697,7 +721,7 @@ app.post('/problem/:id/testdata/upload', app.multer.array('file'), async (req, r
}
});
app.get('/problem/:id/testdata/delete/:filename', async (req, res) => {
app.post('/problem/:id/testdata/delete/:filename', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
@ -804,3 +828,62 @@ app.get('/problem/:id/statistics/:type', async (req, res) => {
});
}
});
/*
app.post('/problem/:id/custom-test', app.multer.fields([{ name: 'code_upload', maxCount: 1 }, { name: 'input_file', maxCount: 1 }]), async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) });
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
let filepath;
if (req.files['input_file']) {
if (req.files['input_file'][0].size > syzoj.config.limit.custom_test_input) throw new ErrorMessage('输入数据过长。');
filepath = req.files['input_file'][0].path;
} else {
if (req.body.input_file_textarea.length > syzoj.config.limit.custom_test_input) throw new ErrorMessage('输入数据过长。');
filepath = await require('tmp-promise').tmpName({ template: '/tmp/tmp-XXXXXX' });
await require('fs-extra').writeFileAsync(filepath, req.body.input_file_textarea);
}
let code;
if (req.files['code_upload']) {
if (req.files['code_upload'][0].size > syzoj.config.limit.submit_code) throw new ErrorMessage('代码过长。');
code = (await require('fs-extra').readFileAsync(req.files['code_upload'][0].path)).toString();
} else {
if (req.body.code.length > syzoj.config.limit.submit_code) throw new ErrorMessage('代码过长。');
code = req.body.code;
}
let custom_test = await CustomTest.create({
input_filepath: filepath,
code: code,
language: req.body.language,
user_id: res.locals.user.id,
problem_id: id
});
await custom_test.save();
let waiting_judge = await WaitingJudge.create({
judge_id: custom_test.id,
priority: 3,
type: 'custom_test'
});
await waiting_judge.save();
res.send({
id: custom_test.id
});
} catch (e) {
syzoj.log(e);
res.send({
err: e
});
}
});
*/

62
modules/submission.js

@ -87,36 +87,46 @@ app.get('/submissions', async (req, res) => {
}
});
app.get('/submissions/:id/ajax', async (req, res) => {
app.get('/submissions/:ids/ajax', async (req, res) => {
try {
let judge_state = await JudgeState.fromID(req.params.id);
if (!judge_state) throw new ErrorMessage('无此提交记录。');
let ids = req.params.ids.split(','), rendered = {};
await judge_state.loadRelationships();
for (let id of ids) {
let judge_state = await JudgeState.fromID(id);
if (!judge_state) throw new ErrorMessage('无此提交记录。');
judge_state.allowedSeeCode = await judge_state.isAllowedSeeCodeBy(res.locals.user);
judge_state.allowedSeeData = await judge_state.isAllowedSeeDataBy(res.locals.user);
await judge_state.loadRelationships();
let contest;
if (judge_state.type === 1) {
contest = await Contest.fromID(judge_state.type_info);
contest.ended = await contest.isEnded();
judge_state.allowedSeeCode = await judge_state.isAllowedSeeCodeBy(res.locals.user);
judge_state.allowedSeeData = await judge_state.isAllowedSeeDataBy(res.locals.user);
let problems_id = await contest.getProblems();
judge_state.problem_id = problems_id.indexOf(judge_state.problem_id) + 1;
judge_state.problem.title = syzoj.utils.removeTitleTag(judge_state.problem.title);
let contest;
if (judge_state.type === 1) {
contest = await Contest.fromID(judge_state.type_info);
contest.ended = await contest.isEnded();
if (contest.type === 'noi' && !contest.ended && !await judge_state.problem.isAllowedEditBy(res.locals.user)) {
if (!['Compile Error', 'Waiting', 'Compiling'].includes(judge_state.status)) {
judge_state.status = 'Submitted';
let problems_id = await contest.getProblems();
judge_state.problem_id = problems_id.indexOf(judge_state.problem_id) + 1;
judge_state.problem.title = syzoj.utils.removeTitleTag(judge_state.problem.title);
if (contest.type === 'noi' && !contest.ended && !await judge_state.problem.isAllowedEditBy(res.locals.user)) {
if (!['Compile Error', 'Waiting', 'Compiling'].includes(judge_state.status)) {
judge_state.status = 'Submitted';
}
}
}
let o = { pending: judge_state.pending, html: null, status: judge_state.status };
o.html = await require('util').promisify(app.render).bind(app)('submissions_item', {
contest: contest,
judge: judge_state
});
rendered[id] = o;
}
res.render('submissions_item', {
contest: contest,
judge: judge_state
});
res.send(rendered);
} catch (e) {
syzoj.log(e);
res.render('error', {
@ -149,6 +159,7 @@ app.get('/submission/:id', async (req, res) => {
judge.allowedRejudge = await judge.problem.isAllowedEditBy(res.locals.user);
judge.allowedManage = await judge.problem.isAllowedManageBy(res.locals.user);
let hideScore = false;
if (contest) {
let problems_id = await contest.getProblems();
judge.problem_id = problems_id.indexOf(judge.problem_id) + 1;
@ -158,10 +169,13 @@ app.get('/submission/:id', async (req, res) => {
if (!['Compile Error', 'Waiting', 'Compiling'].includes(judge.status)) {
judge.status = 'Submitted';
}
hideScore = true;
}
}
res.render('submission', {
hideScore, hideScore,
contest: contest,
judge: judge
});
@ -197,6 +211,7 @@ app.get('/submission/:id/ajax', async (req, res) => {
judge.allowedRejudge = await judge.problem.isAllowedEditBy(res.locals.user);
judge.allowedManage = await judge.problem.isAllowedManageBy(res.locals.user);
let hideScore = false;
if (contest) {
let problems_id = await contest.getProblems();
judge.problem_id = problems_id.indexOf(judge.problem_id) + 1;
@ -206,10 +221,13 @@ app.get('/submission/:id/ajax', async (req, res) => {
if (!['Compile Error', 'Waiting', 'Compiling'].includes(judge.status)) {
judge.status = 'Submitted';
}
hideScore = true;
}
}
res.render('submission_content', {
hideScore, hideScore,
contest: contest,
judge: judge
});
@ -221,12 +239,12 @@ app.get('/submission/:id/ajax', async (req, res) => {
}
});
app.get('/submission/:id/rejudge', async (req, res) => {
app.post('/submission/:id/rejudge', async (req, res) => {
try {
let id = parseInt(req.params.id);
let judge = await JudgeState.fromID(id);
if (judge.pending && !req.query.force) throw new ErrorMessage('无法重新评测一个评测中的提交。');
if (judge.pending && !(res.locals.user && await res.locals.user.hasPrivilege('manage_problem'))) throw new ErrorMessage('无法重新评测一个评测中的提交。');
await judge.loadRelationships();

6
modules/user.js

@ -76,7 +76,7 @@ app.get('/sign_up', async (req, res) => {
});
// Logout
app.get('/logout', async (req, res) => {
app.post('/logout', async (req, res) => {
req.session.user_id = null;
res.clearCookie('login');
res.redirect(req.query.url || '/');
@ -94,6 +94,7 @@ app.get('/user/:id', async (req, res) => {
let statistics = await user.getStatistics();
await user.renderInformation();
user.emailVisible = user.public_email || user.allowedEdit;
res.render('user', {
show_user: user,
@ -152,6 +153,7 @@ app.post('/user/:id/edit', async (req, res) => {
if (res.locals.user && await res.locals.user.hasPrivilege('manage_user')) {
if (!syzoj.utils.isValidUsername(req.body.username)) throw new ErrorMessage('无效的用户名。');
user.username = req.body.username;
user.email = req.body.email;
}
if (res.locals.user && res.locals.user.is_admin) {
@ -165,9 +167,9 @@ app.post('/user/:id/edit', async (req, res) => {
await user.setPrivileges(privileges);
}
user.email = req.body.email;
user.information = req.body.information;
user.sex = req.body.sex;
user.public_email = (req.body.public_email === 'on');
await user.save();

2
package.json

@ -29,6 +29,7 @@
"cheerio": "^1.0.0-rc.1",
"cookie-parser": "^1.4.3",
"cssfilter": "0.0.10",
"csurf": "^1.9.0",
"download": "^5.0.3",
"ejs": "^2.5.2",
"express": "^4.14.0",
@ -36,6 +37,7 @@
"file-size": "^1.0.0",
"fs-extra": "^2.1.2",
"gravatar": "^1.5.2",
"js-yaml": "^3.9.0",
"moemark-renderer": "^1.2.6",
"moment": "^2.15.0",
"multer": "^1.2.0",

2
static/libs/semantic-ui/semantic.min.css vendored

@ -17,7 +17,7 @@
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/*,:after,:before{box-sizing:inherit}html{box-sizing:border-box}input[type=email],input[type=password],input[type=search],input[type=text]{-webkit-appearance:none;-moz-appearance:none}/*! normalize.css v3.0.1 | MIT License | git.io/normalize *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*!
*/*,:after,:before{box-sizing:inherit}html{box-sizing:border-box}input[type=email],input[type=password],input[type=search],input[type=text]{-webkit-appearance:none;-moz-appearance:none}/*! normalize.css v3.0.1 | MIT License | git.io/normalize *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:"Roboto Mono","Bitstream Vera Sans Mono",Menlo,Consolas,"Lucida Console",monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*!
* # Semantic UI 2.2.9 - Site
* http://github.com/semantic-org/semantic-ui/
*

1
static/mathjax.css

@ -48,7 +48,6 @@
.mjx-chartest {display: block; visibility: hidden; position: absolute; top: 0; line-height: normal; font-size: 500%}
.mjx-chartest .mjx-char {display: inline}
.mjx-chartest .mjx-box {padding-top: 1000px}
.MJXc-processing {visibility: hidden; position: fixed; width: 0; height: 0; overflow: hidden}
.MJXc-processed {display: none}
.mjx-test {display: block; font-style: normal; font-weight: normal; font-size: 100%; font-size-adjust: none; text-indent: 0; text-transform: none; letter-spacing: normal; word-spacing: normal; overflow: hidden; height: 1px}
.mjx-ex-box-test {position: absolute; width: 1px; height: 60ex}

34
static/script.js

@ -0,0 +1,34 @@
var addUrlParam = function (url, key, val) {
var newParam = encodeURIComponent(key) + '=' + encodeURIComponent(val);
url = url.split('#')[0];
if (url.indexOf('?') === -1) url += '?' + newParam;
else url += '&' + newParam;
return url;
};
$(function () {
$(document).on('click', 'a[href-post]', function (e) {
e.preventDefault();
var form = document.createElement('form');
form.style.display = 'none';
form.method = 'post';
form.action = $(this).attr('href-post');
form.target = '_self';
var input = document.createElement('input');
input.type = 'hidden';
input.name = '_csrf';
input.value = document.head.getAttribute('data-csrf-token');
form.appendChild(input);
document.body.appendChild(form);
form.submit();
});
$('form').each(function () {
this.action = addUrlParam(this.action || location.href, '_csrf', document.head.getAttribute('data-csrf-token'));
});
});

6
static/style.css

@ -361,6 +361,9 @@ body > .ui.page.dimmer {
}
*/
:not(.status_detail).status.success,
.title:hover .status_detail.status.success,
.title.active .status_detail.status.success,
:not(.status_detail).status.submitted,
.title:hover .status_detail.status.submitted,
.title.active .status_detail.status.submitted,
@ -376,6 +379,9 @@ body > .ui.page.dimmer {
color: red;
}
:not(.status_detail).status.invalid_interaction,
.title:hover .status_detail.status.invalid_interaction,
.title.active .status_detail.status.invalid_interaction,
:not(.status_detail).status.runtime_error,
.title:hover .status_detail.status.runtime_error,
.title.active .status_detail.status.runtime_error,

72
utility.js

@ -87,7 +87,7 @@ module.exports = {
let whiteList = Object.assign({}, require('xss/lib/default').whiteList);
delete whiteList.audio;
delete whiteList.video;
for (let tag in whiteList) whiteList[tag] = whiteList[tag].concat(['id', 'style', 'class']);
for (let tag in whiteList) whiteList[tag] = whiteList[tag].concat(['style', 'class']);
let xss = new XSS.FilterXSS({
css: {
whiteList: Object.assign({}, require('cssfilter/lib/default').whiteList, {
@ -95,14 +95,19 @@ module.exports = {
top: true,
bottom: true,
left: true,
right: true
right: true,
"white-space": true
})
},
whiteList: whiteList,
stripIgnoreTag: true
});
let replaceXSS = s => {
return xss.process(s);
s = xss.process(s);
if (s) {
s = `<div style="position: relative; overflow: hidden; ">${s}</div>`;
}
return s;
};
let replaceUI = s => {
if (noReplaceUI) return s;
@ -204,7 +209,7 @@ module.exports = {
let list = await (await fs.readdirAsync(dir)).filterAsync(async x => await syzoj.utils.isFile(path.join(dir, x)));
let res = [];
if (!list.includes('data_rule.txt')) {
if (!list.includes('data.yml')) {
res[0] = {};
res[0].cases = [];
for (let file of list) {
@ -242,52 +247,37 @@ module.exports = {
return getLastInteger(a.input) - getLastInteger(b.input);
});
res.spj = list.some(s => s.startsWith('spj_'));
} else {
let lines = (await fs.readFileAsync(path.join(dir, 'data_rule.txt'))).toString().split('\r').join('').split('\n').filter(x => x.length !== 0);
let config = require('js-yaml').load((await fs.readFileAsync(dir + '/data.yml')));
let input, output, answer;
if (submitAnswer) {
if (lines.length < 4) throw '无效的数据配置文件(data_rule.txt)。';
input = lines[lines.length - 3];
output = lines[lines.length - 2];
answer = lines[lines.length - 1];
} else {
if (lines.length < 3) throw '无效的数据配置文件(data_rule.txt)。';
input = lines[lines.length - 2];
output = lines[lines.length - 1];
}
let input = config.inputFile, output = config.outputFile, answer = config.userOutput;
for (let s = 0; s < lines.length - (submitAnswer ? 3 : 2); ++s) {
res[s] = {};
res[s].cases = [];
let numbers = lines[s].split(' ').filter(x => x);
if (numbers[0].includes(':')) {
let tokens = numbers[0].split(':');
res[s].type = tokens[0] || 'sum';
res[s].score = parseFloat(tokens[1]) || (100 / (lines.length - 2));
numbers.shift();
} else {
res[s].type = 'sum';
res[s].score = 100;
}
for (let i of numbers) {
let testcase = {
input: input.replace('#', i),
output: output.replace('#', i)
};
res = config.subtasks.map(st => ({
score: st.score,
type: st.type,
cases: st.cases.map(c => {
function getFileName(template, id) {
let s = template.split('#').join(String(id));
if (!list.includes(s)) throw `找不到文件 ${s}`;
return s;
}
if (submitAnswer) testcase.answer = answer.replace('#', i);
let o = {};
if (input) o.input = getFileName(input, c);
if (output) o.output = getFileName(output, c);
if (answer) o.answer = getFileName(answer, c);
if (testcase.input !== '-' && !list.includes(testcase.input)) throw `找不到文件 ${testcase.input}`;
if (testcase.output !== '-' && !list.includes(testcase.output)) throw `找不到文件 ${testcase.output}`;
res[s].cases.push(testcase);
}
}
return o;
})
}));
res = res.filter(x => x.cases && x.cases.length !== 0);
res.spj = !!config.specialJudge;
}
res.spj = list.some(s => s.startsWith('spj_'));
return res;
} catch (e) {
console.log(e);

20
views/article.ejs

@ -6,6 +6,17 @@
}
</style>
<div class="padding">
<div class="ui breadcrumb">
<div class="section">讨论</div>
<i class="right angle icon divider"></i>
<% if (problem) { %>
<div class="section">题目</div>
<i class="right angle icon divider"></i>
<a href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'discussion']) %>" class="active section"><%= problem.title %></a>
<% } else { %>
<a href="<%= syzoj.utils.makeUrl(['discussion']) %>" class="section">全局板块</a>
<% } %>
</div>
<h1><%= article.title %></h1>
<p style="font_size: 0.7em"><img style="vertical-align: middle;" src="<%= syzoj.utils.gravatar(article.user.email, 64) %>" width="32" height="32">
<a href="<%= syzoj.utils.makeUrl(['user', article.user_id]) %>"><%= article.user.username %></a><% if (article.user.nameplate) { %><%- article.user.nameplate %><% } %> 于 <%= syzoj.utils.formatDate(article.public_time) %> 发表,<%= syzoj.utils.formatDate(article.update_time) %> 最后更新
@ -25,7 +36,7 @@
<i class="remove icon"></i>
</div>
<a class="ui green ok inverted button" href="<%= syzoj.utils.makeUrl(['article', article.id, 'delete']) %>">
<a class="ui green ok inverted button" href-post="<%= syzoj.utils.makeUrl(['article', article.id, 'delete']) %>">
<i class="checkmark icon"></i>
</a>
@ -38,7 +49,7 @@
</div>
<% if (comments.length) { %>
<div class="ui comments" style="max-width: none;">
<h3 class="ui dividing header">共 <%= comments.length %> 条回复</h3>
<h3 class="ui dividing header">共 <%= commentsCount %> 条回复</h3>
<% for (let comment of comments) { %>
<div class="comment">
<a class="avatar">
@ -65,7 +76,7 @@
<i class="remove icon"></i>
</div>
<a class="ui green ok inverted button" href="<%= syzoj.utils.makeUrl(['article', article.id, 'comment', comment.id, 'delete']) %>">
<a class="ui green ok inverted button" href-post="<%= syzoj.utils.makeUrl(['article', article.id, 'comment', comment.id, 'delete']) %>">
<i class="checkmark icon"></i>
</a>
@ -76,11 +87,10 @@
</div>
<% } %>
</div>
<% } %>
<br>
<div style="margin-bottom: 50px; ">
<% include page %>
</div>
<% } %>
<% if (article.allowedComment) { %>
<form class="ui reply form" method="post" action="<%= syzoj.utils.makeUrl(['article', article.id, 'comment']) %>">
<div class="field">

4
views/article_edit.ejs

@ -6,7 +6,7 @@
}
</style>
<div class="padding">
<form class="ui form" method="post" action="<%= syzoj.utils.makeUrl(['article', article.id, 'edit']) %>">
<form class="ui form" method="post">
<div class="ui top attached tabular menu">
<a class="item active" data-tab="edit">编辑</a>
<a class="item" data-tab="preview" id="preview_tab">预览</a>
@ -39,7 +39,7 @@
<script type="text/javascript">
$(function () {
function render(output, input) {
$.post('/api/markdown', { s: input.val() }, function (s) {
$.post('/api/markdown', { s: input.val(), _csrf: document.head.getAttribute('data-csrf-token') }, function (s) {
// console.log(s);
output.html(s);
});

8
views/contest_ranklist.ejs

@ -37,7 +37,7 @@
i++;
let condition;
if (contest.type === 'acm') condition = item.player.score_details[problem.id] && item.player.score_details[problem.id].accepted && (minPos === -1 || item.player.score_details[problem.id].acceptedTime < min.player.score_details[problem.id].acceptedTime);
else condition = item.player.score_details[problem.id] && item.player.score_details[problem.id].score === 100 && (minPos === -1 || item.player.score_details[problem.id].time < min.player.score_details[problem.id].time);
else condition = item.player.score_details[problem.id] && item.player.score_details[problem.id].score === 100 && (minPos === -1 || item.player.score_details[problem.id].judge_state.submit_time < min.player.score_details[problem.id].judge_state.submit_time);
if (condition) {
min = item;
minPos = i;
@ -54,9 +54,7 @@
<tr>
<%
if (contest.type === 'noi' || contest.type === 'ioi') {
console.log(i);
console.log(lastItem);
if (i === 1 || item.player.score !== lastItem.player.score) rank++;
if (i === 1 || item.player.score !== lastItem.player.score) rank = i;
} else if (contest.type === 'acm') {
for (let problem of problems) {
if (item.player.score_details[problem.id] && item.player.score_details[problem.id].accepted) {
@ -66,7 +64,7 @@
}
item.player.timeSum = timeSum;
if (i === 1 || item.player.score !== lastItem.player.score || item.player.timeSum !== lastItem.player.timeSum) rank++;
if (i === 1 || item.player.score !== lastItem.player.score || item.player.timeSum !== lastItem.player.timeSum) rank = i;
}
%>
<td>

1
views/contest_submissions.ejs

@ -39,6 +39,7 @@
<div class="menu">
<div class="item" data-value="">不限<i class="dropdown icon" style="visibility: hidden; "></i></div>
<% for (let status in this.icon) { %>
<% if (this.iconHidden.includes(status)) continue; %>
<div class="item" data-value="<%= status %>"><span class="status <%= status.toLowerCase().split(' ').join('_') %>"><i class="<%= this.icon[status] %> icon"></i> <%= status %></div>
<% } %>
</div>

30
views/custom_test_content.ejs

@ -0,0 +1,30 @@
<% include util %>
<div style="padding-bottom: 1.5rem; ">
<div class="ui styled fluid accordion" id="custom-test-accordion">
<div class="title"<% if (custom_test.pending) { %> style="cursor: auto; "<% } %>>
<div class="ui grid">
<div class="four wide column"><i class="dropdown icon"></i>自定义测试</div>
<div class="four wide column status status_detail <%= getStatusMeta(custom_test.status).toLowerCase().split(' ').join('_') %>">
<i class="<%= icon[getStatusMeta(custom_test.status)] || 'remove' %> icon"></i>
<%= custom_test.status %></div>
<% if (!custom_test.pending) { %>
<div class="four wide column">用时:<span style="font-weight: normal; "><%= custom_test.time %> ms</span></div>
<div class="four wide column">内存:<span style="font-weight: normal; "><%= custom_test.memory %> KiB</span></div>
<% } %>
</div>
</div>
<% if (!custom_test.pending) { %>
<div class="content">
<strong>选手输出</strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= custom_test.result.user_out %></code></pre></div>
</div>
<% } %>
</div>
<% if (!custom_test.pending) { %>
<script>
$(function () {
$('#custom-test-accordion').accordion();
});
</script>
<% } %>
</div>

36
views/discussion.ejs

@ -3,23 +3,45 @@
<div class="padding">
<div class="ui grid">
<div class="row">
<div class="column">
<a href="<%= syzoj.utils.makeUrl(['article', 0, 'edit']) %>" class="ui mini right floated button">发帖</a>
<div class="ten wide column">
<div class="ui breadcrumb">
<div class="section">讨论</div>
<i class="right angle icon divider"></i>
<% if (problem) { %>
<div class="section">题目</div>
<i class="right angle icon divider"></i>
<div class="active section"><%= problem.title %></div>
<% } else { %>
<div class="section">全局板块</div>
<% } %>
</div>
</div>
<div class="six wide right aligned column">
<% if (problem) { %>
<a style="margin-left: 10px; " href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>" class="ui labeled icon mini blue button">
<i class="arrow left icon"></i>
返回题目
</a>
<% } %>
<a style="margin-left: 10px; " href="<%= syzoj.utils.makeUrl(['article', 0, 'edit'], problem ? { problem_id: problem.id } : null) %>" class="ui labeled icon mini button">
<i class="write icon"></i>
发帖
</a>
</div>
</div>
</div>
<table class="ui very basic table">
<table class="ui very basic center aligned table">
<thead>
<tr>
<th>标题</th>
<th>作者</th>
<th>发表时间</th>
<th class="left aligned" style="width: 65%; ">标题</th>
<th style="width: 15%; ">作者</th>
<th style="width: 20%; ">发表时间</th>
</tr>
</thead>
<tbody>
<% for (let article of articles) { %>
<tr>
<td><a href="<%= syzoj.utils.makeUrl(['article', article.id]) %>"><%= article.title %></a></td>
<td class="left aligned"><a href="<%= syzoj.utils.makeUrl(['article', article.id]) %>"><%= article.title %></a></td>
<td><a href="<%= syzoj.utils.makeUrl(['user', article.user_id]) %>"><%= article.user.username %></a><% if (article.user.nameplate) { %><%- article.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(article.public_time) %></td>
</tr>

6
views/footer.ejs

@ -7,10 +7,6 @@
<script src="/libs/semantic-ui/semantic.min.js"></script>
<script src="/libs/Chart.js/Chart.bundle.min.js"></script>
<script type="text/javascript">
$("#logout").click(function () {
window.location.href = <%- JSON.stringify(syzoj.utils.makeUrl(['logout'], { url: req.originalUrl })) %>;
});
</script>
<script src="/script.js?20170710"></script>
</body>
</html>

6
views/header.ejs

@ -1,13 +1,13 @@
<% include util %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<head data-csrf-token="<%= req.csrfToken() %>">
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<title><%= title %> - <%= syzoj.config.title %></title>
<link href="/libs/semantic-ui/semantic.min.css" rel="stylesheet">
<link href="/tomorrow.css" rel="stylesheet">
<link href="/mathjax.css" rel="stylesheet">
<link href="/mathjax.css?20170713" rel="stylesheet">
<link href="/libs/KaTeX/katex.min.css" rel="stylesheet">
<link href="/libs/morris.js/morris.css" rel="stylesheet">
<link href="/style.css?20170706" rel="stylesheet">
@ -34,7 +34,7 @@
<%= user.username %><% if (user.nameplate) { %><%- user.nameplate %><% } %> <i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="<%= syzoj.utils.makeUrl(['user', user.id, 'edit']) %>"><i class="edit icon"></i>修改资料</a>
<a class="item" href="#" id="logout"><i class="power icon"></i>注销</a>
<a class="item" href-post="<%= syzoj.utils.makeUrl(['logout'], { url: req.originalUrl }) %>"><i class="power icon"></i>注销</a>
</div>
</div>
</a>

4
views/help.ejs

@ -21,7 +21,7 @@
<div>
<p>在<a href="/problem/0/edit">添加题目页面</a>填写题面,题目内容使用 Markdown 与 TeX 公式。</p>
<h3 class="ui header">测试数据</h3>
<p>在添加好的题目页面选择「上传测试数据」,可以调整时间和内存限制。数据包是一个 <code>zip</code> 压缩包:</p>
<p>在添加好的题目页面选择「管理」,可以调整时间和内存限制。数据包是一个 <code>zip</code> 压缩包:</p>
<div class="ui existing segment" style="border-radius: 0.285714rem; font-size: 14px;"><pre style="margin-top: 0px; margin-bottom: 0px;"><code>xxx.zip
<span class="hljs-string">|</span>
<span class="hljs-string">|--data_rule.txt</span>
@ -57,7 +57,7 @@ output#.out
<li><code>mul</code>:测试点分数按百分比相乘;</li>
<li><code>min</code>:取各测试点最低分;</li>
</ul>
<p>注意:<span><code>zip</code> 包内<strong>没有</strong>一层文件夹,直接是上述文件。</span>
<p>注意:<span><code>zip</code> 包内<strong>没有</strong>一层文件夹,直接是上述文件。如果不希望将数据打包,可以通过题目页面的「测试数据」按钮进入测试数据文件上传的页面。</span>
</p>
<p>如果没有 <code>data_rule.txt</code>,则评测系统会自动将 <code>.in</code> 文件与同名的 <code>.out</code> 或 <code>.ans</code> 匹配。</p>
<p>如果需要配置提交答案题目的数据,请添加额外的一行,表示用户提交的文件名:</p>

2
views/index.ejs

@ -58,7 +58,7 @@
(function () {
var html = <%- JSON.stringify(user.information) %>;
var elem = document.createElement('div');
elem.style = 'overflow: hidden; width: 100%; ';
elem.style = 'overflow: hidden; width: 100%; position: relative; ';
elem.style.maxHeight = lineHeight + 'px';
elem.innerHTML = html;
var imgs = Array.prototype.slice.call(elem.getElementsByTagName('img'));

3
views/login.ejs

@ -58,7 +58,8 @@ function login() {
type: 'POST',
data: {
"username": $("#username").val(),
"password": password
"password": password,
"_csrf": document.head.getAttribute('data-csrf-token')
},
async: true,
success: function(data) {

147
views/problem.ejs

@ -38,7 +38,9 @@ div[class*=ace_br] {
<div class="row" style="margin-top: -15px">
<span class="ui label">内存限制:<%= problem.memory_limit %> MiB</span>
<span class="ui label">时间限制:<%= problem.time_limit %> ms</span>
<% if (problem.file_io) { %>
<% if (problem.type === 'interaction') { %>
<span class="ui label">题目类型:交互</span>
<% } else if (problem.file_io) { %>
<span class="ui label">输入文件: <%= problem.file_io_input_name %></span>
<span class="ui label">输出文件: <%= problem.file_io_output_name %></span>
<% } else { %>
@ -46,10 +48,12 @@ div[class*=ace_br] {
<% } %>
</div>
<% } %>
<div class="row" style="margin-top: -<%= problem.type === 'submit-answer' ? 15 : 23 %>px">
<span class="ui label">题目类型:<%= { 'submit-answer': '答案提交', 'interaction': '交互', 'traditional': '传统' }[problem.type] %></span>
<span class="ui label">评测方式:<%= (testcases && !testcases.error) ? (testcases.spj ? 'Special Judge' : '文本比较') : '无测试数据' %></span>
</div>
<% if (problem.type !== 'interaction') { %>
<div class="row" style="margin-top: -<%= problem.type === 'submit-answer' ? 15 : 23 %>px">
<span class="ui label">题目类型:<%= { 'submit-answer': '答案提交', 'interaction': '交互', 'traditional': '传统' }[problem.type] %></span>
<span class="ui label">评测方式:<%= (testcases && !testcases.error) ? (testcases.spj ? 'Special Judge' : '文本比较') : '无测试数据' %></span>
</div>
<% } %>
<% if (!contest) { %>
<div class="row" style="margin-top: -23px">
<span class="ui label">上传者:
@ -82,6 +86,9 @@ div[class*=ace_br] {
<a class="small ui primary button" href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>">转到题库</a>
<% } %>
<a class="small ui positive button" href="<%= syzoj.utils.makeUrl(['contest', contest.id, 'submissions'], { problem_id: pid }) %>">提交记录</a>
<% if (problem.additional_file) { %>
<a class="small ui teal button" href="<%= syzoj.utils.makeUrl(['contest', contest.id, pid, 'download', 'additional_file']) %>">附加文件</a>
<% } %>
<a href="<%= syzoj.utils.makeUrl(['contest', contest.id]) %>" class="ui orange button">返回比赛</a>
<% } else { %>
<% if (testcases && !testcases.error) { %>
@ -89,9 +96,17 @@ div[class*=ace_br] {
<% } %>
<a class="small ui positive button" href="<%= syzoj.utils.makeUrl(['submissions'], { problem_id: problem.id }) %>">提交记录</a>
<a class="small ui orange button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'statistics', problem.type === 'submit-answer' ? 'min' : 'fastest']) %>">统计</a>
<a class="small ui brown button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'discussion']) %>" style="position: relative; ">
讨论
<% if (discussionCount) { %>
<div class="floating ui red tiny circular label"><%= discussionCount %></div>
<% } %>
</a>
<a class="small ui yellow button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'testdata']) %>">测试数据</a>
<% if (problem.additional_file) { %>
<a class="small ui teal button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download', 'additional_file']) %>">附加文件</a>
<% } %>
<% } %>
<% if (problem.additional_file) { %><a class="small ui teal button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download', 'additional_file'], (contest && !contest.ended) ? { contest_id: contest.id } : undefined) %>">附加文件</a><% } %>
</div>
<% if (!contest) { %>
<div class="ui buttons right floated">
@ -101,10 +116,32 @@ div[class*=ace_br] {
<% } %>
<% if (problem.allowedManage) { %>
<% if (problem.is_public) { %>
<a class="small ui button" id="dis_public" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'dis_public']) %>">取消公开</a>
<a class="small ui button" id="dis_public" href-post="<%= syzoj.utils.makeUrl(['problem', problem.id, 'dis_public']) %>">取消公开</a>
<% } else { %>
<a class="small ui button" id="public" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'public']) %>">公开</a>
<a class="small ui button" id="public" href-post="<%= syzoj.utils.makeUrl(['problem', problem.id, 'public']) %>">公开</a>
<% } %>
<div class="ui basic modal" id="modal-delete">
<div class="ui icon header">
<i class="trash icon"></i>
<p style="margin-top: 15px; ">删除题目</p>
</div>
<div class="content" style="text-align: center; ">
<p>确认删除此题目吗?提交记录、讨论以及测试数据将一并删除。<br>
删除题目导致的修改用户提交、通过数量可能会耗费一些时间。</p>
<b>警告:删除比赛中的题目会导致系统错乱!请确认没有比赛使用此题目。</b>
</div>
<div class="actions">
<div class="ui red basic cancel inverted button">
<i class="remove icon"></i>
</div>
<a class="ui green ok inverted button" href-post="<%= syzoj.utils.makeUrl(['problem', problem.id, 'delete']) %>">
<i class="checkmark icon"></i>
</a>
</div>
</div>
<div class="small ui red button" onclick="$('#modal-delete').modal('show')">删除</div>
<% } %>
</div>
<% } %>
@ -267,6 +304,7 @@ div[class*=ace_br] {
</div>
</div>
</div>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; "><button type="submit" class="ui button">提交</button></div>
<% } else { %>
<input name="language" type="hidden" id="form">
<input name="code" type="hidden">
@ -297,8 +335,11 @@ div[class*=ace_br] {
</div>
</div>
</div>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; ">
<button type="submit" class="ui button">提交</button>
<!--div onclick="show_custom_test()" class="ui positive button">自定义测试</div-->
</div>
<% } %>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; "><button type="submit" class="ui button">提交</button></div>
</form>
</div>
</div>
@ -343,6 +384,94 @@ div[class*=ace_br] {
});
});
</script>
<script src="https://cdn.staticfile.org/css-element-queries/0.4.0/ResizeSensor.min.js"></script>
<div class="ui modal" id="modal-custom-test">
<div class="header">
自定义测试
</div>
<div class="content" style="padding-bottom: 0; ">
<div class="ui form">
<form id="form-custom-test" action="<%= syzoj.utils.makeUrl(['problem', problem.id, 'custom-test']) %>">
<input type="hidden" name="code">
<input type="hidden" name="language">
<div class="field">
<label>输入文件</label>
<textarea name="input_file_textarea" style="font-family: 'Roboto Mono', 'Bitstream Vera Sans Mono', 'Menlo', 'Consolas', 'Lucida Console', monospace; "></textarea>
</div>
<div class="inline fields" style="width: 100%; ">
<div class="field" style="margin: 0 auto; ">
<label for="answer">或者,上传输入文件</label>
<input type="file" name="input_file">
</div>
</div>
</form>
</div>
<div id="custom-test-result"></div>
</div>
<div class="actions" style="text-align: center; ">
<div class="ui green button" id="submit-custom-test">提交</div>
</div>
</div>
<script>
var custom_test_id;
$('#submit-custom-test').click(function () {
$('#submit-custom-test').addClass('disabled');
var form = document.getElementById('form-custom-test');
$(form).find('[name=language]').val($('#languages-menu .item.active').data('value'));
$(form).find('[name=code]').val(editor.getValue());
var code_upload = $('#answer')[0].cloneNode(true);
code_upload.style.display = 'none';
code_upload.name = 'code_upload';
form.appendChild(code_upload);
$.ajax({
url: form.action,
type: 'post',
data: new FormData(form),
cache: false,
contentType: false,
processData: false,
success: function (data) {
custom_test_id = data.id;
update_custom_test_result();
}
});
form.removeChild(code_upload);
});
function show_custom_test() {
$('#modal-custom-test').modal('show');
new ResizeSensor($('#modal-custom-test'), function () {
$(window).resize();
});
}
function update_custom_test_result() {
$.get('/custom-test/' + custom_test_id + '/ajax', function (data) {
if (data.err) {
alert(data.err);
}
if ($('#custom-test-result').html() !== data.html) {
$('#custom-test-result').html(data.html);
}
if (data.pending) {
setTimeout(function () {
update_custom_test_result();
}, 500);
} else {
$('#submit-custom-test').removeClass('disabled');
}
});
}
</script>
<% } else { %>
<script>
function submit_code() {

2
views/problem_data.ejs

@ -95,7 +95,7 @@ function getIcon(filename) {
<i class="remove icon"></i>
</div>
<a class="ui green ok inverted button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'testdata', 'delete', file.filename]) %>">
<a class="ui green ok inverted button" href-post="<%= syzoj.utils.makeUrl(['problem', problem.id, 'testdata', 'delete', file.filename]) %>">
<i class="checkmark icon"></i>
</a>

2
views/problem_edit.ejs

@ -71,7 +71,7 @@
<script type="text/javascript">
$(function () {
function render(output, input) {
$.post('/api/markdown', { s: input.val() }, function (s) {
$.post('/api/markdown', { s: input.val(), _csrf: document.head.getAttribute('data-csrf-token') }, function (s) {
// console.log(s);
output.html(s);
});

94
views/problem_manage.ejs

@ -25,59 +25,61 @@
<input type="number" name="memory_limit" value="<%= problem.memory_limit %>">
</div>
</div>
<% if (!problem.file_io) { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()" checked>
<label for="std-io">标准 IO</label>
<div id="io-type"<% if (problem.type === 'interaction') { %> style="display: none; "<% } %>>
<% if (!problem.file_io) { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()" checked>
<label for="std-io">标准 IO</label>
</div>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()">
<label for="file-io">文件 IO</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()">
<label for="file-io">文件 IO</label>
</div>
</div>
</div>
</div>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>" disabled>
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>" disabled>
</div>
</div>
<% } else { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()">
<label for="std-io">标准 IO</label>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>" disabled>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()" checked>
<label for="file-io">文件 IO</label>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>" disabled>
</div>
</div>
</div>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>">
<% } else { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox" id="std-io-div">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()">
<label for="std-io">标准 IO</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()" checked>
<label for="file-io">文件 IO</label>
</div>
</div>
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>">
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>">
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>">
</div>
</div>
<% } %>
</div>
<% } %>
</div>
<div class="ui <%= problem.type === 'submit-answer' ? 'active ' : '' %>tab" data-tab="submit-answer" style="margin-bottom: 10px; ">
<b>为了避免系统出错,已有提交的题目不允许在提交答案和非提交答案之间更改。</b><br>
@ -125,11 +127,15 @@ $(function () {
$('a[data-tab="traditional"]').click(function () {
$('input[name=type]').val('traditional');
if ($('div[data-tab="interaction"]').attr('data-tab', 'traditional').length) $('a[data-tab="traditional"]').click();
$('#io-type').show();
});
$('a[data-tab="interaction"]').click(function () {
$('input[name=type]').val('interaction');
if ($('div[data-tab="traditional"]').attr('data-tab', 'interaction').length) $('a[data-tab="interaction"]').click();
$('#io-type').hide();
});
$('a[data-tab="submit-answer"]').click(function () {

8
views/problem_testcases.ejs

@ -12,6 +12,8 @@ let subtaskType = {
%>
<% if (testcases.spj) { %>
<p>评测方式:Special Judge</p>
<% } else if (problem.type === 'interaction') { %>
<p>评测方式:交互</p>
<% } else { %>
<p>评测方式:文本比较</p>
<% } %>
@ -33,12 +35,12 @@ let subtaskType = {
<tr class="center aligned">
<td style="width: 33%; "><%= testcase.input || '-' %></td>
<td style="width: 33%; "><%= testcase.output || '-' %></td>
<td style="width: 33%; "><%= testcase.answer %></td>
<td style="width: 33%; "><%= testcase.answer || '-' %></td>
</tr>
<% } else { %>
<tr class="center aligned">
<td style="width: 50%; "><%= testcase.input %></td>
<td style="width: 50%; "><%= testcase.output %></td>
<td style="width: 50%; "><%= testcase.input || '-' %></td>
<td style="width: 50%; "><%= testcase.output || '-' %></td>
</tr>
<% } %>
<% } %>

2
views/ranklist.ejs

@ -42,7 +42,7 @@
(function () {
var html = <%- JSON.stringify(user.information) %>;
var elem = document.createElement('div');
elem.style = 'overflow: hidden; width: 100%; ';
elem.style = 'overflow: hidden; width: 100%; position: relative; ';
elem.style.maxHeight = lineHeight + 'px';
elem.innerHTML = html;
var imgs = Array.prototype.slice.call(elem.getElementsByTagName('img'));

3
views/sign_up.ejs

@ -62,7 +62,8 @@ function submit() {
username: $("#username").val(),
password: password,
email: $("#email").val(),
prevUrl: <%- JSON.stringify(req.query.url || '/') %>
prevUrl: <%- JSON.stringify(req.query.url || '/') %>,
_csrf: document.head.getAttribute('data-csrf-token')
},
success: function(data) {
error_code = data.error_code;

2
views/statistics.ejs

@ -15,7 +15,7 @@ let types = {
<script src="/libs/morris.js/morris.min.js"></script>
<script>
function getColorOfScore(score) {
let color = [];
var color = [];
color[0] = 'red';
color[1] = '#ff4b00';
color[2] = '#ff6200';

102
views/submission_content.ejs

@ -36,18 +36,23 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<th>编号</th>
<th>题目名称</th>
<th>状态</th>
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %>
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && contest.type === 'acm') && !hideScore) { %>
<th>分数</th>
<% } %>
<% if (judge.problem.type !== 'submit-answer') { %>
<th>总时间</th>
<th>内存</th>
<% if (!hideScore) { %>
<th>总时间</th>
<th>内存</th>
<% } %>
<th>代码</th>
<% } else { %>
<th>文件大小</th>
<% } %>
<th>提交者</th>
<th>提交时间</th>
<% if (judge.allowedRejudge) { %>
<th>重新评测</th>
<% } %>
</tr>
</thead>
<tbody>
@ -58,12 +63,14 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<i class="<%= icon[getStatusMeta(judge.status)] || 'remove' %> icon"></i>
<%= judge.status %>
</td>
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %>
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && contest.type === 'acm') && !hideScore) { %>
<td class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></td>
<% } %>
<% if (judge.problem.type !== 'submit-answer') { %>
<td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<% if (!hideScore) { %>
<td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<% } %>
<% if (judge.allowedSeeCode) { %>
<td><%= syzoj.config.languages[judge.language].show %> / <%= syzoj.utils.formatSize(judge.codeLength) %></td>
<% } else { %>
@ -74,6 +81,42 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %></td>
<% if (judge.allowedRejudge) { %>
<td>
<a id="rejudge-button" onclick="check_rejudge()" style="color: #000; " href="#"><i class="repeat icon"></i></a>
<div class="ui basic modal" id="modal-rejudge">
<div class="ui icon header">
<i class="retweet icon"></i>
<p style="margin-top: 15px; ">重新评测</p>
</div>
<div class="content" style="text-align: center; ">
<p>确认重新评测该提交记录吗?</p>
<p id="warning_pending"><strong>警告:只有管理员可以重新评测一个未评测完成的记录,<br>这种情况一般发生在评测服务中断后,如果一个提交正在被评测,<br>则将其重新评测会导致系统错乱!</strong></p>
<script>
var pending = <%= judge.pending %>;
function check_rejudge() {
if (pending) {
$('#warning_pending').css('display', '');
} else {
$('#warning_pending').css('display', 'none');
}
$('#modal-rejudge').modal('show');
}
</script>
</div>
<div class="actions">
<div class="ui red basic cancel inverted button">
<i class="remove icon"></i>
</div>
<a class="ui green ok inverted button" href-post="<%= syzoj.utils.makeUrl(['submission', judge.id, 'rejudge']) %>">
<i class="checkmark icon"></i>
</a>
</div>
</div>
</td>
<% } %>
</tr>
</tbody>
</table>
@ -87,9 +130,6 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
</script>
<% if (judge.problem.type !== 'submit-answer' && judge.allowedSeeCode) { %>
<div class="ui existing segment" style="position: relative; ">
<% if (judge.allowedRejudge) { %>
<a id="rejudge-button" href="<%= syzoj.utils.makeUrl(['submission', judge.id, 'rejudge']) %>" class="ui button" style="position: absolute; top: 0px; right: -4px; border-top-left-radius: 0; border-bottom-right-radius: 0; <% if (judge.pending) { %>display: none; <% } %>">重新评测</a>
<% } %>
<pre style="margin-top: 0; margin-bottom: 0; "><code id="code"><%- judge.code %></code></pre>
</div>
<script>
@ -108,8 +148,11 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<% if (judge.problem.type !== 'submit-answer' && judge.result.compiler_output && judge.status === 'Compile Error' && judge.allowedSeeCode) { %>
<h3 class="ui header">编译信息</h3>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%- syzoj.utils.ansiToHTML(judge.result.compiler_output) %></code></pre></div>
<% } else if (judge.result.message) { %>
<h3 class="ui header">系统调试信息</h3>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= judge.result.message %></code></pre></div>
<% } else if (judge.result.spj_compiler_output) { %>
<h3 class="ui header">Special Judge 编译信息</h3>
<h3 class="ui header"><%= judge.problem.type === 'interaction' ? '交互程序' : 'Special Judge ' %>编译信息</h3>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%- syzoj.utils.ansiToHTML(judge.result.spj_compiler_output) %></code></pre></div>
<% } else if (judge.allowedSeeCase && judge.result.subtasks && (judge.result.subtasks.length !== 1 || judge.result.subtasks[0].case_num)) { %>
<div class="ui styled fluid accordion" id="subtasks_list">
@ -134,15 +177,21 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<% let testcase = subtask_cases[i]; %>
<div class="title<% if (testcase.pending || !judge.allowedSeeData || testcase.status === 'Skipped') { %> pending<% } %> auto_update"<% if (testcase.pending || !judge.allowedSeeData) { %> style="cursor: auto; "<% } %>>
<div class="ui grid">
<div class="three wide column"><i class="dropdown icon"></i>测试点 #<%= i + 1 %></div>
<div class="<%= judge.problem.type === 'submit-answer' ? 'four' : 'three' %> wide column"><i class="dropdown icon"></i>测试点 #<%= i + 1 %></div>
<div class="four wide column status status_detail <%= getStatusMeta(testcase.status).toLowerCase().split(' ').join('_') %>">
<i class="<%= icon[getStatusMeta(testcase.status)] || 'remove' %> icon"></i>
<%= testcase.status %></div>
<% if (!testcase.pending && testcase.status !== 'Skipped') { %>
<% if (!testcase.score) testcase.score = testcase.status === 'Accepted' ? 100 : 0; %>
<div class="three wide column">得分:<span style="font-weight: normal; "><%= parseFloat(testcase.score.toFixed(2)).toString() %></span></div>
<div class="three wide column">用时:<span style="font-weight: normal; "><%= testcase.time_used %> ms</span></div>
<div class="three wide column">内存:<span style="font-weight: normal; "><%= testcase.memory_used %> KiB</span></div>
<% if (judge.problem.type === 'submit-answer') { %>
<% if (testcase.length != null) { %>
<div class="three wide column">文件大小:<span style="font-weight: normal; "><%= syzoj.utils.formatSize(testcase.length) %></span></div>
<% } %>
<% } else { %>
<div class="three wide column">得分:<span style="font-weight: normal; "><%= parseFloat(testcase.score.toFixed(2)).toString() %></span></div>
<div class="three wide column">用时:<span style="font-weight: normal; "><%= testcase.time_used %> ms</span></div>
<div class="three wide column">内存:<span style="font-weight: normal; "><%= testcase.memory_used %> KiB</span></div>
<% } %>
<% } %>
</div>
</div>
@ -151,12 +200,14 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<p>
<strong>输入文件<% if (testcase.input_file_name) { %>(<span style="font-family: monospace; "><%= testcase.input_file_name %></span>)<% } %></strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.input %></code></pre></div>
<strong>输出文件<% if (testcase.output_file_name) { %>(<span style="font-family: monospace; "><%= testcase.output_file_name %></span>)<% } %></strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.answer %></code></pre></div>
<strong>选手输出</strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.user_out %></code></pre></div>
<% if (judge.problem.type !== 'interaction') { %>
<strong>输出文件<% if (testcase.output_file_name) { %>(<span style="font-family: monospace; "><%= testcase.output_file_name %></span>)<% } %></strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.answer %></code></pre></div>
<strong>选手输出</strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.user_out %></code></pre></div>
<% } %>
<% if (testcase.spj_message) { %>
<strong>Special Judge 信息</strong>
<strong><%= judge.problem.type === 'interaction' ? '交互程序' : 'Special Judge ' %>信息</strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.spj_message %></code></pre></div>
<% } %>
<% if (testcase.user_err) { %>
@ -189,7 +240,7 @@ $(function() {
});
</script>
<% if (!isPending(judge.status)) { %><div><div id="stop_ajax"></div><div id="show_rejudge"></div></div><% } %>
<% if (!isPending(judge.status)) { %><div><div id="not_pending"></div></div><% } %>
<script>
<% if (isPending(judge.status)) { %>
document.addEventListener('mousedown', function (event) {
@ -202,12 +253,6 @@ function update_submission() {
setTimeout(function () {
$.get('/submission/<%= judge.id %>/ajax', function (data) {
var e = $('#submission_content'), x = $($.parseHTML(data));
if (x.find('#show_rejudge').length) {
try {
document.getElementById('rejudge-button').style.display = '';
} catch (e) {}
}
if (e.find('td.status').text().trim() != x.find('td.status').text().trim()) {
var a = e.find('div.auto_update');
if (!a.length) {
@ -228,10 +273,11 @@ function update_submission() {
}
window.applyTextFit();
if (!x.find('#stop_ajax').length) update_submission();
if (!x.find('#not_pending').length) update_submission();
else pending = false;
}
}
else if (!x.find('#stop_ajax').length) update_submission();
else if (!x.find('#not_pending').length) update_submission();
});
}, 500);
}

24
views/submissions.ejs

@ -37,6 +37,7 @@
<div class="menu">
<div class="item" data-value="">不限<i class="dropdown icon" style="visibility: hidden; "></i></div>
<% for (let status in this.icon) { %>
<% if (this.iconHidden.includes(status)) continue; %>
<div class="item" data-value="<%= status %>"><span class="status <%= status.toLowerCase().split(' ').join('_') %>"><i class="<%= this.icon[status] %> icon"></i> <%= status %></div>
<% } %>
</div>
@ -77,7 +78,9 @@
</tr>
</thead>
<tbody>
<% let ids = []; %>
<% for (let judge of judge_state) { %>
<% if (judge.pending) ids.push(judge.id); %>
<tr id="submissions_<%= judge.id %>"><% include submissions_item %></tr>
<% } %>
</tbody>
@ -93,5 +96,26 @@ $(function () {
$('#select_language').dropdown();
$('#select_status').dropdown();
});
var ids = <%= JSON.stringify(ids) %>;
function update() {
setTimeout(function () {
$.get('/submissions/' + ids.join(',') + '/ajax', function (data) {
var newIDs = [];
for (var id in data) {
if (data[id].pending) newIDs.push(id);
var e = $('#submissions_' + id);
if (e.find('span.status').text().trim() !== data[id].status) e.html(data[id].html);
}
if (newIDs.length > 0) {
ids = newIDs;
update();
}
});
}, 500);
}
update();
</script>
<% include footer %>

41
views/submissions_item.ejs

@ -18,36 +18,19 @@ textFit(e, { maxFontSize: 14 });
</a></td>
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><span class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></span></a></td>
<% } %>
<% if (judge.problem.type !== 'submit-answer') { %>
<td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<% if (judge.allowedSeeCode) { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / <%= syzoj.utils.formatSize(judge.code.length) %></td>
<% } else { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / 隐藏</td>
<% } %>
<% } else { %>
<td>-</td><td>-</td>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% if (judge.problem.type !== 'submit-answer') { %>
<td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<% if (judge.allowedSeeCode) { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / <%= syzoj.utils.formatSize(judge.code.length) %></td>
<% } else { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / 隐藏</td>
<% } %>
<% } else { %>
<td>-</td><td>-</td>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% } %>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %>
<% if (isPending(judge.status)) { %>
<script>
function update_judge_<%= judge.id %>() {
setTimeout(function () {
<%
let url = syzoj.utils.makeUrl(['submissions', judge.id, 'ajax']);
%>
$.get('<%= url %>', function (data) {
var e = $('#submissions_<%= judge.id %>');
if (e.html() != data) e.html(data);
else update_judge_<%= judge.id %>();
});
}, 500);
}
update_judge_<%= judge.id %>();
</script>
<% } %>
</td>

14
views/user.ejs

@ -49,12 +49,14 @@
<div class="ui bottom attached segment"><%= show_user.username %><% if (show_user.nameplate) { %><%- show_user.nameplate %><% } %></div>
</div>
</div>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">Email</h4>
<div class="ui bottom attached segment" class="font-content"><%= show_user.email %></div>
</div>
</div>
<% if (show_user.emailVisible) { %>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">Email</h4>
<div class="ui bottom attached segment" class="font-content"><%= show_user.email %></div>
</div>
</div>
<% } %>
<div class="row">
<div class="column">
<h4 class="ui top attached block header">个性签名</h4>

9
views/user_edit.ejs

@ -23,7 +23,14 @@
</div>
<div class="field">
<label for="email">Email</label>
<input class="font-content" type="email" id="email" name="email" value="<%= edited_user.email %>">
<input class="font-content" type="email" id="email" name="email" value="<%= edited_user.email %>"<% if (!user.allowedManage) { %> readonly<% } %>>
</div>
<div class="inline field">
<label class="ui header">公开 Email</label>
<div class="ui toggle checkbox">
<input id="public_email" name="public_email" type="checkbox" <% if (edited_user.public_email) { %> checked<% } %>>
<label> </label>
</div>
</div>
<div class="field">
<label for="information">个性签名</label>

11
views/util.ejs

@ -19,21 +19,28 @@ this.alpha = number => {
this.icon = {
'Accepted': 'checkmark',
'Success': 'checkmark', // Custom test
'Wrong Answer': 'remove',
'Runtime Error': 'bomb',
'Invalid Interaction': 'ban',
'Time Limit Exceeded': 'clock',
'Memory Limit Exceeded': 'disk outline',
'Memory Limit Exceeded': 'microchip',
'Output Limit Exceeded': 'print',
'File Error': 'file outline',
'Waiting': 'hourglass half',
'Running': 'spinner',
'Compiling': 'spinner',
'Compile Error': 'code',
'Submitted': 'checkmark',
'Submitted': 'checkmark', // NOI contests
'System Error': 'server',
'No Testdata': 'folder open outline',
'Partially Correct': 'minus',
'Judgement Failed': 'server',
'Skipped': 'ban'
};
this.iconHidden = [
'Success',
'Submitted'
];
%>

Loading…
Cancel
Save