Browse Source

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

pull/6/head
Tian Yunhao 6 years ago
parent
commit
d935fdffca
  1. 3
      README.en.md
  2. 3
      README.md
  3. 1
      app.js
  4. 142
      config-example.json
  5. 134
      language-config.json
  6. 73
      libs/markdown.js
  7. 7
      libs/submissions_process.js
  8. 2
      modules/contest.js
  9. 2
      modules/problem.js
  10. 9
      modules/submission.js
  11. 17
      package.json
  12. 19
      static/style.css
  13. 147
      static/syzoj.svg
  14. 38
      utility.js
  15. 4
      views/admin_rejudge.ejs
  16. 2
      views/article.ejs
  17. 2
      views/footer.ejs
  18. 2
      views/header.ejs
  19. 8
      views/help.ejs
  20. 10
      views/problem.ejs
  21. 2
      views/statistics.ejs
  22. 8
      views/status_label.ejs
  23. 36
      views/submission.ejs
  24. 27
      views/submissions.ejs
  25. 18
      views/submissions_item.ejs
  26. 8
      views/util.ejs

3
README.en.md

@ -1,4 +1,5 @@
# SYZOJ 2
<p align="center"><img src="static/syzoj.svg" width="250"></p>
[中文](README.md) | English
An online judge system for algorithm competition.

3
README.md

@ -1,4 +1,5 @@
# SYZOJ 2
<p align="center"><img src="static/syzoj.svg" width="250"></p>
中文 | [English](README.en.md)
一个用于算法竞赛的在线评测系统。

1
app.js

@ -11,6 +11,7 @@ const options = commandLineArgs(optionDefinitions);
global.syzoj = {
rootDir: __dirname,
config: require('object-assign-deep')({}, require('./config-example.json'), require(options.config)),
languages: require('./language-config.json'),
configDir: options.config,
models: [],
modules: [],

142
config-example.json

@ -5,7 +5,7 @@
"db": {
"database": "syzoj",
"username": "syzoj",
"password": null,
"password": "@DATABASE_PASSWORD@",
"host": "127.0.0.1"
},
"logo": {
@ -65,112 +65,52 @@
"edit_contest_problem_list": 10,
"edit_problem_tag_list": 10
},
"languages": {
"cpp": {
"show": "C++",
"highlight": "cpp",
"version": "GCC 5.4.0",
"editor": "c_cpp"
},
"cpp11": {
"show": "C++11",
"highlight": "cpp",
"version": "GCC 5.4.0",
"editor": "c_cpp"
},
"csharp": {
"show": "C#",
"highlight": "csharp",
"version": "MCS 4.8.0.0, Mono 4.8.0",
"editor": "csharp"
},
"c": {
"show": "C",
"highlight": "c",
"version": "GCC 5.4.0",
"editor": "c_cpp"
},
"vala": {
"show": "Vala",
"highlight": "vala",
"version": "Vala 0.30.1, GCC 5.4.0",
"editor": "vala"
},
"java": {
"show": "Java",
"highlight": "java",
"version": "GCC 5.4.0",
"editor": "java"
},
"pascal": {
"show": "Pascal",
"highlight": "pascal",
"version": "FPC 3.0.0",
"editor": "pascal"
},
"lua": {
"show": "Lua",
"highlight": "lua",
"version": "Lua 5.2.4",
"editor": "lua"
},
"luajit": {
"show": "LuaJIT",
"highlight": "lua",
"version": "LuaJIT 2.0.4",
"editor": "lua"
},
"python2": {
"show": "Python 2",
"highlight": "python",
"version": "CPython 2.7.12",
"editor": "python"
},
"python3": {
"show": "Python 3",
"highlight": "python",
"version": "CPython 3.5.2",
"editor": "python"
},
"nodejs": {
"show": "Node.js",
"highlight": "js",
"version": "7.7.3",
"editor": "javascript"
},
"ruby": {
"show": "Ruby",
"highlight": "ruby",
"version": "2.3.1",
"editor": "ruby"
},
"haskell": {
"show": "Haskell",
"highlight": "haskell",
"version": "GHC 7.10.3",
"editor": "haskell"
},
"ocaml": {
"show": "OCaml",
"highlight": "ocaml",
"version": "Ocamlbuild 4.02.3",
"editor": "ocaml"
},
"vbnet": {
"show": "Visual Basic",
"highlight": "vbnet",
"version": "VBNC 0.0.0.5943, Mono 4.8.0",
"editor": "vbscript"
}
},
"enabled_languages": [
"cpp",
"cpp11",
"cpp17",
"cpp-noilinux",
"cpp11-noilinux",
"cpp11-clang",
"cpp17-clang",
"c",
"c-noilinux",
"csharp",
"java",
"pascal",
"python2",
"python3",
"nodejs",
"ruby",
"haskell"
],
"filter_enabled_languages": [
"cpp",
"cpp11",
"cpp17",
"cpp-noilinux",
"cpp11-noilinux",
"cpp11-clang",
"cpp17-clang",
"c",
"c-noilinux",
"csharp",
"java",
"pascal",
"python2",
"python3",
"nodejs",
"ruby",
"haskell"
],
"links": [
{
"title": "LibreOJ",
"url": "https://loj.ac/"
}
],
"session_secret": "233",
"session_secret": "@SESSION_SECRET@",
"rabbitMQ": "amqp://localhost/",
"email_jwt_secret": "test",
"email_jwt_secret": "@EMAIL_JWT_SECRET@",
"google_analytics": "UA-XXXXXXXX-X"
}

134
language-config.json

