From 5ca424da0bf7a3bb9d4c0920e80a3c279d0df6ab Mon Sep 17 00:00:00 2001 From: masnn Date: Fri, 12 Jun 2020 23:14:19 +0800 Subject: [PATCH] markdown preview --- build/wiki.js | 1 - hydro/handler/domain.js | 1 + hydro/handler/misc.js | 10 +++ hydro/handler/record.js | 12 ++- hydro/handler/training.js | 12 ++- hydro/handler/wiki.js | 2 +- hydro/lib/nav.js | 24 +++--- hydro/loader.js | 22 ++--- hydro/service/server.js | 111 +++++++++++++------------- module/module-pastebin/handler.js | 9 ++- package.json | 2 +- templates/partials/nav.html | 6 +- templates/partials/problem_default.md | 6 +- templates/record_main.html | 6 +- ui/components/cmeditor/vjcmeditor.js | 3 +- 15 files changed, 123 insertions(+), 104 deletions(-) diff --git a/build/wiki.js b/build/wiki.js index b709210b7..9154128e3 100644 --- a/build/wiki.js +++ b/build/wiki.js @@ -20,7 +20,6 @@ module.exports = () => { } else { content.content = `${content.content}${line}\n`; } - res[pagename].push(content); } } return res; diff --git a/hydro/handler/domain.js b/hydro/handler/domain.js index eaae0840b..158a2a81f 100644 --- a/hydro/handler/domain.js +++ b/hydro/handler/domain.js @@ -48,6 +48,7 @@ class DomainCreateHandler extends Handler { async post({ id, name }) { await domain.add(id, this.user._id, name); this.response.body = { domainId: id }; + this.response.redirect = this.url('homepage', { domainId: id }); } } diff --git a/hydro/handler/misc.js b/hydro/handler/misc.js index 6d383621f..659188d04 100644 --- a/hydro/handler/misc.js +++ b/hydro/handler/misc.js @@ -2,6 +2,7 @@ const { PERM_JUDGE, PERM_LOGGEDIN } = require('../permission'); const file = require('../model/file'); const user = require('../model/user'); +const markdown = require('../lib/markdown'); const db = require('../service/db'); const { Route, Handler } = require('../service/server'); @@ -54,12 +55,21 @@ class SwitchLanguageHandler extends Handler { } } +class MarkdownHandler extends Handler { + async post({ text, safe = true }) { + this.response.body = safe + ? markdown.safe.render(text) + : markdown.unsafe.render(text); + } +} + async function apply() { Route('file_download', '/fs/:id/:secret', FileDownloadHandler); Route('file_download_with_name', '/fs/:id/:name/:secret', FileDownloadHandler); Route('status', '/status', StatusHandler); Route('status_update', '/status/update', StatusUpdateHandler); Route('switch_language', '/language/:lang', SwitchLanguageHandler); + Route('markdown', '/markdown', MarkdownHandler); } apply.updateStatus = function updateStatus(args) { diff --git a/hydro/handler/record.js b/hydro/handler/record.js index c6e4f6a5f..3b25680ce 100644 --- a/hydro/handler/record.js +++ b/hydro/handler/record.js @@ -14,7 +14,7 @@ const RecordHandler = contest.ContestHandlerMixin(Handler); class RecordListHandler extends RecordHandler { async get({ - domainId, page = 1, tid, uidOrName, + domainId, page = 1, pid, tid, uidOrName, }) { this.response.template = 'record_main.html'; const q = { tid }; @@ -24,6 +24,7 @@ class RecordListHandler extends RecordHandler { { _id: parseInt(uidOrName) }, ]; } + if (pid) q.pid = pid; const rdocs = await record.getMany(domainId, q, { _id: -1 }, page, await system.get('RECORD_PER_PAGE')); const [udict, pdict] = await Promise.all([ user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)), @@ -34,7 +35,14 @@ class RecordListHandler extends RecordHandler { ['record_main', null], ]; this.response.body = { - path, page, rdocs, pdict, udict, + path, + page, + rdocs, + pdict, + udict, + fliterPid: pid, + fliterTid: tid, + fliterUidOrName: uidOrName, }; } } diff --git a/hydro/handler/training.js b/hydro/handler/training.js index d9da22c86..5ad2b0645 100644 --- a/hydro/handler/training.js +++ b/hydro/handler/training.js @@ -28,12 +28,16 @@ async function _parseDagJson(domainId, dag) { for (const nid of node.requireNids) { assert(ids.has(nid), `required nid ${nid} not found`); } + const tasks = []; for (const i in node.pids) { - // eslint-disable-next-line no-await-in-loop - const pdoc = await problem.get(domainId, node.pids[i]); // FIXME no-await-in-loop - assert(pdoc, `Problem not found: ${node.pids[i]}`); - node.pids[i] = pdoc.docId; + tasks.push(problem.get(domainId, node.pids[i]).then((pdoc) => { + if (!pdoc) throw new ProblemNotFoundError(domainId, node.pids[i]); + node.pids[i] = pdoc.docId; + })); } + // FIXME no-await-in-loop + // eslint-disable-next-line no-await-in-loop + await Promise.all(tasks); const newNode = { _id: parseInt(node._id), title: node.title, diff --git a/hydro/handler/wiki.js b/hydro/handler/wiki.js index ab865cce9..dabb5dd0d 100644 --- a/hydro/handler/wiki.js +++ b/hydro/handler/wiki.js @@ -2,7 +2,7 @@ const { Route, Handler } = require('../service/server'); const { NotFoundError } = require('../error'); class WikiHandler extends Handler { - async get({ name: page }) { + async get({ page }) { if (!global.Hydro.wiki[page]) throw new NotFoundError(page); const contents = global.Hydro.wiki[page]; const path = [ diff --git a/hydro/lib/nav.js b/hydro/lib/nav.js index 6221c745d..cbda158ad 100644 --- a/hydro/lib/nav.js +++ b/hydro/lib/nav.js @@ -3,26 +3,26 @@ const permission = require('../permission'); global.Hydro.ui.nav = []; const trueChecker = () => true; -const Item = (path, name, prefix, perm, checker) => { +const Item = (name, args, prefix, perm, checker) => { if (perm && checker) { checker = ((_chk) => (handler) => _chk(handler) && handler.user.hasPerm(perm))(checker); } else if (perm) { checker = (handler) => handler.user.hasPerm(perm); } else if (!checker) checker = trueChecker; global.Hydro.ui.nav.push({ - path, name, prefix, checker, + name, args: args || {}, prefix, checker, }); }; -Item('/', 'homepage', 'homepage'); -Item('/p', 'problem_main', 'problem', permission.PERM_VIEW_PROBLEM); -Item('/t', 'training_main', 'training', permission.PERM_VIEW_TRAINING); -Item('/homework', 'homework_main', 'homework', permission.PERM_VIEW_HOMEWORK); -Item('/discuss', 'discussion_main', 'discussion', permission.PERM_VIEW_DISCUSSION); -Item('/c', 'contest_main', 'contest', permission.PERM_VIEW_CONTEST); -Item('/record', 'record_main', 'record'); -Item('/ranking', 'ranking', 'ranking'); -Item('/domain/dashboard', 'domain_dashboard', 'domain', permission.PERM_MANAGE); -Item('/manage/dashboard', 'manage', 'manage', null, (handler) => handler.user.priv === 1); +Item('homepage', null, 'homepage'); +Item('problem_main', null, 'problem', permission.PERM_VIEW_PROBLEM); +Item('training_main', null, 'training', permission.PERM_VIEW_TRAINING); +Item('homework_main', null, 'homework', permission.PERM_VIEW_HOMEWORK); +Item('discussion_main', null, 'discussion', permission.PERM_VIEW_DISCUSSION); +Item('contest_main', null, 'contest', permission.PERM_VIEW_CONTEST); +Item('record_main', null, 'record'); +Item('ranking', null, 'ranking'); +Item('domain_dashboard', null, 'domain', permission.PERM_MANAGE); +Item('manage_dashboard', null, 'manage', null, (handler) => handler.user.priv === 1); global.Hydro.lib.nav = module.exports = Item; diff --git a/hydro/loader.js b/hydro/loader.js index 9555e742b..10521e3be 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -16,14 +16,6 @@ function ensureDir(dir) { } } -function superRequire(p) { - // eslint-disable-next-line camelcase - if (typeof __non_webpack_require__ === 'function') { - // eslint-disable-next-line no-undef - return __non_webpack_require__(p); - } return require(p); -} - let pending = []; const active = []; const fail = []; @@ -35,7 +27,7 @@ async function handler() { try { console.log(`Handler init: ${i}`); console.time(`Handler init: ${i}`); - superRequire(p); + eval('require')(p); console.timeEnd(`Handler init: ${i}`); } catch (e) { fail.push(i); @@ -50,7 +42,7 @@ async function locale() { const p = `${os.tmpdir()}/hydro/tmp/${i}/locale.json`; if (fs.existsSync(p) && !fail.includes(i)) { try { - global.Hydro.lib.i18n(superRequire(p)); + global.Hydro.lib.i18n(eval('require')(p)); console.log(`Locale init: ${i}`); } catch (e) { fail.push(i); @@ -65,7 +57,7 @@ async function template() { const p = `${os.tmpdir()}/hydro/tmp/${i}/template.json`; if (fs.existsSync(p) && !fail.includes(i)) { try { - Object.assign(global.Hydro.template, superRequire(p)); + Object.assign(global.Hydro.template, eval('require')(p)); console.log(`Template init: ${i}`); } catch (e) { fail.push(i); @@ -82,7 +74,7 @@ async function model() { try { console.log(`Model init: ${i}`); console.time(`Model init: ${i}`); - superRequire(p); + eval('require')(p); console.timeEnd(`Model init: ${i}`); } catch (e) { fail.push(i); @@ -99,7 +91,7 @@ async function lib() { try { console.log(`Lib init: ${i}`); console.time(`Lib init: ${i}`); - superRequire(p); + eval('require')(p); console.timeEnd(`Lib init: ${i}`); } catch (e) { fail.push(i); @@ -116,7 +108,7 @@ async function service() { try { console.log(`Service init: ${i}`); console.time(`Service init: ${i}`); - superRequire(p); + eval('require')(p); console.timeEnd(`Service init: ${i}`); } catch (e) { fail.push(i); @@ -133,7 +125,7 @@ async function script() { if (fs.existsSync(p) && !fail.includes(i)) { try { console.time(`Script init: ${i}`); - superRequire(p); + eval('require')(p); console.timeEnd(`Script init: ${i}`); } catch (e) { fail.push(i); diff --git a/hydro/service/server.js b/hydro/service/server.js index f2c42a865..27b284e57 100644 --- a/hydro/service/server.js +++ b/hydro/service/server.js @@ -186,14 +186,16 @@ class Handler { url(name, kwargs = {}) { // eslint-disable-line class-methods-use-this delete kwargs.__keywords; - let res; - const { anchor } = kwargs; - if (kwargs.query) { - const { query } = kwargs; - delete kwargs.query; - res = router.url(name, kwargs, { query }); + if (this.args.domainId !== 'system') { + name += '_with_domainId'; + kwargs.domainId = kwargs.domainId || this.args.domainId; } - res = router.url(name, kwargs); + let res; + const { anchor, query } = kwargs; + delete kwargs.anchor; + delete kwargs.query; + if (query) res = router.url(name, kwargs, { query }); + else res = router.url(name, kwargs); if (anchor) return `${res}#${anchor}`; return res; } @@ -311,60 +313,55 @@ class Handler { } } -function Route(name, route, RouteHandler, permission = null) { - router.all(name, route, async (ctx) => { - const h = new RouteHandler(ctx); +async function handle(ctx, HandlerClass, permission) { + const h = new HandlerClass(ctx); + try { + const method = ctx.method.toLowerCase(); + const args = { + domainId: 'system', ...ctx.params, ...ctx.query, ...ctx.request.body, + }; + + if (h.___prepare) await h.___prepare(args); + if (permission) h.checkPerm(permission); + try { - const method = ctx.method.toLowerCase(); - // TODO(masnn) domainId - const args = { - domainId: 'system', ...ctx.params, ...ctx.query, ...ctx.request.body, - }; - if (h.request.host !== await system.get('server.host')) { - try { - args.domainId = (h.request.url.split('//')[1] || '') - .split(`.${await system.get('server.host')}`)[0] || 'system'; - } catch (e) { - // Ignore + for (const key in validate) { + if (args[key]) { + args[key] = validate[key](args[key], key); } } - - if (h.___prepare) await h.___prepare(args); - if (permission) h.checkPerm(permission); - - try { - for (const key in validate) { - if (args[key]) { - args[key] = validate[key](args[key], key); - } - } - } catch (e) { - if (e instanceof ValidationError) throw e; - throw new ValidationError('Argument check failed'); - } - if (h.__prepare) await h.__prepare(args); - 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); - if (h[`_${method}`]) await h[`_${method}`](args); - if (h[method]) await h[method](args); - - if (method === 'post' && ctx.request.body.operation) { - const operation = `_${ctx.request.body.operation}` - .replace(/_([a-z])/gm, (s) => s[1].toUpperCase()); - if (h[`${method}${operation}`]) await h[`${method}${operation}`](args); - } - - if (h.cleanup) await h.cleanup(args); - if (h._cleanup) await h._cleanup(args); - if (h.__cleanup) await h.__cleanup(args); - if (h.___cleanup) await h.___cleanup(args); } catch (e) { - if (h.onerror) await h.onerror(e); + if (e instanceof ValidationError) throw e; + throw new ValidationError('Argument check failed'); } - }); + h.args = args; + if (h.__prepare) await h.__prepare(args); + 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); + if (h[`_${method}`]) await h[`_${method}`](args); + if (h[method]) await h[method](args); + + if (method === 'post' && ctx.request.body.operation) { + const operation = `_${ctx.request.body.operation}` + .replace(/_([a-z])/gm, (s) => s[1].toUpperCase()); + if (h[`${method}${operation}`]) await h[`${method}${operation}`](args); + } + + if (h.cleanup) await h.cleanup(args); + if (h._cleanup) await h._cleanup(args); + if (h.__cleanup) await h.__cleanup(args); + if (h.___cleanup) await h.___cleanup(args); + } catch (e) { + if (h.onerror) await h.onerror(e); + } +} + +function Route(name, route, RouteHandler, permission = null) { + router.all(name, route, (ctx) => handle(ctx, RouteHandler, permission)); + router.all(`${name}_with_domainId`, `/d/:domainId${route}`, (ctx) => handle(ctx, RouteHandler, permission)); } class ConnectionHandler { @@ -461,7 +458,7 @@ function Validate(key, func) { async function start() { app.use(morgan(':method :url :status :res[content-length] - :response-time ms')); app.use(router.routes()).use(router.allowedMethods()); - Route('*', Handler); + Route('notfound_handler', '*', Handler); if (cluster.worker.id === 1) { const port = await system.get('server.port'); server.listen(port); diff --git a/module/module-pastebin/handler.js b/module/module-pastebin/handler.js index 158bd1060..256fd7bd3 100644 --- a/module/module-pastebin/handler.js +++ b/module/module-pastebin/handler.js @@ -3,6 +3,12 @@ const { PERM_LOGGEDIN } = global.Hydro.permission; const { pastebin } = global.Hydro.model; const { nav } = global.Hydro.lib; +class PasteMainHandler extends Handler { + async get() { + this.response.redirect = this.url('paste_creaate'); + } +} + class PasteShowHandler extends Handler { async get({ docId }) { const doc = await pastebin.get(docId); @@ -42,9 +48,10 @@ class PasteCreateHandler extends Handler { } async function apply() { + Route('paste', '/paste', PasteMainHandler); Route('paste_create', '/paste/create', PasteCreateHandler); Route('paste_show', '/paste/:docId', PasteShowHandler); - nav('/paste/create', 'pastebin', 'pastebin', PERM_LOGGEDIN); + nav('paste', null, 'paste', PERM_LOGGEDIN); } global.Hydro.handler.pastebin = module.exports = apply; diff --git a/package.json b/package.json index fe740953a..b0e5d5631 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.1.1", + "version": "2.2.1", "main": "hydro/loader.js", "repository": "https://github.com/hydro-dev/Hydro.git", "author": "masnn", diff --git a/templates/partials/nav.html b/templates/partials/nav.html index e84b8d03b..2fd846374 100644 --- a/templates/partials/nav.html +++ b/templates/partials/nav.html @@ -1,6 +1,6 @@ -{% macro nav_item(target_page_url, target_page_name, target_page_prefix) %} +{% macro nav_item(target_page_name, target_page_args, target_page_prefix) %} @@ -12,7 +12,7 @@ {% for item in Context.nav %} {% if item.checker(handler) %} - {{ nav_item(item.path, item.name, item.prefix) }} + {{ nav_item(item.name, item.args, item.prefix) }} {% endif %} {% endfor %} diff --git a/templates/partials/problem_default.md b/templates/partials/problem_default.md index a1003be44..a47706a29 100644 --- a/templates/partials/problem_default.md +++ b/templates/partials/problem_default.md @@ -7,7 +7,7 @@ Given two integers x and y, print the sum. # Format ## Input -Two integers x and y, satisfying 0 <= x, y <= 32767. +Two integers x and y, satisfying $0\leq x,y\leq 32767$ . ## Output One integer, the sum of x and y. @@ -15,16 +15,16 @@ One integer, the sum of x and y. # Sample 1 ## Input + ``` 123 500 ``` ## Output + ``` 623 ``` # Limitation 1s, 1024KiB for each test case. - -``` diff --git a/templates/record_main.html b/templates/record_main.html index 264e7e299..e92f26d88 100644 --- a/templates/record_main.html +++ b/templates/record_main.html @@ -25,19 +25,19 @@
diff --git a/ui/components/cmeditor/vjcmeditor.js b/ui/components/cmeditor/vjcmeditor.js index 67935e959..06c1abebb 100644 --- a/ui/components/cmeditor/vjcmeditor.js +++ b/ui/components/cmeditor/vjcmeditor.js @@ -92,8 +92,9 @@ export default class VjCmEditor extends SimpleMDE { } async markdown(text) { + // FIXME preview wont update after first toggle const data = await request.ajax({ - url: '/preview', + url: '/markdown', method: 'post', data: $.param({ text }, true), dataType: 'text',