You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Hydro/packages/ui-default/index.ts

168 lines
6.0 KiB
TypeScript

/* eslint-disable global-require */
import {
ContestModel, Context, Handler, ObjectId, param, PERM, PRIV, ProblemModel, Schema,
SettingModel, SystemModel, SystemSettings, Types, UserModel,
} from 'hydrooj';
import convert from 'schemastery-jsonschema';
import markdown from './backendlib/markdown';
class WikiHelpHandler extends Handler {
noCheckPermView = true;
async get() {
const LANGS = SettingModel.langs;
const languages = {};
for (const key in LANGS) {
if (LANGS[key].hidden) continue;
languages[`${LANGS[key].display}(${key})`] = LANGS[key].compile || LANGS[key].execute;
}
this.response.body = { languages };
this.response.template = 'wiki_help.html';
}
}
class WikiAboutHandler extends Handler {
noCheckPermView = true;
async get() {
let raw = SystemModel.get('ui-default.about') || '';
// TODO template engine
raw = raw.replace(/{{ name }}/g, this.domain.ui?.name || SystemModel.get('server.name')).trim();
const lines = raw.split('\n');
const sections = [];
for (const line of lines) {
if (line.startsWith('# ')) {
const id = line.split(' ')[1];
sections.push({
id,
title: line.split(id)[1].trim(),
content: '',
});
} else sections[sections.length - 1].content += `${line}\n`;
}
this.response.template = 'about.html';
this.response.body = { sections };
}
}
class SetThemeHandler extends Handler {
noCheckPermView = true;
async get({ theme }) {
this.checkPriv(PRIV.PRIV_USER_PROFILE);
await UserModel.setById(this.user._id, { theme });
this.back();
}
}
class LegacyModeHandler extends Handler {
noCheckPermView = true;
@param('legacy', Types.Boolean)
@param('nohint', Types.Boolean)
async get(domainId: string, legacy = false, nohint = false) {
this.session.legacy = legacy;
this.session.nohint = nohint;
this.back();
}
}
class MarkdownHandler extends Handler {
noCheckPermView = true;
async post({ text, inline = false }) {
this.response.body = inline
? markdown.renderInline(text)
: markdown.render(text);
this.response.type = 'text/html';
this.response.status = 200;
}
}
class SystemConfigSchemaHandler extends Handler {
async get() {
const schema = convert(Schema.intersect(SystemSettings) as any, true);
this.response.body = schema;
}
}
class RichMediaHandler extends Handler {
async renderUser(domainId, payload) {
let d = payload.domainId || domainId;
const cur = payload.domainId ? await UserModel.getById(payload.domainId, this.user._id) : this.user;
if (!cur.hasPerm(PERM.PERM_VIEW)) d = domainId;
const udoc = Number.isNaN(+payload.id) ? await UserModel.getByUname(d, payload.id) : await UserModel.getById(d, +payload.id);
return await this.renderHTML('partials/user.html', { udoc });
}
async renderProblem(domainId, payload) {
const cur = payload.domainId ? await UserModel.getById(payload.domainId, this.user._id) : this.user;
let pdoc = cur.hasPerm(PERM.PERM_VIEW | PERM.PERM_VIEW_PROBLEM)
? await ProblemModel.get(payload.domainId || domainId, payload.id) || ProblemModel.default
: ProblemModel.default;
if (pdoc.hidden && !cur.own(pdoc) && !cur.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) pdoc = ProblemModel.default;
return await this.renderHTML('partials/problem.html', { pdoc });
}
async renderContest(domainId, payload) {
const cur = payload.domainId ? await UserModel.getById(payload.domainId, this.user._id) : this.user;
const tdoc = cur.hasPerm(PERM.PERM_VIEW | PERM.PERM_VIEW_CONTEST)
? await ContestModel.get(payload.domainId || domainId, new ObjectId(payload.id))
: null;
if (tdoc) return await this.renderHTML('partials/contest.html', { tdoc });
return '';
}
async renderHomework(domainId, payload) {
const cur = payload.domainId ? await UserModel.getById(payload.domainId, this.user._id) : this.user;
const tdoc = cur.hasPerm(PERM.PERM_VIEW | PERM.PERM_VIEW_HOMEWORK)
? await ContestModel.get(payload.domainId || domainId, new ObjectId(payload.id))
: null;
if (tdoc) return await this.renderHTML('partials/homework.html', { tdoc });
return '';
}
async post({ domainId, items }) {
const res = [];
for (const item of items || []) {
if (item.domainId && item.domainId === domainId) delete item.domainId;
if (item.type === 'user') res.push(this.renderUser(domainId, item).catch(() => ''));
else if (item.type === 'problem') res.push(this.renderProblem(domainId, item).catch(() => ''));
else if (item.type === 'contest') res.push(this.renderContest(domainId, item).catch(() => ''));
else if (item.type === 'homework') res.push(this.renderHomework(domainId, item).catch(() => ''));
else res.push('');
}
this.response.body = await Promise.all(res);
}
}
export function apply(ctx: Context) {
if (process.env.HYDRO_CLI) return;
ctx.Route('wiki_help', '/wiki/help', WikiHelpHandler);
ctx.Route('wiki_about', '/wiki/about', WikiAboutHandler);
ctx.Route('set_theme', '/set_theme/:theme', SetThemeHandler);
ctx.Route('set_legacy', '/legacy', LegacyModeHandler);
ctx.Route('markdown', '/markdown', MarkdownHandler);
ctx.Route('config_schema', '/manage/config/schema.json', SystemConfigSchemaHandler, PRIV.PRIV_EDIT_SYSTEM);
ctx.Route('media', '/media', RichMediaHandler);
ctx.plugin(require('./backendlib/builder'));
ctx.on('handler/after/DiscussionRaw', async (that) => {
if (that.args.render && that.response.type === 'text/markdown') {
that.response.type = 'text/html';
that.response.body = await markdown.render(that.response.body);
}
});
ctx.on('handler/after', async (that) => {
that.UiContext.SWConfig = {
preload: SystemModel.get('ui-default.preload'),
hosts: [
`http://${that.request.host}`,
`https://${that.request.host}`,
SystemModel.get('server.url'),
SystemModel.get('server.cdn'),
],
domains: SystemModel.get('ui-default.domains') || [],
};
});
}