@ -0,0 +1,134 @@
{
"cpp": {
"show": "C++",
"highlight": "cpp",
"version": "GCC 8.2.0",
"editor": "c_cpp"
},
"cpp11": {
"show": "C++ 11",
"highlight": "cpp",
"version": "GCC 8.2.0",
"editor": "c_cpp"
},
"cpp17": {
"show": "C++ 17",
"highlight": "cpp",
"version": "GCC 8.2.0",
"editor": "c_cpp"
},
"cpp-noilinux": {
"show": "C++ (NOI)",
"highlight": "cpp",
"version": "GCC 4.8.4 (NOILinux 1.4.1)",
"editor": "c_cpp"
},
"cpp11-noilinux": {
"show": "C++ 11 (NOI)",
"highlight": "cpp",
"version": "GCC 4.8.4 (NOILinux 1.4.1)",
"editor": "c_cpp"
},
"cpp11-clang": {
"show": "C++ 11 (Clang)",
"highlight": "cpp",
"version": "Clang 7.0.1",
"editor": "c_cpp"
},
"cpp17-clang": {
"show": "C++ 17 (Clang)",
"highlight": "cpp",
"version": "Clang 7.0.1",
"editor": "c_cpp"
},
"c": {
"show": "C",
"highlight": "c",
"version": "Clang 7.0.1",
"editor": "c_cpp"
},
"c-noilinux": {
"show": "C (NOI)",
"highlight": "c",
"version": "GCC 4.8.4 (NOILinux 1.4.1)",
"editor": "c_cpp"
},
"csharp": {
"show": "C#",
"highlight": "csharp",
"version": "Mono 5.16.0.220",
"editor": "csharp"
},
"vala": {
"show": "Vala",
"highlight": "vala",
"version": "Vala 0.40.8, Clang 7.0.1",
"editor": "vala"
},
"java": {
"show": "Java",
"highlight": "java",
"version": "OpenJDK 10.0.2",
"editor": "java"
},
"pascal": {
"show": "Pascal",
"highlight": "pascal",
"version": "Free Pascal 3.0.4",
"editor": "pascal"
},
"lua": {
"show": "Lua",
"highlight": "lua",
"version": "Lua 5.3.3",
"editor": "lua"
},
"luajit": {
"show": "LuaJIT",
"highlight": "lua",
"version": "LuaJIT 2.1.0",
"editor": "lua"
},
"python2": {
"show": "Python 2",
"highlight": "python",
"version": "PyPy 6.0.0 (Python 2.7.13)",
"editor": "python"
},
"python3": {
"show": "Python 3",
"highlight": "python",
"version": "PyPy 6.0.0 (Python 3.5.3)",
"editor": "python"
},
"nodejs": {
"show": "Node.js",
"highlight": "js",
"version": "10.14.0",
"editor": "javascript"
},
"ruby": {
"show": "Ruby",
"highlight": "ruby",
"version": "2.5.1",
"editor": "ruby"
},
"haskell": {
"show": "Haskell",
"highlight": "haskell",
"version": "GHC 8.6.2",
"editor": "haskell"
},
"ocaml": {
"show": "OCaml",
"highlight": "ocaml",
"version": "Ocaml 4.05.0",
"editor": "ocaml"
},
"vbnet": {
"show": "Visual Basic",
"highlight": "vbnet",
"version": "Mono 4.7",
"editor": "vbscript"
}
}

73
libs/markdown.js

