mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
题解功能
This commit is contained in:
1 parent
ec727d58aa
commit
41261f8c4b
20 files changed
+825
-1277
No files matched your search
+7
-1
@@ -91,6 +91,12 @@ class RecordNotFoundError extends NotFoundError {
|
||||
this.params = [rid];
|
||||
}
|
||||
}
|
||||
class SolutionNotFoundError extends NotFoundError {
|
||||
constructor(psid) {
|
||||
super('SolutionNotFoundError');
|
||||
this.params = [psid];
|
||||
}
|
||||
}
|
||||
class TrainingNotFoundError extends NotFoundError {
|
||||
constructor(tid) {
|
||||
super('TrainingNotFoundError');
|
||||
@@ -116,7 +122,7 @@ module.exports = {
|
||||
UserNotFoundError, VerifyPasswordError, ProblemDataNotFoundError,
|
||||
OpcountExceededError, PermissionError, NoProblemError,
|
||||
ValidationError, ProblemNotFoundError, TrainingNotFoundError,
|
||||
ContestNotFoundError, RecordNotFoundError
|
||||
ContestNotFoundError, RecordNotFoundError, SolutionNotFoundError
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -45,7 +45,7 @@ MIDDLEWARE(async (ctx, next) => {
|
||||
}
|
||||
};
|
||||
ctx.back = () => ctx.redirect(ctx.request.headers.referer || '/');
|
||||
ctx.csrf_token = await token.add(token.TYPE_CSRF_TOKEN, 600, ctx.path);
|
||||
ctx.csrf_token = (await token.add(token.TYPE_CSRF_TOKEN, 600, ctx.path))[0];
|
||||
await next();
|
||||
if (ctx.session.sid)
|
||||
await token.update(ctx.session.sid, tokenType, expireSeconds, Object.assign({
|
||||
|
||||
+321
-443
@@ -1,15 +1,15 @@
|
||||
const
|
||||
fs = require('fs'),
|
||||
bson = require('bson'),
|
||||
paginate = require('../lib/paginate'),
|
||||
validator = require('../lib/validator'),
|
||||
problem = require('../model/problem'),
|
||||
record = require('../model/record'),
|
||||
user = require('../model/user'),
|
||||
solution = require('../model/solution'),
|
||||
system = require('../model/system'),
|
||||
{ GET, POST } = require('../service/server'),
|
||||
{ ROUTE } = require('../service/server'),
|
||||
gridfs = require('../service/gridfs'),
|
||||
queue = require('../service/queue'),
|
||||
{ requirePerm } = require('./tools'),
|
||||
{ NoProblemError, ProblemDataNotFoundError, BadRequestError } = require('../error'),
|
||||
{ constants } = require('../options'),
|
||||
{
|
||||
@@ -19,253 +19,344 @@ const
|
||||
PERM_CREATE_PROBLEM,
|
||||
PERM_READ_PROBLEM_DATA,
|
||||
PERM_EDIT_PROBLEM,
|
||||
PERM_JUDGE
|
||||
PERM_JUDGE,
|
||||
PERM_VIEW_PROBLEM_SOLUTION,
|
||||
PERM_CREATE_PROBLEM_SOLUTION,
|
||||
PERM_EDIT_PROBLEM_SOLUTION,
|
||||
PERM_DELETE_PROBLEM_SOLUTION,
|
||||
PERM_EDIT_PROBLEM_SOLUTION_REPLY,
|
||||
PERM_REPLY_PROBLEM_SOLUTION
|
||||
} = require('../permission');
|
||||
|
||||
queue.assert('judge');
|
||||
|
||||
GET('/p', requirePerm(PERM_VIEW_PROBLEM), async ctx => {
|
||||
ctx.templateName = 'problem_main.html';
|
||||
let q = {},
|
||||
page = ctx.query.page || 1;
|
||||
if (ctx.query.category) q.category = ctx.query.category;
|
||||
if (!ctx.state.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
|
||||
let pdocs = await problem.getMany(q, { pid: 1 }, page, constants.PROBLEM_PER_PAGE);
|
||||
ctx.body = {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
['problem_main', null]
|
||||
],
|
||||
page, pdocs, category: ''
|
||||
};
|
||||
});
|
||||
GET('/problem/random', requirePerm(PERM_VIEW_PROBLEM), async ctx => {
|
||||
let q = {};
|
||||
if (!ctx.state.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
|
||||
let pid = await problem.random(q);
|
||||
if (!pid) throw new NoProblemError();
|
||||
ctx.body = { pid };
|
||||
});
|
||||
GET('/p/:pid', requirePerm(PERM_VIEW_PROBLEM), async ctx => {
|
||||
ctx.templateName = 'problem_detail.html';
|
||||
let uid = ctx.state.user._id,
|
||||
pid = ctx.params.pid;
|
||||
let pdoc = await problem.get({ pid, uid });
|
||||
if (pdoc.hidden) ctx.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
|
||||
let udoc = await user.getById(pdoc.owner);
|
||||
ctx.body = {
|
||||
path: [
|
||||
class ProblemHandler {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx;
|
||||
this.ctx.templateName = 'problem_main.html';
|
||||
this.ctx.checkPerm(PERM_VIEW_PROBLEM);
|
||||
}
|
||||
async get({ page = 1, category = null }) {
|
||||
let q = {};
|
||||
if (category) q.category = category;
|
||||
if (!this.ctx.state.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
|
||||
let pdocs = await problem.getMany(q, { pid: 1 }, page, constants.PROBLEM_PER_PAGE);
|
||||
this.ctx.body = {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
['problem_main', null]
|
||||
],
|
||||
page, pdocs, category: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemRandomHandler extends ProblemHandler {
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
async get() {
|
||||
let q = {};
|
||||
if (!this.ctx.state.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
|
||||
let pid = await problem.random(q);
|
||||
if (!pid) throw new NoProblemError();
|
||||
this.ctx.body = { pid };
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemDetailHandler extends ProblemHandler {
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
this.uid = ctx.state.user._id;
|
||||
this.pid = ctx.params.pid;
|
||||
this.ctx.templateName = 'problem_detail.html';
|
||||
}
|
||||
async _prepare() {
|
||||
if (this.pid) this.pdoc = await problem.get(this);
|
||||
if (this.pdoc.hidden && this.pdoc.owner != this.uid) this.ctx.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
|
||||
if (this.pdoc) this.udoc = await user.getById(this.pdoc.owner);
|
||||
this.ctx.body = {
|
||||
pdoc: this.pdoc,
|
||||
udoc: this.udoc,
|
||||
title: (this.pdoc || {}).title || ''
|
||||
};
|
||||
}
|
||||
async get() {
|
||||
this.ctx.body.path = [
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
[pdoc.title, null, true]
|
||||
],
|
||||
pdoc, udoc, title: pdoc.title
|
||||
};
|
||||
});
|
||||
GET('/p/:pid/submit', requirePerm(PERM_SUBMIT_PROBLEM), async ctx => {
|
||||
ctx.templateName = 'problem_submit.html';
|
||||
let uid = ctx.state.user._id,
|
||||
pid = ctx.params.pid;
|
||||
let pdoc = await problem.get({ pid, uid });
|
||||
if (pdoc.hidden) ctx.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
|
||||
let udoc = await user.getById(pdoc.owner);
|
||||
ctx.body = {
|
||||
path: [
|
||||
[this.pdoc.title, null, true]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemSubmitHandler extends ProblemDetailHandler {
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
this.ctx.templateName = 'problem_submit.html';
|
||||
this.ctx.checkPerm(PERM_SUBMIT_PROBLEM);
|
||||
}
|
||||
async get() {
|
||||
let rdocs = await record.getUserInProblemMulti(this.uid, this.pdoc._id).sort({ _id: -1 }).limit(10).toArray();
|
||||
this.ctx.body = {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
[this.pdoc.title, `/p/${this.pid}`, true],
|
||||
['problem_submit', null]
|
||||
],
|
||||
pdoc: this.pdoc,
|
||||
udoc: this.udoc,
|
||||
rdocs,
|
||||
title: this.pdoc.title
|
||||
};
|
||||
}
|
||||
async post() {
|
||||
let { lang, code } = this.ctx.request.body;
|
||||
let rid = await record.add({
|
||||
uid: this.uid, lang, code, pid: this.pdoc._id
|
||||
});
|
||||
await queue.push('judge', rid);
|
||||
this.ctx.state.user.nSubmit++;
|
||||
this.ctx.body = { rid };
|
||||
this.ctx.setRedirect = `/r/${rid}`;
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemSettingsHandler extends ProblemDetailHandler {
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
this.ctx.templateName = 'problem_settings.html';
|
||||
}
|
||||
async prepare() {
|
||||
if (this.pdoc.owner != this.uid) this.ctx.checkPerm(PERM_EDIT_PROBLEM);
|
||||
}
|
||||
async get() {
|
||||
this.ctx.body.path = [
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
[pdoc.title, `/p/${pid}`, true],
|
||||
['problem_submit', null]
|
||||
],
|
||||
pdoc, udoc, title: pdoc.title
|
||||
};
|
||||
});
|
||||
POST('/p/:pid/submit', requirePerm(PERM_SUBMIT_PROBLEM), async ctx => {
|
||||
let pdoc = await problem.get({ pid: ctx.params.pid });
|
||||
let rid = await record.add({
|
||||
uid: ctx.state.user._id,
|
||||
lang: ctx.request.body.lang,
|
||||
code: ctx.request.body.code,
|
||||
pid: pdoc._id
|
||||
});
|
||||
await queue.push('judge', rid);
|
||||
ctx.state.user.nSubmit++;
|
||||
ctx.body = { rid };
|
||||
ctx.setRedirect = `/r/${rid}`;
|
||||
});
|
||||
GET('/p/:pid/settings', async ctx => {
|
||||
ctx.templateName = 'problem_settings.html';
|
||||
let pdoc = await problem.get({ pid: ctx.params.pid, uid: ctx.state.user._id });
|
||||
if (pdoc.owner != ctx.state.user._id) ctx.checkPerm(PERM_EDIT_PROBLEM);
|
||||
ctx.body = {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
[pdoc.title, `/p/${ctx.params.pid}`, true],
|
||||
[this.pdoc.title, `/p/${this.pid}`, true],
|
||||
['problem_settings', null]
|
||||
], pdoc
|
||||
};
|
||||
});
|
||||
POST('/p/:pid/settings', async ctx => {
|
||||
ctx.templateName = 'problem_settings.html';
|
||||
// TODO(masnn)
|
||||
ctx.back();
|
||||
});
|
||||
GET('/p/:pid/edit', async ctx => {
|
||||
ctx.templateName = 'problem_edit.html';
|
||||
let pdoc = await problem.get({ pid: ctx.params.pid, uid: ctx.state.user._id });
|
||||
if (pdoc.owner != ctx.state.user._id) ctx.checkPerm(PERM_EDIT_PROBLEM);
|
||||
ctx.body = {
|
||||
path: [
|
||||
];
|
||||
}
|
||||
async post() {
|
||||
// TODO(masnn)
|
||||
this.ctx.back();
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemEditHandler extends ProblemDetailHandler {
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
this.ctx.templateName = 'problem_edit.html';
|
||||
}
|
||||
async prepare() {
|
||||
if (this.pdoc.owner != this.uid) this.ctx.checkPerm(PERM_EDIT_PROBLEM);
|
||||
}
|
||||
async get() {
|
||||
this.ctx.body.path = [
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
[pdoc.title, `/p/${ctx.params.pid}`, true],
|
||||
[this.pdoc.title, `/p/${this.ctx.params.pid}`, true],
|
||||
['problem_edit', null]
|
||||
],
|
||||
pdoc, page_name: 'problem_edit'
|
||||
};
|
||||
});
|
||||
POST('/p/:pid/edit', async ctx => {
|
||||
let title = validator.checkTitle(ctx.request.body.title);
|
||||
let content = validator.checkContent(ctx.request.body.content);
|
||||
let pid = validator.checkPid(ctx.request.body.pid);
|
||||
let pdoc = await problem.get({ pid: ctx.params.pid });
|
||||
await problem.edit(pdoc._id, {
|
||||
title, content, pid
|
||||
});
|
||||
ctx.setRedirect = `/p/${pid}`;
|
||||
});
|
||||
GET('/p/:pid/upload', async ctx => {
|
||||
ctx.templateName = 'problem_upload.html';
|
||||
let pdoc = await problem.get({ pid: ctx.params.pid, uid: ctx.state.user._id });
|
||||
if (pdoc.owner != ctx.state.user._id) ctx.checkPerm(PERM_EDIT_PROBLEM);
|
||||
let md5;
|
||||
if (pdoc.data && typeof pdoc.data == 'object') {
|
||||
let files = await gridfs.find({ _id: pdoc.data }).toArray();
|
||||
md5 = files[0].md5;
|
||||
];
|
||||
this.ctx.page_name = 'problem_edit';
|
||||
}
|
||||
ctx.body = { pdoc, md5 };
|
||||
});
|
||||
POST('/p/:pid/upload', async ctx => {
|
||||
ctx.templateName = 'problem_upload.html';
|
||||
if (!ctx.request.files.file) throw new BadRequestError();
|
||||
let pdoc = await problem.get({ pid: ctx.params.pid, uid: ctx.state.user._id });
|
||||
if (pdoc.owner != ctx.state.user._id) ctx.checkPerm(PERM_EDIT_PROBLEM);
|
||||
const r = fs.createReadStream(ctx.request.files.file.path);
|
||||
let f = gridfs.openUploadStream('data.zip');
|
||||
await new Promise((resolve, reject) => {
|
||||
r.pipe(f);
|
||||
f.once('finish', resolve);
|
||||
f.once('error', reject);
|
||||
});
|
||||
let md5;
|
||||
if (pdoc.data && typeof pdoc.data == 'object') {
|
||||
gridfs.delete(pdoc.data);
|
||||
async post() {
|
||||
let title = validator.checkTitle(this.ctx.request.body.title);
|
||||
let content = validator.checkContent(this.ctx.request.body.content);
|
||||
let pid = validator.checkPid(this.ctx.request.body.pid);
|
||||
let pdoc = await problem.get({ pid: this.ctx.params.pid });
|
||||
await problem.edit(pdoc._id, {
|
||||
title, content, pid
|
||||
});
|
||||
this.ctx.setRedirect = `/p/${pid}`;
|
||||
}
|
||||
pdoc = await problem.edit(pdoc._id, { data: f.id });
|
||||
if (pdoc.data && typeof pdoc.data == 'object') {
|
||||
let files = await gridfs.find({ _id: pdoc.data }).toArray();
|
||||
md5 = files[0].md5;
|
||||
}
|
||||
|
||||
class ProblemDataUploadHandler extends ProblemDetailHandler {
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
this.ctx.templateName = 'problem_upload.html';
|
||||
}
|
||||
ctx.body = { pdoc, md5 };
|
||||
});
|
||||
GET('/p/:pid/data', async ctx => {
|
||||
let pdoc = await problem.get({ pid: ctx.params.pid, uid: ctx.state.user._id });
|
||||
if (!ctx.state.user._id == pdoc.owner) ctx.checkPerm([PERM_READ_PROBLEM_DATA, PERM_JUDGE]);
|
||||
if (!pdoc.data) throw new ProblemDataNotFoundError(ctx.params.pid);
|
||||
else if (typeof pdoc.data == 'string') ctx.setRedirect = pdoc.data.split('from:')[1];
|
||||
ctx.attachment(`${pdoc.title}.zip`);
|
||||
ctx.body = gridfs.openDownloadStream(pdoc.data);
|
||||
});
|
||||
GET('/problem/create', requirePerm(PERM_CREATE_PROBLEM), async ctx => {
|
||||
ctx.templateName = 'problem_edit.html';
|
||||
ctx.body = {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
async prepare() {
|
||||
if (this.pdoc.owner != this.uid) this.ctx.checkPerm(PERM_EDIT_PROBLEM);
|
||||
if (this.pdoc.data && typeof this.pdoc.data == 'object') {
|
||||
let files = await gridfs.find({ _id: this.pdoc.data }).toArray();
|
||||
this.md5 = files[0].md5;
|
||||
}
|
||||
}
|
||||
async get() {
|
||||
this.ctx.body.md5 = this.md5;
|
||||
}
|
||||
async post() {
|
||||
if (!this.ctx.request.files.file) throw new BadRequestError();
|
||||
const r = fs.createReadStream(this.ctx.request.files.file.path);
|
||||
let f = gridfs.openUploadStream('data.zip');
|
||||
await new Promise((resolve, reject) => {
|
||||
r.pipe(f);
|
||||
f.once('finish', resolve);
|
||||
f.once('error', reject);
|
||||
});
|
||||
if (this.pdoc.data && typeof this.pdoc.data == 'object')
|
||||
gridfs.delete(this.pdoc.data);
|
||||
this.pdoc = await problem.edit(this.pdoc._id, { data: f.id });
|
||||
if (this.pdoc.data && typeof this.pdoc.data == 'object') {
|
||||
let files = await gridfs.find({ _id: this.pdoc.data }).toArray();
|
||||
this.md5 = files[0].md5;
|
||||
}
|
||||
this.ctx.body.md5 = this.md5;
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemDataDownloadHandler extends ProblemDetailHandler {
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
async get({ pid }) {
|
||||
if (this.uid != this.pdoc.owner) this.ctx.checkPerm([PERM_READ_PROBLEM_DATA, PERM_JUDGE]);
|
||||
if (!this.pdoc.data) throw new ProblemDataNotFoundError(pid);
|
||||
else if (typeof this.pdoc.data == 'string') this.ctx.setRedirect = this.pdoc.data.split('from:')[1];
|
||||
this.ctx.attachment(`${this.pdoc.title}.zip`);
|
||||
this.ctx.body = gridfs.openDownloadStream(this.pdoc.data);
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemSolutionHandler extends ProblemDetailHandler {
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
ctx.templateName = 'problem_solution.html';
|
||||
}
|
||||
async get() {
|
||||
let page = this.ctx.query.page || 1;
|
||||
this.ctx.checkPerm(PERM_VIEW_PROBLEM_SOLUTION);
|
||||
let [psdocs, pcount, pscount] = await paginate(solution.getMulti(this.pdoc._id), page, constants.SOLUTION_PER_PAGE);
|
||||
let uids = [this.pdoc.owner], docids = [];
|
||||
for (let psdoc of psdocs) {
|
||||
docids.push(psdoc._id);
|
||||
uids.push(psdoc.owner);
|
||||
if (psdoc.reply.length) {
|
||||
for (let psrdoc of psdoc.reply) {
|
||||
uids.push(psrdoc.owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
let udict = await user.getList(uids);
|
||||
this.ctx.body.path = [
|
||||
['problem_main', '/p'],
|
||||
['problem_create', null]
|
||||
], page_name: 'problem_create'
|
||||
};
|
||||
});
|
||||
POST('/problem/create', requirePerm(PERM_CREATE_PROBLEM), async ctx => {
|
||||
let { title, pid, content, hidden } = ctx.request.body;
|
||||
validator.checkPid(pid);
|
||||
pid = pid || await system.incPidCounter();
|
||||
await problem.add({ title, content, owner: ctx.state.user._id, pid, hidden });
|
||||
ctx.body = { pid };
|
||||
ctx.setRedirect = `/p/${pid}/settings`;
|
||||
});
|
||||
[this.pdoc.title, `/p/${this.pdoc.pid}`, true],
|
||||
['problem_solution', null]
|
||||
];
|
||||
this.ctx.body = Object.assign(this.ctx.body, {
|
||||
psdocs, page, pcount, pscount, udict
|
||||
});
|
||||
}
|
||||
async post({ psid }) {
|
||||
if (psid) this.psdoc = await solution.get(psid);
|
||||
}
|
||||
async post_submit({ content }) {
|
||||
this.ctx.checkPerm(PERM_CREATE_PROBLEM_SOLUTION);
|
||||
await solution.add(this.pdoc._id, this.uid, content);
|
||||
this.ctx.back();
|
||||
}
|
||||
async post_edit_solution({ content }) {
|
||||
if (this.psdoc.owner != this.uid) this.ctx.checkPerm(PERM_EDIT_PROBLEM_SOLUTION);
|
||||
this.psdoc = await solution.edit(this.psdoc._id, content);
|
||||
this.ctx.body.psdoc = this.psdoc;
|
||||
this.ctx.back();
|
||||
}
|
||||
async post_delete_solution() {
|
||||
if (this.psdoc.owner != this.uid) this.ctx.checkPerm(PERM_DELETE_PROBLEM_SOLUTION);
|
||||
await solution.del(this.psdoc._id);
|
||||
this.ctx.back();
|
||||
}
|
||||
async post_reply({ psid, content }) {
|
||||
this.ctx.checkPerm(PERM_REPLY_PROBLEM_SOLUTION);
|
||||
let psdoc = await solution.get(psid);
|
||||
await solution.reply(psdoc._id, this.uid, content);
|
||||
}
|
||||
async post_edit_reply({ content }) {
|
||||
let { psid, psrid } = this.ctx.request.body;
|
||||
let [psdoc, psrdoc] = await solution.getReply(psid, psrid);
|
||||
if ((!psdoc) || psdoc.pid != this.pdoc._id) throw new SolutionNotFoundError(psid);
|
||||
if (psrdoc.owner != this.uid) this.ctx.checkPerm(PERM_EDIT_PROBLEM_SOLUTION_REPLY);
|
||||
await solution.editReply(psid, psrid, content);
|
||||
}
|
||||
async post_delete_reply({ psid, psrid }) {
|
||||
let [psdoc, psrdoc] = await solution.getReply(psid, psrid);
|
||||
if ((!psdoc) || psdoc.pid != this.pdoc._id) throw new SolutionNotFoundError(psid);
|
||||
if (psrdoc.owner != this.uid) this.ctx.checkPerm(PERM_EDIT_PROBLEM_SOLUTION_REPLY);
|
||||
await solution.delReply(psid, psrid);
|
||||
this.ctx.back();
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemSolutionRawHandler extends ProblemDetailHandler {
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
ctx.checkPerm(PERM_VIEW_PROBLEM_SOLUTION);
|
||||
}
|
||||
async get({ psid }) {
|
||||
let psdoc = await solution.get(psid);
|
||||
this.ctx.response.type = 'text/markdown';
|
||||
this.ctx.body = psdoc.content;
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemSolutionReplyRawHandler extends ProblemDetailHandler {
|
||||
constructor(ctx) {
|
||||
super(ctx);
|
||||
ctx.checkPerm(PERM_VIEW_PROBLEM_SOLUTION);
|
||||
}
|
||||
async get({ psid }) {
|
||||
let [psdoc, psrdoc] = await solution.getReply(psid);
|
||||
if ((!psdoc) || psdoc.pid != this.pdoc._id) throw new SolutionNotFoundError(psid);
|
||||
this.ctx.response.type = 'text/markdown';
|
||||
this.ctx.body = psrdoc.content;
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemCreateHandler {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx;
|
||||
this.ctx.templateName = 'problem_edit.html';
|
||||
this.ctx.checkPerm(PERM_CREATE_PROBLEM);
|
||||
}
|
||||
async get() {
|
||||
this.ctx.body = {
|
||||
path: [
|
||||
['Hydro', '/'],
|
||||
['problem_main', '/p'],
|
||||
['problem_create', null]
|
||||
], page_name: 'problem_create'
|
||||
};
|
||||
}
|
||||
async post() {
|
||||
let { title, pid, content, hidden } = this.ctx.request.body;
|
||||
validator.checkPid(pid);
|
||||
pid = pid || await system.incPidCounter();
|
||||
await problem.add({ title, content, owner: this.ctx.state.user._id, pid, hidden });
|
||||
this.ctx.body = { pid };
|
||||
this.ctx.setRedirect = `/p/${pid}/settings`;
|
||||
}
|
||||
}
|
||||
|
||||
ROUTE('/p', ProblemHandler);
|
||||
ROUTE('/problem/random', ProblemRandomHandler);
|
||||
ROUTE('/p/:pid', ProblemDetailHandler);
|
||||
ROUTE('/p/:pid/submit', ProblemSubmitHandler);
|
||||
ROUTE('/p/:pid/settings', ProblemSettingsHandler);
|
||||
ROUTE('/p/:pid/edit', ProblemEditHandler);
|
||||
ROUTE('/p/:pid/upload', ProblemDataUploadHandler);
|
||||
ROUTE('/p/:pid/data', ProblemDataDownloadHandler);
|
||||
ROUTE('/p/:pid/solution', ProblemSolutionHandler);
|
||||
ROUTE('/p/:pid/solution/:psid/raw', ProblemSolutionRawHandler);
|
||||
ROUTE('/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionReplyRawHandler);
|
||||
ROUTE('/problem/create', ProblemCreateHandler);
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@app.route('/p/category/{category:[^/]*}/random', 'problem_category_random')
|
||||
class ProblemCategoryRandomHandler(base.Handler):
|
||||
@base.requirePerm(builtin.PERM_VIEW_PROBLEM)
|
||||
@base.get_argument
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, category: str):
|
||||
if not self.has_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN):
|
||||
f = {'hidden': False}
|
||||
else:
|
||||
f = {}
|
||||
query = ProblemCategoryHandler.build_query(category)
|
||||
pid = await problem.get_random_id(self.domainId, **query, **f)
|
||||
if pid:
|
||||
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
|
||||
else:
|
||||
self.json_or_redirect(self.referer_or_main)
|
||||
|
||||
|
||||
@app.route('/p/{pid}/submit', 'problem_submit')
|
||||
class ProblemSubmitHandler(base.Handler):
|
||||
@base.requirePerm(builtin.PERM_SUBMIT_PROBLEM)
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
# TODO(twd2): check status, eg. test, hidden problem, ...
|
||||
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
|
||||
pdoc = await problem.get(self.domainId, pid, uid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
|
||||
domain.get_user(self.domainId, pdoc['owner_uid']))
|
||||
if uid == None:
|
||||
rdocs = []
|
||||
else:
|
||||
# TODO(iceboy): needs to be in sync with contest_detail_problem_submit
|
||||
rdocs = await record \
|
||||
.get_user_in_problem_multi(uid, self.domainId, pdoc['_id']) \
|
||||
.sort([('_id', -1)]) \
|
||||
.limit(10) \
|
||||
.to_list()
|
||||
if not self.prefer_json:
|
||||
path_components = self.build_path(
|
||||
(self.translate('problem_main'), self.reverse_url('problem_main')),
|
||||
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
|
||||
(self.translate('problem_submit'), None))
|
||||
self.render('problem_submit.html', pdoc=pdoc, udoc=udoc, rdocs=rdocs, dudoc=dudoc,
|
||||
page_title=pdoc['title'], path_components=path_components)
|
||||
else:
|
||||
self.json({'rdocs': rdocs})
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.requirePerm(builtin.PERM_SUBMIT_PROBLEM)
|
||||
@base.route_argument
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
@base.limitRate('add_record', 60, 100)
|
||||
async def post(self, *, pid: document.convert_doc_id, lang: str, code: str):
|
||||
# TODO(twd2): check status, eg. test, hidden problem, ...
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
rid = await record.add(self.domainId, pdoc['_id'], constant.record.TYPE_SUBMISSION,
|
||||
self.user['_id'], lang, code)
|
||||
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
|
||||
|
||||
|
||||
@app.route('/p/{pid}/pretest', 'problem_pretest')
|
||||
class ProblemPretestHandler(base.Handler):
|
||||
@base.requirePerm(builtin.PERM_SUBMIT_PROBLEM)
|
||||
@@ -324,103 +415,6 @@ class ProblemPretestConnection(record_handler.RecordVisibilityMixin, base.Connec
|
||||
async def on_close(self):
|
||||
bus.unsubscribe(self.on_record_change)
|
||||
|
||||
@app.route('/p/{pid}/data', 'problem_data')
|
||||
class ProblemDataHandler(base.Handler):
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
# Judges will have PRIV_READ_PROBLEM_DATA,
|
||||
# domain administrators will have PERM_READ_PROBLEM_DATA,
|
||||
# problem owner will have PERM_READ_PROBLEM_DATA_SELF.
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if type(pdoc['data']) is dict:
|
||||
return self.redirect(self.reverse_url('problem_data',
|
||||
domainId=pdoc['data']['domain'],
|
||||
pid=pdoc['data']['pid']))
|
||||
if (not self.own(pdoc, builtin.PERM_READ_PROBLEM_DATA_SELF)
|
||||
and not self.has_perm(builtin.PERM_READ_PROBLEM_DATA)):
|
||||
self.check_priv(builtin.PRIV_READ_PROBLEM_DATA)
|
||||
fdoc = await problem.get_data(pdoc)
|
||||
if not fdoc:
|
||||
raise error.ProblemDataNotFoundError(self.domainId, pid)
|
||||
self.redirect(options.cdn_prefix.rstrip('/') + \
|
||||
self.reverse_url('fs_get', domainId=builtin.domainId_SYSTEM,
|
||||
secret=fdoc['metadata']['secret']))
|
||||
|
||||
|
||||
@app.route('/p/copy', 'problem_copy')
|
||||
class ProblemCopyHandler(base.Handler):
|
||||
MAX_PROBLEMS_PER_REQUEST = 20
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.requirePerm(builtin.PERM_CREATE_PROBLEM)
|
||||
async def get(self):
|
||||
self.render('problem_copy.html')
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.requirePerm(builtin.PERM_CREATE_PROBLEM)
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
@base.limitRate('copy_problems', 30, 10)
|
||||
async def post(self, *, src_domainId: str, src_pids: str,
|
||||
numeric_pid: bool=False, hidden: bool=False):
|
||||
src_ddoc, src_dudoc = await asyncio.gather(domain.get(src_domainId),
|
||||
domain.get_user(src_domainId, self.user['_id']))
|
||||
if not src_dudoc:
|
||||
src_dudoc = {}
|
||||
if not self.dudoc_has_perm(ddoc=src_ddoc, dudoc=src_dudoc, udoc=self.user,
|
||||
perm=builtin.PERM_VIEW_PROBLEM):
|
||||
# TODO: This is the source domain's PermissionError.
|
||||
raise error.PermissionError(builtin.PERM_VIEW_PROBLEM)
|
||||
|
||||
src_pids = misc.dedupe(map(document.convert_doc_id, src_pids.replace('\r\n', '\n').split('\n')))
|
||||
if len(src_pids) > self.MAX_PROBLEMS_PER_REQUEST:
|
||||
raise error.BatchCopyLimitExceededError(self.MAX_PROBLEMS_PER_REQUEST, len(src_pids))
|
||||
pdocs = await problem.get_multi(domainId=src_domainId, doc_id={'$in': src_pids}) \
|
||||
.sort('doc_id', 1) \
|
||||
.to_list()
|
||||
|
||||
exist_pids = [pdoc['_id'] for pdoc in pdocs]
|
||||
if len(src_pids) != len(exist_pids):
|
||||
for pid in src_pids:
|
||||
if pid not in exist_pids:
|
||||
raise error.ProblemNotFoundError(src_domainId, pid)
|
||||
|
||||
for pdoc in pdocs:
|
||||
if pdoc.get('hidden', False):
|
||||
if not self.dudoc_has_perm(ddoc=src_ddoc, dudoc=src_dudoc, udoc=self.user,
|
||||
perm=builtin.PERM_VIEW_PROBLEM_HIDDEN):
|
||||
# TODO: This is the source domain's PermissionError.
|
||||
raise error.PermissionError(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
|
||||
for pdoc in pdocs:
|
||||
pid = None
|
||||
if numeric_pid:
|
||||
pid = await domain.inc_pid_counter(self.domainId)
|
||||
await problem.copy(pdoc, self.domainId, self.user['_id'], pid, hidden)
|
||||
|
||||
self.redirect(self.reverse_url('problem_main'))
|
||||
|
||||
|
||||
@app.route('/p/{pid}/edit', 'problem_edit')
|
||||
class ProblemEditHandler(base.Handler):
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
|
||||
pdoc = await problem.get(self.domainId, pid, uid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
|
||||
domain.get_user(self.domainId, pdoc['owner_uid']))
|
||||
path_components = self.build_path(
|
||||
(self.translate('problem_main'), self.reverse_url('problem_main')),
|
||||
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
|
||||
(self.translate('problem_edit'), None))
|
||||
self.render('problem_edit.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
|
||||
page_title=pdoc['title'], path_components=path_components)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@@ -435,101 +429,6 @@ class ProblemEditHandler(base.Handler):
|
||||
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
|
||||
|
||||
|
||||
@app.route('/p/{pid}/settings', 'problem_settings')
|
||||
class ProblemSettingsHandler(base.Handler):
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
|
||||
pdoc = await problem.get(self.domainId, pid, uid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
|
||||
domain.get_user(self.domainId, pdoc['owner_uid']))
|
||||
path_components = self.build_path(
|
||||
(self.translate('problem_main'), self.reverse_url('problem_main')),
|
||||
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
|
||||
(self.translate('problem_settings'), None))
|
||||
self.render('problem_settings.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
|
||||
categories=problem.get_categories(),
|
||||
page_title=pdoc['title'], path_components=path_components)
|
||||
|
||||
def split_tags(self, s):
|
||||
s = s.replace(',', ',') # Chinese ', '
|
||||
return list(filter(lambda _: _ != '', map(lambda _: _.strip(), s.split(','))))
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post(self, *, pid: document.convert_doc_id, hidden: bool=False,
|
||||
category: str, tag: str,
|
||||
difficulty_setting: int, difficulty_admin: str=''):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
category = self.split_tags(category)
|
||||
tag = self.split_tags(tag)
|
||||
for c in category:
|
||||
if not (c in builtin.PROBLEM_CATEGORIES
|
||||
or c in builtin.PROBLEM_SUB_CATEGORIES):
|
||||
raise error.ValidationError('category')
|
||||
if difficulty_setting not in problem.SETTING_DIFFICULTY_RANGE:
|
||||
raise error.ValidationError('difficulty_setting')
|
||||
if difficulty_admin:
|
||||
try:
|
||||
difficulty_admin = int(difficulty_admin)
|
||||
except ValueError:
|
||||
raise error.ValidationError('difficulty_admin')
|
||||
else:
|
||||
difficulty_admin = None
|
||||
await problem.edit(self.domainId, pdoc['_id'], hidden=hidden,
|
||||
category=category, tag=tag,
|
||||
difficulty_setting=difficulty_setting, difficulty_admin=difficulty_admin)
|
||||
await job.difficulty.update_problem(self.domainId, pdoc['_id'])
|
||||
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
|
||||
|
||||
|
||||
@app.route('/p/{pid}/upload', 'problem_upload')
|
||||
class ProblemUploadHandler(base.Handler):
|
||||
def get_content_type(self, filename):
|
||||
if os.path.splitext(filename)[1].lower() != '.zip':
|
||||
raise error.FileTypeNotAllowedError(filename)
|
||||
return 'application/zip'
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
if (not self.own(pdoc, builtin.PERM_READ_PROBLEM_DATA_SELF)
|
||||
and not self.has_perm(builtin.PERM_READ_PROBLEM_DATA)):
|
||||
self.check_priv(builtin.PRIV_READ_PROBLEM_DATA)
|
||||
md5 = await fs.get_md5(await problem.get_data(pdoc))
|
||||
self.render('problem_upload.html', pdoc=pdoc, md5=md5)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.multipart_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post(self, *, pid: document.convert_doc_id, file: objectid.ObjectId):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
if (not self.own(pdoc, builtin.PERM_READ_PROBLEM_DATA_SELF)
|
||||
and not self.has_perm(builtin.PERM_READ_PROBLEM_DATA)):
|
||||
self.check_priv(builtin.PRIV_READ_PROBLEM_DATA)
|
||||
if pdoc.get('data') and type(pdoc['data']) is objectid.ObjectId:
|
||||
await fs.unlink(pdoc['data'])
|
||||
await problem.set_data(self.domainId, pid, file)
|
||||
self.json_or_redirect(self.url)
|
||||
|
||||
|
||||
@app.route('/p/{pid}/statistics', 'problem_statistics')
|
||||
class ProblemStatisticsHandler(base.Handler):
|
||||
@base.route_argument
|
||||
@@ -549,25 +448,4 @@ class ProblemStatisticsHandler(base.Handler):
|
||||
self.render('problem_statistics.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
|
||||
page_title=pdoc['title'], path_components=path_components)
|
||||
|
||||
|
||||
@app.route('/p/search', 'problem_search')
|
||||
class ProblemSearchHandler(base.Handler):
|
||||
@base.get_argument
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, q: str):
|
||||
q = q.strip()
|
||||
if not q:
|
||||
self.json_or_redirect(self.referer_or_main)
|
||||
return
|
||||
try:
|
||||
pdoc = await problem.get(self.domainId, document.convert_doc_id(q))
|
||||
except error.ProblemNotFoundError:
|
||||
pdoc = None
|
||||
if pdoc:
|
||||
self.redirect(self.reverse_url('problem_detail', pid=pdoc['_id']))
|
||||
return
|
||||
self.redirect('http://cn.bing.com/search?q={0}+site%3A{1}' \
|
||||
.format(parse.quote(q), parse.quote(options.url_prefix)))
|
||||
|
||||
*/
|
||||
@@ -97,379 +97,3 @@ POST('/r/:rid/rejudge', requirePerm(PERM_REJUDGE), async ctx => {
|
||||
}
|
||||
ctx.back();
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
@app.route('/p/category/{category:[^/]*}/random', 'problem_category_random')
|
||||
class ProblemCategoryRandomHandler(base.Handler):
|
||||
@base.requirePerm(builtin.PERM_VIEW_PROBLEM)
|
||||
@base.get_argument
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, category: str):
|
||||
if not self.has_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN):
|
||||
f = {'hidden': False}
|
||||
else:
|
||||
f = {}
|
||||
query = ProblemCategoryHandler.build_query(category)
|
||||
pid = await problem.get_random_id(self.domainId, **query, **f)
|
||||
if pid:
|
||||
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
|
||||
else:
|
||||
self.json_or_redirect(self.referer_or_main)
|
||||
|
||||
|
||||
@app.route('/p/{pid}/submit', 'problem_submit')
|
||||
class ProblemSubmitHandler(base.Handler):
|
||||
@base.requirePerm(builtin.PERM_SUBMIT_PROBLEM)
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
# TODO(twd2): check status, eg. test, hidden problem, ...
|
||||
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
|
||||
pdoc = await problem.get(self.domainId, pid, uid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
|
||||
domain.get_user(self.domainId, pdoc['owner_uid']))
|
||||
if uid == None:
|
||||
rdocs = []
|
||||
else:
|
||||
# TODO(iceboy): needs to be in sync with contest_detail_problem_submit
|
||||
rdocs = await record \
|
||||
.get_user_in_problem_multi(uid, self.domainId, pdoc['_id']) \
|
||||
.sort([('_id', -1)]) \
|
||||
.limit(10) \
|
||||
.to_list()
|
||||
if not self.prefer_json:
|
||||
path_components = self.build_path(
|
||||
(self.translate('problem_main'), self.reverse_url('problem_main')),
|
||||
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
|
||||
(self.translate('problem_submit'), None))
|
||||
self.render('problem_submit.html', pdoc=pdoc, udoc=udoc, rdocs=rdocs, dudoc=dudoc,
|
||||
page_title=pdoc['title'], path_components=path_components)
|
||||
else:
|
||||
self.json({'rdocs': rdocs})
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.requirePerm(builtin.PERM_SUBMIT_PROBLEM)
|
||||
@base.route_argument
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
@base.limitRate('add_record', 60, 100)
|
||||
async def post(self, *, pid: document.convert_doc_id, lang: str, code: str):
|
||||
# TODO(twd2): check status, eg. test, hidden problem, ...
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
rid = await record.add(self.domainId, pdoc['_id'], constant.record.TYPE_SUBMISSION,
|
||||
self.user['_id'], lang, code)
|
||||
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
|
||||
|
||||
|
||||
@app.route('/p/{pid}/pretest', 'problem_pretest')
|
||||
class ProblemPretestHandler(base.Handler):
|
||||
@base.requirePerm(builtin.PERM_SUBMIT_PROBLEM)
|
||||
@base.route_argument
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
@base.limitRate('add_record', 60, 100)
|
||||
async def post(self, *, pid: document.convert_doc_id, lang: str, code: str,
|
||||
data_input: str, data_output: str):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
# don't need to check hidden status
|
||||
# create zip file, TODO(twd2): check file size
|
||||
post = await self.request.post()
|
||||
content = list(zip(post.getall('data_input'), post.getall('data_output')))
|
||||
output_buffer = io.BytesIO()
|
||||
zip_file = zipfile.ZipFile(output_buffer, 'a', zipfile.ZIP_DEFLATED)
|
||||
config_content = str(len(content)) + '\n'
|
||||
for i, (data_input, data_output) in enumerate(content):
|
||||
input_file = 'input{0}.txt'.format(i)
|
||||
output_file = 'output{0}.txt'.format(i)
|
||||
config_content += '{0}|{1}|1|10|262144\n'.format(input_file, output_file)
|
||||
zip_file.writestr('Input/{0}'.format(input_file), data_input)
|
||||
zip_file.writestr('Output/{0}'.format(output_file), data_output)
|
||||
zip_file.writestr('Config.ini', config_content)
|
||||
# mark all files as created in Windows :p
|
||||
for zfile in zip_file.filelist:
|
||||
zfile.create_system = 0
|
||||
zip_file.close()
|
||||
fid = await fs.add_data('application/zip', output_buffer.getvalue())
|
||||
output_buffer.close()
|
||||
rid = await record.add(self.domainId, pdoc['_id'], constant.record.TYPE_PRETEST,
|
||||
self.user['_id'], lang, code, fid)
|
||||
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
|
||||
|
||||
|
||||
@app.connection_route('/p/{pid}/pretest-conn', 'problem_pretest-conn')
|
||||
class ProblemPretestConnection(record_handler.RecordVisibilityMixin, base.Connection):
|
||||
async def on_open(self):
|
||||
await super(ProblemPretestConnection, self).on_open()
|
||||
self.pid = document.convert_doc_id(self.request.match_info['pid'])
|
||||
bus.subscribe(self.on_record_change, ['record_change'])
|
||||
|
||||
async def on_record_change(self, e):
|
||||
rdoc = e['value']
|
||||
if rdoc['uid'] != self.user['_id'] or \
|
||||
rdoc['domainId'] != self.domainId or rdoc['pid'] != self.pid:
|
||||
return
|
||||
# check permission for visibility: contest
|
||||
if rdoc['tid']:
|
||||
show_status, tdoc = await self.rdoc_contest_visible(rdoc)
|
||||
if not show_status:
|
||||
return
|
||||
self.send(rdoc=rdoc)
|
||||
|
||||
async def on_close(self):
|
||||
bus.unsubscribe(self.on_record_change)
|
||||
|
||||
@app.route('/p/{pid}/data', 'problem_data')
|
||||
class ProblemDataHandler(base.Handler):
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
# Judges will have PRIV_READ_PROBLEM_DATA,
|
||||
# domain administrators will have PERM_READ_PROBLEM_DATA,
|
||||
# problem owner will have PERM_READ_PROBLEM_DATA_SELF.
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if type(pdoc['data']) is dict:
|
||||
return self.redirect(self.reverse_url('problem_data',
|
||||
domainId=pdoc['data']['domain'],
|
||||
pid=pdoc['data']['pid']))
|
||||
if (not self.own(pdoc, builtin.PERM_READ_PROBLEM_DATA_SELF)
|
||||
and not self.has_perm(builtin.PERM_READ_PROBLEM_DATA)):
|
||||
self.check_priv(builtin.PRIV_READ_PROBLEM_DATA)
|
||||
fdoc = await problem.get_data(pdoc)
|
||||
if not fdoc:
|
||||
raise error.ProblemDataNotFoundError(self.domainId, pid)
|
||||
self.redirect(options.cdn_prefix.rstrip('/') + \
|
||||
self.reverse_url('fs_get', domainId=builtin.domainId_SYSTEM,
|
||||
secret=fdoc['metadata']['secret']))
|
||||
|
||||
|
||||
@app.route('/p/copy', 'problem_copy')
|
||||
class ProblemCopyHandler(base.Handler):
|
||||
MAX_PROBLEMS_PER_REQUEST = 20
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.requirePerm(builtin.PERM_CREATE_PROBLEM)
|
||||
async def get(self):
|
||||
self.render('problem_copy.html')
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.requirePerm(builtin.PERM_CREATE_PROBLEM)
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
@base.limitRate('copy_problems', 30, 10)
|
||||
async def post(self, *, src_domainId: str, src_pids: str,
|
||||
numeric_pid: bool=False, hidden: bool=False):
|
||||
src_ddoc, src_dudoc = await asyncio.gather(domain.get(src_domainId),
|
||||
domain.get_user(src_domainId, self.user['_id']))
|
||||
if not src_dudoc:
|
||||
src_dudoc = {}
|
||||
if not self.dudoc_has_perm(ddoc=src_ddoc, dudoc=src_dudoc, udoc=self.user,
|
||||
perm=builtin.PERM_VIEW_PROBLEM):
|
||||
# TODO: This is the source domain's PermissionError.
|
||||
raise error.PermissionError(builtin.PERM_VIEW_PROBLEM)
|
||||
|
||||
src_pids = misc.dedupe(map(document.convert_doc_id, src_pids.replace('\r\n', '\n').split('\n')))
|
||||
if len(src_pids) > self.MAX_PROBLEMS_PER_REQUEST:
|
||||
raise error.BatchCopyLimitExceededError(self.MAX_PROBLEMS_PER_REQUEST, len(src_pids))
|
||||
pdocs = await problem.get_multi(domainId=src_domainId, doc_id={'$in': src_pids}) \
|
||||
.sort('doc_id', 1) \
|
||||
.to_list()
|
||||
|
||||
exist_pids = [pdoc['_id'] for pdoc in pdocs]
|
||||
if len(src_pids) != len(exist_pids):
|
||||
for pid in src_pids:
|
||||
if pid not in exist_pids:
|
||||
raise error.ProblemNotFoundError(src_domainId, pid)
|
||||
|
||||
for pdoc in pdocs:
|
||||
if pdoc.get('hidden', False):
|
||||
if not self.dudoc_has_perm(ddoc=src_ddoc, dudoc=src_dudoc, udoc=self.user,
|
||||
perm=builtin.PERM_VIEW_PROBLEM_HIDDEN):
|
||||
# TODO: This is the source domain's PermissionError.
|
||||
raise error.PermissionError(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
|
||||
for pdoc in pdocs:
|
||||
pid = None
|
||||
if numeric_pid:
|
||||
pid = await domain.inc_pid_counter(self.domainId)
|
||||
await problem.copy(pdoc, self.domainId, self.user['_id'], pid, hidden)
|
||||
|
||||
self.redirect(self.reverse_url('problem_main'))
|
||||
|
||||
|
||||
@app.route('/p/{pid}/edit', 'problem_edit')
|
||||
class ProblemEditHandler(base.Handler):
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
|
||||
pdoc = await problem.get(self.domainId, pid, uid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
|
||||
domain.get_user(self.domainId, pdoc['owner_uid']))
|
||||
path_components = self.build_path(
|
||||
(self.translate('problem_main'), self.reverse_url('problem_main')),
|
||||
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
|
||||
(self.translate('problem_edit'), None))
|
||||
self.render('problem_edit.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
|
||||
page_title=pdoc['title'], path_components=path_components)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post(self, *, pid: document.convert_doc_id, title: str, content: str):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
await problem.edit(self.domainId, pdoc['_id'], title=title, content=content)
|
||||
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
|
||||
|
||||
|
||||
@app.route('/p/{pid}/settings', 'problem_settings')
|
||||
class ProblemSettingsHandler(base.Handler):
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
|
||||
pdoc = await problem.get(self.domainId, pid, uid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
|
||||
domain.get_user(self.domainId, pdoc['owner_uid']))
|
||||
path_components = self.build_path(
|
||||
(self.translate('problem_main'), self.reverse_url('problem_main')),
|
||||
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
|
||||
(self.translate('problem_settings'), None))
|
||||
self.render('problem_settings.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
|
||||
categories=problem.get_categories(),
|
||||
page_title=pdoc['title'], path_components=path_components)
|
||||
|
||||
def split_tags(self, s):
|
||||
s = s.replace(',', ',') # Chinese ', '
|
||||
return list(filter(lambda _: _ != '', map(lambda _: _.strip(), s.split(','))))
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post(self, *, pid: document.convert_doc_id, hidden: bool=False,
|
||||
category: str, tag: str,
|
||||
difficulty_setting: int, difficulty_admin: str=''):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
category = self.split_tags(category)
|
||||
tag = self.split_tags(tag)
|
||||
for c in category:
|
||||
if not (c in builtin.PROBLEM_CATEGORIES
|
||||
or c in builtin.PROBLEM_SUB_CATEGORIES):
|
||||
raise error.ValidationError('category')
|
||||
if difficulty_setting not in problem.SETTING_DIFFICULTY_RANGE:
|
||||
raise error.ValidationError('difficulty_setting')
|
||||
if difficulty_admin:
|
||||
try:
|
||||
difficulty_admin = int(difficulty_admin)
|
||||
except ValueError:
|
||||
raise error.ValidationError('difficulty_admin')
|
||||
else:
|
||||
difficulty_admin = None
|
||||
await problem.edit(self.domainId, pdoc['_id'], hidden=hidden,
|
||||
category=category, tag=tag,
|
||||
difficulty_setting=difficulty_setting, difficulty_admin=difficulty_admin)
|
||||
await job.difficulty.update_problem(self.domainId, pdoc['_id'])
|
||||
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
|
||||
|
||||
|
||||
@app.route('/p/{pid}/upload', 'problem_upload')
|
||||
class ProblemUploadHandler(base.Handler):
|
||||
def get_content_type(self, filename):
|
||||
if os.path.splitext(filename)[1].lower() != '.zip':
|
||||
raise error.FileTypeNotAllowedError(filename)
|
||||
return 'application/zip'
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
if (not self.own(pdoc, builtin.PERM_READ_PROBLEM_DATA_SELF)
|
||||
and not self.has_perm(builtin.PERM_READ_PROBLEM_DATA)):
|
||||
self.check_priv(builtin.PRIV_READ_PROBLEM_DATA)
|
||||
md5 = await fs.get_md5(await problem.get_data(pdoc))
|
||||
self.render('problem_upload.html', pdoc=pdoc, md5=md5)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.multipart_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post(self, *, pid: document.convert_doc_id, file: objectid.ObjectId):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM)
|
||||
if (not self.own(pdoc, builtin.PERM_READ_PROBLEM_DATA_SELF)
|
||||
and not self.has_perm(builtin.PERM_READ_PROBLEM_DATA)):
|
||||
self.check_priv(builtin.PRIV_READ_PROBLEM_DATA)
|
||||
if pdoc.get('data') and type(pdoc['data']) is objectid.ObjectId:
|
||||
await fs.unlink(pdoc['data'])
|
||||
await problem.set_data(self.domainId, pid, file)
|
||||
self.json_or_redirect(self.url)
|
||||
|
||||
|
||||
@app.route('/p/{pid}/statistics', 'problem_statistics')
|
||||
class ProblemStatisticsHandler(base.Handler):
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id):
|
||||
# TODO(twd2)
|
||||
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
|
||||
pdoc = await problem.get(self.domainId, pid, uid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
|
||||
domain.get_user(self.domainId, pdoc['owner_uid']))
|
||||
path_components = self.build_path(
|
||||
(self.translate('problem_main'), self.reverse_url('problem_main')),
|
||||
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
|
||||
(self.translate('problem_statistics'), None))
|
||||
self.render('problem_statistics.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
|
||||
page_title=pdoc['title'], path_components=path_components)
|
||||
|
||||
|
||||
@app.route('/p/search', 'problem_search')
|
||||
class ProblemSearchHandler(base.Handler):
|
||||
@base.get_argument
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, q: str):
|
||||
q = q.strip()
|
||||
if not q:
|
||||
self.json_or_redirect(self.referer_or_main)
|
||||
return
|
||||
try:
|
||||
pdoc = await problem.get(self.domainId, document.convert_doc_id(q))
|
||||
except error.ProblemNotFoundError:
|
||||
pdoc = None
|
||||
if pdoc:
|
||||
self.redirect(self.reverse_url('problem_detail', pid=pdoc['_id']))
|
||||
return
|
||||
self.redirect('http://cn.bing.com/search?q={0}+site%3A{1}' \
|
||||
.format(parse.quote(q), parse.quote(options.url_prefix)))
|
||||
|
||||
*/
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
@app.route('/p/{pid}/solution', 'problem_solution')
|
||||
class ProblemSolutionHandler(base.OperationHandler):
|
||||
SOLUTIONS_PER_PAGE = 20
|
||||
|
||||
@base.requirePerm(builtin.PERM_VIEW_PROBLEM_SOLUTION)
|
||||
@base.get_argument
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id, page: int=1):
|
||||
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
|
||||
pdoc = await problem.get(self.domainId, pid, uid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
psdocs, pcount, pscount = await pagination.paginate(
|
||||
problem.get_multi_solution(self.domainId, pdoc['doc_id']),
|
||||
page, self.SOLUTIONS_PER_PAGE)
|
||||
uids = {pdoc['owner_uid']}
|
||||
uids.update(psdoc['owner_uid'] for psdoc in psdocs)
|
||||
for psdoc in psdocs:
|
||||
if 'reply' in psdoc:
|
||||
uids.update(psrdoc['owner_uid'] for psrdoc in psdoc['reply'])
|
||||
udict, dudict, pssdict = await asyncio.gather(
|
||||
user.get_dict(uids),
|
||||
domain.get_dict_user_by_uid(self.domainId, uids),
|
||||
problem.get_dict_solution_status(
|
||||
self.domainId, (psdoc['doc_id'] for psdoc in psdocs), self.user['_id']))
|
||||
dudict[self.user['_id']] = self.domain_user
|
||||
path_components = self.build_path(
|
||||
(self.translate('problem_main'), self.reverse_url('problem_main')),
|
||||
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
|
||||
(self.translate('problem_solution'), None))
|
||||
self.render('problem_solution.html', path_components=path_components,
|
||||
pdoc=pdoc, psdocs=psdocs, page=page, pcount=pcount, pscount=pscount,
|
||||
udict=udict, dudict=dudict, pssdict=pssdict)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.requirePerm(builtin.PERM_CREATE_PROBLEM_SOLUTION)
|
||||
@base.route_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post_submit(self, *, pid: document.convert_doc_id, content: str):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
await problem.add_solution(self.domainId, pdoc['doc_id'], self.user['_id'], content)
|
||||
self.json_or_redirect(self.url)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post_edit_solution(self, *, pid: document.convert_doc_id,
|
||||
psid: document.convert_doc_id, content: str):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
psdoc = await problem.get_solution(self.domainId, psid, pdoc['doc_id'])
|
||||
if not self.own(psdoc, builtin.PERM_EDIT_PROBLEM_SOLUTION_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM_SOLUTION)
|
||||
psdoc = await problem.set_solution(self.domainId, psdoc['doc_id'],
|
||||
content=content)
|
||||
self.json_or_redirect(self.url)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post_delete_solution(self, *, pid: document.convert_doc_id,
|
||||
psid: document.convert_doc_id):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
psdoc = await problem.get_solution(self.domainId, psid, pdoc['doc_id'])
|
||||
if not self.own(psdoc, builtin.PERM_DELETE_PROBLEM_SOLUTION_SELF):
|
||||
self.check_perm(builtin.PERM_DELETE_PROBLEM_SOLUTION)
|
||||
await oplog.add(self.user['_id'], oplog.TYPE_DELETE_DOCUMENT, doc=psdoc)
|
||||
await problem.delete_solution(self.domainId, psdoc['doc_id'])
|
||||
self.json_or_redirect(self.url)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post_edit_reply(self, *, pid: document.convert_doc_id,
|
||||
psid: document.convert_doc_id, psrid: document.convert_doc_id,
|
||||
content: str):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
psdoc, psrdoc = await problem.get_solution_reply(self.domainId, psid, psrid)
|
||||
if not psdoc or psdoc['parent_doc_id'] != pdoc['doc_id']:
|
||||
raise error.DocumentNotFoundError(self.domainId, document.TYPE_PROBLEM_SOLUTION, psid)
|
||||
if not self.own(psrdoc, builtin.PERM_EDIT_PROBLEM_SOLUTION_REPLY_SELF):
|
||||
self.check_perm(builtin.PERM_EDIT_PROBLEM_SOLUTION_REPLY)
|
||||
await problem.edit_solution_reply(self.domainId, psid, psrid, content)
|
||||
self.json_or_redirect(self.url)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.route_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post_delete_reply(self, *, pid: document.convert_doc_id,
|
||||
psid: document.convert_doc_id, psrid: document.convert_doc_id):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
psdoc, psrdoc = await problem.get_solution_reply(self.domainId, psid, psrid)
|
||||
if not psdoc or psdoc['parent_doc_id'] != pdoc['doc_id']:
|
||||
raise error.DocumentNotFoundError(self.domainId, document.TYPE_PROBLEM_SOLUTION, psid)
|
||||
if not self.own(psrdoc, builtin.PERM_DELETE_PROBLEM_SOLUTION_REPLY_SELF):
|
||||
self.check_perm(builtin.PERM_DELETE_PROBLEM_SOLUTION_REPLY)
|
||||
await oplog.add(self.user['_id'], oplog.TYPE_DELETE_SUB_DOCUMENT, sub_doc=psrdoc,
|
||||
doc_type=psdoc['doc_type'], doc_id=psdoc['doc_id'])
|
||||
await problem.delete_solution_reply(self.domainId, psid, psrid)
|
||||
self.json_or_redirect(self.url)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.requirePerm(builtin.PERM_VOTE_PROBLEM_SOLUTION)
|
||||
@base.route_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def upvote_downvote(self, *,
|
||||
pid: document.convert_doc_id,
|
||||
psid: document.convert_doc_id,
|
||||
value: int):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
psdoc = await problem.get_solution(self.domainId, psid, pdoc['doc_id'])
|
||||
psdoc, pssdoc = await problem.vote_solution(self.domainId, psdoc['doc_id'],
|
||||
self.user['_id'], value)
|
||||
self.json_or_redirect(self.url, vote=psdoc['vote'], user_vote=pssdoc['vote'])
|
||||
|
||||
post_upvote = functools.partialmethod(upvote_downvote, value=1)
|
||||
post_downvote = functools.partialmethod(upvote_downvote, value=-1)
|
||||
|
||||
@base.requirePriv(builtin.PRIV_USER_PROFILE)
|
||||
@base.requirePerm(builtin.PERM_REPLY_PROBLEM_SOLUTION)
|
||||
@base.route_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post_reply(self, *,
|
||||
pid: document.convert_doc_id,
|
||||
psid: document.convert_doc_id,
|
||||
content: str):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
psdoc = await problem.get_solution(self.domainId, psid, pdoc['doc_id'])
|
||||
await problem.reply_solution(self.domainId, psdoc['doc_id'], self.user['_id'], content)
|
||||
self.json_or_redirect(self.url)
|
||||
|
||||
|
||||
@app.route('/p/{pid}/solution/{psid:\w{24}}/raw', 'problem_solution_raw')
|
||||
class ProblemSolutionRawHandler(base.Handler):
|
||||
@base.requirePerm(builtin.PERM_VIEW_PROBLEM_SOLUTION)
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id, psid: document.convert_doc_id):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
psdoc = await problem.get_solution(self.domainId, psid, pdoc['doc_id'])
|
||||
self.response.content_type = 'text/markdown'
|
||||
self.response.text = psdoc['content']
|
||||
|
||||
|
||||
@app.route('/p/{pid}/solution/{psid:\w{24}}/{psrid:\w{24}}/raw', 'problem_solution_reply_raw')
|
||||
class ProblemSolutionReplyRawHandler(base.Handler):
|
||||
@base.requirePerm(builtin.PERM_VIEW_PROBLEM_SOLUTION)
|
||||
@base.route_argument
|
||||
@base.sanitize
|
||||
async def get(self, *, pid: document.convert_doc_id, psid: document.convert_doc_id,
|
||||
psrid: objectid.ObjectId):
|
||||
pdoc = await problem.get(self.domainId, pid)
|
||||
if pdoc.get('hidden', False):
|
||||
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
|
||||
psdoc, psrdoc = await problem.get_solution_reply(self.domainId, psid, psrid)
|
||||
if not psdoc or psdoc['parent_doc_id'] != pdoc['doc_id']:
|
||||
raise error.DocumentNotFoundError(self.domainId, document.TYPE_PROBLEM_SOLUTION, psid)
|
||||
self.response.content_type = 'text/markdown'
|
||||
self.response.text = psrdoc['content']
|
||||
*/
|
||||
+24
-8
@@ -38,6 +38,24 @@ function datetime_span(dt, { relative = true } = {}) {
|
||||
dt.toLocaleString()
|
||||
);
|
||||
}
|
||||
function* paginate(page, num_pages) {
|
||||
let radius = 2, first, last;
|
||||
if (page > 1) {
|
||||
yield ['first', 1];
|
||||
yield ['previous', page - 1];
|
||||
}
|
||||
if (page <= radius) [first, last] = [1, Math.min(1 + radius * 2, num_pages)];
|
||||
else if (page >= num_pages - radius) [first, last] = [Math.max(1, num_pages - radius * 2), num_pages];
|
||||
else[first, last] = [page - radius, page + radius];
|
||||
if (first > 1) yield ['ellipsis', 0];
|
||||
for (let page0 = first; page0 < last + 1; page0++) {
|
||||
if (page0 != page) yield ['page', page0];
|
||||
else yield ['current', page];
|
||||
}
|
||||
if (last < num_pages) yield ['ellipsis', 0];
|
||||
if (page < num_pages) yield ['next', page + 1];
|
||||
yield ['last', num_pages];
|
||||
}
|
||||
class Nunjucks extends nunjucks.Environment {
|
||||
constructor() {
|
||||
super(
|
||||
@@ -71,6 +89,7 @@ class Nunjucks extends nunjucks.Environment {
|
||||
this.addGlobal('static_url', str => `/${str}`);
|
||||
this.addGlobal('reverse_url', str => str);
|
||||
this.addGlobal('datetime_span', datetime_span);
|
||||
this.addGlobal('paginate', paginate);
|
||||
this.addGlobal('perm', perm);
|
||||
this.addGlobal('builtin', builtin);
|
||||
this.addGlobal('status', builtin.STATUS);
|
||||
@@ -112,19 +131,16 @@ MIDDLEWARE(async (ctx, next) => {
|
||||
try {
|
||||
try {
|
||||
await next();
|
||||
if (ctx.body || ctx.templateName) {
|
||||
if (ctx.preferJson) return;
|
||||
if (ctx.setRedirect) {
|
||||
ctx.response.type = 'application/octet-stream';
|
||||
ctx.redirect(ctx.setRedirect);
|
||||
} else if (ctx.body || ctx.templateName) {
|
||||
if (ctx.query.noTemplate || ctx.preferJson) return;
|
||||
if (ctx.query.template || ctx.templateName) {
|
||||
ctx.body = ctx.body || {};
|
||||
Object.assign(ctx.body, JSON.parse(ctx.query.data || '{}'));
|
||||
await ctx.render(ctx.query.template || ctx.templateName, ctx.body);
|
||||
} else if (ctx.setRedirect) {
|
||||
ctx.response.type = 'application/octet-stream';
|
||||
ctx.redirect(ctx.setRedirect);
|
||||
}
|
||||
} else if (ctx.setRedirect) {
|
||||
ctx.response.type = 'application/octet-stream';
|
||||
ctx.redirect(ctx.setRedirect);
|
||||
} else {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
const { ValidationError } = require('../error');
|
||||
module.exports = async function paginate(cursor, page, page_size) {
|
||||
if (page <= 0) throw new ValidationError('page');
|
||||
let [count, page_docs] = await Promise.all([
|
||||
cursor.count(),
|
||||
cursor.skip((page - 1) * page_size).limit(page_size).toArray()
|
||||
]);
|
||||
let num_pages = Math.floor((count + page_size - 1) / page_size);
|
||||
return [page_docs, num_pages, count];
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
const
|
||||
{ ObjectID } = require('bson'),
|
||||
{ SolutionNotFoundError } = require('../error'),
|
||||
validator = require('../lib/validator'),
|
||||
db = require('../service/db.js'),
|
||||
coll = db.collection('solution');
|
||||
|
||||
/**
|
||||
* @param {string} pid
|
||||
* @param {number} owner
|
||||
* @param {string} content
|
||||
*/
|
||||
async function add(pid, owner, content) {
|
||||
validator.checkContent(content);
|
||||
pid = new ObjectID(pid);
|
||||
let res = await coll.insertOne({ content, owner, pid, reply: [], vote: 0 });
|
||||
return res.insertedId;
|
||||
}
|
||||
async function get(psid) {
|
||||
psid = new ObjectID(psid);
|
||||
let psdoc = await coll.findOne({ _id: psid });
|
||||
if (!psdoc) throw new SolutionNotFoundError();
|
||||
return psdoc;
|
||||
}
|
||||
async function getMany(query, sort, page, limit) {
|
||||
return await coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit).toArray();
|
||||
}
|
||||
async function edit(_id, content) {
|
||||
validator.checkContent(content);
|
||||
await coll.findOneAndUpdate({ _id }, { $set: { content } });
|
||||
let psdoc = await get(_id);
|
||||
if (!psdoc) throw new SolutionNotFoundError(_id);
|
||||
return psdoc;
|
||||
}
|
||||
async function del(psid) {
|
||||
psid = new ObjectID(psid);
|
||||
return await coll.deleteOne({ _id: psid });
|
||||
}
|
||||
async function count(query) {
|
||||
return await coll.find(query).count();
|
||||
}
|
||||
function getMulti(pid) {
|
||||
return coll.find({ pid }).sort({ vote: -1 });
|
||||
}
|
||||
async function reply(psid, owner, content) {
|
||||
psid = new ObjectID(psid);
|
||||
validator.checkContent(content);
|
||||
return await coll.findOneAndUpdate({ _id: psid }, { $push: { reply: { content, owner, _id: new ObjectID() } } });
|
||||
}
|
||||
async function getReply(psid, psrid) {
|
||||
psid = new ObjectID(psid);
|
||||
psrid = new ObjectID(psrid);
|
||||
let psdoc = await coll.findOne({ _id: psid, reply: { $elemMatch: { _id: psrid } } });
|
||||
if (!psdoc) return [null, null];
|
||||
for (let psrdoc of psdoc)
|
||||
if (psrdoc._id == psrid)
|
||||
return [psdoc, psrdoc];
|
||||
return [psdoc, null];
|
||||
}
|
||||
async function editReply(psid, psrid, content) {
|
||||
validator.checkContent(content);
|
||||
psid = new ObjectID(psid);
|
||||
psrid = new ObjectID(psrid);
|
||||
let psdoc = await coll.findOne({ _id: psid, reply: { $elemMatch: { _id: psrid } } });
|
||||
let reply = psdoc.reply;
|
||||
for (let i in reply)
|
||||
if (reply[i]._id == psrid) {
|
||||
reply[i].content = content;
|
||||
break;
|
||||
}
|
||||
return await coll.findOneAndUpdate({ _id: psdoc._id }, { $set: { reply } });
|
||||
}
|
||||
async function delReply(psid, psrid) {
|
||||
return await coll.findOneAndUpdate({ _id: psid }, { $pull: { reply: { _id: psrid } } });
|
||||
}
|
||||
module.exports = {
|
||||
count,
|
||||
add,
|
||||
get,
|
||||
edit,
|
||||
del,
|
||||
getMany,
|
||||
getMulti,
|
||||
reply,
|
||||
getReply,
|
||||
editReply,
|
||||
delReply
|
||||
};
|
||||
+7
-1
@@ -33,6 +33,12 @@ async function getById(_id) {
|
||||
udoc.perm = role.perm;
|
||||
return new USER(udoc);
|
||||
}
|
||||
async function getList(uids) {
|
||||
let r = {};
|
||||
for (let uid of uids)
|
||||
r[uid] = await getById(uid);
|
||||
return r;
|
||||
}
|
||||
async function getByUname(uname) {
|
||||
let unameLower = uname.trim().toLowerCase();
|
||||
let udoc = await coll.findOne({ unameLower });
|
||||
@@ -112,5 +118,5 @@ module.exports = {
|
||||
changePassword, create, getByEmail,
|
||||
getById, getByUname, getMany,
|
||||
setById, setEmail,
|
||||
setPassword
|
||||
setPassword, getList
|
||||
};
|
||||
+2
-1
@@ -39,7 +39,8 @@ let options = {
|
||||
},
|
||||
constants: {
|
||||
PROBLEM_PER_PAGE: 100,
|
||||
RECORD_PER_PAGE: 100
|
||||
RECORD_PER_PAGE: 100,
|
||||
SOLUTION_PER_PAGE: 20
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+33
-1
@@ -3,7 +3,8 @@ const
|
||||
morgan = require('koa-morgan'),
|
||||
Body = require('koa-body'),
|
||||
Router = require('koa-router'),
|
||||
sockjs = require('sockjs');
|
||||
sockjs = require('sockjs'),
|
||||
validator = require('../lib/validator');
|
||||
|
||||
const options = require('../options');
|
||||
|
||||
@@ -41,6 +42,35 @@ function GET(route, ...handler) {
|
||||
function POST(route, ...handler) {
|
||||
router.post(route, ...handler);
|
||||
}
|
||||
/**
|
||||
* @param {string} route
|
||||
* @param {...import('koa').Middleware} handler
|
||||
*/
|
||||
function ALL(route, ...handler) {
|
||||
router.all(route, ...handler);
|
||||
}
|
||||
/**
|
||||
* @param {string} route
|
||||
* @param {class} handler
|
||||
*/
|
||||
function ROUTE(route, handler) {
|
||||
router.all(route, async (ctx) => {
|
||||
let h = new handler(ctx);
|
||||
let method = ctx.method.toLowerCase();
|
||||
let args = Object.assign({}, ctx.params, ctx.query, ctx.request.body);
|
||||
if (args.content) validator.checkContent(args.content);
|
||||
if (args.title) validator.checkContent(args.title);
|
||||
if (h._prepare) await h._prepare(args);
|
||||
if (h.prepare) await h.prepare(args);
|
||||
if (h[`_${method}`]) await h[`_${method}`](args);
|
||||
if (h[method]) await h[method](args);
|
||||
console.log(ctx.request.body);
|
||||
if (method == 'post' && ctx.request.body.operation) {
|
||||
if (h[`${method}_${ctx.request.body.operation}`])
|
||||
await h[`${method}_${ctx.request.body.operation}`](args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback SockJSHandler
|
||||
@@ -97,9 +127,11 @@ function SOCKET(prefix, middlewares, handler) {
|
||||
sock.installHandlers(server);
|
||||
}
|
||||
|
||||
exports.ROUTE = ROUTE;
|
||||
exports.MIDDLEWARE = MIDDLEWARE;
|
||||
exports.GET = GET;
|
||||
exports.POST = POST;
|
||||
exports.ALL = ALL;
|
||||
exports.SOCKET = SOCKET;
|
||||
exports.start = function start() {
|
||||
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
{% macro render(
|
||||
view, docs, udict,
|
||||
comment_ref,
|
||||
reply_ref,
|
||||
comment_post_perm,
|
||||
comment_edit_perm,
|
||||
comment_edit_self_perm,
|
||||
comment_delete_perm,
|
||||
comment_delete_self_perm,
|
||||
reply_post_perm,
|
||||
reply_edit_perm,
|
||||
reply_edit_self_perm,
|
||||
reply_delete_perm,
|
||||
reply_delete_self_perm,
|
||||
comment_placeholder = 'Write Your Comment',
|
||||
comment_post_op = 'reply',
|
||||
comment_edit_op = 'edit_reply',
|
||||
comment_post_text = 'Comment',
|
||||
comment_edit_text = 'Update',
|
||||
reply_post_op = 'tail_reply',
|
||||
reply_post_text = 'Reply',
|
||||
reply_edit_op = 'edit_tail_reply',
|
||||
reply_edit_text = 'Update',
|
||||
comment_delete_op = 'delete_reply',
|
||||
reply_delete_op = 'delete_tail_reply'
|
||||
) %}
|
||||
{% import "components/comments_base.html" as comment_base with context %}
|
||||
{% import "components/comments_"~view~".html" as comment_impl with context %}
|
||||
<ul class="dczcomments__list view--{{ view }}">
|
||||
{% if handler.has_perm(comment_post_perm) %}
|
||||
{{ comment_impl.comment(true, handler.user) }}
|
||||
{% endif %}
|
||||
{% for doc in docs %}
|
||||
{{ comment_impl.comment(false, udict[doc['owner_uid']], doc) }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endmacro %}
|
||||
@@ -1,46 +0,0 @@
|
||||
{% macro comment_form() %}
|
||||
<div class="hasjs--hide">
|
||||
<form method="post" class="dczcomments__box" name="dczcomments__box__form" data-hotkey="ctrl+enter:submit,esc:vjCommentBoxCancel">
|
||||
<div>
|
||||
<textarea name="content" class="textbox" data-markdown required></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" class="rounded primary button dczcomments__box__submit" value="{{ _(comment_post_text) }} (Ctrl+Enter)" data-value-reply="{{ _(reply_post_text) }} (Ctrl+Enter)" data-value-comment="{{ _(comment_post_text) }} (Ctrl+Enter)" data-value-comment-update="{{ _(comment_edit_text) }} (Ctrl+Enter)" data-value-reply-update="{{ _(reply_edit_text) }} (Ctrl+Enter)">
|
||||
<input type="button" class="rounded button nojs--hide" name="dczcomments__box__cancel" value="{{ _('Cancel') }} (Esc)">
|
||||
</div>
|
||||
<input type="hidden" name="operation" value="{{ comment_post_op }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
</form>
|
||||
</div>
|
||||
<div class="nojs--hide">
|
||||
<textarea class="textbox" name="dczcomments__dummy-box" readonly data-form="{{ {"operation": comment_post_op}|json }}" placeholder="{{ _(comment_placeholder) }}"></textarea>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro comment_operations(doc) %}
|
||||
<div class="dczcomments__operations nojs--hide">
|
||||
{% if handler.has_perm(reply_post_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Reply') }}" name="dczcomments__op-reply-comment" data-form="{{ {'operation': reply_post_op, comment_ref: doc['doc_id']}|json }}"><span class="icon icon-reply"></span></a>
|
||||
{% endif %}
|
||||
{% if handler.own(doc, comment_edit_self_perm) or handler.has_perm(comment_edit_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" name="dczcomments__op-edit-comment" data-form="{{ {'operation': comment_edit_op, comment_ref: doc['doc_id']}|json }}"><span class="icon icon-edit"></span></a>
|
||||
{% endif %}
|
||||
{% if handler.own(doc, comment_delete_self_perm) or handler.has_perm(comment_delete_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" name="dczcomments__op-delete-comment" data-form="{{ {'operation': comment_delete_op, comment_ref: doc['doc_id']}|json }}"><span class="icon icon-delete"></span></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro reply_operations(doc, rdoc) %}
|
||||
<div class="dczcomments__operations nojs--hide">
|
||||
{% if handler.has_perm(reply_post_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Reply') }}" name="dczcomments__op-reply-reply"><span class="icon icon-reply"></span></a>
|
||||
{% endif %}
|
||||
{% if handler.own(rdoc, reply_edit_self_perm) or handler.has_perm(reply_edit_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" name="dczcomments__op-edit-reply" data-form="{{ {'operation': reply_edit_op, comment_ref: doc['doc_id'], reply_ref: rdoc['_id']}|json }}"><span class="icon icon-edit"></span></a>
|
||||
{% endif %}
|
||||
{% if handler.own(rdoc, reply_delete_self_perm) or handler.has_perm(reply_delete_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" name="dczcomments__op-delete-reply" data-form="{{ {'operation': reply_delete_op, comment_ref: doc['doc_id'], reply_ref: rdoc['_id']}|json }}"><span class="icon icon-delete"></span></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -9,13 +9,13 @@
|
||||
{% if not mode_create %}
|
||||
<div class="clearfix">
|
||||
<div class="supplementary dczcomments__supplementary">
|
||||
{{ user.render_inline(udoc, dudict[udoc['_id']], avatar=false) }}
|
||||
@ {{ datetime_span(rdoc['_id'].generationTime) }}
|
||||
{{ user.render_inline(udoc, avatar=false) }}
|
||||
@ {{ datetime_span(rdoc['_id'])|safe }}
|
||||
</div>
|
||||
{{ comment_base.reply_operations(doc, rdoc) }}
|
||||
{{ reply_operations(doc, rdoc) }}
|
||||
</div>
|
||||
<div class="typo" data-emoji-enabled data-raw-url="{{ reverse_url('discussion_tail_reply_raw', did=doc['parent_doc_id'], drid=doc['doc_id'], drrid=rdoc['_id']) }}">
|
||||
{{ rdoc['content']|markdown }}
|
||||
{{ rdoc['content']|markdown|safe }}
|
||||
</div>
|
||||
<div class="commentbox-edit-target"></div>
|
||||
{% else %}
|
||||
@@ -26,38 +26,129 @@
|
||||
</li>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro comment(mode_create, udoc, doc = {}) %}
|
||||
{% macro comment_operations(doc) %}
|
||||
<div class="dczcomments__operations nojs--hide">
|
||||
{% if handler.has_perm(reply_post_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Reply') }}" name="dczcomments__op-reply-comment"
|
||||
data-form="{"operation":"{{reply_post_op}}", "{{comment_ref}}":"{{doc._id}}"}"
|
||||
><span class="icon icon-reply"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.has_perm(comment_edit_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" name="dczcomments__op-edit-comment"
|
||||
data-form="{"operation":"{{comment_edit_op}}","{{comment_ref}}":"{{doc._id}}"}"
|
||||
><span class="icon icon-edit"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.has_perm(comment_delete_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" name="dczcomments__op-delete-comment"
|
||||
data-form="{"operation":"{{comment_delete_op}}","{{comment_ref}}":"{{doc._id}}"}"
|
||||
><span class="icon icon-delete"></span></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro reply_operations(doc, rdoc) %}
|
||||
<div class="dczcomments__operations nojs--hide">
|
||||
{% if handler.has_perm(reply_post_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Reply') }}" name="dczcomments__op-reply-reply"><span class="icon icon-reply"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.has_perm(reply_edit_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" name="dczcomments__op-edit-reply"
|
||||
data-form="{"operation":"{{reply_edit_op}}","{{comment_ref}}":"{{doc._id}}","{{reply_ref}}":"{{rdoc._id}}"}"
|
||||
><span class="icon icon-edit"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.has_perm(reply_delete_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" name="dczcomments__op-delete-reply"
|
||||
data-form="{"operation":"{{reply_delete_op}}","{{comment_ref}}":"{{doc._id}}","{{reply_ref}}":"{{rdoc._id}}"}"
|
||||
><span class="icon icon-delete"></span></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render(
|
||||
view, docs, udict,
|
||||
comment_ref,
|
||||
reply_ref,
|
||||
comment_post_perm,
|
||||
comment_edit_perm,
|
||||
comment_edit_self_perm,
|
||||
comment_delete_perm,
|
||||
comment_delete_self_perm,
|
||||
reply_post_perm,
|
||||
reply_edit_perm,
|
||||
reply_edit_self_perm,
|
||||
reply_delete_perm,
|
||||
reply_delete_self_perm,
|
||||
comment_placeholder = 'Write Your Comment',
|
||||
comment_post_op = 'reply',
|
||||
comment_edit_op = 'edit_reply',
|
||||
comment_post_text = 'Comment',
|
||||
comment_edit_text = 'Update',
|
||||
reply_post_op = 'tail_reply',
|
||||
reply_post_text = 'Reply',
|
||||
reply_edit_op = 'edit_tail_reply',
|
||||
reply_edit_text = 'Update',
|
||||
comment_delete_op = 'delete_reply',
|
||||
reply_delete_op = 'delete_tail_reply'
|
||||
) %}
|
||||
<ul class="dczcomments__list view--discussion">
|
||||
{% if handler.has_perm(comment_post_perm) %}
|
||||
<li class="dczcomments__item">
|
||||
<div class="media">
|
||||
<div class="media__left top">
|
||||
<img src="{{ udoc['gravatar']|gravatar_url }}" width="60" height="60" class="medium user-profile-avatar">
|
||||
</div>
|
||||
<div class="media__body top no-heading">
|
||||
{% if not mode_create %}
|
||||
<div class="hasjs--hide">
|
||||
<form method="post" class="dczcomments__box" name="dczcomments__box__form" data-hotkey="ctrl+enter:submit,esc:vjCommentBoxCancel">
|
||||
<div>
|
||||
<textarea name="content" class="textbox" data-markdown required></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" class="rounded primary button dczcomments__box__submit" value="{{ _(comment_post_text) }} (Ctrl+Enter)" data-value-reply="{{ _(reply_post_text) }} (Ctrl+Enter)" data-value-comment="{{ _(comment_post_text) }} (Ctrl+Enter)" data-value-comment-update="{{ _(comment_edit_text) }} (Ctrl+Enter)" data-value-reply-update="{{ _(reply_edit_text) }} (Ctrl+Enter)">
|
||||
<input type="button" class="rounded button nojs--hide" name="dczcomments__box__cancel" value="{{ _('Cancel') }} (Esc)">
|
||||
</div>
|
||||
<input type="hidden" name="operation" value="{{ comment_post_op }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
</form>
|
||||
</div>
|
||||
<div class="nojs--hide">
|
||||
<textarea class="textbox" name="dczcomments__dummy-box" readonly data-form="{"operation":"{{comment_post_op}}"}" placeholder="{{ _(comment_placeholder) }}"></textarea>
|
||||
</div>
|
||||
<div class="commentbox-placeholder"></div>
|
||||
<ul style="display:none">
|
||||
{{ reply(true, udoc) }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for doc in docs %}
|
||||
{% set udoc = udict[doc.owner] %}
|
||||
<li class="dczcomments__item">
|
||||
<div class="media">
|
||||
<div class="media__left top">
|
||||
<img src="{{ udoc['gravatar']|gravatar_url }}" width="60" height="60" class="medium user-profile-avatar">
|
||||
</div>
|
||||
<div class="media__body top no-heading">
|
||||
<div class="clearfix">
|
||||
<div class="supplementary dczcomments__supplementary">
|
||||
{{ user.render_inline(udoc, dudict[udoc['_id']], avatar=false) }}
|
||||
@ {{ datetime_span(doc['_id'].generationTime) }}
|
||||
{{ user.render_inline(udoc, avatar=false) }}
|
||||
@ {{ datetime_span(doc['_id'])|safe }}
|
||||
</div>
|
||||
{{ comment_base.comment_operations(doc) }}
|
||||
{{ comment_operations(doc) }}
|
||||
</div>
|
||||
<div class="typo" data-emoji-enabled data-raw-url="{{ reverse_url('discussion_reply_raw', did=doc['parent_doc_id'], drid=doc['doc_id']) }}">
|
||||
{{ doc['content']|markdown }}
|
||||
{{ doc['content']|markdown|safe }}
|
||||
</div>
|
||||
<div class="commentbox-edit-target"></div>
|
||||
<ul class="dczcomments__replies commentbox-reply-target">
|
||||
{% for rdoc in doc['reply'] %}
|
||||
{{ reply(mode_create, udict[rdoc['owner_uid']], doc, rdoc) }}
|
||||
{{ reply(mode_create, udict[rdoc['owner']], doc, rdoc) }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{{ comment_base.comment_form() }}
|
||||
<div class="commentbox-placeholder"></div>
|
||||
<ul style="display:none">
|
||||
{{ reply(mode_create, udoc) }}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endmacro %}
|
||||
@@ -1,96 +1,177 @@
|
||||
{% import "components/user.html" as user with context %}
|
||||
{% macro reply(mode_create, udoc, doc = {}, rdoc = {}) %}
|
||||
<li class="dczcomments__reply{% if mode_create %} commentbox-container{% endif %}">
|
||||
<div class="media">
|
||||
<div class="media__body top no-heading">
|
||||
{% if not mode_create %}
|
||||
<div class="clearfix">
|
||||
<div class="supplementary dczcomments__supplementary">
|
||||
{{ user.render_inline(udoc, badge=false) }}
|
||||
@ {{ datetime_span(rdoc['_id'].generationTime) }}
|
||||
</div>
|
||||
{{ comment_base.reply_operations(doc, rdoc) }}
|
||||
</div>
|
||||
<div class="typo" data-emoji-enabled data-raw-url="{{ reverse_url('problem_solution_reply_raw', pid=doc['parent_doc_id'], psid=doc['doc_id'], psrid=rdoc['_id']) }}">
|
||||
{{ rdoc['content']|markdown }}
|
||||
</div>
|
||||
<div class="commentbox-edit-target"></div>
|
||||
{% else %}
|
||||
<div class="clearfix">
|
||||
<div class="supplementary dczcomments__supplementary">
|
||||
{{ user.render_inline(udoc, badge=false) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="commentbox-placeholder"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{% macro comment(mode_create, udoc, doc = {}) %}
|
||||
{% macro render(
|
||||
view, docs, udict,
|
||||
comment_ref,
|
||||
reply_ref,
|
||||
comment_post_perm,
|
||||
comment_edit_perm,
|
||||
comment_edit_self_perm,
|
||||
comment_delete_perm,
|
||||
comment_delete_self_perm,
|
||||
reply_post_perm,
|
||||
reply_edit_perm,
|
||||
reply_edit_self_perm,
|
||||
reply_delete_perm,
|
||||
reply_delete_self_perm,
|
||||
comment_placeholder = 'Write Your Comment',
|
||||
comment_post_op = 'reply',
|
||||
comment_edit_op = 'edit_reply',
|
||||
comment_post_text = 'Comment',
|
||||
comment_edit_text = 'Update',
|
||||
reply_post_op = 'tail_reply',
|
||||
reply_post_text = 'Reply',
|
||||
reply_edit_op = 'edit_tail_reply',
|
||||
reply_edit_text = 'Update',
|
||||
comment_delete_op = 'delete_reply',
|
||||
reply_delete_op = 'delete_tail_reply'
|
||||
) %}
|
||||
<ul class="dczcomments__list view--solution">
|
||||
{% if handler.has_perm(comment_post_perm) %}
|
||||
<li class="dczcomments__item">
|
||||
<div class="media">
|
||||
<div class="media__left top">
|
||||
{% if not mode_create %}
|
||||
<div class="vote vote--discussion">
|
||||
{% if handler.has_perm(perm.PERM_VOTE_PROBLEM_SOLUTION) %}
|
||||
<div class="vote-number rotator--enabled">{{ doc['vote'] }}</div>
|
||||
<div class="vote-op clearfix">
|
||||
<form method="post">
|
||||
<input type="hidden" name="operation" value="upvote">
|
||||
<input type="hidden" name="psid" value="{{ doc['doc_id'] }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<button type="submit" class="vote-button upvote{% if pssdict[doc['doc_id']]['vote'] == 1 %} active{% endif %}" data-tooltip="{{ _('Upvote') }}" data-tooltip-pos="bottom center" aria-label="{{ _('Upvote') }}"><span class="icon icon-vote--up"></span></button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="hidden" name="operation" value="downvote">
|
||||
<input type="hidden" name="psid" value="{{ doc['doc_id'] }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<button type="submit" class="vote-button downvote{% if pssdict[doc['doc_id']]['vote'] == -1 %} active{% endif %}" data-tooltip="{{ _('Downvote') }}" data-tooltip-pos="bottom center" aria-label="{{ _('Downvote') }}"><span class="icon icon-vote--down"></span></button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="vote-number">{{ doc['vote'] }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="vote vote--discussion">
|
||||
<div class="vote-number">0</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="media__body top no-heading">
|
||||
{% if not mode_create %}
|
||||
<div class="clearfix">
|
||||
<div class="supplementary dczcomments__supplementary">
|
||||
{{ user.render_inline(udoc, dudict[udoc['_id']]) }}
|
||||
@ {{ datetime_span(doc['_id'].generationTime) }}
|
||||
{{ user.render_inline(udoc) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hasjs--hide">
|
||||
<form method="post" class="dczcomments__box" name="dczcomments__box__form" data-hotkey="ctrl+enter:submit,esc:vjCommentBoxCancel">
|
||||
<div>
|
||||
<textarea name="content" class="textbox" data-markdown required></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" class="rounded primary button dczcomments__box__submit" value="{{ _(comment_post_text) }} (Ctrl+Enter)" data-value-reply="{{ _(reply_post_text) }} (Ctrl+Enter)" data-value-comment="{{ _(comment_post_text) }} (Ctrl+Enter)" data-value-comment-update="{{ _(comment_edit_text) }} (Ctrl+Enter)" data-value-reply-update="{{ _(reply_edit_text) }} (Ctrl+Enter)">
|
||||
<input type="button" class="rounded button nojs--hide" name="dczcomments__box__cancel" value="{{ _('Cancel') }} (Esc)">
|
||||
</div>
|
||||
<input type="hidden" name="operation" value="{{ comment_post_op }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
</form>
|
||||
</div>
|
||||
<div class="nojs--hide">
|
||||
<textarea class="textbox" name="dczcomments__dummy-box" readonly data-form="{"operation":"{{comment_post_op}}"}" placeholder="{{ _(comment_placeholder) }}"></textarea>
|
||||
</div>
|
||||
<div class="commentbox-placeholder"></div>
|
||||
<ul style="display:none">
|
||||
<li class="dczcomments__reply commentbox-container">
|
||||
<div class="media">
|
||||
<div class="media__body top no-heading">
|
||||
<div class="clearfix">
|
||||
<div class="supplementary dczcomments__supplementary">
|
||||
{{ user.render_inline(udoc, badge=false) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="commentbox-placeholder"></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for doc in docs %}
|
||||
{% set udoc = udict[doc.owner] %}
|
||||
<li class="dczcomments__item">
|
||||
<div class="media">
|
||||
<div class="media__left top">
|
||||
<div class="vote vote--discussion">
|
||||
{% if handler.has_perm(perm.PERM_VOTE_PROBLEM_SOLUTION) %}
|
||||
<div class="vote-number rotator--enabled">{{ doc.vote }}</div>
|
||||
<div class="vote-op clearfix">
|
||||
<form method="post">
|
||||
<input type="hidden" name="operation" value="upvote">
|
||||
<input type="hidden" name="psid" value="{{ doc._id }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<button type="submit" class="vote-button upvote{% if pssdict[doc._id].vote == 1 %} active{% endif %}" data-tooltip="{{ _('Upvote') }}" data-tooltip-pos="bottom center" aria-label="{{ _('Upvote') }}"><span class="icon icon-vote--up"></span></button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="hidden" name="operation" value="downvote">
|
||||
<input type="hidden" name="psid" value="{{ doc._id }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ handler.csrf_token }}">
|
||||
<button type="submit" class="vote-button downvote{% if pssdict[doc._id].vote == -1 %} active{% endif %}" data-tooltip="{{ _('Downvote') }}" data-tooltip-pos="bottom center" aria-label="{{ _('Downvote') }}"><span class="icon icon-vote--down"></span></button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="vote-number">{{ doc.vote }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="media__body top no-heading">
|
||||
<div class="clearfix">
|
||||
<div class="supplementary dczcomments__supplementary">
|
||||
{{ user.render_inline(udoc) }}
|
||||
@ {{ datetime_span(doc._id)|safe }}
|
||||
</div>
|
||||
<div class="dczcomments__operations nojs--hide">
|
||||
{% if handler.has_perm(reply_post_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Reply') }}" name="dczcomments__op-reply-comment"
|
||||
data-form="{"operation":"{{reply_post_op}}", "{{comment_ref}}":"{{doc._id}}"}"
|
||||
><span class="icon icon-reply"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.has_perm(comment_edit_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" name="dczcomments__op-edit-comment"
|
||||
data-form="{"operation":"{{comment_edit_op}}","{{comment_ref}}":"{{doc._id}}"}"
|
||||
><span class="icon icon-edit"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.has_perm(comment_delete_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" name="dczcomments__op-delete-comment"
|
||||
data-form="{"operation":"{{comment_delete_op}}","{{comment_ref}}":"{{doc._id}}"}"
|
||||
><span class="icon icon-delete"></span></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ comment_base.comment_operations(doc) }}
|
||||
</div>
|
||||
<div class="typo" data-emoji-enabled data-raw-url="{{ reverse_url('problem_solution_raw', pid=doc['parent_doc_id'], psid=doc['doc_id']) }}">
|
||||
{{ doc['content']|markdown }}
|
||||
{{ doc['content']|markdown|safe }}
|
||||
</div>
|
||||
<div class="commentbox-edit-target"></div>
|
||||
<ul class="dczcomments__replies commentbox-reply-target">
|
||||
{% for rdoc in doc['reply'] %}
|
||||
{{ reply(mode_create, udict[rdoc['owner_uid']], doc, rdoc) }}
|
||||
{% set udoc = udict[rdoc['owner']] %}
|
||||
<li class="dczcomments__reply{% if mode_create %} commentbox-container{% endif %}">
|
||||
<div class="media">
|
||||
<div class="media__body top no-heading">
|
||||
<div class="clearfix">
|
||||
<div class="supplementary dczcomments__supplementary">
|
||||
{{ user.render_inline(udoc, badge=false) }} @ {{ datetime_span(rdoc['_id'])|safe }}
|
||||
</div>
|
||||
<div class="dczcomments__operations nojs--hide">
|
||||
{% if handler.has_perm(reply_post_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Reply') }}" name="dczcomments__op-reply-reply"><span class="icon icon-reply"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.has_perm(reply_edit_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" name="dczcomments__op-edit-reply"
|
||||
data-form="{"operation":"{{reply_edit_op}}","{{comment_ref}}":"{{doc._id}}","{{reply_ref}}":"{{rdoc._id}}"}"
|
||||
><span class="icon icon-edit"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.has_perm(reply_delete_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" name="dczcomments__op-delete-reply"
|
||||
data-form="{"operation":"{{reply_delete_op}}","{{comment_ref}}":"{{doc._id}}","{{reply_ref}}":"{{rdoc._id}}"}"
|
||||
><span class="icon icon-delete"></span></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="typo" data-emoji-enabled data-raw-url="{{ reverse_url('problem_solution_reply_raw', pid=doc['parent_doc_id'], psid=doc['doc_id'], psrid=rdoc['_id']) }}">
|
||||
{{ rdoc['content']|markdown|safe }}
|
||||
</div>
|
||||
<div class="commentbox-edit-target"></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="clearfix">
|
||||
<div class="supplementary dczcomments__supplementary">
|
||||
{{ user.render_inline(udoc, dudict[udoc['_id']]) }}
|
||||
</div>
|
||||
</div>
|
||||
{{ comment_base.comment_form() }}
|
||||
<div class="commentbox-placeholder"></div>
|
||||
<ul style="display:none">
|
||||
{{ reply(mode_create, udoc) }}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endmacro %}
|
||||
@@ -1,6 +1,5 @@
|
||||
{% macro render_inline(
|
||||
udoc,
|
||||
dudoc={},
|
||||
avatar=true,
|
||||
badge=true,
|
||||
modbadge=true
|
||||
@@ -9,16 +8,12 @@
|
||||
{% if avatar %}
|
||||
<img class="small user-profile-avatar v-center" src="{{ udoc['gravatar']|default('')|gravatar_url }}" width="20" height="20">
|
||||
{% endif %}
|
||||
<a class="user-profile-name" href="{{ reverse_url('user_detail', uid=udoc['_id']|default(0)) }}">
|
||||
{% if dudoc['display_name'] %}
|
||||
{{ dudoc['display_name'] }} ({{ udoc['uname'] }})
|
||||
{% else %}
|
||||
{{ udoc['uname'] }}
|
||||
{% endif %}
|
||||
<a class="user-profile-name" href="/u/{{ udoc['_id']|default(0) }}">
|
||||
{{ udoc['uname'] }}
|
||||
</a>
|
||||
{% if badge %}
|
||||
<a class="user-profile-badge v-center badge--lv{{ dudoc['level']|default(0) }}" href="{{ reverse_url('user_detail', uid=udoc['_id']|default(0)) }}" title="LV {{ dudoc['level']|default(0) }}: Top {{ vj4.model.builtin.LEVELS[dudoc['level']|default(0)]|default('N/A') }}%">LV {{ dudoc['level']|default(0) }}</a>
|
||||
{% if modbadge and handler.dudoc_has_perm(udoc, dudoc, perm.PERM_MOD_BADGE) %}
|
||||
<a class="user-profile-badge v-center badge--lv{{ udoc['level']|default(0) }}" href="/u/{{ udoc['_id']|default(0) }}" title="LV {{ udoc['level']|default(0) }}: Top {{ builtin.LEVELS[udoc['level']|default(0)]|default('N/A') }}%">LV {{ udoc['level']|default(0) }}</a>
|
||||
{% if modbadge and handler.has_perm(perm.PERM_MOD_BADGE) %}
|
||||
<span class="user-profile-badge v-center badge--mod" title="Moderator">MOD</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% set page_name = "problem_solution" %}
|
||||
{% extends "layout/basic.html" %}
|
||||
{% import "components/comments.html" as comments with context %}
|
||||
{% import "components/comments_solution.html" as comments with context %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="medium-9 columns">
|
||||
@@ -41,9 +42,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="medium-3 columns">
|
||||
{% with owner_udoc=udict[pdoc['owner_uid']], owner_dudoc=dudict[pdoc['owner_uid']] %}
|
||||
{% set owner_udoc = udict[pdoc.owner] %}
|
||||
{% include "partials/problem_sidebar.html" %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -16,13 +16,10 @@ export default function ({ watch, production }) {
|
||||
|
||||
function compilerCallback(err, stats) {
|
||||
if (err) {
|
||||
// config errors
|
||||
console.error(err.stack || err);
|
||||
if (err.details) console.error(err.details);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
console.log(stats.toString(outputOptions));
|
||||
fs.writeFileSync(root('./.webpackStats.json'), JSON.stringify(stats.toJson(), null, 2));
|
||||
if (!watch && stats.hasErrors()) process.exitCode = 1;
|
||||
}
|
||||
|
||||
@@ -13,23 +13,17 @@ function getClosestInstance($dom) {
|
||||
|
||||
function onBoxCancel(ev) {
|
||||
const commentBox = getClosestInstance($(ev.currentTarget));
|
||||
if (commentBox) {
|
||||
commentBox.onCancel(ev);
|
||||
}
|
||||
if (commentBox) commentBox.onCancel(ev);
|
||||
}
|
||||
|
||||
function onBoxSubmit(ev) {
|
||||
const commentBox = getClosestInstance($(ev.currentTarget));
|
||||
if (commentBox) {
|
||||
commentBox.onSubmit(ev);
|
||||
}
|
||||
if (commentBox) commentBox.onSubmit(ev);
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
if (initialized) return;
|
||||
$(document).on('submit', '[name="dczcomments__box__form"]', onBoxSubmit);
|
||||
$(document).on('vjCommentBoxCancel', '[name="dczcomments__box__form"]', onBoxCancel);
|
||||
$(document).on('click', '[name="dczcomments__box__cancel"]', onBoxCancel);
|
||||
@@ -37,77 +31,75 @@ function init() {
|
||||
}
|
||||
|
||||
export default class CommentBox extends DOMAttachedObject {
|
||||
static DOMAttachKey = 'vjCommentBoxInstance';
|
||||
static DOMAttachKey = 'vjCommentBoxInstance';
|
||||
|
||||
constructor($dom, options = {}) {
|
||||
super($dom);
|
||||
init(); // delay initialize
|
||||
this.$box = $template.clone();
|
||||
this.$box.data('instance', this);
|
||||
this.options = {
|
||||
initialText: null,
|
||||
mode: null,
|
||||
form: null,
|
||||
onCancel: () => null,
|
||||
...options,
|
||||
};
|
||||
if (this.options.initialText) {
|
||||
this.setText(this.options.initialText);
|
||||
constructor($dom, options = {}) {
|
||||
super($dom);
|
||||
init();
|
||||
this.$box = $template.clone();
|
||||
this.$box.data('instance', this);
|
||||
this.options = {
|
||||
initialText: null,
|
||||
mode: null,
|
||||
form: null,
|
||||
onCancel: () => null,
|
||||
...options,
|
||||
};
|
||||
if (this.options.initialText) this.setText(this.options.initialText);
|
||||
if (this.options.mode) {
|
||||
const submitButton = this.$box.find('.dczcomments__box__submit');
|
||||
submitButton.val(submitButton.attr(`data-value-${this.options.mode}`));
|
||||
}
|
||||
}
|
||||
if (this.options.mode) {
|
||||
const submitButton = this.$box.find('.dczcomments__box__submit');
|
||||
submitButton.val(submitButton.attr(`data-value-${this.options.mode}`));
|
||||
|
||||
getTextareaHandler() {
|
||||
const $textarea = this.$box.find('textarea');
|
||||
return TextareaHandler.getOrConstruct($textarea);
|
||||
}
|
||||
}
|
||||
|
||||
getTextareaHandler() {
|
||||
const $textarea = this.$box.find('textarea');
|
||||
return TextareaHandler.getOrConstruct($textarea);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.getTextareaHandler().focus();
|
||||
return this;
|
||||
}
|
||||
|
||||
setText(text) {
|
||||
this.getTextareaHandler().val(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
getText() {
|
||||
return this.getTextareaHandler().val();
|
||||
}
|
||||
|
||||
insertText(text) {
|
||||
const handler = this.getTextareaHandler();
|
||||
handler.val(handler.val() + text);
|
||||
return this;
|
||||
}
|
||||
|
||||
appendTo($dom) {
|
||||
this.$box.appendTo($dom);
|
||||
this.$box.trigger('vjContentNew');
|
||||
return this;
|
||||
}
|
||||
|
||||
async onSubmit() {
|
||||
try {
|
||||
await request.post('', {
|
||||
...this.options.form,
|
||||
content: this.getText(),
|
||||
});
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
Notification.error(error.message);
|
||||
focus() {
|
||||
this.getTextareaHandler().focus();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
async onCancel(ev) {
|
||||
await this.options.onCancel(ev);
|
||||
this.$box.remove();
|
||||
this.detach();
|
||||
}
|
||||
setText(text) {
|
||||
this.getTextareaHandler().val(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
getText() {
|
||||
return this.getTextareaHandler().val();
|
||||
}
|
||||
|
||||
insertText(text) {
|
||||
const handler = this.getTextareaHandler();
|
||||
handler.val(handler.val() + text);
|
||||
return this;
|
||||
}
|
||||
|
||||
appendTo($dom) {
|
||||
this.$box.appendTo($dom);
|
||||
this.$box.trigger('vjContentNew');
|
||||
return this;
|
||||
}
|
||||
|
||||
async onSubmit() {
|
||||
try {
|
||||
await request.post('', {
|
||||
...this.options.form,
|
||||
content: this.getText(),
|
||||
});
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
Notification.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async onCancel(ev) {
|
||||
await this.options.onCancel(ev);
|
||||
this.$box.remove();
|
||||
this.detach();
|
||||
}
|
||||
}
|
||||
|
||||
_.assign(CommentBox, DOMAttachedObject);
|
||||
@@ -1,5 +1,3 @@
|
||||
import attachObjectMeta from './util/objectMeta';
|
||||
|
||||
export const LANG_TEXTS = {
|
||||
c: 'C',
|
||||
cc: 'C++',
|
||||
|
||||
Reference in new issue
Block a user