完善设置

This commit is contained in:
masnn committed 2020-05-22 12:15:07 +08:00
1 parent fc0d9873a2
commit 7edb29d7b8
43 files changed
+219 -184

No files matched your search

+12 -4
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-await-in-loop */
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
@@ -5,6 +6,8 @@ const yaml = require('js-yaml');
const { root, ignoreFailure, rmdir } = require('./utils');
const template = require('./template');
const fsp = fs.promises;
function getFiles(folder) {
const res = [];
const files = fs.readdirSync(root(folder));
@@ -48,11 +51,15 @@ async function build(type) {
}
const data = zlib.gzipSync(Buffer.from(yaml.safeDump(builtin)), { level: -1 });
fs.writeFileSync(root('.build/module/builtin.hydro'), data);
console.log('Build::Module');
await require('./buildModule')(type);
console.log('Build::Main');
await require('./webpack')(type);
const t = fs.readdirSync(root('.build/module'));
for (const f of t) {
if (fs.statSync(root(`.build/module/${f}`)).isDirectory()) rmdir(root(`.build/module/${f}`));
if (fs.statSync(root(`.build/module/${f}`)).isDirectory()) {
rmdir(root(`.build/module/${f}`));
}
}
const modules = [
'builtin', 'contest-rule-homework', 'judger', 'migrate-vijos', 'module-pastebin',
@@ -61,15 +68,16 @@ async function build(type) {
const j = {};
for (const m of modules) {
try {
const d = fs.readFileSync(root(`.build/module/${m}.hydro`));
const d = await fsp.readFile(root(`.build/module/${m}.hydro`));
j[m] = d.toString('base64');
} catch (e) {
console.error(`Module pack failed: ${m}`);
}
}
fs.writeFileSync(root('.build/module.json'), JSON.stringify(j));
await fsp.writeFile(root('.build/module.json'), JSON.stringify(j));
console.log('Build::Full');
await require('./webpack')(type, '-full');
// fs.unlinkSync(root('.build/module.json'));
await fsp.unlink(root('.build/module.json'));
}
module.exports = build;
+3 -14
View File
@@ -43,20 +43,9 @@ class DiscussionHandler extends Handler {
// TODO(twd2): do more visibility check eg. contest
// TODO(twd2): exclude problem/contest discussions?
// TODO(iceboy): continuation based pagination.
if (type && docId) {
if (type === 'problem') {
const pdoc = await problem.getById(docId);
if (!pdoc) throw new DiscussionNodeNotFoundError(type, docId);
if (pdoc.hidden) this.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
this.vnode = pdoc;
} else if (type === 'contest') {
const tdoc = await contest.get(docId);
if (!tdoc) throw new DiscussionNodeNotFoundError(type, docId);
this.vnode = tdoc;
} else throw new DiscussionNodeNotFoundError(type, docId);
this.vnode.parentType = type;
this.vnode.parentId = docId;
}
this.vnode = await discussion.getVnode({ parentType: type, parentId: docId }, this);
this.ddoc.parentType = this.ddoc.parentType || this.vnode.type;
this.ddoc.parentId = this.ddoc.parentId || this.vnode.id;
}
}
+9 -1
View File
@@ -186,9 +186,17 @@ class ManageSettingHandler extends ManageHandler {
}
async post(args) {
const tasks = [];
for (const key in args) {
await system.set(key, args[key]);
if (typeof args[key] === 'object') {
const subtasks = [];
for (const sub in args[key]) {
subtasks.push(system.set(`${key}.${sub}`, args[key][sub]));
}
tasks.push(Promise.all(subtasks));
} else tasks.push(system.set(key, args[key]));
}
await Promise.all(tasks);
this.back();
}
}
@@ -1,10 +1,6 @@
const crypto = require('crypto');
module.exports = {
_b64encode: (str) => Buffer.from(str).toString('base64'),
_b64decode: (str) => Buffer.from(str, 'base64').toString(),
_md5: (str) => crypto.createHash('md5').update(str).digest('hex'),
_sha1: (str) => crypto.createHash('sha1').update(str).digest('hex'),
global.Hydro.lib['hash.hydro'] = module.exports = {
/**
* @param {string} password
* @param {string} salt
@@ -21,7 +17,4 @@ module.exports = {
const res = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha256');
return res.toString('Hex');
},
salt() {
return String.random();
},
};
+40 -38
View File
@@ -1,43 +1,45 @@
const nodemailer = require('nodemailer');
const { SendMailError } = require('../error');
module.exports = {
/**
* @param {string} to
* @param {string} subject
* @param {string} text
* @param {string} html
*/
async sendMail(to, subject, text, html) {
let t;
try {
const system = require('../model/system');
const [host, port, secure, user, pass, from] = await Promise.all([
system.get('smtp.host'),
system.get('smtp.port'),
system.get('smtp.secure'),
system.get('smtp.user'),
system.get('smtp.pass'),
system.get('smtp.from'),
]);
const transporter = nodemailer.createTransport({
host, port, secure, auth: { user, pass },
/**
* @param {string} to
* @param {string} subject
* @param {string} text
* @param {string} html
*/
async function sendMail(to, subject, text, html) {
let t;
try {
const { system } = global.Hydro.model;
const [host, port, secure, user, pass, from] = await Promise.all([
system.get('smtp.host'),
system.get('smtp.port'),
system.get('smtp.secure'),
system.get('smtp.user'),
system.get('smtp.pass'),
system.get('smtp.from'),
]);
const transporter = nodemailer.createTransport({
host, port, secure, auth: { user, pass },
});
t = await new Promise((resolve, reject) => {
transporter.sendMail({
from,
to,
subject,
text,
html,
}, (err, info) => {
if (err) reject(err);
else resolve(info);
});
t = await new Promise((resolve, reject) => {
transporter.sendMail({
from,
to,
subject,
text,
html,
}, (err, info) => {
if (err) reject(err);
else resolve(info);
});
});
} catch (e) {
throw new SendMailError(to);
}
return t;
},
});
} catch (e) {
throw new SendMailError(to);
}
return t;
}
global.Hydro.lib.mail = module.exports = {
sendMail,
};
+2 -1
View File
@@ -21,7 +21,8 @@ class Markdown extends MarkdownIt {
this.use(katex);
}
}
module.exports = {
global.Hydro.lib.markdown = module.exports = {
unsafe: new Markdown(false),
safe: new Markdown(true),
};
+2 -1
View File
@@ -126,4 +126,5 @@ function rstr2hex(input) {
}
return output;
}
module.exports = (s) => rstr2hex(rstrMD5(unescape(encodeURIComponent(s))));
global.Hydro.lib.md5 = module.exports = (s) => rstr2hex(rstrMD5(unescape(encodeURIComponent(s))));
+19 -13
View File
@@ -1,10 +1,10 @@
const md5 = require('./md5');
exports.gravatar = function gravatar(email, size = 32) {
return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${size}`;
};
function gravatar(email, s = 32) {
return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${s}`;
}
exports.datetime_span = function datetimeSpan(dt, relative = true, format = '%Y-%m-%d %H:%M:%S') {
function datetimeSpan(dt, relative = true, format = '%Y-%m-%d %H:%M:%S') {
if (!dt) return 'DATETIME_SPAN_ERROR';
if (dt.generationTime) dt = new Date(dt.generationTime * 1000);
else if (typeof dt === 'number' || typeof dt === 'string') dt = new Date(dt);
@@ -13,9 +13,9 @@ exports.datetime_span = function datetimeSpan(dt, relative = true, format = '%Y-
dt.getTime() / 1000,
dt.format(format),
);
};
}
exports.paginate = function* paginate(page, numPages) {
function* paginate(page, numPages) {
const radius = 2; let first; let
last;
if (page > 1) {
@@ -25,7 +25,9 @@ exports.paginate = function* paginate(page, numPages) {
if (page <= radius) [first, last] = [1, Math.min(1 + radius * 2, numPages)];
else if (page >= numPages - radius) {
[first, last] = [Math.max(1, numPages - radius * 2), numPages];
} else [first, last] = [page - radius, page + radius];
} 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];
@@ -34,15 +36,19 @@ exports.paginate = function* paginate(page, numPages) {
if (last < numPages) yield ['ellipsis', 0];
if (page < numPages) yield ['next', page + 1];
yield ['last', numPages];
};
}
exports.size = function formatSize(size, base = 1) {
size *= base;
function size(s, base = 1) {
s *= base;
const unit = 1024;
const unitNames = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
for (const unitName of unitNames) {
if (size < unit) return '{0} {1}'.format(Math.round(size), unitName);
size /= unit;
if (s < unit) return '{0} {1}'.format(Math.round(s), unitName);
s /= unit;
}
return '{0} {1}'.format(Math.round(size * unit), unitNames[unitNames.length - 1]);
return '{0} {1}'.format(Math.round(s * unit), unitNames[unitNames.length - 1]);
}
global.Hydro.lib.misc = module.exports = {
gravatar, datetimeSpan, paginate, size,
};
+4 -2
View File
@@ -1,6 +1,6 @@
const { ValidationError } = require('../error');
module.exports = async function paginate(cursor, page, pageSize) {
async function paginate(cursor, page, pageSize) {
if (page <= 0) throw new ValidationError('page');
const [count, pageDocs] = await Promise.all([
cursor.count(),
@@ -8,4 +8,6 @@ module.exports = async function paginate(cursor, page, pageSize) {
]);
const numPages = Math.floor((count + pageSize - 1) / pageSize);
return [pageDocs, numPages, count];
};
}
global.Hydro.lib.paginate = module.exports = paginate;
+22 -22
View File
@@ -28,26 +28,26 @@ class Nunjucks extends nunjucks.Environment {
}
const env = new Nunjucks();
module.exports = {
render(name, state) {
Object.assign(state, {
typeof: (o) => typeof o,
static_url: (str) => `/${str}`,
datetime_span: misc.datetime_span,
paginate: misc.paginate,
perm,
status: builtin.status,
size: misc.size,
gravatar: misc.gravatar,
model,
Context: global.Hydro.ui,
isIE: (str) => str.includes('MSIE') || str.includes('rv:11.0'),
function render(name, state) {
Object.assign(state, {
typeof: (o) => typeof o,
static_url: (str) => `/${str}`,
datetimeSpan: misc.datetimeSpan,
paginate: misc.paginate,
perm,
status: builtin.status,
size: misc.size,
gravatar: misc.gravatar,
model,
Context: global.Hydro.ui,
isIE: (str) => str.includes('MSIE') || str.includes('rv:11.0'),
});
return new Promise((resolve, reject) => {
env.render(name, state, (err, res) => {
if (err) reject(err);
else resolve(res);
});
return new Promise((resolve, reject) => {
env.render(name, state, (err, res) => {
if (err) reject(err);
else resolve(res);
});
});
},
};
});
}
global.Hydro.lib.template = module.exports = { render };
+1 -1
View File
@@ -29,7 +29,7 @@ const checkIntro = (s) => { if (!isIntro(s)) throw new ValidationError('intro');
const isDescription = () => true;
const checkDescription = (s) => { if (!isDescription(s)) throw new ValidationError('description'); else return s; };
module.exports = {
global.Hydro.lib.validator = module.exports = {
isTitle,
checkTitle,
isUid,
+7 -4
View File
@@ -25,12 +25,15 @@ try {
// eslint-disable-next-line import/no-unresolved
const f = require('../.build/module.json');
for (const filename in f) {
const m = { ...yaml.safeLoad(zlib.gunzipSync(f[filename])), filename, isBuiltin: true };
const m = {
...yaml.safeLoad(zlib.gunzipSync(Buffer.from(f[filename], 'base64'))),
filename,
isBuiltin: true,
};
pending.push(m);
console.log(filename);
}
} catch (e) {
// Builtin module is in the module directory
console.log(e);
}
async function preload() {
@@ -225,7 +228,7 @@ async function load() {
});
const builtinLib = [
'axios', 'download', 'i18n', 'mail', 'markdown',
'md5', 'misc', 'paginate', 'pwhash', 'rank',
'md5', 'misc', 'paginate', 'hash.hydro', 'rank',
'template', 'validator', 'nav',
];
for (const i of builtinLib) require(`./lib/${i}`);
+1 -1
View File
@@ -19,6 +19,6 @@ function index() {
return coll.createIndex('expireAt', { expireAfterSeconds: 0 });
}
module.exports = {
global.Hydro.model.blacklist = module.exports = {
add, get, del, index,
};
+2 -1
View File
@@ -229,7 +229,8 @@ const STATUS_CODES = {
22: 'progress',
30: 'ignored',
};
module.exports = {
global.Hydro.model.builtin = module.exports = {
BUILTIN_ROLES,
CATEGORIES,
VIEW_LANGS,
+1 -1
View File
@@ -342,7 +342,7 @@ const ContestHandlerMixin = (c) => class extends c {
}
};
global.Hydro['model.contest'] = module.exports = {
global.Hydro.model.contest = module.exports = {
RULES,
ContestHandlerMixin,
add,
+9 -5
View File
@@ -142,15 +142,19 @@ async function getVnode(ddoc, handler) {
const pdoc = await problem.getById(ddoc.parentId);
if (!pdoc) return null;
if (pdoc.hidden && handler) handler.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
return { ...pdoc, parentType: ddoc.parentType, parentId: ddoc.parentId };
return { ...pdoc, type: ddoc.parentType, id: ddoc.parentId };
} if (ddoc.parentType === 'contest') {
const tdoc = await contest.get(ddoc.parentId);
return { ...tdoc, parentType: ddoc.parentType, parentId: ddoc.parentId };
return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId };
} if (ddoc.parentType === 'node') {
const ndoc = await getNode(ddoc.parentId);
return { ...ndoc, parentType: ddoc.parentType, parentId: ddoc.parentId };
return { ...ndoc, type: ddoc.parentType, id: ddoc.parentId };
}
return null;
return {
title: 'Missing Node',
type: 'Unknown',
id: new ObjectID(),
};
}
async function getListVnodes(ddocs, handler) {
@@ -162,7 +166,7 @@ async function getListVnodes(ddocs, handler) {
return res;
}
module.exports = {
global.Hydro.model.discussion = module.exports = {
add,
get,
edit,
+23 -23
View File
@@ -3,26 +3,26 @@ const db = require('../service/db.js');
const coll = db.collection('opcount');
module.exports = {
/**
* @param {string} op
* @param {string} ident
* @param {number} period_secs
* @param {number} max_operations
*/
async inc(op, ident, periodSecs, maxOperations) {
const curTime = new Date().getTime();
const beginAt = new Date(curTime - (curTime % (periodSecs * 1000)));
const expireAt = new Date(beginAt.getTime() + periodSecs * 1000);
try {
await coll.findOneAndUpdate({
ident,
beginAt,
expireAt,
op: { $not: { $gte: maxOperations } },
}, { $inc: { op: 1 } }, { upsert: true });
} catch (e) {
throw new OpcountExceededError(op, periodSecs, maxOperations);
}
},
};
/**
* @param {string} op
* @param {string} ident
* @param {number} period_secs
* @param {number} max_operations
*/
async function inc(op, ident, periodSecs, maxOperations) {
const curTime = new Date().getTime();
const beginAt = new Date(curTime - (curTime % (periodSecs * 1000)));
const expireAt = new Date(beginAt.getTime() + periodSecs * 1000);
try {
await coll.findOneAndUpdate({
ident,
beginAt,
expireAt,
op: { $not: { $gte: maxOperations } },
}, { $inc: { op: 1 } }, { upsert: true });
} catch (e) {
throw new OpcountExceededError(op, periodSecs, maxOperations);
}
}
global.Hydro.model.opcount = module.exports = { inc };
+1 -1
View File
@@ -104,7 +104,7 @@ async function rejudge(rid) {
});
}
module.exports = {
global.Hydro.model.record = module.exports = {
add,
get,
getMany,
+10 -2
View File
@@ -47,9 +47,17 @@ const SYSTEM_SETTINGS = [
Setting('setting_smtp', 'smtp.user', null, null, 'text', 'SMTP Username'),
Setting('setting_smtp', 'smtp.pass', null, null, 'password', 'SMTP Password'),
Setting('setting_smtp', 'smtp.host', null, null, 'text', 'SMTP Server Host'),
Setting('setting_smtp', 'smtp.port', null, null, 'text', 'SMTP Server Port'),
Setting('setting_smtp', 'smtp.port', null, 465, 'text', 'SMTP Server Port'),
Setting('setting_smtp', 'smtp.from', null, null, 'text', 'Mail From'),
Setting('setting_smtp', 'smtp.secure', null, null, 'checkbox', 'SSL'),
Setting('setting_smtp', 'smtp.secure', null, false, 'checkbox', 'SSL'),
Setting('setting_server', 'listen.port', null, 8888, 'text', 'Server Port', ''),
Setting('setting_constant', 'PROBLEM_PER_PAGE', null, 100, 'text', 'Problems per Page'),
Setting('setting_constant', 'CONTEST_PER_PAGE', null, 20, 'text', 'Contests per Page'),
Setting('setting_constant', 'DISCUSSION_PER_PAGE', null, 50, 'text', 'Discussion per Page'),
Setting('setting_constant', 'RECORD_PER_PAGE', null, 100, 'text', 'Record per Page'),
Setting('setting_constant', 'SOLUTION_PER_PAGE', null, 20, 'text', 'Solutions per Page'),
Setting('setting_constant', 'TRAINING_PER_PAGE', null, 10, 'text', 'Training per Page'),
Setting('setting_constant', 'REPLY_PER_PAGE', null, 50, 'text', 'Reply per Page'),
];
global.Hydro.model.setting = module.exports = {
+1 -1
View File
@@ -106,7 +106,7 @@ async function getListStatus(list, uid) {
return result;
}
module.exports = {
global.Hydro.model.solution = module.exports = {
count,
add,
get,
+2 -1
View File
@@ -22,7 +22,8 @@ async function set(_id, value) {
function inc(field) {
return update(field, { $inc: { value: 1 } }, { upsert: true });
}
module.exports = {
global.Hydro.model.system = module.exports = {
get,
update,
inc,
+1 -1
View File
@@ -10,7 +10,7 @@ function index() {
]);
}
module.exports = {
global.Hydro.model.token = module.exports = {
TYPE_SESSION: 0,
TYPE_CSRF_TOKEN: 1,
TYPE_REGISTER: 2,
+1 -1
View File
@@ -35,7 +35,7 @@ async function setStatus(tid, uid, $set) {
return await collStatus.findOne({ tid, uid }); // eslint-disable-line no-return-await
}
module.exports = {
global.Hydro.model.training = module.exports = {
getPids(tdoc) {
const pids = new Set();
for (const node of tdoc.dag) {
+13 -10
View File
@@ -1,7 +1,7 @@
const system = require('./system');
const { UserNotFoundError, UserAlreadyExistError } = require('../error');
const perm = require('../permission');
const pwhash = require('../lib/pwhash');
const pwhash = require('../lib/hash.hydro');
const db = require('../service/db');
const coll = db.collection('user');
@@ -12,8 +12,8 @@ class USER {
this._id = user._id;
this.mail = user.mail;
this.uname = user.uname;
this.salt = user.salt;
this.hash = user.hash;
this.salt = () => user.salt;
this.hash = () => user.hash;
this.perm = user.perm;
this.viewLang = user.language || 'zh_CN';
this.codeLang = user.codeLang || 'c';
@@ -31,7 +31,9 @@ class USER {
}
checkPassword(password) {
return pwhash.check(password, this.salt, this.hash);
const h = global.Hydro.lib[`hash.${this.hashType}`];
if (!h) throw new Error('Unknown hash method');
return h.check(password, this.salt(), this.hash());
}
}
@@ -71,9 +73,9 @@ async function getByEmail(mail, ignoreMissing = false) {
}
function setPassword(uid, password) {
const salt = pwhash.salt();
const salt = String.random();
return coll.findOneAndUpdate({ _id: uid }, {
$set: { salt, hash: pwhash.hash(password, salt) },
$set: { salt, hash: pwhash.hash(password, salt), hashType: 'hydro' },
});
}
@@ -88,11 +90,11 @@ function setEmail(uid, mail) {
async function changePassword(uid, currentPassword, newPassword) {
const udoc = await getById(uid);
udoc.checkPassword(currentPassword);
const salt = pwhash.salt();
const salt = String.random();
return await coll.findOneAndUpdate({ // eslint-disable-line no-return-await
_id: udoc._id,
}, {
$set: { salt, hash: pwhash.hash(newPassword, salt) },
$set: { salt, hash: pwhash.hash(newPassword, salt), hashType: 'hydro' },
});
}
@@ -106,7 +108,7 @@ async function inc(_id, field, n = 1) {
async function create({
uid, mail, uname, password, regip = '127.0.0.1', role = 'default',
}) {
const salt = pwhash.salt();
const salt = String.random();
if (!uid) uid = system.inc('user');
try {
await Promise.all([
@@ -118,6 +120,7 @@ async function create({
unameLower: uname.trim().toLowerCase(),
password: pwhash.hash(password, salt),
salt,
hashType: 'hydro',
regat: new Date(),
regip,
loginat: new Date(),
@@ -179,7 +182,7 @@ function index() {
]);
}
module.exports = {
global.Hydro.model.user = module.exports = {
changePassword,
create,
getByEmail,
+3 -3
View File
@@ -2,7 +2,7 @@ const { defaults } = require('lodash');
const db = require('../service/db');
const builtin = require('../model/builtin');
const system = require('../model/system');
const pwhash = require('../lib/pwhash');
const pwhash = require('../lib/hash.hydro');
const { udoc } = require('../interface');
const collUser = db.collection('user');
@@ -42,7 +42,7 @@ async function run() {
}
await Promise.all(tasks);
try {
const salt = pwhash.salt();
const salt = String.random();
await collUser.insertMany([
defaults({
_id: 0,
@@ -79,4 +79,4 @@ async function run() {
await collRole.insertMany(builtin.BUILTIN_ROLES);
}
module.exports = { run };
global.Hydro.script.install = module.exports = { run };
+1 -1
View File
@@ -5,4 +5,4 @@ async function run() {
console.log('Dropped');
}
module.exports = { run };
global.Hydro.script.uninstall = module.exports = { run };
+2 -1
View File
@@ -1,7 +1,8 @@
const { EventEmitter } = require('events');
const bus = new EventEmitter();
module.exports = {
global.Hydro.service.bus = module.exports = {
subscribe(events, handler) {
for (const event of events) bus.on(event, handler);
},
+2 -1
View File
@@ -2,4 +2,5 @@ const { GridFSBucket } = require('mongodb');
const db = require('./db');
if (!db.s) db.s = {};
module.exports = new GridFSBucket(db);
global.Hydro.service.gridfs = module.exports = new GridFSBucket(db);
+2
View File
@@ -57,6 +57,8 @@ manage_join_applications: Join Applications
manage_role: System Role
manage_user: System User
manage_permission: System Permission
manage_module: Module Management
manage_setting: System Settings
timeago_locale: en
perm_general: General
perm_problem: Problems
+1
View File
@@ -316,6 +316,7 @@ manage_role: 管理角色
manage_user: 管理用户
manage_permission: 管理权限
manage_module: 管理模块
manage_setting: 系统设置
domain_join: 加入域
'Join {0}': '加入 {0}'
You are not allowed to join the domain. The link is either invalid or expired.: 您无法加入该域,链接无效或已过期。
@@ -10,7 +10,7 @@
<div class="clearfix">
<div class="supplementary dczcomments__supplementary">
{{ user.render_inline(udoc, avatar=false) }}
@ {{ datetime_span(rdoc['_id'])|safe }}
@ {{ datetimeSpan(rdoc['_id'])|safe }}
</div>
{{ reply_operations(doc, rdoc) }}
</div>
@@ -113,7 +113,7 @@
<div class="clearfix">
<div class="supplementary dczcomments__supplementary">
{{ user.render_inline(udoc, avatar=false) }}
@ {{ datetime_span(doc['_id'])|safe }}
@ {{ datetimeSpan(doc['_id'])|safe }}
</div>
<div class="dczcomments__operations nojs--hide">
{% if handler.hasPerm(reply_post_perm) %}
+2 -2
View File
@@ -110,7 +110,7 @@
<div class="clearfix">
<div class="supplementary dczcomments__supplementary">
{{ user.render_inline(udoc) }}
@ {{ datetime_span(doc._id)|safe }}
@ {{ datetimeSpan(doc._id)|safe }}
</div>
<div class="dczcomments__operations nojs--hide">
{% if handler.hasPerm(reply_post_perm) %}
@@ -142,7 +142,7 @@
<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 }}
{{ user.render_inline(udoc, badge=false) }} @ {{ datetimeSpan(rdoc['_id'])|safe }}
</div>
<div class="dczcomments__operations nojs--hide">
{% if handler.hasPerm(reply_post_perm) %}
+1 -1
View File
@@ -1,6 +1,6 @@
{% import "components/user.html" as user with context %}
{% macro render_time(tdoc_time) %}
{{ datetime_span(tdoc_time, false, '%Y-%m-%d %H:%M')|safe }}
{{ datetimeSpan(tdoc_time, false, '%Y-%m-%d %H:%M')|safe }}
{% endmacro %}
{% macro render_duration(tdoc) %}
+1 -1
View File
@@ -51,7 +51,7 @@
{% else %}
<td class="col--status record-status--border"><a href="/r/{{ rdoc._id }}">{{ _('Submitted') }}</a></td>
{% endif %}
<td class="col--submit-at">{{ datetime_span(rdoc._id)|safe }}</td>
<td class="col--submit-at">{{ datetimeSpan(rdoc._id)|safe }}</td>
{% else %}
<td class="col--status record-status--border">{{ _('No Submissions') }}</td>
<td class="col--submit-at">-</td>
+2 -2
View File
@@ -83,8 +83,8 @@
<div class="media">
<div class="media__left medium">
<div class="contest__date numbox">
<div class="numbox__num large">{{ datetime_span(tdoc['beginAt'], false, '%d')|safe }}</div>
<div class="numbox__text">{{ datetime_span(tdoc['beginAt'], false, '%Y-%m')|safe }}</div>
<div class="numbox__num large">{{ datetimeSpan(tdoc['beginAt'], false, '%d')|safe }}</div>
<div class="numbox__text">{{ datetimeSpan(tdoc['beginAt'], false, '%Y-%m')|safe }}</div>
</div>
</div>
<div class="media__body medium">
+2 -2
View File
@@ -8,7 +8,7 @@
<div class="section__body">
<ul class="supplementary list">
<li>
<a class="discussion-node-tag" href="/discuss/{{ ddoc.parentType }}/{{ ddoc.parentId }}">
<a class="discussion-node-tag" href="/discuss/{{ vnode.type }}/{{ vnode.id }}">
{% if ddoc.parentType == 'node' %}
<span class="v-center icon icon-tag"></span>
{% elif ddoc.parentType == 'contest' %}
@@ -22,7 +22,7 @@
<li>
{{ user.render_inline(udict[ddoc.owner]) }}
@
{{ datetime_span(ddoc['_id'].generationTime)|safe }}
{{ datetimeSpan(ddoc._id)|safe }}
</li>
</ul>
<div class="typo topic__content" data-emoji-enabled data-raw-url="/discuss/{{ ddoc._id }}/raw">
+1 -1
View File
@@ -94,7 +94,7 @@
<span class="sessionlist__icon icon icon-platform--{{ session.updateUa.icon }}"></span>
</div>
<div class="media__body medium typo">
<p>{{ _('Last Update At') }}: {{ datetime_span(session.updateAt)|safe }}</p>
<p>{{ _('Last Update At') }}: {{ datetimeSpan(session.updateAt)|safe }}</p>
<p>{{ _('Location') }}: {{ _(session.updateGeoip) }} ({{ session.updateIp }})</p>
<p>{{ _('Operating System') }}: {{ session.updateUa.os }}</p>
<p>{{ _('Browser') }}: {{ session.updateUa.browser }}</p>
+2 -2
View File
@@ -27,8 +27,8 @@
<div class="media">
<div class="media__left medium">
<div class="contest__date numbox">
<div class="numbox__num large">{{ datetime_span(tdoc['beginAt'], false, '%d')|safe }}</div>
<div class="numbox__text">{{ datetime_span(tdoc['beginAt'], false, '%Y-%m')|safe }}</div>
<div class="numbox__num large">{{ datetimeSpan(tdoc['beginAt'], false, '%d')|safe }}</div>
<div class="numbox__text">{{ datetimeSpan(tdoc['beginAt'], false, '%Y-%m')|safe }}</div>
</div>
</div>
<div class="media__body medium">
+1 -1
View File
@@ -32,7 +32,7 @@
<li>
{{ user.render_inline(udict[ddoc.owner], badge=false) }}
@
{{ datetime_span(ddoc.updateAt)|safe }}
{{ datetimeSpan(ddoc.updateAt)|safe }}
</li>
</ul>
</div>
+1 -1
View File
@@ -14,5 +14,5 @@
<td class="col--time">
{{ (rdoc.time / 1000)|round(1) }}s
</td>
<td class="col--submit-at">{{ datetime_span(rdoc['_id'].generationTime) }}</td>
<td class="col--submit-at">{{ datetimeSpan(rdoc['_id'].generationTime) }}</td>
</tr>
+2 -2
View File
@@ -81,11 +81,11 @@
{{ _('Submit At') }}
</dt>
<dd>
{{ datetime_span(rdoc._id)|safe }}
{{ datetimeSpan(rdoc._id)|safe }}
</dd>
{% if rdoc.judgeAt %}
<dt>{{ _('Judged At') }}</dt>
<dd>{{ datetime_span(rdoc.judgeAt)|safe }}</dd>
<dd>{{ datetimeSpan(rdoc.judgeAt)|safe }}</dd>
{% endif %}
{% if judge_udoc %}
<dt>{{ _('Judged By') }}</dt>
+1 -1
View File
@@ -26,5 +26,5 @@
<td class="col--time">{% if rdoc['status'] == status.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == status.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == status.STATUS_OUTPUT_LIMIT_EXCEEDED %}&ge;{% endif %}{{ rdoc.time }}ms</td>
<td class="col--memory">{% if rdoc['status'] == status.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == status.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == status.STATUS_OUTPUT_LIMIT_EXCEEDED %}&ge;{% endif %}{{ size(rdoc.memory, 1024) }}</td>
<td class="col--lang">{{ model.builtin.LANG_TEXTS[rdoc['lang']] }}</td>
<td class="col--submit-at">{{ datetime_span(rdoc['_id'])|safe }}</td>
<td class="col--submit-at">{{ datetimeSpan(rdoc['_id'])|safe }}</td>
</tr>
+3 -3
View File
@@ -17,10 +17,10 @@
<small>({{ _('UID') }}: {{ udoc['_id'] }})</small>
</h1>
<p>
{{ _('Registered at') }} {{ datetime_span(udoc['regat'])|safe }},
{{ _('last login at') }} {{ datetime_span(udoc['loginat'])|safe }},
{{ _('Registered at') }} {{ datetimeSpan(udoc['regat'])|safe }},
{{ _('last login at') }} {{ datetimeSpan(udoc['loginat'])|safe }},
{% if sdoc %}
{{ _('last active at') }} {{ datetime_span(sdoc['updateAt'])|safe }}.
{{ _('last active at') }} {{ datetimeSpan(sdoc['updateAt'])|safe }}.
{% else %}
{{ _('currently offline') }}.
{% endif %}