@ -35,10 +35,12 @@ let config = {
highlight: require('./highlight')
};
let uuid = require('uuid');
function render(s, cb) {
if (!s.trim()) return cb('');
let mathCnt = 0, mathPending = 0, maths = new Array(), hlCnt = 0, hlPending = 0, hls = new Array(), res, callback, ss, cache = render.cache, cacheOption = render.cacheOption, finished = false;
let mathCnt = 0, mathPending = 0, maths = new Array(), mathID = new Array(), hlCnt = 0, hlPending = 0, hls = new Array(), hlID = new Array(), res, callback, ss, cache = render.cache, cacheOption = render.cacheOption, finished = false;
if (cacheOption.result) {
let x = cache.get('RES_' + s);
if (x !== undefined) return cb(x);
@ -59,35 +61,38 @@ function render(s, cb) {
if (cacheOption.highlight) cache.set('H_' + lang + '_' + code, res);
if (!--hlPending) finish();
});
return '<span id="hl-' + id + '"></span>';
return hlID[id] = uuid();
},
mathRenderer: function(str, display) {
if (cacheOption.math) {
let x = cache.get('M_' + display + '_' + str);
if (x !== undefined) return x;
let mathFinish = (error, result) => {
if (error) maths[id] = '<p><div style="display: inline-block; border: 1px solid #000; "><strong>' + error.toString() + '</strong></div></p>';
else if (display) maths[id] = '<p style="text-align: center; ">' + result + '</p>';
else maths[id] = result;
if (cacheOption.math) cache.set('M_' + display + '_' + str, maths[id]);
if (!--mathPending) finish();
}
const id = mathCnt;
mathCnt++, mathPending++;
try {
let res = katex.renderToString(str, { displayMode: display });
if (cacheOption.math) cache.set('M_' + display + '_' + str, res);
return res;
let x = cache.get('M_' + display + '_' + str);
if (x !== undefined) process.nextTick(() => mathFinish(null, x));
else {
let res = katex.renderToString(str, { displayMode: display });
process.nextTick(() => mathFinish(null, '<span style="zoom: 1.01; ">' + res + '</span>'));
}
} catch (e) {
const id = mathCnt;
mathCnt++, mathPending++;
mj.typeset({
math: str,
format: display ? 'TeX' : 'inline-TeX',
html: true, css: true,
svg: true,
width: 0
}, function (data) {
if (data.errors) maths[id] = '<p><div style="display: inline-block; border: 1px solid #000; "><strong>' + data.errors.toString() + '</strong></div></p>';
else if (display) maths[id] = '<p style="text-align: center; ">' + data.html + '</p>';
else maths[id] = data.html;
if (cacheOption.math) cache.set('M_' + display + '_' + str, maths[id]);
if (!--mathPending) finish();
}, data => {
mathFinish(data.errors, data.svg);
});
return '<span id="math-' + id + '"></span>';
}
return mathID[id] = uuid();
}
});
@ -95,22 +100,40 @@ function render(s, cb) {
if (finished || !res || mathPending || hlPending) return;
finished = true;
if (maths.length || hls.length) {
let x = new (require('jsdom').JSDOM)().window.document.createElement('div');
x.innerHTML = res;
for (let i = 0; i < maths.length; i++) {
x.querySelector('#math-' + i).outerHTML = maths[i];
res = res.replace(mathID[i], maths[i]);
}
for (let i = 0; i < hls.length; i++) {
x.querySelector('#hl-' + i).outerHTML = hls[i];
res = res.replace(hlID[i], hls[i]);
}
res = x.innerHTML;
}
if (cacheOption.result) cache.set('RES_' + s, res);
cb(res);
}
try {
res = MoeMark(s);
let XSS = require('xss');
let CSSFilter = require('cssfilter');
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(['style', 'class']);
let xss = new XSS.FilterXSS({
whiteList: whiteList,
stripIgnoreTag: true,
onTagAttr: (tag, name, value, isWhiteAttr) => {
if (tag.toLowerCase() === 'img' && name.toLowerCase() === 'src' && value.startsWith('data:image/')) return name + '="' + XSS.escapeAttrValue(value) + '"';
}
});
let replaceXSS = s => {
s = xss.process(s);
if (s) {
s = `<div style="position: relative; overflow: hidden; ">${s}</div>`;
}
return s;
};
res = replaceXSS(MoeMark(s));
if (mathPending == 0 && hlPending == 0) {
finish();
}

7
libs/submissions_process.js

@ -5,7 +5,7 @@ const getSubmissionInfo = (s, displayConfig) => ({
userId: s.user_id,
problemName: s.problem.title,
problemId: s.problem_id,
language: displayConfig.showCode ? ((s.language != null && s.language !== '') ? syzoj.config.languages[s.language].show : null) : null,
language: displayConfig.showCode ? ((s.language != null && s.language !== '') ? syzoj.languages[s.language].show : null) : null,
codeSize: displayConfig.showCode ? syzoj.utils.formatSize(s.code_length) : null,
submitTime: syzoj.utils.formatDate(s.submit_time),
});
@ -18,7 +18,8 @@ const getRoughResult = (x, displayConfig) => {
return {
result: x.status,
time: displayConfig.showUsage ? x.total_time : null,
memory: displayConfig.showUsage ? x.max_memory : null,
memory: displayConfig.showUsage ? syzoj.utils.formatSize((x.max_memory * 1024) || 0, 2) : null,
precise_memory: displayConfig.showUsage ? x.max_memory : null,
score: displayConfig.showScore ? x.score : null
};
}
@ -72,4 +73,4 @@ const processOverallResult = (source, config) => {
};
}
module.exports = { getRoughResult, getSubmissionInfo, processOverallResult };
module.exports = { getRoughResult, getSubmissionInfo, processOverallResult };

2
modules/contest.js

@ -412,7 +412,7 @@ app.get('/contest/submission/:id', async (req, res) => {
if (judge.problem.type !== 'submit-answer') {
judge.codeLength = judge.code.length;
judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight);
judge.code = await syzoj.utils.highlight(judge.code, syzoj.languages[judge.language].highlight);
}
res.render('submission', {

2
modules/problem.js

@ -592,7 +592,7 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
const curUser = res.locals.user;
if (!problem) throw new ErrorMessage('无此题目。');
if (problem.type !== 'submit-answer' && !syzoj.config.languages[req.body.language]) throw new ErrorMessage('不支持该语言。');
if (problem.type !== 'submit-answer' && !syzoj.config.enabled_languages.includes(req.body.language)) throw new ErrorMessage('不支持该语言。');
if (!curUser) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) });
let judge_state;

9
modules/submission.js

