mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
bug fixes
This commit is contained in:
10 files changed
+56
-65
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/register",
|
"name": "@hydrooj/register",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -69,21 +69,19 @@ export async function apply(ctx: HydroContext) {
|
|||||||
const parallelism = getConfig('parallelism');
|
const parallelism = getConfig('parallelism');
|
||||||
async function collectInfo() {
|
async function collectInfo() {
|
||||||
const coll = db.collection('status');
|
const coll = db.collection('status');
|
||||||
const compilers = await compilerVersions(langs);
|
const [compilers, size] = await Promise.all([
|
||||||
|
compilerVersions(langs),
|
||||||
|
stackSize(),
|
||||||
|
]);
|
||||||
await coll.updateOne(
|
await coll.updateOne(
|
||||||
{ mid: info.mid, type: 'server' },
|
{ mid: info.mid, type: 'server' },
|
||||||
{ $set: { compilers } },
|
{ $set: { compilers, stackSize: size } },
|
||||||
{ upsert: true },
|
|
||||||
);
|
|
||||||
const size = await stackSize();
|
|
||||||
await coll.updateOne(
|
|
||||||
{ mid: info.mid, type: 'server' },
|
|
||||||
{ $set: { stackSize: size } },
|
|
||||||
{ upsert: true },
|
{ upsert: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ctx.effect(() => {
|
ctx.effect(() => {
|
||||||
const taskConsumer = TaskModel.consume({ type: 'judge' }, handle, true, parallelism);
|
const taskConsumer = TaskModel.consume({ type: 'judge' }, handle, true, parallelism);
|
||||||
|
collectInfo();
|
||||||
const dispose = ctx.on('system/setting', () => {
|
const dispose = ctx.on('system/setting', () => {
|
||||||
taskConsumer.setConcurrency(getConfig('parallelism'));
|
taskConsumer.setConcurrency(getConfig('parallelism'));
|
||||||
collectInfo();
|
collectInfo();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {
|
import {
|
||||||
ContestModel, Context, fs, getAlphabeticId, ObjectId, PERM, Schema, STATUS, Types, UserModel,
|
avatar, ContestModel, Context, fs, getAlphabeticId, ObjectId, PERM, Schema, STATUS, Types, UserModel,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
const file = fs.readFileSync(path.join(__dirname, 'public/assets/board.html'), 'utf8');
|
const file = fs.readFileSync(path.join(__dirname, 'public/assets/board.html'), 'utf8');
|
||||||
@@ -47,8 +47,9 @@ export async function apply(ctx: Context) {
|
|||||||
team_id: `${udoc._id}`,
|
team_id: `${udoc._id}`,
|
||||||
name: udoc.uname,
|
name: udoc.uname,
|
||||||
organization: udoc.school,
|
organization: udoc.school,
|
||||||
members: [],
|
members: udoc.members?.split(',').filter((t) => t) || [],
|
||||||
coach: '',
|
coach: udoc.coach,
|
||||||
|
badge: { url: avatar(udoc.avatar) },
|
||||||
group: [
|
group: [
|
||||||
...(udoc.group || []),
|
...(udoc.group || []),
|
||||||
i.unrank ? 'unofficial' : 'official',
|
i.unrank ? 'unofficial' : 'official',
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
"postinstall": "node -r @hydrooj/register install.ts"
|
"postinstall": "node -r @hydrooj/register install.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xcpcio/board-app": "^0.64.5"
|
"@xcpcio/board-app": "^0.67.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@xcpcio/types": "^0.67.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,10 +102,13 @@ export function Media(md: MarkdownIt) {
|
|||||||
}
|
}
|
||||||
if (service === 'pdf') {
|
if (service === 'pdf') {
|
||||||
if (src.startsWith('file://') || src.startsWith('./')) src += src.includes('?') ? '&noDisposition=1' : '?noDisposition=1';
|
if (src.startsWith('file://') || src.startsWith('./')) src += src.includes('?') ? '&noDisposition=1' : '?noDisposition=1';
|
||||||
|
// A response with has content-disposition header causes the browser to download the file automatically.
|
||||||
|
// As we cannot control response header from external sites, we block embedding external PDFs.
|
||||||
|
else return '<p>Embedding an external PDF is no longer supported.</p>';
|
||||||
return `\
|
return `\
|
||||||
<object classid="clsid:${uuid().toUpperCase()}">
|
<object classid="clsid:${uuid().toUpperCase()}">
|
||||||
<param name="SRC" value="${src}" >
|
<param name="SRC" value="${src}">
|
||||||
<embed width="100%" style="min-height: 100vh;border: none;" fullscreen="yes" src="${src}">
|
<embed type="application/pdf" width="100%" style="min-height:100vh;border:none;" fullscreen="yes" src="${src}#toolbar=0&navpanes=0">
|
||||||
<noembed></noembed>
|
<noembed></noembed>
|
||||||
</embed>
|
</embed>
|
||||||
</object>`;
|
</object>`;
|
||||||
|
|||||||
@@ -125,14 +125,19 @@ export interface Field {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Result<T extends string, R extends Record<T, Field>> = {
|
type Result<T extends string, R extends Record<T, Field>> = {
|
||||||
[K in keyof R]: ('text' | 'password' | 'username' | 'domain') extends R[K]['type'] ? string
|
[K in keyof R]: R[K]['type'] extends ('text' | 'password' | 'username' | 'domain') ? string
|
||||||
: R[K]['type'] extends 'checkbox' ? boolean
|
: R[K]['type'] extends 'checkbox' ? boolean
|
||||||
: R[K]['type'] extends 'userId' ? number
|
: R[K]['type'] extends 'userId' ? number
|
||||||
: R[K]['type'] extends 'user' ? any
|
: R[K]['type'] extends 'user' ? any
|
||||||
: never;
|
: never;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function prompt<T extends string, R extends Record<T, Field>>(title: string, fields: R): Promise<Result<T, R>> {
|
interface PromptOptions {
|
||||||
|
cancelByClickingBack: boolean;
|
||||||
|
cancelByEsc: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function prompt<T extends string, R extends Record<T, Field>>(title: string, fields: R, options?: PromptOptions): Promise<Result<T, R>> {
|
||||||
let valueCache: Result<T, R> = {} as any;
|
let valueCache: Result<T, R> = {} as any;
|
||||||
const defaultValues = Object.fromEntries(Object.entries(fields)
|
const defaultValues = Object.fromEntries(Object.entries(fields)
|
||||||
.map(([name, field]: [T, Field]) => {
|
.map(([name, field]: [T, Field]) => {
|
||||||
@@ -225,6 +230,8 @@ export async function prompt<T extends string, R extends Record<T, Field>>(title
|
|||||||
const res = await new Dialog({
|
const res = await new Dialog({
|
||||||
$body: $(div),
|
$body: $(div),
|
||||||
$action: [buttonCancel, buttonOk].join('\n'),
|
$action: [buttonCancel, buttonOk].join('\n'),
|
||||||
|
cancelByClickingBack: options?.cancelByClickingBack ?? false,
|
||||||
|
cancelByEsc: options?.cancelByEsc ?? false,
|
||||||
onDispatch(action) {
|
onDispatch(action) {
|
||||||
if (action === 'ok') {
|
if (action === 'ok') {
|
||||||
for (const [name, field] of Object.entries(fields) as [string, Field][]) {
|
for (const [name, field] of Object.entries(fields) as [string, Field][]) {
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ import { browserSupportsWebAuthnAutofill, startAuthentication } from '@simpleweb
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import responsiveCutoff from 'vj/breakpoints.json';
|
import responsiveCutoff from 'vj/breakpoints.json';
|
||||||
import DomDialog from 'vj/components/dialog/DomDialog';
|
import DomDialog from 'vj/components/dialog/DomDialog';
|
||||||
import { InfoDialog } from 'vj/components/dialog/index';
|
import { alert } from 'vj/components/dialog/index';
|
||||||
import Notification from 'vj/components/notification';
|
import Notification from 'vj/components/notification';
|
||||||
import { AutoloadPage } from 'vj/misc/Page';
|
import { AutoloadPage } from 'vj/misc/Page';
|
||||||
import {
|
import { i18n, mediaQuery, request } from 'vj/utils';
|
||||||
i18n, mediaQuery, request, tpl,
|
|
||||||
} from 'vj/utils';
|
|
||||||
|
|
||||||
const signinDialogPage = new AutoloadPage('signinDialogPage', null, () => {
|
const signinDialogPage = new AutoloadPage('signinDialogPage', null, () => {
|
||||||
const signInDialog = DomDialog.getOrConstruct($('.dialog--signin'), {
|
const signInDialog = DomDialog.getOrConstruct($('.dialog--signin'), {
|
||||||
@@ -18,7 +16,7 @@ const signinDialogPage = new AutoloadPage('signinDialogPage', null, () => {
|
|||||||
let authnInitialized = false;
|
let authnInitialized = false;
|
||||||
|
|
||||||
async function initPasskey() {
|
async function initPasskey() {
|
||||||
if (authnInitialized) return;
|
if (authnInitialized || !window.isSecureContext) return;
|
||||||
authnInitialized = true;
|
authnInitialized = true;
|
||||||
const support = await browserSupportsWebAuthnAutofill();
|
const support = await browserSupportsWebAuthnAutofill();
|
||||||
if (!support) return;
|
if (!support) return;
|
||||||
@@ -79,9 +77,7 @@ const signinDialogPage = new AutoloadPage('signinDialogPage', null, () => {
|
|||||||
|
|
||||||
$('[data-lostpass]').on('click', (e) => {
|
$('[data-lostpass]').on('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
new InfoDialog({
|
alert(i18n('Relax and try to remember your password.'));
|
||||||
$body: tpl.typoMsg(i18n('Relax and try to remember your password.')),
|
|
||||||
}).open();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,9 @@ $highlight-button-color = #F6DF45
|
|||||||
.row
|
.row
|
||||||
max-width: 100vw !important;
|
max-width: 100vw !important;
|
||||||
|
|
||||||
|
.data-table td
|
||||||
|
line-height: 1.5
|
||||||
|
|
||||||
.col--rank
|
.col--rank
|
||||||
width: rem(60px)
|
width: rem(60px)
|
||||||
text-align: center
|
text-align: center
|
||||||
|
|||||||
@@ -1,42 +1,22 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import React from 'react';
|
import { prompt } from 'vj/components/dialog';
|
||||||
import DomainSelectAutoComplete from 'vj/components/autocomplete/DomainSelectAutoComplete';
|
|
||||||
import { ActionDialog } from 'vj/components/dialog';
|
|
||||||
import { NamedPage } from 'vj/misc/Page';
|
import { NamedPage } from 'vj/misc/Page';
|
||||||
import { i18n, tpl } from 'vj/utils';
|
import { i18n } from 'vj/utils';
|
||||||
|
|
||||||
export default new NamedPage('home_domain', () => {
|
export default new NamedPage('home_domain', () => {
|
||||||
$(tpl(
|
|
||||||
<div style={{ display: 'none' }} className="dialog__body--join-domain" id="join-domain-dialog">
|
|
||||||
<div className="row"><div className="columns">
|
|
||||||
<h1>{i18n('Join Domain')}</h1>
|
|
||||||
</div></div>
|
|
||||||
<div className="row">
|
|
||||||
<div className="columns">
|
|
||||||
<label>
|
|
||||||
{i18n('Domain ID')}
|
|
||||||
<div className="textbox-container">
|
|
||||||
<input name="domainId" type="text" className="textbox" data-autofocus />
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
)).appendTo(document.body);
|
|
||||||
const selector: any = DomainSelectAutoComplete.getOrConstruct($('#join-domain-dialog [name="domainId"]'));
|
|
||||||
|
|
||||||
$(document).on('click', '[id="join-domain-button"]', async () => {
|
$(document).on('click', '[id="join-domain-button"]', async () => {
|
||||||
const action = await new ActionDialog({
|
const res = await prompt(i18n('Join Domain'), {
|
||||||
$body: $('.dialog__body--join-domain > div'),
|
domain: {
|
||||||
onDispatch(a) {
|
type: 'domain',
|
||||||
if (a === 'ok' && selector.value() === null) {
|
label: i18n('Domain ID'),
|
||||||
selector.focus();
|
required: true,
|
||||||
return false;
|
autofocus: true,
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
}).open();
|
}, {
|
||||||
if (action !== 'ok') return;
|
cancelByClickingBack: true,
|
||||||
window.location.href = `/domain/join?target=${encodeURIComponent(selector.value()._id.toString())}`;
|
cancelByEsc: true,
|
||||||
|
});
|
||||||
|
if (!res?.domain) return;
|
||||||
|
window.location.href = `/domain/join?target=${encodeURIComponent(res.domain.toString())}`;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -15,11 +15,12 @@ import {
|
|||||||
addSpeculationRules, delay, i18n, pjax, request,
|
addSpeculationRules, delay, i18n, pjax, request,
|
||||||
} from 'vj/utils';
|
} from 'vj/utils';
|
||||||
|
|
||||||
|
const keywords = ['category', 'difficulty', 'namespace'];
|
||||||
const list = [];
|
const list = [];
|
||||||
const filterTags = {};
|
const filterTags = {};
|
||||||
const pinned: Record<string, string[]> = { category: [], difficulty: [], namespace: [] };
|
const pinned: Record<string, string[]> = Object.fromEntries(keywords.map((keyword) => [keyword, []]));
|
||||||
const selections = { category: {}, difficulty: {}, namespace: {} };
|
const selections = Object.fromEntries(keywords.map((keyword) => [keyword, {}]));
|
||||||
const selectedTags: Record<string, string[]> = { category: [], difficulty: [], namespace: [] };
|
const selectedTags: Record<string, string[]> = Object.fromEntries(keywords.map((keyword) => [keyword, []]));
|
||||||
|
|
||||||
let selectedPids: string[] = [];
|
let selectedPids: string[] = [];
|
||||||
let clearSelectionHandler: (() => void) | null = null;
|
let clearSelectionHandler: (() => void) | null = null;
|
||||||
@@ -35,7 +36,7 @@ function setDomSelected($dom, selected, icon?) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parserOptions = {
|
const parserOptions = {
|
||||||
keywords: ['category', 'difficulty', 'namespace'],
|
keywords,
|
||||||
offsets: true,
|
offsets: true,
|
||||||
alwaysArray: true,
|
alwaysArray: true,
|
||||||
tokenize: true,
|
tokenize: true,
|
||||||
@@ -46,8 +47,7 @@ function writeSelectionToInput() {
|
|||||||
const parsedCurrentValue = parser.parse(currentValue, parserOptions) as SearchParserResult;
|
const parsedCurrentValue = parser.parse(currentValue, parserOptions) as SearchParserResult;
|
||||||
const q = parser.stringify({
|
const q = parser.stringify({
|
||||||
...parsedCurrentValue,
|
...parsedCurrentValue,
|
||||||
category: selectedTags.category,
|
...keywords.reduce((acc, keyword) => ({ ...acc, [keyword]: selectedTags[keyword] }), {}),
|
||||||
difficulty: selectedTags.difficulty,
|
|
||||||
text: parsedCurrentValue.text,
|
text: parsedCurrentValue.text,
|
||||||
}, parserOptions);
|
}, parserOptions);
|
||||||
$('[name="q"]').val(q);
|
$('[name="q"]').val(q);
|
||||||
|
|||||||
Reference in new issue
Block a user