upgrade dep:

css-loader 0.28->3.5
del 4.1->5.1
eslint 5.18->6.8
eslint-config-airbnb 17.1->18.1
eslint-loader 2.1->4.0
file-loader 2.0->6.0
fs.extra 8.0->9.0
gulp 2.0->3.0
gulp-nunjucks 4.0->5.0
json-loader 0.5->Removed
style-loader 0.23->1.1
uglifyjs-webpack-plugin 1.3->2.2
yargs 13.2->15.3
katex 0.10->0.11
react-monaco-editor 0.35->0.36
timeage-react 1.2->3.0
timeago.js 3.0->4.0
uuid 3.3->7.0
This commit is contained in:
masnn committed 2020-04-15 12:47:55 +08:00
1 parent 9cb68397b9
commit f9468536de
55 files changed
+776 -1032

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
# Hydro
Hydro是一个高效的信息学在线测评系统。
特点: 易于部署,轻量,功能强大。
## 使用方式
使用 `docker-compose`(推荐)
// TODO(WIP)
+10 -1
View File
@@ -6,6 +6,14 @@ class UserFacingError extends Error {
this.params = [];
}
}
class SystemError extends Error {
constructor(type) {
super(type);
this.code = 500;
// this.stack = '';
this.params = [];
}
}
class BadRequestError extends UserFacingError {
constructor(type) {
super(type);
@@ -154,7 +162,8 @@ module.exports = {
ValidationError, ProblemNotFoundError, TrainingNotFoundError,
ContestNotFoundError, RecordNotFoundError, SolutionNotFoundError,
AlreadyVotedError, ContestNotAttendedError, ContestNotLiveError,
ContestScoreboardHiddenError, ContestAlreadyAttendedError
ContestScoreboardHiddenError, ContestAlreadyAttendedError, UserFacingError,
SystemError
};
/*
-1
View File
@@ -52,7 +52,6 @@ async function run() {
await coll_user.createIndex('unameLower', { unique: true });
await coll_user.createIndex('mailLower', { sparse: true });
await coll_role.insertMany(builtin.BUILTIN_ROLES);
await coll_user.insertMany(builtin.BUILTIN_USERS);
await coll_blacklist.createIndex('expireAt', { expireAfterSeconds: 0 });
await coll_token.createIndex([{ uid: 1 }, { tokenType: 1 }, { updateAt: -1 }], { sparse: true });
await coll_token.createIndex('expireAt', { expireAfterSeconds: 0 });
+6 -4
View File
@@ -1,6 +1,7 @@
const
path = require('path'),
{ ObjectID } = require('bson'),
{ MongoError } = require('mongodb'),
Koa = require('koa'),
morgan = require('koa-morgan'),
Body = require('koa-body'),
@@ -14,7 +15,7 @@ const
token = require('../model/token'),
opcount = require('../model/opcount'),
{ UserNotFoundError, BlacklistedError, PermissionError,
NotFoundError } = require('../error');
NotFoundError, UserFacingError } = require('../error');
const options = require('../options');
let http = options.listen.https ? require('https') : require('http');
@@ -60,6 +61,7 @@ class Handler {
url_prefix: '/'
};
this._handler = {};
this.session = {};
}
renderHTML(name, context) {
console.time(name);
@@ -130,7 +132,7 @@ class Handler {
await this.renderBody();
} catch (error) {
if (this.preferJson) this.response.body = { error };
else await this.render('bsod.html', { error });
else await this.render(error instanceof UserFacingError ? 'error.html' : 'bsod.html', { error });
}
await this.putResponse();
await this.saveCookie();
@@ -186,9 +188,9 @@ class Handler {
async onerror(error) {
console.error(error.message, error.params);
console.error(error.stack);
this.response.template = 'error.html';
this.response.template = error instanceof UserFacingError ? 'error.html' : 'bsod.html';
this.response.body = { error };
await this.___cleanup();
await this.___cleanup().catch(() => { });
}
}
function Route(route, handler) {
-2
View File
@@ -22,7 +22,6 @@
<meta name="msapplication-TileImage" content="{{ static_url('mstile-144x144.png') }}">
<meta name="theme-color" content="#56758f">
<link rel="stylesheet" media="all" href="{{ static_url('vendors.css') }}">
<link rel="stylesheet" media="all" href="{{ static_url('vj4.css') }}">
<title>{{ handler.render_title(page_title) }}</title>
<script>
@@ -39,7 +38,6 @@
<script type="text/javascript" src="{{ static_url('shim.js') }}"></script>
<![endif]-->
<script type="text/javascript" src="{{ static_url('manifest.js') }}"></script>
<script type="text/javascript" src="{{ static_url('vendors.js') }}"></script>
<script type="text/javascript" src="{{ static_url('locale/{0}.js'.format(handler.user.lang)) }}"></script>
<script type="text/javascript" src="{{ static_url('vj4.js') }}"></script>
</body>
+5 -1
View File
@@ -89,6 +89,10 @@ module.exports = {
"react/forbid-prop-types": "off",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/anchor-is-valid": "off",
"jsx-a11y/click-events-have-key-events": "off"
"jsx-a11y/click-events-have-key-events": "off",
"max-classes-per-file": "off",
"react/jsx-props-no-spreading": "off",
"react/static-property-placement": "off",
"no-param-reassign": "off"
}
};
+2 -2
View File
@@ -35,8 +35,8 @@ function buildSequence(pages, type) {
}
}
return pages
.filter(p => p && p[`${type}Loading`])
.map(p => ({
.filter((p) => p && p[`${type}Loading`])
.map((p) => ({
page: p,
func: p[`${type}Loading`],
type,
+17 -8
View File
@@ -37,12 +37,21 @@ export default function (env = {}) {
return {
loader: 'babel-loader',
options: {
...require(root('package.json')).babelForProject,
...require(root('package.json')).babelForProject, // eslint-disable-line
cacheDirectory,
},
};
}
function cssLoader() {
return {
loader: 'css-loader',
options: {
importLoaders: 1,
},
};
}
function postcssLoader() {
return {
loader: 'postcss-loader',
@@ -64,9 +73,7 @@ export default function (env = {}) {
function extractCssLoader() {
return {
loader: ExtractCssPlugin.loader,
options: {
publicPath: '/public/path/to/',
},
options: { publicPath: '/' },
};
}
@@ -127,12 +134,12 @@ export default function (env = {}) {
},
{
test: /\.styl$/,
use: [extractCssLoader(), 'css-loader?importLoaders=1', postcssLoader(), 'stylus-loader']
use: [extractCssLoader(), cssLoader(), postcssLoader(), 'stylus-loader']
,
},
{
test: /\.css$/,
use: [extractCssLoader(), 'css-loader?importLoaders=1', postcssLoader()]
use: [extractCssLoader(), cssLoader(), postcssLoader()]
,
},
],
@@ -143,6 +150,8 @@ export default function (env = {}) {
},
},
plugins: [
new webpack.ProgressPlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
@@ -179,7 +188,7 @@ export default function (env = {}) {
default: {
preferPathResolver: 'webpack',
use: [
require('rupture')(),
require('rupture')(), // eslint-disable-line global-require
],
import: [
'~vj/common/common.inc.styl',
@@ -215,7 +224,7 @@ export default function (env = {}) {
new webpack.LoaderOptionsPlugin({
options: {
context: root(),
customInterpolateName: (url, name, options) => beautifyOutputUrl(url),
customInterpolateName: (url) => beautifyOutputUrl(url),
},
}),
+1 -1
View File
@@ -24,7 +24,7 @@ export default function touch(mtime) {
return;
}
touchFile(file)
.catch(err => this.emit('error', err))
.catch((err) => this.emit('error', err))
.then(() => {
this.push(file);
callback();
@@ -15,7 +15,7 @@ export default class StaticManifestPlugin {
.map((asset) => {
const { name } = asset;
// Skip files listed in ignore
if (_.some(this.ignore, ignorePattern => name.indexOf(ignorePattern) > -1)) {
if (_.some(this.ignore, (ignorePattern) => name.indexOf(ignorePattern) > -1)) {
return null;
}
// Skip calculating hash for names like ?xxxx
+1 -1
View File
@@ -21,7 +21,7 @@ export default async function ({ watch, production }) {
log(chalk.green('Finished: %s'), chalk.reset(name));
taskList[uid] = false;
if (Object.values(taskList).filter(b => b).length === 0) {
if (Object.values(taskList).filter((b) => b).length === 0) {
if (watch) {
gulpTasks.watch();
}
-1
View File
@@ -6,7 +6,6 @@ import webpackConfig from './config/webpack';
export default function ({ watch, production }) {
const compiler = webpack(webpackConfig({ watch, production }));
compiler.apply(new webpack.ProgressPlugin());
function compilerCallback(err, stats) {
if (err) {
+1 -1
View File
@@ -3,7 +3,7 @@ import _ from 'lodash';
export default function mapWebpackUrlPrefix(mapList) {
const rules = mapList.map((mappingRule) => {
const regex = mappingRule.prefix.split('/').map(s => _.escapeRegExp(s)).join('[\\/\\\\]');
const regex = mappingRule.prefix.split('/').map((s) => _.escapeRegExp(s)).join('[\\/\\\\]');
return {
from: new RegExp(`^${regex}`, mappingRule.flag),
to: mappingRule.replace,
+3 -3
View File
@@ -57,7 +57,7 @@ export default class DOMAttachedObject {
.find(this.DOMAttachSelector)
.addBack(this.DOMAttachSelector)
.get()
.forEach(element => this.getOrConstruct($(element), ...args));
.forEach((element) => this.getOrConstruct($(element), ...args));
if (process.env.NODE_ENV !== 'production') {
console.timeEnd(`DOMAttachedObject[${this.DOMAttachKey}]: attachAll`);
}
@@ -96,8 +96,8 @@ export default class DOMAttachedObject {
throw new Error(`'DOMAttachSelector' should be specified`);
}
}
$(document).on('vjContentNew', e => this.attachAll(e.target));
$(document).on('vjContentRemove', e => this.detachAll(e.target));
$(document).on('vjContentNew', (e) => this.attachAll(e.target));
$(document).on('vjContentRemove', (e) => this.detachAll(e.target));
if (attach) {
this.attachAll();
}
+8 -8
View File
@@ -27,7 +27,7 @@ export default class Calendar {
<div class="calendar__body-container"></div>
</div>
`);
this.events = events.map(ev => ({
this.events = events.map((ev) => ({
...ev,
beginAt: moment(ev.beginAt),
endAt: moment(ev.endAt),
@@ -258,11 +258,11 @@ export default class Calendar {
// layout banners
const layout = bannersByWeek
.map(banners => _
.map((banners) => _
.sortBy(banners, [
banner => banner.beginAt.valueOf(),
banner => (banner.beginTrunc ? 0 : 1), // truncated events first
banner => -banner.endAt.valueOf(), // long events first
(banner) => banner.beginAt.valueOf(),
(banner) => (banner.beginTrunc ? 0 : 1), // truncated events first
(banner) => -banner.endAt.valueOf(), // long events first
]))
.map((banners) => {
const dayBitmap = _
@@ -274,12 +274,12 @@ export default class Calendar {
// find available space
const vIndexMax = _.max(_
.range(beginDay, endDay + 1)
.map(day => dayBitmap[day].length));
.map((day) => dayBitmap[day].length));
let vIndex = 0;
for (; vIndex < vIndexMax; ++vIndex) {
if (_.every(_
.range(beginDay, endDay + 1)
.map(day => !dayBitmap[day][vIndex]) // eslint-disable-line no-loop-func
.map((day) => !dayBitmap[day][vIndex]) // eslint-disable-line no-loop-func
)) { // eslint-disable-line function-paren-newline
break;
}
@@ -290,7 +290,7 @@ export default class Calendar {
}
});
// merge adjacent cells and arrange banners by vertical index
const vMaxLength = _.max(_.range(0, 7).map(day => dayBitmap[day].length));
const vMaxLength = _.max(_.range(0, 7).map((day) => dayBitmap[day].length));
const weekBanners = _
.fill(new Array(vMaxLength), 1)
.map(() => []);
+2 -2
View File
@@ -7,7 +7,7 @@ function runEmojify($container) {
emojify.run($container[0]);
return;
}
$container.find('[data-emoji-enabled]').get().forEach(element => emojify.run(element));
$container.find('[data-emoji-enabled]').get().forEach((element) => emojify.run(element));
}
const emojifyPage = new AutoloadPage('emojifyPage', () => {
@@ -15,7 +15,7 @@ const emojifyPage = new AutoloadPage('emojifyPage', () => {
img_dir: `${UiContext.cdn_prefix}img/emoji`,
});
runEmojify($('body'));
$(document).on('vjContentNew', e => runEmojify($(e.target)));
$(document).on('vjContentNew', (e) => runEmojify($(e.target)));
});
export default emojifyPage;
@@ -7,7 +7,7 @@ const highlighterPage = new AutoloadPage('highlighterPage', () => {
prismjs.highlightBlocks($container);
}
runHighlight($('body'));
$(document).on('vjContentNew', e => runHighlight($(e.target)));
$(document).on('vjContentNew', (e) => runHighlight($(e.target)));
});
});
+1 -1
View File
@@ -54,7 +54,7 @@ function testElementHotkey(hotkey, $element, attr) {
const hotkeyPage = new AutoloadPage('hotkeyPage', () => {
$(document).on('keydown', (ev) => {
const hotkey = ['alt', 'ctrl', 'shift'].filter(modifyKey => ev[`${modifyKey}Key`]);
const hotkey = ['alt', 'ctrl', 'shift'].filter((modifyKey) => ev[`${modifyKey}Key`]);
if (ev.metaKey && !ev.ctrlKey) {
hotkey.push('ctrl');
}
+3 -3
View File
@@ -4,12 +4,12 @@ import 'katex/dist/katex.min.css';
import './katex.styl';
const katexPage = new AutoloadPage('katexPage', () => {
import('katex/dist/contrib/auto-render.min.js').then((renderKatex) => {
import('katex/dist/contrib/auto-render.min.js').then(({ default: katex }) => {
function runKatex($containers) {
$containers.get().forEach(container => renderKatex(container));
$containers.get().forEach((container) => katex(container));
}
runKatex($('.typo'));
$(document).on('vjContentNew', e => runKatex($(e.target).find('.typo')));
$(document).on('vjContentNew', (e) => runKatex($(e.target).find('.typo')));
});
});
+1 -1
View File
@@ -1,2 +1,2 @@
.katex
font-size: 1em
font-size: 1.2em
+1 -1
View File
@@ -12,7 +12,7 @@ export default class MarkerReactive extends DOMAttachedObject {
}
static initAll() {
$('[data-marker-enabled]').get().forEach(dom => MarkerReactive.initFromDOM($(dom)));
$('[data-marker-enabled]').get().forEach((dom) => MarkerReactive.initFromDOM($(dom)));
}
constructor($target) {
+1 -1
View File
@@ -8,7 +8,7 @@ function expandMenu($menu) {
async function expandAllMenus() {
await delay(200);
$('.menu.collapsed').get().forEach(menu => expandMenu($(menu)));
$('.menu.collapsed').get().forEach((menu) => expandMenu($(menu)));
}
const menuPage = new AutoloadPage('menuPage', () => {
@@ -9,7 +9,7 @@ import 'jquery.easing';
import i18n from 'vj/utils/i18n';
import Message from './MessageComponent';
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
activeId: state.activeId,
item: state.activeId !== null
? state.dialogues[state.activeId]
@@ -5,12 +5,12 @@ import 'jquery-scroll-lock';
import ListItem from './DialogueListItemComponent';
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
activeId: state.activeId,
dialogues: state.dialogues,
});
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
handleClick(id) {
dispatch({
type: 'DIALOGUES_SWITCH_TO',
@@ -28,14 +28,14 @@ export default class MessagePadDialogueListContainer extends React.PureComponent
render() {
const orderedDialogues = _.orderBy(
_.values(this.props.dialogues),
dialogue => (dialogue.isPlaceholder
(dialogue) => (dialogue.isPlaceholder
? Number.POSITIVE_INFINITY
: _.maxBy(dialogue.reply, 'at').at),
'desc',
);
return (
<ol className="messagepad__list" ref="list">
{_.map(orderedDialogues, dialogue => (
{_.map(orderedDialogues, (dialogue) => (
<ListItem
key={dialogue._id}
userName={
@@ -6,7 +6,7 @@ import Icon from 'vj/components/react/IconComponent';
import request from 'vj/utils/request';
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
activeId: state.activeId,
isPosting: state.activeId !== null
? state.isPosting[state.activeId]
@@ -16,7 +16,7 @@ const mapStateToProps = state => ({
: '',
});
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
handleChange(id, value) {
if (id === null) {
return;
@@ -119,8 +119,8 @@ export default class MessagePadInputContainer extends React.PureComponent {
ref="input"
disabled={this.props.isPosting}
value={this.props.inputValue}
onKeyDown={ev => this.handleKeyDown(ev)}
onChange={ev => this.props.handleChange(this.props.activeId, ev.target.value)}
onKeyDown={(ev) => this.handleKeyDown(ev)}
onChange={(ev) => this.props.handleChange(this.props.activeId, ev.target.value)}
/>
</div>
<button
+1 -1
View File
@@ -9,7 +9,7 @@ import MessagePadDialogueList from './MessagePadDialogueListContainer';
import MessagePadDialogueContent from './MessagePadDialogueContentContainer';
import MessagePadInput from './MessagePadInputContainer';
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
loadDialogues() {
dispatch({
type: 'DIALOGUES_LOAD_DIALOGUES',
@@ -6,12 +6,12 @@ export default function reducer(state = {}, action) {
const { messages, udict } = action.payload;
return _.keyBy(
_.filter(
_.map(messages, m => ({
_.map(messages, (m) => ({
sendee_udoc: udict[m.sendee_uid],
sender_udoc: udict[m.sender_uid],
...m,
})),
m => m.sendee_udoc && m.sender_udoc,
(m) => m.sendee_udoc && m.sender_udoc,
),
'_id',
);
+1 -1
View File
@@ -4,7 +4,7 @@ export default function reducer(state = {}, action) {
switch (action.type) {
case 'DIALOGUES_LOAD_DIALOGUES_FULFILLED': {
const dialogues = action.payload.messages;
return _.fromPairs(_.map(dialogues, d => [d._id, '']));
return _.fromPairs(_.map(dialogues, (d) => [d._id, '']));
}
case 'DIALOGUES_CREATE': {
const { id } = action.payload;
@@ -5,7 +5,7 @@ export default function reducer(state = {}, action) {
switch (action.type) {
case 'DIALOGUES_LOAD_DIALOGUES_FULFILLED': {
const dialogues = action.payload.messages;
return _.fromPairs(_.map(dialogues, d => [d._id, false]));
return _.fromPairs(_.map(dialogues, (d) => [d._id, false]));
}
case 'DIALOGUES_CREATE': {
const { id } = action.payload;
@@ -4,17 +4,17 @@ import MonacoEditor from 'react-monaco-editor';
import * as languageEnum from 'vj/constant/language';
const getOptions = lang => ({
const getOptions = (lang) => ({
lineNumbers: true,
mode: languageEnum.LANG_MONACO_MODES[lang],
});
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
code: state.editor.code,
lang: state.editor.lang,
});
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
handleUpdateCode: (code) => {
dispatch({
type: 'SCRATCHPAD_EDITOR_UPDATE_CODE',
@@ -33,7 +33,7 @@ export default class ScratchpadEditorContainer extends React.PureComponent {
value={this.props.code}
options={getOptions(this.props.lang)}
ref="editor"
onChange={code => this.props.handleUpdateCode(code)}
onChange={(code) => this.props.handleUpdateCode(code)}
/>
);
}
@@ -10,13 +10,13 @@ import Panel from './PanelComponent';
import PanelButton from './PanelButtonComponent';
import ScratchpadPretestTabPane from './ScratchpadPretestTabPaneContainer';
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
current: state.pretest.current,
tabs: state.pretest.tabs,
meta: state.pretest.meta,
});
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
handleClickAdd() {
dispatch({
type: 'SCRATCHPAD_PRETEST_ADD_DATA',
@@ -60,7 +60,7 @@ export default class ScratchpadPretestContainer extends React.PureComponent {
<Tabs
className="scratchpad__panel-tab flex-col flex-fill"
activeKey={this.props.current}
onChange={tabId => this.props.handleSwitchData(tabId)}
onChange={(tabId) => this.props.handleSwitchData(tabId)}
animation="slide-horizontal"
renderTabBar={() => (
<ScrollableInkTabBar
@@ -89,7 +89,7 @@ export default class ScratchpadPretestContainer extends React.PureComponent {
)}
renderTabContent={() => <TabContent />}
>
{this.props.tabs.map(tabId => (
{this.props.tabs.map((tabId) => (
<TabPane tab={this.props.meta[tabId].title} key={tabId}>
<ScratchpadPretestTabPane id={tabId} />
</TabPane>
@@ -4,11 +4,11 @@ import { connect } from 'react-redux';
import i18n from 'vj/utils/i18n';
import DataInput from './DataInputComponent';
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
data: state.pretest.data,
});
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
handleDataChange(id, type, value) {
dispatch({
type: 'SCRATCHPAD_PRETEST_DATA_CHANGE',
@@ -36,12 +36,12 @@ export default class ScratchpadPretestTabPaneContainer extends React.PureCompone
<DataInput
title={i18n('Sample Input')}
value={this.props.input}
onChange={v => this.props.handleDataChange(this.props.id, 'input', v)}
onChange={(v) => this.props.handleDataChange(this.props.id, 'input', v)}
/>
<DataInput
title={i18n('Sample Output')}
value={this.props.output}
onChange={v => this.props.handleDataChange(this.props.id, 'output', v)}
onChange={(v) => this.props.handleDataChange(this.props.id, 'output', v)}
/>
</div>
);
@@ -11,7 +11,7 @@ import Panel from './PanelComponent';
import PanelButton from './PanelButtonComponent';
import ScratchpadRecordsTable from './ScratchpadRecordsTableContainer';
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
loadSubmissions() {
dispatch({
type: 'SCRATCHPAD_RECORDS_LOAD_SUBMISSIONS',
@@ -11,9 +11,9 @@ import { parse as parseMongoId } from 'vj/utils/mongoId';
import i18n from 'vj/utils/i18n';
import * as recordEnum from 'vj/constant/record';
const shouldShowDetail = data => recordEnum.STATUS_SCRATCHPAD_SHOW_DETAIL_FLAGS[data.status];
const shouldShowDetail = (data) => recordEnum.STATUS_SCRATCHPAD_SHOW_DETAIL_FLAGS[data.status];
const isPretest = data => data.type === recordEnum.TYPE_PRETEST;
const isPretest = (data) => data.type === recordEnum.TYPE_PRETEST;
const getRecordDetail = (data) => {
if (!shouldShowDetail(data)) {
@@ -41,7 +41,7 @@ const getRecordDetail = (data) => {
});
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
items: state.records.items,
});
@@ -64,14 +64,13 @@ export default class ScratchpadRecordsRowContainer extends React.PureComponent {
const { data } = this.props;
const submitAt = parseMongoId(data._id).timestamp * 1000;
return (
<tr onClick={ev => this.handleRowClick(ev, data._id)}>
<tr onClick={(ev) => this.handleRowClick(ev, data._id)}>
<td className={`col--detail record-status--border ${recordEnum.STATUS_CODES[data.status]}`}>
<span className={`icon record-status--icon ${recordEnum.STATUS_CODES[data.status]}`}></span>
<span className="icol icol--pretest">
{isPretest(data)
? <span className={`flag record-status--background ${recordEnum.STATUS_CODES[data.status]}`}>{i18n('Pretest')}</span>
: ''
}
: ''}
</span>
{getRecordDetail(data)}
</td>
@@ -3,7 +3,7 @@ import classNames from 'classnames';
import { connect } from 'react-redux';
import ScratchpadRecordsRow from './ScratchpadRecordsRowContainer';
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
rows: state.records.rows,
isLoading: state.ui.records.isLoading,
});
@@ -23,7 +23,7 @@ export default class ScratchpadRecordsTableContainer extends React.PureComponent
<col className="col--at" />
</colgroup>
<tbody>
{this.props.rows.map(rowId => (
{this.props.rows.map((rowId) => (
<ScratchpadRecordsRow key={rowId} id={rowId} />
))}
</tbody>
@@ -1,3 +1,4 @@
/* eslint-disable react/static-property-placement */
import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
@@ -13,14 +14,14 @@ import Toolbar, {
ToolbarSplitComponent as ToolbarSplit,
} from './ToolbarComponent';
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
pretestVisible: state.ui.pretest.visible,
recordsVisible: state.ui.records.visible,
isPosting: state.ui.isPosting,
editorLang: state.editor.lang,
});
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
togglePanel(uiElement) {
dispatch({
type: 'SCRATCHPAD_UI_TOGGLE_VISIBILITY',
@@ -37,8 +38,8 @@ const mapDispatchToProps = dispatch => ({
const state = context.store.getState();
const { pretest } = state;
const testCases = pretest.tabs;
const inputs = testCases.map(tabId => pretest.data[tabId].input);
const outputs = testCases.map(tabId => pretest.data[tabId].output);
const inputs = testCases.map((tabId) => pretest.data[tabId].input);
const outputs = testCases.map((tabId) => pretest.data[tabId].output);
const req = request.post(Context.postPretestUrl, {
lang: state.editor.lang,
code: state.editor.code,
@@ -103,7 +104,7 @@ export default class ScratchpadToolbarContainer extends React.PureComponent {
className="select"
disabled={this.props.isPosting}
value={this.props.editorLang}
onChange={ev => this.props.setEditorLanguage(ev.target.value)}
onChange={(ev) => this.props.setEditorLanguage(ev.target.value)}
>
{_.map(languageEnum.LANG_TEXTS, (val, key) => (
<option value={key} key={key}>{val}</option>
+6 -6
View File
@@ -23,7 +23,7 @@ panes: [{
function buildNestedPane([a, ...panes]) {
const elements = [
a,
...panes.filter(p => p.props.visible),
...panes.filter((p) => p.props.visible),
];
if (elements.length === 1) {
return a;
@@ -43,11 +43,11 @@ function buildNestedPane([a, ...panes]) {
));
}
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
ui: state.ui,
});
const mapDispatchToProps = dispatch => ({
const mapDispatchToProps = (dispatch) => ({
changeUiSize: _.debounce((uiElement, size) => {
dispatch({
type: 'SCRATCHPAD_UI_CHANGE_SIZE',
@@ -73,7 +73,7 @@ export default class ScratchpadContainer extends React.PureComponent {
minSize={250}
split="vertical"
primary="second"
onChange={size => this.handleChangeSize('main', size)}
onChange={(size) => this.handleChangeSize('main', size)}
>
<Dom className="scratchpad__problem" childDom={$('.problem-content').get(0)} />
{buildNestedPane([
@@ -83,12 +83,12 @@ export default class ScratchpadContainer extends React.PureComponent {
</SplitPaneFillOverlay>,
{
props: this.props.ui.pretest,
onChange: size => this.handleChangeSize('pretest', size),
onChange: (size) => this.handleChangeSize('pretest', size),
element: <ScratchpadPretest key="pretest" />,
},
{
props: this.props.ui.records,
onChange: size => this.handleChangeSize('records', size),
onChange: (size) => this.handleChangeSize('records', size),
element: <ScratchpadRecords key="records" />,
},
])}
+1 -1
View File
@@ -1,4 +1,4 @@
import uuid from 'uuid/v4';
import { v4 as uuid } from 'uuid';
import _ from 'lodash';
const initialId = uuid();
+5 -6
View File
@@ -1,11 +1,10 @@
import timeagoFactory from 'timeago.js';
import * as timeago from 'timeago.js';
import { AutoloadPage } from 'vj/misc/PageLoader';
import i18n from 'vj/utils/i18n';
const timeago = timeagoFactory();
timeago.setLocale(i18n('timeago_locale'));
timeago.register(i18n('timeago_locale'));
function runRelativeTime($container) {
$container.find('span.time.relative[data-timestamp]').get().forEach((element) => {
@@ -27,14 +26,14 @@ function cancelRelativeTime($container) {
return;
}
$element.removeAttr('data-has-timeago');
timeagoFactory.cancel(element);
timeago.cancel(element);
});
}
const relativeTimePage = new AutoloadPage('relativeTimePage', () => {
runRelativeTime($('body'));
$(document).on('vjContentNew', e => runRelativeTime($(e.target)));
$(document).on('vjContentRemove', e => cancelRelativeTime($(e.target)));
$(document).on('vjContentNew', (e) => runRelativeTime($(e.target)));
$(document).on('vjContentRemove', (e) => cancelRelativeTime($(e.target)));
});
export default relativeTimePage;
+1 -1
View File
@@ -18,7 +18,7 @@ function applyRotator(element) {
}
const votePage = new AutoloadPage('votePage', () => {
$('.vote-number.rotator--enabled').get().forEach(element => applyRotator(element));
$('.vote-number.rotator--enabled').get().forEach((element) => applyRotator(element));
$(document).on('click', '.vote-button', (ev) => {
const $button = $(ev.currentTarget);
const $container = $button.closest('.vote');
+3 -3
View File
@@ -1,6 +1,6 @@
// load all page stylesheets
const pageStyleReq = require.context('../', true, /\.page\.styl$/i);
pageStyleReq.keys().map(key => pageStyleReq(key).default);
pageStyleReq.keys().map((key) => pageStyleReq(key).default);
export class Page {
constructor(name, autoload, afterLoading, beforeLoading) {
@@ -60,12 +60,12 @@ export class PageLoader {
}
getAutoloadPages() {
const pages = this.pageInstances.filter(page => page && page.autoload);
const pages = this.pageInstances.filter((page) => page && page.autoload);
return pages;
}
getNamedPage(pageName) {
const pages = this.pageInstances.filter(page => page && page.isNameMatch(pageName));
const pages = this.pageInstances.filter((page) => page && page.isNameMatch(pageName));
if (pages.length > 0) {
return pages[0];
}
+18 -19
View File
@@ -27,41 +27,40 @@
"babel-plugin-prismjs": "^1.0.2",
"chalk": "^2.4.2",
"copy-webpack-plugin": "^4.6.0",
"css-loader": "^0.28.11",
"del": "^4.1.1",
"eslint": "^5.16.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-import-resolver-webpack": "^0.11.1",
"eslint-loader": "^2.1.2",
"css-loader": "^3.5.2",
"del": "^5.1.0",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.1.0",
"eslint-import-resolver-webpack": "^0.12.1",
"eslint-loader": "^4.0.0",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.13.0",
"file-loader": "^2.0.0",
"file-loader": "^6.0.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"fs-extra": "^8.0.1",
"fs-extra": "^9.0.0",
"gulp": "^4.0.2",
"gulp-cli": "^2.2.0",
"gulp-iconfont": "^10.0.2",
"gulp-if": "^2.0.2",
"gulp-nunjucks": "^4.0.0",
"gulp-if": "^3.0.0",
"gulp-nunjucks": "^5.0.1",
"gulp-plumber": "^1.2.1",
"gulp-svgmin": "^2.2.0",
"imports-loader": "^0.8.0",
"js-yaml": "^3.13.1",
"json-loader": "^0.5.7",
"monaco-editor-webpack-plugin": "^1.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"rupture": "^0.7.1",
"style-loader": "^0.23.1",
"style-loader": "^1.1.3",
"stylus": "^0.54.5",
"stylus-loader": "^3.0.2",
"through2": "^3.0.1",
"uglifyjs-webpack-plugin": "^1.3.0",
"uglifyjs-webpack-plugin": "^2.2.0",
"vinyl-buffer": "^1.0.1",
"webpack": "^4.42.1",
"yargs": "^13.2.4"
"yargs": "^15.3.1"
},
"dependencies": {
"@babel/runtime-corejs2": "^7.0.0",
@@ -76,7 +75,7 @@
"jquery-scroll-lock": "^3.1.3",
"jquery.easing": "^1.4.1",
"jquery.transit": "^0.9.12",
"katex": "^0.10.2",
"katex": "^0.11.1",
"lodash": "^4.17.13",
"mini-css-extract-plugin": "^0.9.0",
"moment": "^2.24.0",
@@ -90,7 +89,7 @@
"react": "^15.6.1",
"react-codemirror": "^1.0.0",
"react-dom": "^15.6.1",
"react-monaco-editor": "^0.35.0",
"react-monaco-editor": "^0.36.0",
"react-redux": "^5.0.6",
"react-split-pane": "^0.1.65",
"redux": "^3.7.2",
@@ -102,9 +101,9 @@
"sticky-kit": "^1.1.3",
"tether": "^1.4.6",
"tether-drop": "^1.4.2",
"timeago-react": "^1.2.2",
"timeago.js": "^3.0.2",
"uuid": "^3.3.2",
"timeago-react": "^3.0.0",
"timeago.js": "^4.0.2",
"uuid": "^7.0.3",
"vj-simplemde": "0.0.8"
},
"scripts": {
+1 -1
View File
@@ -29,7 +29,7 @@ const page = new NamedPage('domain_manage_role', () => {
function ensureAndGetSelectedRoles() {
const roles = _.map(
$('.domain-roles tbody [type="checkbox"]:checked'),
ch => $(ch).closest('tr').attr('data-role'),
(ch) => $(ch).closest('tr').attr('data-role'),
);
if (roles.length === 0) {
Notification.error(i18n('Please select at least one role to perform this operation.'));
+2 -2
View File
@@ -73,7 +73,7 @@ const page = new NamedPage('domain_manage_user', () => {
function ensureAndGetSelectedUsers() {
const users = _.map(
$('.domain-users tbody [type="checkbox"]:checked'),
ch => $(ch).closest('tr').attr('data-uid'),
(ch) => $(ch).closest('tr').attr('data-uid'),
);
if (users.length === 0) {
Notification.error(i18n('Please select at least one user to perform this operation.'));
@@ -153,7 +153,7 @@ const page = new NamedPage('domain_manage_user', () => {
$('[name="add_user"]').click(() => handleClickAddUser());
$('[name="remove_selected"]').click(() => handleClickRemoveSelected());
$('[name="set_roles"]').click(() => handleClickSetSelected());
$('.domain-users [name="role"]').change(ev => handleChangeUserRole(ev));
$('.domain-users [name="role"]').change((ev) => handleChangeUserRole(ev));
});
export default page;
+1 -1
View File
@@ -26,7 +26,7 @@ const page = new NamedPage('home_messages', () => {
}
async function mountComponent() {
const SockJs = await import('sockjs-client');
const { default: SockJs } = await import('sockjs-client');
const { default: MessagePadApp } = await import('../components/messagepad');
const { default: MessagePadReducer } = await import('../components/messagepad/reducers');
const {
+1 -1
View File
@@ -6,7 +6,7 @@ import { parse as parseMongoId } from 'vj/utils/mongoId';
const page = new NamedPage('homework_main', () => {
// Homework Calendar
if (Context.docs) {
const events = Context.docs.map(doc => ({
const events = Context.docs.map((doc) => ({
beginAt: doc.begin_at * 1000,
endAt: doc.end_at * 1000,
title: doc.title,
+1 -1
View File
@@ -3,7 +3,7 @@ import { NamedPage } from 'vj/misc/PageLoader';
import * as recordEnum from 'vj/constant/record';
const page = new NamedPage('judge_playground', async () => {
const SockJs = await import('sockjs-client');
const { default: SockJs } = await import('sockjs-client');
const sock = new SockJs('/judge/consume-conn');
+1 -1
View File
@@ -160,7 +160,7 @@ const page = new NamedPage(['problem_detail', 'contest_detail_problem', 'homewor
if (reactLoaded) return;
$('.loader-container').show();
const SockJs = await import('sockjs-client');
const { default: SockJs } = await import('sockjs-client');
const { default: ScratchpadApp } = await import('../components/scratchpad');
const { default: ScratchpadReducer } = await import('../components/scratchpad/reducers');
const {
+4 -4
View File
@@ -20,7 +20,7 @@ function setDomSelected($dom, selected) {
async function updateSelection(sendRequest = true) {
dirtyCategories.forEach(({ type, category, subcategory }) => {
let item = categories[category];
const isSelected = item.select || _.some(item.children, c => c.select);
const isSelected = item.select || _.some(item.children, (c) => c.select);
setDomSelected(item.$tag, isSelected);
if (isSelected) {
selections.push(category);
@@ -43,8 +43,8 @@ async function updateSelection(sendRequest = true) {
if (sendRequest) {
// a list of categories which subcategory is selected
const requestCategoryTags = _.uniq(selections
.filter(s => s.indexOf(',') !== -1)
.map(s => s.split(',')[0]));
.filter((s) => s.indexOf(',') !== -1)
.map((s) => s.split(',')[0]));
// drop the category if its subcategory is selected
const requestTags = _.uniq(_.pullAll(selections, requestCategoryTags));
let url;
@@ -53,7 +53,7 @@ async function updateSelection(sendRequest = true) {
} else {
url = substitute(decodeURIComponent(Context.getProblemUrlWithCategory), {
category: requestTags
.map(tag => tag.split(',').map(encodeURIComponent).join(','))
.map((tag) => tag.split(',').map(encodeURIComponent).join(','))
.join('+'), // build a beautiful URL
});
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { NamedPage } from 'vj/misc/PageLoader';
const page = new NamedPage('record_detail', async () => {
const SockJs = await import('sockjs-client');
const { default: SockJs } = await import('sockjs-client');
const { DiffDOM } = await import('diff-dom');
const sock = new SockJs(Context.socketUrl);
+1 -1
View File
@@ -2,7 +2,7 @@ import { NamedPage } from 'vj/misc/PageLoader';
import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete';
const page = new NamedPage('record_main', async () => {
const SockJs = await import('sockjs-client');
const { default: SockJs } = await import('sockjs-client');
const { DiffDOM } = await import('diff-dom');
const sock = new SockJs(Context.socketUrl);
+1 -1
View File
@@ -1,3 +1,3 @@
export default function (ms) {
return new Promise(resolve => setTimeout(resolve, ms));
return new Promise((resolve) => setTimeout(resolve, ms));
}
+1 -1
View File
@@ -4,7 +4,7 @@ export default function parseQueryString(str) {
.replace(/(^\?)/, '')
.split('&')
.forEach((n) => {
const [key, value] = n.split('=').map(v => decodeURIComponent(v));
const [key, value] = n.split('=').map((v) => decodeURIComponent(v));
obj[key] = value;
});
return obj;
+1 -1
View File
@@ -1,7 +1,7 @@
// Modified from jquery-pjax to support multiple fragments and jQuery 3.0
// https://github.com/defunkt/jquery-pjax/blob/master/jquery.pjax.js
import uuid from 'uuid/v4';
import { v4 as uuid } from 'uuid';
import Notification from 'vj/components/notification';
import NProgress from 'vj/components/nprogress';
+611 -895
View File
File diff suppressed because it is too large. Load diff