|
|
|
@ -1,24 +1,82 @@
|
|
|
|
|
/* eslint-disable no-return-await */
|
|
|
|
|
/* eslint-disable camelcase */
|
|
|
|
|
const { readdirSync, readFileSync } = require('fs');
|
|
|
|
|
const { join } = require('path');
|
|
|
|
|
const crypto = require('crypto');
|
|
|
|
|
const { tmpdir } = require('os');
|
|
|
|
|
const { ObjectID } = require('mongodb');
|
|
|
|
|
const bus = require('hydrooj/src/service/bus');
|
|
|
|
|
const { PERM } = require('hydrooj/src/model/builtin');
|
|
|
|
|
const markdown = require('./backendlib/markdown');
|
|
|
|
|
import crypto from 'crypto';
|
|
|
|
|
import { readdir, readFile } from 'fs-extra';
|
|
|
|
|
import { join } from 'path';
|
|
|
|
|
import { tmpdir } from 'os';
|
|
|
|
|
import { ObjectID } from 'mongodb';
|
|
|
|
|
import * as bus from 'hydrooj/src/service/bus';
|
|
|
|
|
import { Route, Handler } from 'hydrooj/src/service/server';
|
|
|
|
|
import { PERM } from 'hydrooj/src/model/builtin';
|
|
|
|
|
import markdown from './backendlib/markdown';
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
system, domain, user, setting, problem, contest,
|
|
|
|
|
} = global.Hydro.model;
|
|
|
|
|
const { Route, Handler, UiContextBase } = global.Hydro.service.server;
|
|
|
|
|
|
|
|
|
|
class WikiHelpHandler extends Handler {
|
|
|
|
|
constructor(args) {
|
|
|
|
|
super(args);
|
|
|
|
|
this.noCheckPermView = true;
|
|
|
|
|
interface ConstantArgs {
|
|
|
|
|
lang: string;
|
|
|
|
|
domainId: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cache = {};
|
|
|
|
|
|
|
|
|
|
const basedir = join(tmpdir(), 'hydro', 'public');
|
|
|
|
|
async function constant(args: ConstantArgs) {
|
|
|
|
|
// CompileLangs
|
|
|
|
|
const payload = [`window.LANGS=${JSON.stringify(setting.langs)};`];
|
|
|
|
|
|
|
|
|
|
// Locale
|
|
|
|
|
let { lang } = args;
|
|
|
|
|
if (!global.Hydro.locales[lang]) lang = system.get('server.language');
|
|
|
|
|
payload[0] += `window.LOCALES=${JSON.stringify(global.Hydro.locales[lang])};`;
|
|
|
|
|
|
|
|
|
|
// Extra style
|
|
|
|
|
let [nav_logo_dark, nav_logo_dark_2x] = system.getMany([
|
|
|
|
|
'ui-default.nav_logo_dark', 'ui-default.nav_logo_dark_2x',
|
|
|
|
|
]);
|
|
|
|
|
const ddoc = await domain.get(args.domainId);
|
|
|
|
|
nav_logo_dark = ddoc.ui?.nav_logo_dark || nav_logo_dark;
|
|
|
|
|
nav_logo_dark_2x = ddoc.ui?.nav_logo_dark_2x || nav_logo_dark_2x;
|
|
|
|
|
payload[0] += `\
|
|
|
|
|
const e = document.createElement('style');
|
|
|
|
|
e.innerHTML = \`\
|
|
|
|
|
${nav_logo_dark ? `.nav__logo { background-image: url(${nav_logo_dark}) !important }` : ''}
|
|
|
|
|
${nav_logo_dark_2x ? `\
|
|
|
|
|
@media
|
|
|
|
|
only screen and (-webkit-min-device-pixel-ratio: 1.5),
|
|
|
|
|
only screen and (min-resolution: 1.5dppx),
|
|
|
|
|
only screen and (min-resolution: 144dpi) {
|
|
|
|
|
.nav__logo, .header--mobile__domain {
|
|
|
|
|
background-image: url(${nav_logo_dark_2x}) !important
|
|
|
|
|
}
|
|
|
|
|
}` : ''}\`;
|
|
|
|
|
document.body.appendChild(e);`;
|
|
|
|
|
|
|
|
|
|
const files = await readdir(basedir);
|
|
|
|
|
const pages = files
|
|
|
|
|
.filter((file) => file.endsWith('.page.js'))
|
|
|
|
|
.map((i) => readFile(join(basedir, i), 'utf-8'));
|
|
|
|
|
payload.push(...await Promise.all(pages));
|
|
|
|
|
|
|
|
|
|
const c = crypto.createHash('sha1');
|
|
|
|
|
c.update(JSON.stringify(payload));
|
|
|
|
|
const version = c.digest('hex');
|
|
|
|
|
cache[version] = { version, payload };
|
|
|
|
|
return version;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bus.on('handler/after', async (that) => {
|
|
|
|
|
if (that.response.template) {
|
|
|
|
|
that.UiContext.constantVersion = await constant({
|
|
|
|
|
domainId: that.domainId,
|
|
|
|
|
lang: that.session.viewLang || that.user.viewLang,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
class WikiHelpHandler extends Handler {
|
|
|
|
|
noCheckPermView = true;
|
|
|
|
|
|
|
|
|
|
async get({ domainId }) {
|
|
|
|
|
const LANGS = setting.langs;
|
|
|
|
@ -34,72 +92,15 @@ class WikiHelpHandler extends Handler {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class WikiAboutHandler extends Handler {
|
|
|
|
|
constructor(args) {
|
|
|
|
|
super(args);
|
|
|
|
|
this.noCheckPermView = true;
|
|
|
|
|
}
|
|
|
|
|
noCheckPermView = true;
|
|
|
|
|
|
|
|
|
|
async get() {
|
|
|
|
|
this.response.template = 'about.html';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class UiConstantsHandler extends Handler {
|
|
|
|
|
constructor(args) {
|
|
|
|
|
super(args);
|
|
|
|
|
this.noCheckPermView = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async get() {
|
|
|
|
|
this.response.body = `window.LANGS=${JSON.stringify(setting.langs)}`;
|
|
|
|
|
this.response.type = 'text/javascript';
|
|
|
|
|
this.ctx.set('nolog', '1');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class UiSettingsHandler extends Handler {
|
|
|
|
|
constructor(args) {
|
|
|
|
|
super(args);
|
|
|
|
|
this.noCheckPermView = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async get({ domainId }) {
|
|
|
|
|
const [nav_logo_dark, nav_logo_dark_2x] = system.getMany([
|
|
|
|
|
'ui-default.nav_logo_dark', 'ui-default.nav_logo_dark_2x',
|
|
|
|
|
]);
|
|
|
|
|
const ddoc = await domain.get(domainId);
|
|
|
|
|
this.response.body = await this.renderHTML('extra.css', {
|
|
|
|
|
nav_logo_dark,
|
|
|
|
|
nav_logo_dark_2x,
|
|
|
|
|
...(ddoc.ui || {}),
|
|
|
|
|
});
|
|
|
|
|
this.response.type = 'text/css';
|
|
|
|
|
this.ctx.set('nolog', '1');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class LocaleHandler extends Handler {
|
|
|
|
|
constructor(args) {
|
|
|
|
|
super(args);
|
|
|
|
|
this.noCheckPermView = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async get({ id }) {
|
|
|
|
|
// eslint-disable-next-line prefer-destructuring
|
|
|
|
|
id = id.split('.')[0];
|
|
|
|
|
// TODO use language_default setting
|
|
|
|
|
if (!global.Hydro.locales[id]) id = system.get('server.language');
|
|
|
|
|
this.response.body = `window.LOCALES=${JSON.stringify(global.Hydro.locales[id])}`;
|
|
|
|
|
this.response.type = 'text/javascript';
|
|
|
|
|
this.ctx.set('nolog', '1');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class SetThemeHandler extends Handler {
|
|
|
|
|
constructor(args) {
|
|
|
|
|
super(args);
|
|
|
|
|
this.noCheckPermView = true;
|
|
|
|
|
}
|
|
|
|
|
noCheckPermView = true
|
|
|
|
|
|
|
|
|
|
async get({ theme }) {
|
|
|
|
|
await user.setById(this.user._id, { theme });
|
|
|
|
@ -108,10 +109,7 @@ class SetThemeHandler extends Handler {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MarkdownHandler extends Handler {
|
|
|
|
|
constructor(args) {
|
|
|
|
|
super(args);
|
|
|
|
|
this.noCheckPermView = true;
|
|
|
|
|
}
|
|
|
|
|
noCheckPermView = true;
|
|
|
|
|
|
|
|
|
|
async post({ text, html = false, inline = false }) {
|
|
|
|
|
this.response.body = inline
|
|
|
|
@ -122,6 +120,14 @@ class MarkdownHandler extends Handler {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class UiConstantsHandler extends Handler {
|
|
|
|
|
noCheckPermView = true;
|
|
|
|
|
|
|
|
|
|
async get({ version }) {
|
|
|
|
|
this.response.body = cache[version];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class RichMediaHandler extends Handler {
|
|
|
|
|
async renderUser(domainId, payload) {
|
|
|
|
|
let d = payload.domainId || domainId;
|
|
|
|
@ -162,35 +168,11 @@ class RichMediaHandler extends Handler {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getHash = (i) => {
|
|
|
|
|
const shasum = crypto.createHash('sha1');
|
|
|
|
|
const file = readFileSync(join(tmpdir(), 'hydro', 'public', i));
|
|
|
|
|
shasum.update(file);
|
|
|
|
|
return shasum.digest('hex').substr(0, 10);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getUrl = (files) => files.map((i) => `/${i}?${getHash(i)}`);
|
|
|
|
|
|
|
|
|
|
bus.on('app/started', () => {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
const files = readdirSync(join(tmpdir(), 'hydro', 'public'));
|
|
|
|
|
const pages = files.filter((file) => file.endsWith('.page.js'));
|
|
|
|
|
const themes = files.filter((file) => file.endsWith('.theme.js'));
|
|
|
|
|
UiContextBase.extraPages = getUrl(pages);
|
|
|
|
|
UiContextBase.themes = {};
|
|
|
|
|
for (const theme of themes) {
|
|
|
|
|
UiContextBase.themes[theme] = `/${theme}?${getHash(theme)}`;
|
|
|
|
|
}
|
|
|
|
|
}, 1000);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
global.Hydro.handler.ui = async () => {
|
|
|
|
|
Route('wiki_help', '/wiki/help', WikiHelpHandler);
|
|
|
|
|
Route('wiki_about', '/wiki/about', WikiAboutHandler);
|
|
|
|
|
Route('ui_constants', '/ui-constants.js', UiConstantsHandler);
|
|
|
|
|
Route('locale', '/locale/:id', LocaleHandler);
|
|
|
|
|
Route('set_theme', '/set_theme/:id', SetThemeHandler);
|
|
|
|
|
Route('ui_extracss', '/extra.css', UiSettingsHandler);
|
|
|
|
|
Route('constant', '/constant', UiConstantsHandler);
|
|
|
|
|
Route('markdown', '/markdown', MarkdownHandler);
|
|
|
|
|
Route('media', '/media', RichMediaHandler);
|
|
|
|
|
};
|