diff --git a/README.en.md b/README.en.md index b37d6ee..60efb70 100644 --- a/README.en.md +++ b/README.en.md @@ -1,4 +1,5 @@ -# SYZOJ 2 +

+ [中文](README.md) | English An online judge system for algorithm competition. diff --git a/README.md b/README.md index 68ee5c4..c2bf5e8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# SYZOJ 2 +

+ 中文 | [English](README.en.md) 一个用于算法竞赛的在线评测系统。 diff --git a/app.js b/app.js index 4b9eb6e..20e8fc1 100644 --- a/app.js +++ b/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: [], diff --git a/config-example.json b/config-example.json index f0d3019..fcadf04 100644 --- a/config-example.json +++ b/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" } diff --git a/language-config.json b/language-config.json new file mode 100644 index 0000000..ae84b79 --- /dev/null +++ b/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" + } +} diff --git a/libs/markdown.js b/libs/markdown.js index 19b3519..9527145 100644 --- a/libs/markdown.js +++ b/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 ''; + 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] = '

' + error.toString() + '

'; + else if (display) maths[id] = '

' + result + '

'; + 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, '' + res + '')); + } } 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] = '

' + data.errors.toString() + '

'; - else if (display) maths[id] = '

' + data.html + '

'; - 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 ''; } + 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 = `
${s}
`; + } + return s; + }; + + res = replaceXSS(MoeMark(s)); if (mathPending == 0 && hlPending == 0) { finish(); } diff --git a/libs/submissions_process.js b/libs/submissions_process.js index 44d4503..2eeb679 100644 --- a/libs/submissions_process.js +++ b/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 }; \ No newline at end of file +module.exports = { getRoughResult, getSubmissionInfo, processOverallResult }; diff --git a/modules/contest.js b/modules/contest.js index 9a9165d..303de97 100644 --- a/modules/contest.js +++ b/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', { diff --git a/modules/problem.js b/modules/problem.js index fe42845..8b46c72 100644 --- a/modules/problem.js +++ b/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; diff --git a/modules/submission.js b/modules/submission.js index 9fb21f5..c602d42 100644 --- a/modules/submission.js +++ b/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); diff --git a/package.json b/package.json index ff8eed6..91484c4 100644 --- a/package.json +++ b/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", diff --git a/static/style.css b/static/style.css index 5f076c6..b0d3ce9 100644 --- a/static/style.css +++ b/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; +} diff --git a/static/syzoj.svg b/static/syzoj.svg new file mode 100644 index 0000000..b174373 --- /dev/null +++ b/static/syzoj.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/utility.js b/utility.js index 6304b22..36c3b69 100644 --- a/utility.js +++ b/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 = `
${s}
`; - } - 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', ''); diff --git a/views/admin_rejudge.ejs b/views/admin_rejudge.ejs index f300906..4e68204 100644 --- a/views/admin_rejudge.ejs +++ b/views/admin_rejudge.ejs @@ -19,8 +19,8 @@ diff --git a/views/article.ejs b/views/article.ejs index 92b955c..b106c67 100644 --- a/views/article.ejs +++ b/views/article.ejs @@ -63,7 +63,7 @@
<%= syzoj.utils.formatDate(comment.public_time) %>
-
<%- comment.content %>
+
<%- comment.content %>
<% if (comment.allowedEdit) { %>
删除
-