mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
完善翻译,添加翻译缺失检查工具
This commit is contained in:
1 parent
82c193b5ee
commit
13e3e0ec98
19 files changed
+242
-90
No files matched your search
+39
-22
@@ -1,14 +1,17 @@
|
||||
import { ObjectID } from 'mongodb';
|
||||
import {
|
||||
VerifyPasswordError, UserAlreadyExistError, InvalidTokenError,
|
||||
NotFoundError,
|
||||
UserNotFoundError,
|
||||
PermissionError,
|
||||
} from '../error';
|
||||
import * as bus from '../service/bus';
|
||||
import {
|
||||
Route, Connection, Handler, ConnectionHandler,
|
||||
Route, Connection, Handler, ConnectionHandler, param, Types,
|
||||
} from '../service/server';
|
||||
import * as misc from '../lib/misc';
|
||||
import md5 from '../lib/md5';
|
||||
import * as mail from '../lib/mail';
|
||||
import * as contest from '../model/contest';
|
||||
import * as message from '../model/message';
|
||||
import * as document from '../model/document';
|
||||
@@ -21,6 +24,7 @@ import * as discussion from '../model/discussion';
|
||||
import * as token from '../model/token';
|
||||
import * as training from '../model/training';
|
||||
import { PERM, PRIV } from '../model/builtin';
|
||||
import { isContent, isPassword, isEmail } from '../lib/validator';
|
||||
|
||||
const { geoip, useragent } = global.Hydro.lib;
|
||||
|
||||
@@ -133,29 +137,38 @@ class HomeSecurityHandler extends Handler {
|
||||
if (useragent) this.response.body.icon = useragent.icon;
|
||||
}
|
||||
|
||||
async postChangePassword({ current, password, verifyPassword }) {
|
||||
@param('current', Types.String)
|
||||
@param('password', Types.String, isPassword)
|
||||
@param('verifyPassword', Types.String)
|
||||
async postChangePassword(current: string, password: string, verifyPassword: string) {
|
||||
if (password !== verifyPassword) throw new VerifyPasswordError();
|
||||
await user.changePassword(this.user._id, current, password);
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postChangeMail({ domainId, currentPassword, mail }) {
|
||||
@param('currentPassword', Types.String)
|
||||
@param('mail', Types.String, isEmail)
|
||||
async postChangeMail(domainId: string, currentPassword: string, email: string) {
|
||||
this.limitRate('send_mail', 3600, 30);
|
||||
this.user.checkPassword(currentPassword);
|
||||
const udoc = await user.getByEmail(domainId, mail);
|
||||
if (udoc) throw new UserAlreadyExistError(mail);
|
||||
const udoc = await user.getByEmail(domainId, email);
|
||||
if (udoc) throw new UserAlreadyExistError(email);
|
||||
const [code] = await token.add(
|
||||
token.TYPE_CHANGEMAIL,
|
||||
await system.get('changemail_token_expire_seconds'),
|
||||
{ uid: this.user._id, mail },
|
||||
{ uid: this.user._id, email },
|
||||
);
|
||||
await mail.sendMail(mail, 'Change Email', 'user_changemail_mail.html', {
|
||||
url: this.url('user_changemail_with_code', { code }), uname: this.user.uname,
|
||||
const m = await this.renderHTML('user_changemail_mail.html', {
|
||||
path: `home/changeMail/${code}`,
|
||||
uname: this.user.uname,
|
||||
url_prefix: await system.get('server.url'),
|
||||
});
|
||||
await mail.sendMail(email, 'Change Email', 'user_changemail_mail', m);
|
||||
this.response.template = 'user_changemail_mail_sent.html';
|
||||
}
|
||||
|
||||
async postDeleteToken({ tokenDigest }) {
|
||||
@param('tokenDigest', Types.String)
|
||||
async postDeleteToken(domainId: string, tokenDigest: string) {
|
||||
const sessions = await token.getSessionListByUid(this.user._id);
|
||||
for (const session of sessions) {
|
||||
if (tokenDigest === md5(session._id)) {
|
||||
@@ -174,9 +187,8 @@ class HomeSecurityHandler extends Handler {
|
||||
}
|
||||
|
||||
class HomeSettingsHandler extends Handler {
|
||||
async get({ category }) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
category = category[0]; // Category would be splitted into array
|
||||
@param('category', Types.String)
|
||||
async get(domainId: string, category: string) {
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
[`home_${category}`, null],
|
||||
@@ -195,7 +207,7 @@ class HomeSettingsHandler extends Handler {
|
||||
} else throw new NotFoundError();
|
||||
}
|
||||
|
||||
async post(args) {
|
||||
async post(args: any) {
|
||||
const $set = {};
|
||||
for (const key in args) {
|
||||
if (setting.SETTINGS_BY_KEY[key] && !setting.SETTINGS_BY_KEY[key].disabled) {
|
||||
@@ -208,15 +220,16 @@ class HomeSettingsHandler extends Handler {
|
||||
}
|
||||
|
||||
class UserChangemailWithCodeHandler extends Handler {
|
||||
async get({ domainId, code }) {
|
||||
@param('code', Types.String)
|
||||
async get(domainId: string, code: string) {
|
||||
const tdoc = await token.get(code, token.TYPE_CHANGEMAIL);
|
||||
if (!tdoc || tdoc.uid !== this.user._id) {
|
||||
throw new InvalidTokenError(code);
|
||||
}
|
||||
const udoc = await user.getByEmail(domainId, tdoc.mail);
|
||||
if (udoc) throw new UserAlreadyExistError(tdoc.mail);
|
||||
const udoc = await user.getByEmail(domainId, tdoc.email);
|
||||
if (udoc) throw new UserAlreadyExistError(tdoc.email);
|
||||
await Promise.all([
|
||||
user.setEmail(this.user._id, tdoc.mail),
|
||||
user.setEmail(this.user._id, tdoc.email),
|
||||
token.del(code, token.TYPE_CHANGEMAIL),
|
||||
]);
|
||||
this.response.redirect = this.url('home_security');
|
||||
@@ -252,20 +265,24 @@ class HomeMessagesHandler extends Handler {
|
||||
this.response.template = 'home_messages.html';
|
||||
}
|
||||
|
||||
async postSend({ uid, content }) {
|
||||
@param('uid', Types.Int)
|
||||
@param('content', Types.String, isContent)
|
||||
async postSend(domainId: string, uid: number, content: string) {
|
||||
const udoc = await user.getById('system', uid);
|
||||
if (!udoc) throw new UserNotFoundError(uid);
|
||||
const mdoc = await message.send(this.user._id, uid, content);
|
||||
// TODO(twd2): improve here: projection
|
||||
if (this.user._id !== uid) {
|
||||
await bus.publish(`user_message-${uid}`, { mdoc, udoc });
|
||||
bus.publish(`user_message-${uid}`, { mdoc, udoc });
|
||||
}
|
||||
this.back({ mdoc, udoc });
|
||||
}
|
||||
|
||||
async postDeleteMessage({ messageId }) {
|
||||
// TODO limitations
|
||||
await message.del(messageId);
|
||||
@param('messageId', Types.ObjectID)
|
||||
async postDeleteMessage(domainId: string, messageId: ObjectID) {
|
||||
const msg = await message.get(messageId);
|
||||
if ([msg.from, msg.to].includes(this.user._id)) await message.del(messageId);
|
||||
else throw new PermissionError();
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -4,7 +4,7 @@ declare global {
|
||||
interface String {
|
||||
format: (...args: Array<any>) => string;
|
||||
rawformat: (object: any) => string;
|
||||
translate: (language: string | undefined) => string;
|
||||
translate: (...languages: string[]) => string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,12 @@ String.prototype.rawformat = function rawFormat(object) {
|
||||
return [res[0], object, res[1]].join();
|
||||
};
|
||||
|
||||
String.prototype.translate = function translate(language = 'zh_CN') {
|
||||
if (locales[language]) {
|
||||
if (locales[language][this] !== undefined) return locales[language][this];
|
||||
String.prototype.translate = function translate(...languages: string[]) {
|
||||
if (languages.length == 0) languages = ['zh_CN'];
|
||||
for (const language of languages) {
|
||||
if (locales[language]) {
|
||||
if (locales[language][this] !== undefined) return locales[language][this];
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -510,7 +510,10 @@ export const ContestHandlerMixin = (c) => class extends c {
|
||||
problem.getList(domainId, tdoc.pids, true),
|
||||
]);
|
||||
const rankedTsdocs = RULES[tdoc.rule].rank(tsdocs);
|
||||
const rows = RULES[tdoc.rule].scoreboard(isExport, (str) => (str ? str.toString().translate(this.user.language) : ''), tdoc, rankedTsdocs, udict, pdict);
|
||||
const rows = RULES[tdoc.rule].scoreboard(
|
||||
isExport, this.translate.bind(this),
|
||||
tdoc, rankedTsdocs, udict, pdict,
|
||||
);
|
||||
return [tdoc, rows, udict];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as db from '../service/db';
|
||||
const coll = db.collection('token');
|
||||
|
||||
export const TYPE_SESSION = 0;
|
||||
export const TYPE_CSRF_TOKEN = 1;
|
||||
export const TYPE_TOKEN = 1;
|
||||
export const TYPE_REGISTRATION = 2;
|
||||
export const TYPE_CHANGEMAIL = 3;
|
||||
export const TYPE_OAUTH = 4;
|
||||
@@ -62,7 +62,9 @@ export async function del(tokenId: string, tokenType: number) {
|
||||
return !!result.deletedCount;
|
||||
}
|
||||
|
||||
export async function createOrUpdate(tokenType: number, expireSeconds: number, data: object) {
|
||||
export async function createOrUpdate(
|
||||
tokenType: number, expireSeconds: number, data: any,
|
||||
): Promise<string> {
|
||||
const d = await coll.findOne({ tokenType, ...data });
|
||||
if (!d) {
|
||||
const res = await add(tokenType, expireSeconds, data);
|
||||
@@ -87,7 +89,7 @@ export function delByUid(uid: number) {
|
||||
global.Hydro.model.token = {
|
||||
TYPE_SESSION,
|
||||
TYPE_CHANGEMAIL,
|
||||
TYPE_CSRF_TOKEN,
|
||||
TYPE_TOKEN,
|
||||
TYPE_OAUTH,
|
||||
TYPE_REGISTRATION,
|
||||
TYPE_LOSTPASS,
|
||||
|
||||
+51
-42
@@ -24,6 +24,9 @@ import {
|
||||
UserFacingError, ValidationError, PrivilegeError,
|
||||
CsrfTokenError, InvalidOperationError, MethodNotAllowedError,
|
||||
} from '../error';
|
||||
import hash from '../lib/hash.hydro';
|
||||
import { lrucache } from '../utils';
|
||||
import { Udoc } from '../interface';
|
||||
|
||||
let enableLog = true;
|
||||
|
||||
@@ -158,6 +161,17 @@ export function param(name: string, ...args: any): MethodDecorator {
|
||||
};
|
||||
}
|
||||
|
||||
export function requireCsrfToken(target: any, funcName: string, obj: any) {
|
||||
const originalMethod = obj.value;
|
||||
obj.value = async function func(...args: any[]) {
|
||||
if (this.getCsrfToken(this.session._id) !== this.args.csrfToken) {
|
||||
throw new CsrfTokenError(this.args.csrfToken);
|
||||
}
|
||||
return await originalMethod.call(this, ...args);
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
||||
export async function prepare() {
|
||||
server = http.createServer(app.callback());
|
||||
app.keys = await system.get('session.keys');
|
||||
@@ -208,7 +222,7 @@ export class Handler {
|
||||
|
||||
csrfToken: string;
|
||||
|
||||
user: any;
|
||||
user: Udoc;
|
||||
|
||||
constructor(ctx: Koa.Context) {
|
||||
this.ctx = ctx;
|
||||
@@ -242,18 +256,22 @@ export class Handler {
|
||||
this.session = {};
|
||||
}
|
||||
|
||||
@lrucache
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
getCsrfToken(id: string) {
|
||||
return hash('csrf_token', id);
|
||||
}
|
||||
|
||||
async renderHTML(name: string, context: any): Promise<string> {
|
||||
if (enableLog) console.time(name);
|
||||
// FIXME fix this ugly hack
|
||||
// @ts-ignore
|
||||
this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) };
|
||||
const res = await render(name, Object.assign(context, {
|
||||
const UserContext = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) };
|
||||
const res = await render(name, {
|
||||
handler: this,
|
||||
// @ts-ignore
|
||||
url: (...args) => this.url(...args),
|
||||
_: (str: string) => (str ? str.toString().translate(this.user.viewLang || this.session.viewLang) : ''),
|
||||
user: this.user,
|
||||
}));
|
||||
UserContext,
|
||||
url: this.url.bind(this),
|
||||
_: this.translate.bind(this),
|
||||
...context,
|
||||
});
|
||||
if (enableLog) console.timeEnd(name);
|
||||
return res;
|
||||
}
|
||||
@@ -262,11 +280,12 @@ export class Handler {
|
||||
await opcount.inc(op, this.request.ip, periodSecs, maxOperations);
|
||||
}
|
||||
|
||||
translate(str) {
|
||||
return str ? str.toString().translate(this.user.viewLang || this.session.viewLang) : '';
|
||||
translate(str: string) {
|
||||
if (!str) return '';
|
||||
return str.toString().translate(this.user.viewLang, this.session.viewLang);
|
||||
}
|
||||
|
||||
renderTitle(str) {
|
||||
renderTitle(str: string) {
|
||||
return `${this.translate(str)} - Hydro`;
|
||||
}
|
||||
|
||||
@@ -304,7 +323,7 @@ export class Handler {
|
||||
}
|
||||
}
|
||||
|
||||
url(name, kwargs = {}) {
|
||||
url(name: string, kwargs = {}) {
|
||||
let res = '#';
|
||||
const args: any = { ...kwargs };
|
||||
try {
|
||||
@@ -324,7 +343,6 @@ export class Handler {
|
||||
}
|
||||
|
||||
async render(name: string, context: any) {
|
||||
// @ts-ignore
|
||||
this.response.body = await this.renderHTML(name, context);
|
||||
this.response.type = 'text/html';
|
||||
}
|
||||
@@ -352,14 +370,6 @@ export class Handler {
|
||||
if (bdoc) throw new BlacklistedError(this.request.ip);
|
||||
}
|
||||
|
||||
async getCsrfToken() {
|
||||
this.csrfToken = await token.createOrUpdate(token.TYPE_CSRF_TOKEN, 600, {
|
||||
path: this.request.path,
|
||||
uid: this.session.uid,
|
||||
});
|
||||
this.UIContext.csrfToken = this.csrfToken;
|
||||
}
|
||||
|
||||
async init({ domainId }) {
|
||||
const xff = await system.get('server.xff');
|
||||
if (xff) this.request.ip = this.request.headers[xff.toLowerCase()];
|
||||
@@ -367,15 +377,13 @@ export class Handler {
|
||||
this.getSession(),
|
||||
this.getBdoc(),
|
||||
]);
|
||||
[this.user] = await Promise.all([
|
||||
[this.user, this.UIContext.token] = await Promise.all([
|
||||
user.getById(domainId, this.session.uid),
|
||||
this.getCsrfToken(),
|
||||
token.createOrUpdate(
|
||||
token.TYPE_TOKEN, 300, { uid: this.session.uid },
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
async checkCsrfToken(csrfToken: any) {
|
||||
const sdoc = await token.get(csrfToken, token.TYPE_CSRF_TOKEN, false);
|
||||
if (!sdoc || sdoc.uid !== this.user._id) throw new CsrfTokenError(csrfToken);
|
||||
this.csrfToken = this.getCsrfToken(this.session._id || String.random(32));
|
||||
}
|
||||
|
||||
async finish() {
|
||||
@@ -494,7 +502,6 @@ async function handle(ctx, HandlerClass, checker) {
|
||||
await h.init(args);
|
||||
if (checker) checker.call(h);
|
||||
if (method === 'post') {
|
||||
await h.checkCsrfToken(args.csrfToken);
|
||||
if (operation) {
|
||||
if (typeof h[`post${operation}`] !== 'function') {
|
||||
throw new InvalidOperationError(operation);
|
||||
@@ -580,18 +587,11 @@ export class ConnectionHandler {
|
||||
}
|
||||
|
||||
async renderHTML(name: string, context: any): Promise<string> {
|
||||
if (enableLog) console.time(name);
|
||||
// FIXME fix this ugly hack
|
||||
// @ts-ignore
|
||||
this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) };
|
||||
const res = await render(name, Object.assign(context, {
|
||||
handler: this,
|
||||
// @ts-ignore
|
||||
url: (...args) => this.url(...args),
|
||||
_: (str: string) => (str ? str.toString().translate(this.user.viewLang || this.session.viewLang) : ''),
|
||||
user: this.user,
|
||||
url: this.url.bind(this),
|
||||
_: this.translate.bind(this),
|
||||
}));
|
||||
if (enableLog) console.timeEnd(name);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -675,7 +675,7 @@ export class ConnectionHandler {
|
||||
|
||||
async init({ domainId }) {
|
||||
try {
|
||||
this.session = await token.get(this.request.params.token, token.TYPE_CSRF_TOKEN, true);
|
||||
this.session = await token.get(this.request.params.token, token.TYPE_TOKEN, true);
|
||||
} catch (e) {
|
||||
this.session = { uid: 0 };
|
||||
}
|
||||
@@ -736,5 +736,14 @@ export async function start() {
|
||||
}
|
||||
|
||||
global.Hydro.service.server = {
|
||||
Types, param, Handler, ConnectionHandler, Route, Connection, Middleware, prepare, start,
|
||||
Types,
|
||||
param,
|
||||
requireCsrfToken,
|
||||
Handler,
|
||||
ConnectionHandler,
|
||||
Route,
|
||||
Connection,
|
||||
Middleware,
|
||||
prepare,
|
||||
start,
|
||||
};
|
||||
@@ -1,8 +1,10 @@
|
||||
import fs from 'fs';
|
||||
import { serialize } from 'v8';
|
||||
import cluster from 'cluster';
|
||||
import path from 'path';
|
||||
import * as superagent from 'superagent';
|
||||
import proxy from 'superagent-proxy';
|
||||
import Lru from 'lru-cache';
|
||||
|
||||
declare global {
|
||||
interface StringConstructor {
|
||||
@@ -24,6 +26,21 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const lru = new Lru(1000);
|
||||
|
||||
export function lrucache(target: any, funcName: string, obj: any) {
|
||||
const originalMethod = obj.value;
|
||||
obj.value = function func(...args: any[]) {
|
||||
const key = serialize({ funcName, args });
|
||||
let val = lru.get(key);
|
||||
if (val) return val;
|
||||
val = originalMethod.call(this, ...args);
|
||||
lru.set(key, val);
|
||||
return val;
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
||||
proxy(superagent);
|
||||
|
||||
if (!cluster.worker) {
|
||||
|
||||
+9
-1
@@ -41,6 +41,7 @@ __langname: 简体中文
|
||||
"What's file?": 什么是文件?
|
||||
(All Problems): (所有题目)
|
||||
(None): (无)
|
||||
(Not changed): 未更改
|
||||
Aborted: 已放弃
|
||||
About Hydro: 关于 Hydro
|
||||
About Markdown: 关于 Markdown
|
||||
@@ -337,9 +338,10 @@ hour(s): 小时
|
||||
If enabled, source code will be emailed to you after the submission is accepted.: 如果启用,在您通过题目后,源代码会以 Email 的形式发送给您。
|
||||
If left blank, the built-in template of the corresponding language will be used.: 若留空,则将使用对应语言的内置代码模板。
|
||||
Images: 图片
|
||||
Import from: 导入自
|
||||
Import problem from syzoj.: 从运行 syzoj 系统的 OJ 导入题目。
|
||||
Import Problem: 导入题目
|
||||
import: 导入
|
||||
Import: 导入
|
||||
In 1 day: 一天后
|
||||
In 1 month: 一个月后
|
||||
In 1 week: 一周后
|
||||
@@ -378,6 +380,7 @@ Last Submit At: 最后递交于
|
||||
Last Update At: 最后活动时间
|
||||
LaTeX Expressions: LaTeX 表达式
|
||||
Learn More: 了解更多
|
||||
Leave blank to use numeric pid.: 留空来使用数字题号。
|
||||
Limitations: 限制
|
||||
Links: 链接
|
||||
List View: 列表视图
|
||||
@@ -388,6 +391,8 @@ Login to Attend Contest: 登录后参加比赛
|
||||
Login to Claim Homework: 登录后认领作业
|
||||
Login to Create a Discussion: 登录后创建讨论
|
||||
Login to Submit: 登录后递交
|
||||
Login with Github: 使用 Github 登录
|
||||
Login with Google: 使用 Google 登录
|
||||
Login: 登录
|
||||
Logout All Sessions: 注销所有会话
|
||||
Logout This Session: 注销该会话
|
||||
@@ -639,6 +644,7 @@ Sorry, there is no problem in the problem set: 题库中没有题目
|
||||
Sorry, there is no training plan.: 目前没有训练计划。
|
||||
Sorry: 对不起
|
||||
Sort by: 排序
|
||||
Source type: 来源类型
|
||||
Star Topic: 收藏讨论
|
||||
Star: 收藏
|
||||
Start at: 开始于
|
||||
@@ -663,6 +669,7 @@ Technical Information: 技术信息
|
||||
Temporary session: 临时会话
|
||||
Terms of Service: 服务条款
|
||||
Test data comes from: 测试数据来自
|
||||
Testdata Config: 测试数据配置
|
||||
Text: 文本
|
||||
The homework's deadline is due but in extension. You can still submit for this problem but your score will be penalized.: 作业已超过截止时间,但仍在延期时间内。您递交题目将无法获得全部分数。
|
||||
The invitation code to enter to successfully join the domain. You can only use letters and numbers in the code and it should not be longer than 64 characters.: 加入此域的邀请码。您只能使用字母和数字,并且不能长于 64 个字符。
|
||||
@@ -681,6 +688,7 @@ This homework is not open and you cannot view problems.: 该作业还未到开
|
||||
This homework is not open.: 该作业还未开放递交。
|
||||
This is the current session: 这是当前的会话
|
||||
This message will be presented to those whose submissions are accepted.: 这条消息将会被展示给通过此题的用户。
|
||||
This page needs JavaScript to work.: 这个页面需要 Javascript。
|
||||
This section cannot be challenged at present, so please complete the following sections first: 该章节目前不可挑战,请先完成以下章节
|
||||
This type of files are not allowed to be uploaded.: 不允许上传这种文件。
|
||||
Time (Seconds): 耗时 (秒)
|
||||
|
||||
+3
-1
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "hydrooj",
|
||||
"version": "2.7.2",
|
||||
"version": "2.7.3",
|
||||
"main": "hydro/loader.ts",
|
||||
"repository": "https://github.com/hydro-dev/Hydro.git",
|
||||
"author": "masnn",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@hydrooj/markdown-it-katex": "^2.1.1",
|
||||
"@types/lru-cache": "^5.1.0",
|
||||
"adm-zip": "^0.4.16",
|
||||
"ansi_up": "^4.0.4",
|
||||
"detect-browser": "^5.1.1",
|
||||
@@ -17,6 +18,7 @@
|
||||
"koa-router": "^8.0.8",
|
||||
"koa-static-cache": "^5.1.3",
|
||||
"lodash": "^4.17.19",
|
||||
"lru-cache": "^6.0.0",
|
||||
"markdown-it": "^11.0.0",
|
||||
"markdown-it-anchor": "^5.3.0",
|
||||
"markdown-it-container": "^3.0.0",
|
||||
|
||||
@@ -121,12 +121,12 @@
|
||||
data-form="{{ {operation: reply_post_op, drid: doc._id}|json }}"
|
||||
><span class="icon icon-reply"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.user.hasPerm(comment_edit_perm) %}
|
||||
{% if (handler.user.hasPerm(comment_edit_self_perm) and doc.owner == handler.user._id) or handler.user.hasPerm(comment_edit_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" name="dczcomments__op-edit-comment"
|
||||
data-form="{{ {operation: comment_edit_op, drid: doc._id}|json }}"
|
||||
><span class="icon icon-edit"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.user.hasPerm(comment_delete_perm) %}
|
||||
{% if (handler.user.hasPerm(comment_delete_self_perm) and doc.owner == handler.user._id) or handler.user.hasPerm(comment_delete_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" name="dczcomments__op-delete-comment"
|
||||
data-form="{{ {operation: comment_delete_op, drid: doc._id}|json }}"
|
||||
><span class="icon icon-delete"></span></a>
|
||||
@@ -139,7 +139,7 @@
|
||||
<div class="commentbox-edit-target"></div>
|
||||
<ul class="dczcomments__replies commentbox-reply-target">
|
||||
{% for rdoc in doc['reply'] %}
|
||||
{{ reply(mode_create, udict[rdoc.owner], doc, rdoc) }}
|
||||
{{ reply(false, udict[rdoc.owner], doc, rdoc) }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -118,12 +118,12 @@
|
||||
data-form="{{ {operation: reply_post_op, psid: doc._id}|json }}"
|
||||
><span class="icon icon-reply"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.user.hasPerm(comment_edit_perm) %}
|
||||
{% if (handler.user.hasPerm(comment_edit_self_perm) and doc.owner == handler.user._id) or handler.user.hasPerm(comment_edit_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" name="dczcomments__op-edit-comment"
|
||||
data-form="{{ {operation: comment_edit_op, psid: doc._id}|json }}"
|
||||
><span class="icon icon-edit"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.user.hasPerm(comment_delete_perm) %}
|
||||
{% if (handler.user.hasPerm(comment_delete_self_perm) and doc.owner == handler.user._id) or handler.user.hasPerm(comment_delete_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" name="dczcomments__op-delete-comment"
|
||||
data-form="{{ {operation: comment_delete_op, psid: doc._id}|json }}"
|
||||
><span class="icon icon-delete"></span></a>
|
||||
@@ -148,12 +148,12 @@
|
||||
{% if handler.user.hasPerm(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.user.hasPerm(reply_edit_perm) %}
|
||||
{% if (handler.user.hasPerm(reply_edit_self_perm) and doc.owner == handler.user._id) or handler.user.hasPerm(reply_edit_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" name="dczcomments__op-edit-reply"
|
||||
data-form="{{ {operation: reply_edit_op, psid: doc._id, psrid: rdoc._id}|json }}"
|
||||
><span class="icon icon-edit"></span></a>
|
||||
{% endif %}
|
||||
{% if doc.owner == handler.state.user._id or handler.user.hasPerm(reply_delete_perm) %}
|
||||
{% if (handler.user.hasPerm(reply_delete_self_perm) and doc.owner == handler.user._id) or handler.user.hasPerm(reply_delete_perm) %}
|
||||
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" name="dczcomments__op-delete-reply"
|
||||
data-form="{{ {operation: reply_delete_op, psid: doc._id, psrid: rdoc._id}|json }}"
|
||||
><span class="icon icon-delete"></span></a>
|
||||
|
||||
@@ -63,12 +63,22 @@
|
||||
udict = udict,
|
||||
comment_ref = 'drid',
|
||||
reply_ref = 'drrid',
|
||||
comment_post_op = 'reply',
|
||||
reply_post_op = 'tail_reply',
|
||||
comment_edit_op = 'edit_reply',
|
||||
comment_delete_op = 'delete_reply',
|
||||
reply_edit_op = 'edit_tail_reply',
|
||||
reply_delete_op = 'delete_tail_reply',
|
||||
comment_post_perm = perm.PERM_REPLY_DISCUSSION,
|
||||
comment_edit_perm = perm.PERM_NONE if ddoc.owner == handler.user._id else perm.PERM_EDIT_DISCUSSION_REPLY,
|
||||
comment_delete_perm = perm.PERM_NONE if ddoc.owner == handler.user._id else perm.PERM_DELETE_DISCUSSION_REPLY,
|
||||
comment_edit_perm = perm.PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION if ddoc.owner == handler.user._id else perm.PERM_EDIT_DISCUSSION_REPLY,
|
||||
comment_delete_perm = perm.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION if ddoc.owner == handler.user._id else perm.PERM_DELETE_DISCUSSION_REPLY,
|
||||
comment_edit_self_perm = perm.PERM_EDIT_DISCUSSION_REPLY_SELF,
|
||||
comment_delete_self_perm = perm.PERM_DELETE_DISCUSSION_REPLY_SELF,
|
||||
reply_post_perm = perm.PERM_REPLY_DISCUSSION,
|
||||
reply_edit_perm = perm.PERM_NONE if ddoc.owner == handler.user._id else perm.PERM_EDIT_DISCUSSION_REPLY,
|
||||
reply_delete_perm = perm.PERM_NONE if ddoc.owner == handler.user._id else perm.PERM_DELETE_DISCUSSION_REPLY
|
||||
reply_edit_perm = perm.PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION if ddoc.owner == handler.user._id else perm.PERM_EDIT_DISCUSSION_REPLY,
|
||||
reply_delete_perm = perm.PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION if ddoc.owner == handler.user._id else perm.PERM_DELETE_DISCUSSION_REPLY,
|
||||
reply_edit_self_perm = perm.PERM_EDIT_DISCUSSION_REPLY_SELF,
|
||||
reply_delete_self_perm = perm.PERM_DELETE_DISCUSSION_REPLY_SELF
|
||||
) }}
|
||||
{{ paginator.render(page, pcount) }}
|
||||
{% if drcount == 0 %}
|
||||
|
||||
@@ -97,7 +97,6 @@
|
||||
<p>{{ _('Location') }}: {{ _(session.updateGeoip.display) }} ({{ session.updateIp }})</p>
|
||||
<p>{{ _('Operating System') }}: {{ session.updateUa.os }}</p>
|
||||
<p>{{ _('Browser') }}: {{ session.updateUa.name }} {{ session.updateUa.version }}</p>
|
||||
<!-- {{ _('User-Agent') }}: {{ session['update_ua']['str'] }} -->
|
||||
{% if session.isCurrent %}
|
||||
<p class="sessionlist__current-session"><span class="icon icon-check"></span> {{ _('This is the current session') }}</p>
|
||||
{% endif %}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
var _htmlNode = document.documentElement;
|
||||
_htmlNode.className = _htmlNode.className.replace(' nojs', ' hasjs');
|
||||
var UiContext = {{ handler.UIContext|json|safe }};
|
||||
var UserContext = {{ handler._user|json|safe }};
|
||||
var UserContext = {{ currentUser|json|safe }};
|
||||
</script>
|
||||
{% endif %}
|
||||
</head>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
const IGNORE_CHECK = ['en'];
|
||||
const RE_TEXT = /_\(['"]([\s\S])*?['"]\)/gmi;
|
||||
const TEMPLATE_ROOT = path.resolve(__dirname, '..', 'templates');
|
||||
const LOCALE_ROOT = path.resolve(__dirname, '..', 'locales');
|
||||
|
||||
const texts = {};
|
||||
let currentFile = '';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function _(str) {
|
||||
if (!texts[str]) texts[str] = [currentFile];
|
||||
else texts[str].push(currentFile);
|
||||
}
|
||||
|
||||
function scan(folder, relative = '') {
|
||||
const files = fs.readdirSync(folder);
|
||||
for (const file of files) {
|
||||
const p = path.join(folder, file);
|
||||
if (fs.statSync(p).isDirectory()) {
|
||||
scan(p, path.join(relative, file));
|
||||
} else {
|
||||
currentFile = path.join(relative, file);
|
||||
const f = fs.readFileSync(p).toString();
|
||||
f.replace(RE_TEXT, (substr) => {
|
||||
try {
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(substr);
|
||||
} catch (e) {
|
||||
console.error('Cannot parse: ', substr, ' in file ', p);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scan(TEMPLATE_ROOT);
|
||||
|
||||
const result = {};
|
||||
|
||||
const locales = fs.readdirSync(LOCALE_ROOT);
|
||||
for (const locale of locales) {
|
||||
if (!IGNORE_CHECK.includes(locale.split('.')[0])) {
|
||||
const p = path.join(LOCALE_ROOT, locale);
|
||||
const f = fs.readFileSync(p).toString();
|
||||
const l = yaml.safeLoad(f);
|
||||
for (const str in texts) {
|
||||
if (!l[str]) {
|
||||
if (result[str]) result[str].locale.push(locale);
|
||||
else result[str] = { str, source: texts[str], locale: [locale] };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`${Object.keys(result).length} translations missing.`);
|
||||
fs.writeFileSync(path.join(__dirname, '..', '__result.json'), JSON.stringify(result, null, 2));
|
||||
console.log(`result wrote to ${path.resolve(__dirname, '..', '__result.json')}`);
|
||||
+1
-1
@@ -5,7 +5,7 @@ async function install() {
|
||||
if (!global.Hydro) throw new Error('Data missing');
|
||||
if (!fs.existsSync('hydro')) fs.mkdirSync('hydro');
|
||||
const hydro = JSON.parse(zlib.gunzipSync(global.Hydro).toString());
|
||||
fs.writeFileSync('development.js', hydro.app);
|
||||
fs.writeFileSync('app.js', hydro.app);
|
||||
for (const i in hydro.modules) {
|
||||
fs.writeFileSync(`${i}.hydro`, Buffer.from(hydro.modules[i], 'base64'));
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ const page = new NamedPage('home_messages', () => {
|
||||
|
||||
reduxStore = store;
|
||||
|
||||
const sock = new SockJs(`/home/messages-conn?token=${UiContext.csrfToken}`);
|
||||
const sock = new SockJs(`/home/messages-conn?token=${UiContext.token}`);
|
||||
sock.onmessage = (message) => {
|
||||
const msg = JSON.parse(message.data);
|
||||
store.dispatch({
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as recordEnum from 'vj/constant/record';
|
||||
const page = new NamedPage('judge_playground', async () => {
|
||||
const { default: SockJs } = await import('sockjs-client');
|
||||
|
||||
const sock = new SockJs(`/judge/consume-conn?token=${UiContext.csrfToken}`);
|
||||
const sock = new SockJs(`/judge/consume-conn?token=${UiContext.token}`);
|
||||
sock.onopen = () => {
|
||||
const div = $('<div class="section visible">').appendTo('#messages');
|
||||
$('<div class="section__header"><h1 class="section__title">Connection opened.</h1></div>')
|
||||
|
||||
@@ -501,6 +501,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.157.tgz#fdac1c52448861dfde1a2e1515dbc46e54926dc8"
|
||||
integrity sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ==
|
||||
|
||||
"@types/lru-cache@^5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03"
|
||||
integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w==
|
||||
|
||||
"@types/markdown-it@^10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-10.0.1.tgz#94e252ab689c8e9ceb9aff2946e0a458390105eb"
|
||||
@@ -4165,6 +4170,13 @@ lru-cache@^5.1.1:
|
||||
dependencies:
|
||||
yallist "^3.0.2"
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
macos-release@^2.2.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.0.tgz#837b39fc01785c3584f103c5599e0f0c8068b49e"
|
||||
@@ -7136,6 +7148,11 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yaml@^1.9.2:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
|
||||
|
||||
Reference in new issue
Block a user