@ -71,9 +71,9 @@ app.get('/submissions', async (req, res) => {
if (req.query.problem_id) {
let problem_id = parseInt(req.query.problem_id);
let problem = await Problem.fromID(problem_id);
if(!problem)
if (!problem)
throw new ErrorMessage("无此题目。");
if(await problem.isAllowedUseBy(res.locals.user)) {
if (await problem.isAllowedUseBy(res.locals.user)) {
where.problem_id = {
$and: [
{ $eq: where.problem_id = problem_id }
@ -91,6 +91,8 @@ app.get('/submissions', async (req, res) => {
if (req.query.problem_id) where.problem_id = parseInt(req.query.problem_id) || -1;
}
let isFiltered = !!(where.problem_id || where.user_id || where.score || where.language || where.status);
let paginate = syzoj.utils.paginate(await JudgeState.count(where), req.query.page, syzoj.config.page.judge_state);
let judge_state = await JudgeState.query(paginate, where, [['id', 'desc']], true);
@ -112,6 +114,7 @@ app.get('/submissions', async (req, res) => {
pushType: 'rough',
form: req.query,
displayConfig: displayConfig,
isFiltered: isFiltered
});
} catch (e) {
syzoj.log(e);
@ -144,7 +147,7 @@ app.get('/submission/:id', async (req, res) => {
if (judge.problem.type !== 'submit-answer') {
judge.codeLength = judge.code.length;
judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight);
judge.code = await syzoj.utils.highlight(judge.code, syzoj.languages[judge.language].highlight);
}
displayConfig.showRejudge = await judge.problem.isAllowedEditBy(res.locals.user);

17
package.json

@ -24,7 +24,7 @@
"homepage": "https://github.com/syzoj/syzoj#readme",
"dependencies": {
"amqplib": "^0.5.2",
"ansi-to-html": "^0.6.8",
"ansi-to-html": "^0.6.9",
"async-lock": "^1.1.3",
"body-parser": "^1.15.2",
"cheerio": "^1.0.0-rc.1",
@ -36,31 +36,30 @@
"express": "^4.14.0",
"express-session": "^1.14.1",
"file-size": "^1.0.0",
"fs-extra": "^7.0.0",
"gravatar": "^1.5.2",
"fs-extra": "^7.0.1",
"gravatar": "^1.8.0",
"javascript-time-ago": "^1.0.30",
"js-yaml": "^3.9.0",
"jsdom": "^13.0.0",
"jsondiffpatch": "0.3.11",
"jsonwebtoken": "^8.3.0",
"jsonwebtoken": "^8.4.0",
"katex": "^0.10.0",
"mathjax-node": "^2.1.1",
"moemark": "^0.3.10",
"moment": "^2.15.0",
"msgpack-lite": "^0.1.26",
"multer": "^1.2.0",
"mysql2": "^1.6.2",
"mysql2": "^1.6.4",
"node-7z": "^0.4.0",
"nodemailer": "^4.1.0",
"nodemailer": "^4.7.0",
"object-assign-deep": "^0.4.0",
"pygmentize-bundled-cached": "^1.1.0",
"randomstring": "^1.1.5",
"request": "^2.74.0",
"request-promise": "^4.1.1",
"sendmail": "^1.1.1",
"sequelize": "^4.41.0",
"sequelize": "^4.41.2",
"session-file-store": "^1.0.0",
"socket.io": "^2.0.3",
"socket.io": "^2.2.0",
"syzoj-divine": "^1.0.2",
"tmp-promise": "^1.0.3",
"waliyun": "^3.1.1",

19
static/style.css

@ -21,15 +21,15 @@ h4,
h5,
body
{
font-family:
font-family:
Lato,
-apple-system,
'PingFang SC',/* Apple */
'Source Han Sans SC',
-apple-system,
'PingFang SC',/* Apple */
'Source Han Sans SC',
'Noto Sans CJK SC', /* Google */
'Microsoft Yahei',
'Lantinghei SC',
'Hiragino Sans GB',
'Microsoft Yahei',
'Lantinghei SC',
'Hiragino Sans GB',
'Microsoft Sans Serif', /* M$ */
'WenQuanYi Micro Hei', /* *nix */
sans-serif;
@ -300,3 +300,8 @@ code {
font-size: 1em;
vertical-align: initial;
}
.ui.selection.dropdown .menu>.item {
padding-left: 0.8rem !important;
padding-right: 0 !important;
}

147
static/syzoj.svg

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="38.25193mm"
height="59.522903mm"
viewBox="0 0 38.25193 59.522903"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="syzoj-with-text.svg"
inkscape:export-filename="/home/Menci/Pictures/syzoj.svg.png"
inkscape:export-xdpi="1383.14"
inkscape:export-ydpi="1383.14">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="77.829033"
inkscape:cy="105.88145"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
inkscape:snap-page="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-global="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="3000"
inkscape:window-height="1876"
inkscape:window-x="0"
inkscape:window-y="56"
inkscape:window-maximized="1"
inkscape:snap-nodes="true">
<sodipodi:guide
position="29.950891,59.522906"
orientation="0,1"
id="guide823"
inkscape:locked="false" />
<sodipodi:guide
position="29.266013,21.270975"
orientation="0,1"
id="guide825"
inkscape:locked="false" />
<sodipodi:guide
position="6.074219e-07,46.028415"
orientation="1,0"
id="guide827"
inkscape:locked="false" />
<sodipodi:guide
position="38.251927,49.193966"
orientation="1,0"
id="guide829"
inkscape:locked="false" />
<sodipodi:guide
position="19.125966,14.424324"
orientation="0,1"
id="guide835"
inkscape:locked="false" />
<sodipodi:guide
position="19.125966,6.8466576"
orientation="0,1"
id="guide837"
inkscape:locked="false" />
<sodipodi:guide
position="34.726563,8.4008045e-07"
orientation="0,1"
id="guide843"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-85.874039,-129.37404)">
<path
style="fill:#0e83cd;fill-opacity:0.94117647;stroke-width:0.26458332"
d="m 104.99974,129.37404 -16.563327,9.56273 v 19.12648 l 16.563327,9.56272 16.56385,-9.56272 v -19.12648 z m -0.50746,8.38967 10.10946,5.83582 -2.63498,1.51981 -7.47448,-4.31447 -4.840016,2.79466 5.246196,3.02927 1.01389,0.58395 7.47448,4.31653 2.22828,1.28519 -2.41587,1.3963 h -0.002 l -8.70489,5.02553 -10.107394,-5.83582 2.634465,-1.5198 7.472929,4.31447 5.85339,-3.38068 -5.24412,-3.0272 -0.82424,-0.47646 h -0.002 l -7.662067,-4.42403 -2.228287,-1.28726 2.228287,-1.28726 7.474477,-4.31395 z"
id="path16"
inkscape:connector-curvature="0" />
<g
aria-label="SYZOJ"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
id="text817">
<path
d="m 94.39228,178.89645 -1.481666,-0.41275 q -0.783167,-0.21166 -1.121834,-0.70908 -0.328083,-0.508 -0.328083,-1.36525 0,-0.62442 0.127,-1.00542 0.137583,-0.39158 0.465667,-0.59266 0.328083,-0.20109 0.73025,-0.26459 0.402166,-0.0635 1.0795,-0.0635 1.291166,0 2.201333,0.22225 l -0.0635,0.77259 q -1.534584,-0.0529 -2.0955,-0.0529 -0.8255,0 -1.100667,0.16933 -0.275166,0.15875 -0.275166,0.84667 0,0.53975 0.1905,0.762 0.201083,0.22225 0.687916,0.34925 l 1.449917,0.40217 q 0.814916,0.22225 1.143,0.71966 0.338666,0.49742 0.338666,1.35467 0,1.2065 -0.550333,1.60867 -0.550333,0.40216 -1.93675,0.40216 -1.164166,0 -2.296583,-0.21166 l 0.07408,-0.78317 q 1.651,0.0529 2.243667,0.0529 0.8255,-0.0106 1.100666,-0.21167 0.28575,-0.20108 0.28575,-0.86783 0,-0.56092 -0.1905,-0.77259 -0.1905,-0.22225 -0.677333,-0.34925 z"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:10.58333302px;font-family:'Exo 2';-inkscape-font-specification:'Exo 2 Medium';stroke-width:0.26458332"
id="path866" />
<path
d="m 100.37815,179.38329 v 2.52941 h -1.058336 v -2.54 l -2.413,-4.7625 h 1.121833 l 1.471084,2.99509 q 0.08467,0.17991 0.3175,0.84666 h 0.07408 q 0.222246,-0.65616 0.317496,-0.83608 l 1.47109,-3.00567 h 1.10066 z"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:10.58333302px;font-family:'Exo 2';-inkscape-font-specification:'Exo 2 Medium';stroke-width:0.26458332"
id="path868" />
<path
d="m 108.40263,181.01312 v 0.89958 h -5.00592 v -0.89958 l 3.77825,-5.50333 h -3.72533 v -0.89959 h 4.87892 v 0.89959 l -3.76767,5.50333 z"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:10.58333302px;font-family:'Exo 2';-inkscape-font-specification:'Exo 2 Medium';stroke-width:0.26458332"
id="path870" />
<path
d="m 112.37667,174.47262 q 1.62983,0 2.27542,0.8255 0.64558,0.8255 0.64558,2.96333 0,2.13784 -0.64558,2.96334 -0.64559,0.8255 -2.27542,0.8255 -1.61925,0 -2.26483,-0.8255 -0.64559,-0.8255 -0.64559,-2.96334 0,-2.13783 0.64559,-2.96333 0.64558,-0.8255 2.26483,-0.8255 z m 1.41817,1.55575 q -0.39159,-0.61383 -1.41817,-0.61383 -1.02658,0 -1.41817,0.61383 -0.381,0.61383 -0.381,2.23308 0,1.61925 0.381,2.23309 0.39159,0.61383 1.41817,0.61383 1.02658,0 1.41817,-0.61383 0.39158,-0.61384 0.39158,-2.23309 0,-1.61925 -0.39158,-2.23308 z"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:10.58333302px;font-family:'Exo 2';-inkscape-font-specification:'Exo 2 Medium';stroke-width:0.26458332"
id="path872" />
<path
d="m 118.53931,174.6102 v 5.70442 q 0,0.83608 -0.37041,1.27 -0.37042,0.43392 -1.06892,0.42333 -0.62442,0 -1.03717,-0.13758 l 0.11642,-0.762 h 0.51858 q 0.78317,0 0.78317,-0.74083 v -5.75734 z"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:10.58333302px;font-family:'Exo 2';-inkscape-font-specification:'Exo 2 Medium';stroke-width:0.26458332"
id="path874" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

38
utility.js

@ -34,36 +34,6 @@ module.exports = {
return path.resolve.apply(null, a);
},
markdown(obj, keys, noReplaceUI) {
let XSS = require('xss');
let CSSFilter = require('cssfilter');
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(['style', 'class']);
let xss = new XSS.FilterXSS({
css: {
whiteList: Object.assign({}, require('cssfilter/lib/default').whiteList, {
'vertical-align': true,
top: true,
bottom: true,
left: true,
right: true,
"white-space": true
})
},
whiteList: whiteList,
stripIgnoreTag: true,
onTagAttr: (tag, name, value, isWhiteAttr) => {
if (tag.toLowerCase() === 'img' && name.toLowerCase() === 'src' && value.startsWith('data:image/')) return name + '="' + XSS.escapeAttrValue(value) + '"';
}
});
let replaceXSS = 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;
@ -90,13 +60,13 @@ module.exports = {
if (!keys) {
if (!obj || !obj.trim()) resolve("");
else markdownRenderer(obj, s => {
resolve(replaceUI(replaceXSS(s)));
resolve(replaceUI(s));
});
} else {
let res = obj, cnt = keys.length;
for (let key of keys) {
markdownRenderer(res[key], (s) => {
res[key] = replaceUI(replaceXSS(s));
res[key] = replaceUI(s);
if (!--cnt) resolve(res);
});
}
@ -121,8 +91,8 @@ module.exports = {
}
return sgn + util.format('%s:%s:%s', toStringWithPad(x / 3600), toStringWithPad(x / 60 % 60), toStringWithPad(x % 60));
},
formatSize(x) {
let res = filesize(x, { fixed: 1 }).calculate();
formatSize(x, precision) {
let res = filesize(x, { fixed: precision || 1 }).calculate();
if (res.result === parseInt(res.result)) res.fixed = res.result.toString();
if (res.suffix.startsWith('Byte')) res.suffix = 'B';
else res.suffix = res.suffix.replace('iB', '');

4
views/admin_rejudge.ejs

@ -19,8 +19,8 @@
<div class="menu">
<div class="item" data-value="">不限</div>
<div class="item" data-value="submit-answer">提交答案</div>
<% for (let lang in syzoj.config.languages) { %>
<div class="item" data-value="<%= lang %>"><%= syzoj.config.languages[lang].show %></div>
<% for (let lang of syzoj.config.filter_enabled_languages) { %>
<div class="item" data-value="<%= lang %>"><%= syzoj.languages[lang].show %></div>
<% } %>
</div>
</div>

2
views/article.ejs

@ -63,7 +63,7 @@
<div class="metadata">
<span class="date"><%= syzoj.utils.formatDate(comment.public_time) %></span>
</div>
<div class="text font-content"><%- comment.content %></div>
<div class="text font-content" style="min-height: 19.5px; "><%- comment.content %></div>
<% if (comment.allowedEdit) { %>
<div class="actions"><a onclick="$('#modal-delete-<%= comment.id %>').modal('show')">删除</a></div>
<div class="ui basic modal" id="modal-delete-<%= comment.id %>">

2
views/footer.ejs

@ -1,5 +1,5 @@
</div>
<div class="ui vertical footer segment" style="margin-top: 10px; ">
<div class="ui vertical footer segment" style="margin-top: 15px; ">
<div class="ui center aligned container">
<span style="color: #999;"><%= syzoj.config.title %> Powered by <a href="https://github.com/syzoj/syzoj" target="_blank">SYZOJ</a>.</span>
</div>

2
views/header.ejs

@ -11,7 +11,7 @@
<link href="/mathjax.css?20181105" rel="stylesheet">
<link href="https://cdnjs.loli.net/ajax/libs/KaTeX/0.10.0/katex.min.css" rel="stylesheet">
<link href="https://cdnjs.loli.net/ajax/libs/morris.js/0.5.1/morris.css" rel="stylesheet">
<link href="/style.css?20181108" rel="stylesheet">
<link href="/style.css?2018110801" rel="stylesheet">
<link href="https://fonts.loli.net/css?family=Fira+Mono" rel="stylesheet">
<link href="https://fonts.loli.net/css?family=Lato:400,700,400italic,700italic&subset=latin" rel="stylesheet">
<link href="https://fonts.loli.net/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i,800,800i&amp;subset=latin-ext" rel="stylesheet">

8
views/help.ejs

@ -5,11 +5,11 @@
<div style="font-content">
<h2 class="ui header">评测</h2>
<p>
C++ 使用 <code>g++</code> 编译,命令为 &nbsp;<code>g++ source_file.cpp -o exec_file -O2 -lm -DONLINE_JUDGE -mx32</code>;
<br> C 使用 <code>gcc</code> 编译,命令为 &nbsp;<code>gcc source_file.c -o exec_file -O2 -lm -DONLINE_JUDGE -mx32</code>;
C++ 使用 <code>clang++</code> 或 <code>g++</code> 编译,命令形如 &nbsp;<code>clang++/g++ source_file.cpp -o exec_file -O2 -lm -DONLINE_JUDGE -mx32 -std=c++03</code>(对于 C++ 11 和 C++ 17,替换命令中的 <code>-std=</code> 参数)
<br> C 使用 <code>clang</code> 编译,命令为 &nbsp;<code>clang source_file.c -o exec_file -O2 -lm -DONLINE_JUDGE -mx32</code>;
<br> Pascal 使用 <code>fpc</code> 编译,命令为 &nbsp;<code>fpc source_file.pas -O2</code>。
<br> Java 编译时会自动检测您的代码中 <code>public class</code>,如果您的代码中没有 <code>public class</code>,请将入口类命名为 <code>Main</code>。
<br> C# 与 Visual Basic 使用 Mono 平台的编译器与运行环境。
<br> C# 使用 Mono 平台的编译器与运行环境。
<br>
</p>
<p>请根据题目中的说明选择使用<strong>标准输入输出</strong>或<strong>文件输入输出</strong>。</p>
@ -82,7 +82,7 @@
<p>Special Judge 程序运行时,其目录下会有四个文件 <code>input</code>、<code>user_out</code>、<code>answer</code>、<code>code</code>,分别对应该测试点的输入文件、用户输出、该测试点的输出文件、用户的代码(对于非提交答案题目)。</p>
<p>Special Judge 程序运行完成后,应将该测试点的得分输出到标准输出(<code>stdout</code>)中(范围为 <code>0</code> 到 <code>100</code>,将自动折合为测试点分数),并将提供给用户的额外信息输出到标准错误输出(<code>stderr</code>)中。</p>
<h4>语言简称</h4>
<p>配置数据时,描述 Special Judge 或交互器的语言时使用语言的简称,它们是 <code>c</code>、<code>cpp</code>、<code>cpp11</code>、<code>csharp</code>、<code>haskell</code>、<code>java</code>、<code>lua</code>、<code>luajit</code>、<code>nodejs</code>、<code>pascal</code>、<code>python2</code>、<code>python3</code>、<code>ruby</code>、<code>vala</code>、<code>vbnet</code>、<code>ocaml</code>。</p>
<p>配置数据时,描述 Special Judge 或交互器的语言时使用语言的简称,它们是 <code>c</code>、<code>cpp</code>、<code>cpp11</code>、<code>cpp17</code>、<code>cpp11-clang</code>、<code>cpp17-clang</code>、<code>csharp</code>、<code>haskell</code>、<code>java</code>、<code>nodejs</code>、<code>pascal</code>、<code>python2</code>、<code>python3</code>、<code>ruby</code>。</p>
</div>
</div>
<% include footer %>

10
views/problem.ejs

@ -310,15 +310,15 @@ div[class*=ace_br] {
<div class="four wide column" style="margin-right: -25px; ">
<div class="ui attached vertical fluid pointing menu" id="languages-menu" style="height: 370px; overflow-y: scroll; overflow-x: hidden; ">
<%
let language = Object.getOwnPropertyNames(syzoj.config.languages).shift();
let language = syzoj.config.enabled_languages[0];
if (state) {
language = state.language;
} else if (lastLanguage) language = lastLanguage;
%>
<% for (lang in syzoj.config.languages) { %>
<a class="item<%= lang === language ? ' active' : '' %>" data-value="<%= lang %>" data-mode="<%= syzoj.config.languages[lang].editor %>">
<%= syzoj.config.languages[lang].show %>
<div class="ui right floated" style="opacity: 0.4; margin-top: 8px; font-size: 0.7em; "><%= syzoj.config.languages[lang].version %></div>
<% for (lang of syzoj.config.enabled_languages) { %>
<a class="item<%= lang === language ? ' active' : '' %>" data-value="<%= lang %>" data-mode="<%= syzoj.languages[lang].editor %>">
<%= syzoj.languages[lang].show %>
<div class="ui right floated" style="opacity: 0.4; margin-top: 8px; font-size: 0.7em; "><%= syzoj.languages[lang].version %></div>
</a>
<% } %>
</div>

2
views/statistics.ejs

@ -83,7 +83,7 @@ function getColorOfScore(score) {
<% if (problem.type !== 'submit-answer') { %>
<td><%= judge.total_time %> ms</td>
<td><%= parseInt(judge.max_memory) || 0 %> K</td>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / <%= syzoj.utils.formatSize(judge.code.length) %></td>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.languages[judge.language].show %></a> / <%= syzoj.utils.formatSize(judge.code.length) %></td>
<% } else { %>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% } %>

8
views/status_label.ejs

@ -20,7 +20,7 @@ const iconList = {
};
Vue.component('status-label', {
template: '#statusIconTemplate',
props: ['status', 'indetail'],
props: ['status', 'indetail', 'progress'],
computed: {
icon() {
if (this.status in iconList) {
@ -31,6 +31,10 @@ Vue.component('status-label', {
},
colorClass() {
return (this.indetail ? 'status_detail ' : '') + this.status.toLowerCase().split(' ').join('_');
},
outputStatus() {
if (this.status === 'Running' && this.progress) return 'Running ' + this.progress.finished + '/' + this.progress.total;
else return this.status;
}
}
})
@ -38,6 +42,6 @@ Vue.component('status-label', {
<script type="text/x-template" id="statusIconTemplate">
<span class="status" :class="colorClass">
<i class="icon" :class="icon"></i>
{{ status }}
{{ outputStatus }}
</span>
</script>

36
views/submission.ejs

@ -47,7 +47,7 @@
</tr>
</thead>
<tbody>
<tr is="submission-item" v-bind:data="roughData" :config="displayConfig" :show-rejudge="showRejudge"></tr>
<tr is="submission-item" v-bind:data="roughData" :config="displayConfig" :show-rejudge="showRejudge" :progress="getProgress()"></tr>
</tbody>
</table>
@ -64,7 +64,7 @@
子任务 #{{ $index + 1 }}
</div>
<div class="four wide column">
<status-label :status="getSubtaskResult(subtask)" :indetail="true"></status-label>
<status-label :status="getSubtaskResult(subtask)" :indetail="true" :progress="getProgress($index)"></status-label>
</div>
<div class="three wide column" v-if="subtask.score != null">
得分:<span style="font-weight: normal; ">{{ Math.trunc(subtask.score) }}</span>
@ -74,7 +74,7 @@
<div class="content" :class="singleSubtask ? 'active' : ''">
<div class="accordion">
<template v-for="curCase, $caseIndex in subtask.cases">
<div class="title">
<div class="title" :class="checkTestcaseOK(curCase) ? '' : 'unexpandable'">
<div class="ui grid">
<div class="three wide column">
<i class="dropdown icon"></i>
@ -234,10 +234,38 @@ const vueApp = new Vue({
},
checkTestcaseOK(c) {
return c.status === TaskStatus.Done;
},
getProgress(i) {
if (!this.detailResult || !this.detailResult.judge || !this.detailResult.judge.subtasks) return {
finished: 0,
total: 0
};
let isPending = status => [TaskStatus.Waiting, TaskStatus.Running].includes(status);
let subtaskProgress = [], allFinished = 0, allTotal = 0;
for (let i in this.detailResult.judge.subtasks) {
let subtaskFinished = 0, subtaskTotal = 0;
for (let j in this.detailResult.judge.subtasks[i].cases) {
subtaskTotal++, allTotal++;
if (!isPending(this.detailResult.judge.subtasks[i].cases[j].status)) subtaskFinished++, allFinished++;
}
subtaskProgress.push({
finished: subtaskFinished,
total: subtaskTotal
});
}
let allProgress = {
finished: allFinished,
total: allTotal
};
return typeof i === 'undefined' ? allProgress : subtaskProgress[i];
}
},
mounted() {
$(document).ready(function(){ $('.ui.accordion').accordion()});
$(document).ready(function(){ $('.ui.accordion').accordion({ selector: { trigger: '.title:not(.unexpandable)' } })});
},
updated() {
$('.ui.accordion').accordion("refresh");

27
views/submissions.ejs

@ -36,10 +36,10 @@
<i class="dropdown icon"></i>
<div class="default text"></div>
<div class="menu">
<div class="item" data-value="">不限</div>
<div class="item" data-value="submit-answer">提交答案</div>
<% for (let lang in syzoj.config.languages) { %>
<div class="item" data-value="<%= lang %>"><%= syzoj.config.languages[lang].show %></div>
<div class="item" data-value=""><b>不限</b></div>
<div class="item" data-value="submit-answer"><b>提交答案</b></div>
<% for (let lang of syzoj.config.filter_enabled_languages) { %>
<div style="font-size: 12.5px; " class="item" data-value="<%= lang %>"><b><%= syzoj.languages[lang].show %></b></div>
<% } %>
</div>
</div>
@ -52,10 +52,10 @@
<i class="dropdown icon"></i>
<div class="default text"></div>
<div class="menu">
<div class="item" data-value="">不限<i class="dropdown icon" style="visibility: hidden; "></i></div>
<div class="item" data-value=""><b>不限</b><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 class="item" data-value="<%= status %>"><span class="status <%= status.toLowerCase().split(' ').join('_') %>"><i class="<%= this.icon[status] %> icon"></i> <b><%= status %></b></div>
<% } %>
</div>
</div>
@ -100,8 +100,23 @@
</tr>
</tbody>
</table>
<% if (!items.length) { %>
<div style="background-color: #fff; height: 18px; margin-top: -18px; "></div>
<div class="ui placeholder segment" style="margin-top: -5px; ">
<div class="ui icon header">
<% if (isFiltered) { %>
<i class="ui search icon" style="margin-bottom: 20px; "></i>
找不到符合条件的提交
<% } else { %>
<i class="ui file icon" style="margin-bottom: 20px; "></i>
暂无提交
<% } %>
</div>
</div>
<% } else { %>
<br>
<% include page %>
<% } %>
</div>
<script src="https://cdnjs.loli.net/ajax/libs/vue/2.4.2/vue.min.js"></script>

18
views/submissions_item.ejs

@ -4,17 +4,17 @@
<script src="https://cdnjs.loli.net/ajax/libs/textfit/2.3.1/textFit.min.js"></script>
<script>
const submissionUrl = <%- JSON.stringify(displayConfig.inContest ?
const submissionUrl = <%- JSON.stringify(displayConfig.inContest ?
syzoj.utils.makeUrl(['contest', 'submission', 'VanDarkholme']) :
syzoj.utils.makeUrl(['submission', 'VanDarkholme'])) %>;
const problemUrl = <%- JSON.stringify(displayConfig.inContest ?
const problemUrl = <%- JSON.stringify(displayConfig.inContest ?
syzoj.utils.makeUrl(['contest', contest.id, 'problem', 'VanDarkholme']) :
syzoj.utils.makeUrl(['problem', 'VanDarkholme'])) %>;
const userUrl = <%- JSON.stringify(syzoj.utils.makeUrl(['user', 'VanDarkholme'])) %>;
Vue.component('submission-item', {
template: '#submissionItemTemplate',
props: ['data', 'config', 'showRejudge'],
props: ['data', 'config', 'showRejudge', 'progress'],
computed: {
statusString() {
const data = this.data;
@ -57,9 +57,9 @@ Vue.component('submission-item', {
<% } %>
<td ref="problemLabel"><a ref="problemLabelTextFit" style="width: 230px; height: 22px; display: block; margin: 0 auto; line-height: 22px;" :href="problemLink"><b>#{{ config.inContest ? alpha(data.info.problemId) : data.info.problemId }}.</b> {{ data.info.problemName }}</a></td>
<% if (active === 'submissions') { %>
<td><a :href="submissionLink"><b><status-label :status="statusString"></status-label></b></a></td>
<td><a :href="submissionLink"><b><status-label :status="statusString" :progress="progress"></status-label></b></a></td>
<% } else { %>
<td><b><status-label :status="statusString"></status-label></b></td>
<td><b><status-label :status="statusString" :progress="progress"></status-label></b></td>
<% } %>
<template v-if="data.result">
@ -69,7 +69,13 @@ Vue.component('submission-item', {
<td v-if="config.showScore"><span class="score" :class="scoreClass">{{ Math.floor(data.result.score || 0) }}</span></td>
<% } %>
<td v-if="config.showUsage">{{ (data.result.time || 0).toString() + ' ms' }}</td>
<td v-if="config.showUsage">{{ (data.result.memory || 0).toString() + ' KiB' }}</td>
<% if (active === 'submissions') { %>
<td v-if="config.showUsage">{{ data.result.memory }}</td>
<% } else { %>
<td v-if="config.showUsage">{{ (data.result.precise_memory || 0).toString() + ' K'}}</td>
<% } %>
</template> <template v-else>
<td v-if="config.showScore"/> <td v-if="config.showUsage"/> <td v-if="config.showUsage"/>
</template>

8
views/util.ejs

@ -9,13 +9,13 @@ const getOrderString = function(order) {
}
this.createSortableTitle = function(item, display, defaultOrder) {
const isCurrent = curSort === item;
const url = syzoj.utils.makeUrl(req,
Object.assign({}, req.query, {
sort: item,
const url = syzoj.utils.makeUrl(req,
Object.assign({}, req.query, {
sort: item,
order: getOrderString(isCurrent ? (!curOrder) : defaultOrder)
}));
const triangle = isCurrent ? `<i class="${curOrder ? "angle up" : "angle down"} icon"></i>` : "";
return `<a href="${url}">${display}${triangle}</a>`;
return `<a class="black-link" href="${url}">${display}${triangle}</a>`;
}
this.isPending = (status) => {

Loading…
Cancel
Save