mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
core: drop array-style problem content (#882)
This commit is contained in:
1 parent
23dfcbeeae
commit
6ae902d3ca
9 files changed
+120
-180
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
import decodeHTML from 'decode-html';
|
import decodeHTML from 'decode-html';
|
||||||
import xml2js from 'xml2js';
|
import xml2js from 'xml2js';
|
||||||
import {
|
import {
|
||||||
_, AdmZip, BadRequestError, buildContent, ContentNode, Context, FileTooLargeError, fs, Handler,
|
_, AdmZip, BadRequestError, buildContent, Context, FileTooLargeError, fs, Handler,
|
||||||
PERM, ProblemConfigFile, ProblemModel, ProblemType, SettingModel, SolutionModel, SystemModel, ValidationError, yaml,
|
PERM, ProblemConfigFile, ProblemModel, ProblemType, SettingModel, SolutionModel, SystemModel, ValidationError, yaml,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
@@ -19,48 +19,14 @@ class FpsProblemImportHandler extends Handler {
|
|||||||
if (!result?.fps) throw new BadRequestError('Selected file is not a valid FPS problemset.');
|
if (!result?.fps) throw new BadRequestError('Selected file is not a valid FPS problemset.');
|
||||||
for (const p of result.fps.item) {
|
for (const p of result.fps.item) {
|
||||||
const markdown = [p.description?.[0], p.input?.[0], p.output?.[0], p.hint?.[0]].some((i) => i?.includes('[md]'));
|
const markdown = [p.description?.[0], p.input?.[0], p.output?.[0], p.hint?.[0]].some((i) => i?.includes('[md]'));
|
||||||
const content: ContentNode[] = [];
|
const content = buildContent({
|
||||||
const stripBBCode = (input: string) => input.replace(/\[md]/g, '').replace(/\[\/md]/g, '');
|
description: p.description?.[0],
|
||||||
|
input: p.input?.[0],
|
||||||
if (p.description?.[0]) {
|
output: p.output?.[0],
|
||||||
content.push({
|
samples: p.sample_input?.map((input, i) => [input, p.sample_output[i]]),
|
||||||
type: 'Text',
|
hint: p.hint?.[0],
|
||||||
subType: markdown ? 'markdown' : 'html',
|
source: p.source?.join(' '),
|
||||||
sectionTitle: this.translate('Description'),
|
}, 'html').replace(/<math xm<x>lns=/g, '<math xmlns=').replace(/\[\/?md]/g, '');
|
||||||
text: stripBBCode(p.description[0]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (p.input?.[0]) {
|
|
||||||
content.push({
|
|
||||||
type: 'Text',
|
|
||||||
subType: markdown ? 'markdown' : 'html',
|
|
||||||
sectionTitle: this.translate('Input Format'),
|
|
||||||
text: stripBBCode(p.input[0]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (p.output?.[0]) {
|
|
||||||
content.push({
|
|
||||||
type: 'Text',
|
|
||||||
subType: markdown ? 'markdown' : 'html',
|
|
||||||
sectionTitle: this.translate('Output Format'),
|
|
||||||
text: stripBBCode(p.output[0]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (p.sample_input?.length) {
|
|
||||||
content.push(...p.sample_input.map((input, i) => ({
|
|
||||||
type: 'Sample',
|
|
||||||
sectionTitle: this.translate('Sample'),
|
|
||||||
payload: [input, p.sample_output[i]],
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
if (p.hint?.[0]) {
|
|
||||||
content.push({
|
|
||||||
type: 'Text',
|
|
||||||
subType: markdown ? 'markdown' : 'html',
|
|
||||||
sectionTitle: this.translate('Hint'),
|
|
||||||
text: stripBBCode(p.hint[0]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const config: ProblemConfigFile = {
|
const config: ProblemConfigFile = {
|
||||||
time: p.time_limit[0]._ + p.time_limit[0].$.unit,
|
time: p.time_limit[0]._ + p.time_limit[0].$.unit,
|
||||||
memory: p.memory_limit[0]._ + p.memory_limit[0].$.unit,
|
memory: p.memory_limit[0]._ + p.memory_limit[0].$.unit,
|
||||||
@@ -72,17 +38,17 @@ class FpsProblemImportHandler extends Handler {
|
|||||||
}
|
}
|
||||||
const title = decodeHTML(p.title.join(' '));
|
const title = decodeHTML(p.title.join(' '));
|
||||||
const tags = _.filter(p.source, (i: string) => i.trim()).flatMap((i) => i.split(' ')).filter((i) => i);
|
const tags = _.filter(p.source, (i: string) => i.trim()).flatMap((i) => i.split(' ')).filter((i) => i);
|
||||||
const pid = await ProblemModel.add(domainId, null, title, buildContent(content, 'html'), this.user._id, tags);
|
const pid = await ProblemModel.add(domainId, null, title, content, this.user._id, tags);
|
||||||
const tasks: Promise<any>[] = [ProblemModel.addTestdata(domainId, pid, 'config.yaml', Buffer.from(yaml.dump(config)))];
|
const tasks: Promise<any>[] = [ProblemModel.addTestdata(domainId, pid, 'config.yaml', Buffer.from(yaml.dump(config)))];
|
||||||
if (!markdown) tasks.push(ProblemModel.edit(domainId, pid, { html: true }));
|
if (!markdown) tasks.push(ProblemModel.edit(domainId, pid, { html: true }));
|
||||||
const addTestdata = (node: any, id: string, ext: string) => {
|
const addTestdata = (node: any, index: string, ext: string) => {
|
||||||
if (!node && typeof node !== 'string') return; // Ignore file not exist
|
if (!node || !['object', 'string'].includes(typeof node)) return; // Ignore file not exist
|
||||||
let c = node;
|
const id = node.$?.name || `${index}`;
|
||||||
if (node.$?.name) {
|
// `filename` attribute introduced by winterant/OnlineJudge
|
||||||
id = node.$.name;
|
// PLEASE, respect the spec
|
||||||
c = node._ || '';
|
const filename = node.$?.filename || `${id}.${ext}`;
|
||||||
}
|
const c = node._ || node;
|
||||||
tasks.push(ProblemModel.addTestdata(domainId, pid, `${id}.${ext}`, Buffer.from(c)));
|
tasks.push(ProblemModel.addTestdata(domainId, pid, filename, Buffer.from(c)));
|
||||||
};
|
};
|
||||||
if (p.test_output) {
|
if (p.test_output) {
|
||||||
for (let i = 0; i < p.test_input.length; i++) {
|
for (let i = 0; i < p.test_input.length; i++) {
|
||||||
@@ -90,6 +56,7 @@ class FpsProblemImportHandler extends Handler {
|
|||||||
addTestdata(p.test_output[i], `${i + 1}`, 'out');
|
addTestdata(p.test_output[i], `${i + 1}`, 'out');
|
||||||
}
|
}
|
||||||
} else if (p.test_input) {
|
} else if (p.test_input) {
|
||||||
|
// Some version of hustoj exports only test_input section
|
||||||
for (let i = 0; i < p.test_input.length / 2; i++) {
|
for (let i = 0; i < p.test_input.length / 2; i++) {
|
||||||
addTestdata(p.test_input[2 * i], `${i + 1}`, 'in');
|
addTestdata(p.test_input[2 * i], `${i + 1}`, 'in');
|
||||||
addTestdata(p.test_input[2 * i + 1], `${i + 1}`, 'out');
|
addTestdata(p.test_input[2 * i + 1], `${i + 1}`, 'out');
|
||||||
|
|||||||
@@ -221,26 +221,7 @@ export interface ProblemConfig {
|
|||||||
hackable?: boolean;
|
hackable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlainContentNode {
|
export type Content = string | Record<string, string>;
|
||||||
type: 'Plain',
|
|
||||||
subType: 'html' | 'markdown',
|
|
||||||
text: string,
|
|
||||||
}
|
|
||||||
export interface TextContentNode {
|
|
||||||
type: 'Text',
|
|
||||||
subType: 'html' | 'markdown',
|
|
||||||
sectionTitle: string,
|
|
||||||
text: string,
|
|
||||||
}
|
|
||||||
export interface SampleContentNode {
|
|
||||||
type: 'Sample',
|
|
||||||
text: string,
|
|
||||||
sectionTitle: string,
|
|
||||||
payload: [string, string],
|
|
||||||
}
|
|
||||||
// TODO drop contentNode support
|
|
||||||
export type ContentNode = PlainContentNode | TextContentNode | SampleContentNode;
|
|
||||||
export type Content = string | ContentNode[] | Record<string, ContentNode[]>;
|
|
||||||
|
|
||||||
export interface Document {
|
export interface Document {
|
||||||
_id: ObjectId;
|
_id: ObjectId;
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { ContentNode } from '../interface';
|
|
||||||
|
|
||||||
export interface ProblemSource {
|
export interface ProblemSource {
|
||||||
description?: string,
|
description?: string,
|
||||||
input?: string,
|
input?: string,
|
||||||
output?: string,
|
output?: string,
|
||||||
samples?: [string, string][],
|
samples?: [string, string][],
|
||||||
|
samplesRaw?: string,
|
||||||
hint?: string,
|
hint?: string,
|
||||||
source?: string,
|
source?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildContent(source: ProblemSource | ContentNode[], type: 'markdown' | 'html' = 'markdown', translate?: Function) {
|
export function buildContent(source: ProblemSource, type: 'markdown' | 'html' = 'markdown', translate?: Function) {
|
||||||
const _ = translate || ((s: string) => s);
|
const _ = translate || ((s: string) => s);
|
||||||
let cnt = 0;
|
let cnt = 0;
|
||||||
|
// Keep it for backward compatibility, but don't add to typings
|
||||||
|
// required by upgrade 90_91
|
||||||
if (source instanceof Array) {
|
if (source instanceof Array) {
|
||||||
return type === 'html'
|
return type === 'html'
|
||||||
? source.map((node) => [
|
? source.map((node) => [
|
||||||
@@ -54,6 +55,7 @@ export function buildContent(source: ProblemSource | ContentNode[], type: 'markd
|
|||||||
sample[1],
|
sample[1],
|
||||||
'</code></pre>',
|
'</code></pre>',
|
||||||
].join('')),
|
].join('')),
|
||||||
|
...source.samplesRaw ? [source.samplesRaw] : [],
|
||||||
...source.hint ? [`<h2>${_('Hint')}</h2>`, source.hint] : [],
|
...source.hint ? [`<h2>${_('Hint')}</h2>`, source.hint] : [],
|
||||||
...source.source ? [`<h2>${_('Source')}</h2>`, source.source] : [],
|
...source.source ? [`<h2>${_('Source')}</h2>`, source.source] : [],
|
||||||
].join('\n')
|
].join('\n')
|
||||||
@@ -69,6 +71,7 @@ export function buildContent(source: ProblemSource | ContentNode[], type: 'markd
|
|||||||
sample[1],
|
sample[1],
|
||||||
'```',
|
'```',
|
||||||
].join('\n')),
|
].join('\n')),
|
||||||
|
...source.samplesRaw ? [source.samplesRaw] : [],
|
||||||
...source.hint ? [`## ${_('Hint')}`, source.hint] : [],
|
...source.hint ? [`## ${_('Hint')}`, source.hint] : [],
|
||||||
...source.source ? [`## ${_('Source')}`, source.source] : [],
|
...source.source ? [`## ${_('Source')}`, source.source] : [],
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|||||||
@@ -45,12 +45,7 @@ function findOverrideContent(dir: string, base: string) {
|
|||||||
if (!files.length) return null;
|
if (!files.length) return null;
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const lang = file.slice(8, -3);
|
const lang = file.slice(8, -3);
|
||||||
let content: string | any[] = fs.readFileSync(path.join(dir, file), 'utf8');
|
languages[lang] = fs.readFileSync(path.join(dir, file), 'utf8');
|
||||||
try {
|
|
||||||
content = JSON.parse(content);
|
|
||||||
if (!(content instanceof Array)) content = JSON.stringify(content);
|
|
||||||
} catch (e) { }
|
|
||||||
languages[lang] = content;
|
|
||||||
}
|
}
|
||||||
return JSON.stringify(languages);
|
return JSON.stringify(languages);
|
||||||
}
|
}
|
||||||
@@ -548,12 +543,12 @@ export class ProblemModel {
|
|||||||
const docId = overridePid
|
const docId = overridePid
|
||||||
? (await ProblemModel.edit(domainId, overridePid, {
|
? (await ProblemModel.edit(domainId, overridePid, {
|
||||||
title: pdoc.title.trim(),
|
title: pdoc.title.trim(),
|
||||||
content: overrideContent || pdoc.content || 'No content',
|
content: overrideContent || pdoc.content.toString() || 'No content',
|
||||||
tag,
|
tag,
|
||||||
difficulty: pdoc.difficulty,
|
difficulty: pdoc.difficulty,
|
||||||
})).docId
|
})).docId
|
||||||
: await ProblemModel.add(
|
: await ProblemModel.add(
|
||||||
domainId, pid, pdoc.title.trim(), overrideContent || pdoc.content || 'No content',
|
domainId, pid, pdoc.title.trim(), overrideContent || pdoc.content.toString() || 'No content',
|
||||||
operator || pdoc.owner, tag, { hidden: pdoc.hidden, difficulty: pdoc.difficulty },
|
operator || pdoc.owner, tag, { hidden: pdoc.hidden, difficulty: pdoc.difficulty },
|
||||||
);
|
);
|
||||||
// TODO delete unused file when updating pdoc
|
// TODO delete unused file when updating pdoc
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable consistent-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
@@ -51,25 +52,7 @@ export const coreScripts: MigrationScript[] = [
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
// Init
|
// Init
|
||||||
...new Array(26).fill(unsupportedUpgrade),
|
...new Array(28).fill(unsupportedUpgrade),
|
||||||
null,
|
|
||||||
async function _28_29() {
|
|
||||||
return await iterateAllProblem(['content', 'html'], async (pdoc) => {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(pdoc.content);
|
|
||||||
if (parsed instanceof Array) {
|
|
||||||
await problem.edit(pdoc.domainId, pdoc.docId, { content: buildContent(parsed, pdoc.html ? 'html' : 'markdown') });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const res = {};
|
|
||||||
for (const key in parsed) {
|
|
||||||
if (typeof parsed[key] === 'string') res[key] = parsed[key];
|
|
||||||
else res[key] = buildContent(parsed[key]);
|
|
||||||
}
|
|
||||||
await problem.edit(pdoc.domainId, pdoc.docId, { content: JSON.stringify(res) });
|
|
||||||
} catch { }
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async function _29_30() {
|
async function _29_30() {
|
||||||
return await iterateAllDomain((ddoc) => RecordModel.coll.updateMany({ domainId: ddoc._id }, { $set: { pdomain: ddoc._id } }));
|
return await iterateAllDomain((ddoc) => RecordModel.coll.updateMany({ domainId: ddoc._id }, { $set: { pdomain: ddoc._id } }));
|
||||||
},
|
},
|
||||||
@@ -685,4 +668,38 @@ export const coreScripts: MigrationScript[] = [
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
async function _90_91() {
|
||||||
|
await document.collStatus.updateMany({ docType: document.TYPE_PROBLEM }, { $unset: { nSubmit: '', nAccept: '' } });
|
||||||
|
const psdocs = await document.coll.find({ docType: document.TYPE_PROBLEM_SOLUTION, vote: { $ne: 0 } })
|
||||||
|
.project({ docId: 1, domainId: 1 }).toArray();
|
||||||
|
for (const psdoc of psdocs) {
|
||||||
|
const filter = { docType: document.TYPE_PROBLEM_SOLUTION, domainId: psdoc.domainId, docId: psdoc.docId };
|
||||||
|
const [upvote, downvote] = await Promise.all([
|
||||||
|
document.collStatus.countDocuments({ ...filter, vote: 1 }),
|
||||||
|
document.collStatus.countDocuments({ ...filter, vote: -1 }),
|
||||||
|
]);
|
||||||
|
if (upvote - downvote !== psdoc.vote) {
|
||||||
|
await document.set(psdoc.domainId, document.TYPE_PROBLEM_SOLUTION, psdoc.docId, { vote: upvote - downvote });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await iterateAllProblem(['domainId', 'docId', 'tag'], async (pdoc) => {
|
||||||
|
if (pdoc.tag?.some((i) => typeof i !== 'string')) {
|
||||||
|
return { tag: pdoc.tag.filter((i) => i).map((i) => i.toString()) };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return await iterateAllProblem(['domainId', 'docId', 'content', 'html'], async (pdoc) => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(pdoc.content);
|
||||||
|
if (parsed instanceof Array) {
|
||||||
|
return { content: buildContent(parsed as any, pdoc.html ? 'html' : 'markdown') };
|
||||||
|
}
|
||||||
|
const res = {};
|
||||||
|
for (const key in parsed) {
|
||||||
|
if (typeof parsed[key] === 'string') res[key] = parsed[key];
|
||||||
|
else res[key] = buildContent(parsed[key]);
|
||||||
|
}
|
||||||
|
return { content: JSON.stringify(res) };
|
||||||
|
} catch { }
|
||||||
|
});
|
||||||
|
},
|
||||||
];
|
];
|
||||||
@@ -2,13 +2,42 @@
|
|||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {
|
import {
|
||||||
AdmZip, buildContent, ContentNode, Context, fs, Handler, PERM,
|
AdmZip, buildContent, Context, FileTooLargeError, fs, Handler, PERM,
|
||||||
ProblemConfigFile, ProblemModel, ValidationError, yaml,
|
ProblemConfigFile, ProblemModel, Schema, ValidationError, yaml,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
const tmpdir = path.join(os.tmpdir(), 'hydro', 'import-qduoj');
|
const tmpdir = path.join(os.tmpdir(), 'hydro', 'import-qduoj');
|
||||||
fs.ensureDirSync(tmpdir);
|
fs.ensureDirSync(tmpdir);
|
||||||
|
|
||||||
|
const StringValue = Schema.object({
|
||||||
|
value: Schema.string(),
|
||||||
|
});
|
||||||
|
const ProblemSchema = Schema.object({
|
||||||
|
tags: Schema.array(Schema.string()),
|
||||||
|
title: Schema.string().required(),
|
||||||
|
description: StringValue,
|
||||||
|
input_description: StringValue,
|
||||||
|
output_description: StringValue,
|
||||||
|
samples: Schema.array(Schema.object({
|
||||||
|
input: Schema.string(),
|
||||||
|
output: Schema.string(),
|
||||||
|
})),
|
||||||
|
hint: StringValue,
|
||||||
|
source: StringValue,
|
||||||
|
display_id: Schema.string(),
|
||||||
|
time_limit: Schema.union([Schema.number(), Schema.string()]).required(),
|
||||||
|
memory_limit: Schema.union([Schema.number(), Schema.string()]).required(),
|
||||||
|
spj: Schema.object({
|
||||||
|
language: Schema.string().required(),
|
||||||
|
code: Schema.string().required(),
|
||||||
|
}),
|
||||||
|
test_case_score: Schema.array(Schema.object({
|
||||||
|
input_name: Schema.string().required(),
|
||||||
|
output_name: Schema.string().required(),
|
||||||
|
score: Schema.number().required(),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
class ImportQduojHandler extends Handler {
|
class ImportQduojHandler extends Handler {
|
||||||
async fromFile(domainId: string, zipfile: string) {
|
async fromFile(domainId: string, zipfile: string) {
|
||||||
let zip: AdmZip;
|
let zip: AdmZip;
|
||||||
@@ -27,55 +56,15 @@ class ImportQduojHandler extends Handler {
|
|||||||
for (const { name: folder } of folders.filter((i) => i.isDirectory())) {
|
for (const { name: folder } of folders.filter((i) => i.isDirectory())) {
|
||||||
if (!fs.existsSync(path.join(tmp, folder, 'problem.json'))) continue;
|
if (!fs.existsSync(path.join(tmp, folder, 'problem.json'))) continue;
|
||||||
const buf = await fs.readFile(path.join(tmp, folder, 'problem.json'));
|
const buf = await fs.readFile(path.join(tmp, folder, 'problem.json'));
|
||||||
const pdoc = JSON.parse(buf.toString());
|
const pdoc = ProblemSchema(JSON.parse(buf.toString()));
|
||||||
const content: ContentNode[] = [];
|
const content = buildContent({
|
||||||
if (pdoc.description?.value) {
|
description: pdoc.description?.value,
|
||||||
content.push({
|
input: pdoc.input_description?.value,
|
||||||
type: 'Text',
|
output: pdoc.output_description?.value,
|
||||||
subType: 'html',
|
samples: pdoc.samples.map((sample) => [sample.input, sample.output]),
|
||||||
sectionTitle: this.translate('Description'),
|
hint: pdoc.hint?.value,
|
||||||
text: pdoc.description.value,
|
source: pdoc.source?.value,
|
||||||
});
|
}, 'html');
|
||||||
}
|
|
||||||
if (pdoc.input_description?.value) {
|
|
||||||
content.push({
|
|
||||||
type: 'Text',
|
|
||||||
subType: 'html',
|
|
||||||
sectionTitle: this.translate('Input Format'),
|
|
||||||
text: pdoc.input_description.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (pdoc.output_description?.value) {
|
|
||||||
content.push({
|
|
||||||
type: 'Text',
|
|
||||||
subType: 'html',
|
|
||||||
sectionTitle: this.translate('Output Format'),
|
|
||||||
text: pdoc.output_description.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (pdoc.samples?.length) {
|
|
||||||
content.push(...pdoc.samples.map((sample) => ({
|
|
||||||
type: 'Sample',
|
|
||||||
sectionTitle: this.translate('Sample'),
|
|
||||||
payload: [sample.input, sample.output],
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
if (pdoc.hint?.value) {
|
|
||||||
content.push({
|
|
||||||
type: 'Text',
|
|
||||||
subType: 'html',
|
|
||||||
sectionTitle: this.translate('Hint'),
|
|
||||||
text: pdoc.hint.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (pdoc.source?.value) {
|
|
||||||
content.push({
|
|
||||||
type: 'Text',
|
|
||||||
subType: 'html',
|
|
||||||
sectionTitle: this.translate('Source'),
|
|
||||||
text: pdoc.source.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (+pdoc.display_id) pdoc.display_id = `P${pdoc.display_id}`;
|
if (+pdoc.display_id) pdoc.display_id = `P${pdoc.display_id}`;
|
||||||
const isValidPid = async (id: string) => {
|
const isValidPid = async (id: string) => {
|
||||||
if (!(/^[A-Za-z]+[0-9A-Za-z]*$/.test(id))) return false;
|
if (!(/^[A-Za-z]+[0-9A-Za-z]*$/.test(id))) return false;
|
||||||
@@ -84,7 +73,7 @@ class ImportQduojHandler extends Handler {
|
|||||||
};
|
};
|
||||||
if (!await isValidPid(pdoc.display_id)) pdoc.display_id = null;
|
if (!await isValidPid(pdoc.display_id)) pdoc.display_id = null;
|
||||||
const pid = await ProblemModel.add(
|
const pid = await ProblemModel.add(
|
||||||
domainId, pdoc.display_id, pdoc.title, buildContent(content, 'html'),
|
domainId, pdoc.display_id, pdoc.title, content,
|
||||||
this.user._id, pdoc.tags || [],
|
this.user._id, pdoc.tags || [],
|
||||||
);
|
);
|
||||||
const config: ProblemConfigFile = {
|
const config: ProblemConfigFile = {
|
||||||
@@ -141,7 +130,7 @@ class ImportQduojHandler extends Handler {
|
|||||||
async post({ domainId }) {
|
async post({ domainId }) {
|
||||||
if (!this.request.files.file) throw new ValidationError('file');
|
if (!this.request.files.file) throw new ValidationError('file');
|
||||||
const stat = await fs.stat(this.request.files.file.filepath);
|
const stat = await fs.stat(this.request.files.file.filepath);
|
||||||
if (stat.size > 128 * 1024 * 1024) throw new ValidationError('file', 'File too large');
|
if (stat.size > 256 * 1024 * 1024) throw new FileTooLargeError('256m');
|
||||||
await this.fromFile(domainId, this.request.files.file.filepath);
|
await this.fromFile(domainId, this.request.files.file.filepath);
|
||||||
this.response.redirect = this.url('problem_main');
|
this.response.redirect = this.url('problem_main');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ export async function run({
|
|||||||
}
|
}
|
||||||
if (!pidMap[pdoc.problem_id]) {
|
if (!pidMap[pdoc.problem_id]) {
|
||||||
const files = {};
|
const files = {};
|
||||||
|
const markdown = [pdoc.description?.[0], pdoc.input?.[0], pdoc.output?.[0], pdoc.hint?.[0]].some((i) => i?.includes('[md]'));
|
||||||
let content = buildContent({
|
let content = buildContent({
|
||||||
description: pdoc.description,
|
description: pdoc.description,
|
||||||
input: pdoc.input,
|
input: pdoc.input,
|
||||||
@@ -175,7 +176,7 @@ export async function run({
|
|||||||
samples: [[pdoc.sample_input.trim(), pdoc.sample_output.trim()]],
|
samples: [[pdoc.sample_input.trim(), pdoc.sample_output.trim()]],
|
||||||
hint: pdoc.hint,
|
hint: pdoc.hint,
|
||||||
source: pdoc.source,
|
source: pdoc.source,
|
||||||
}, 'html').replace(/<math xm<x>lns=/g, '<math xmlns=');
|
}, 'html').replace(/<math xm<x>lns=/g, '<math xmlns=').replace(/\[\/?md]/g, '');
|
||||||
const uploadFiles = content.matchAll(/(?:src|href)="\/upload\/([^"]+\/([^"]+))"/g);
|
const uploadFiles = content.matchAll(/(?:src|href)="\/upload\/([^"]+\/([^"]+))"/g);
|
||||||
for (const file of uploadFiles) {
|
for (const file of uploadFiles) {
|
||||||
try {
|
try {
|
||||||
@@ -191,6 +192,7 @@ export async function run({
|
|||||||
1, pdoc.source?.trim().length ? pdoc.source.split(' ').map((i) => i.trim()).filter((i) => i) : [],
|
1, pdoc.source?.trim().length ? pdoc.source.split(' ').map((i) => i.trim()).filter((i) => i) : [],
|
||||||
{ hidden: pdoc.defunct === 'Y' },
|
{ hidden: pdoc.defunct === 'Y' },
|
||||||
);
|
);
|
||||||
|
if (!markdown) await ProblemModel.edit(domainId, pid, { html: true });
|
||||||
pidMap[pdoc.problem_id] = pid;
|
pidMap[pdoc.problem_id] = pid;
|
||||||
await Promise.all(Object.keys(files).map((filename) => ProblemModel.addAdditionalFile(domainId, pid, filename, files[filename])));
|
await Promise.all(Object.keys(files).map((filename) => ProblemModel.addAdditionalFile(domainId, pid, filename, files[filename])));
|
||||||
if (Object.keys(files).length) report({ message: `move ${Object.keys(files).length} file for problem ${pid}` });
|
if (Object.keys(files).length) report({ message: `move ${Object.keys(files).length} file for problem ${pid}` });
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import * as status from '@hydrooj/utils/lib/status';
|
import * as status from '@hydrooj/utils/lib/status';
|
||||||
import { findFileSync } from '@hydrooj/utils/lib/utils';
|
import { findFileSync } from '@hydrooj/utils/lib/utils';
|
||||||
import {
|
import {
|
||||||
avatar, buildContent, Context,
|
avatar, Context, fs, PERM, PRIV, STATUS, yaml,
|
||||||
fs, PERM, PRIV, STATUS, yaml,
|
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
import jsesc from 'jsesc';
|
import jsesc from 'jsesc';
|
||||||
import nunjucks from 'nunjucks';
|
import nunjucks from 'nunjucks';
|
||||||
@@ -86,14 +85,13 @@ class Nunjucks extends nunjucks.Environment {
|
|||||||
} catch {
|
} catch {
|
||||||
s = content;
|
s = content;
|
||||||
}
|
}
|
||||||
if (typeof s === 'object' && !(s instanceof Array)) {
|
if (typeof s === 'object') {
|
||||||
const langs = Object.keys(s);
|
const langs = Object.keys(s);
|
||||||
const f = langs.filter((i) => i.startsWith(language));
|
const f = langs.filter((i) => i.startsWith(language));
|
||||||
if (s[language]) s = s[language];
|
if (s[language]) s = s[language];
|
||||||
else if (f.length) s = s[f[0]];
|
else if (f.length) s = s[f[0]];
|
||||||
else s = s[langs[0]];
|
else s = s[langs[0]];
|
||||||
}
|
}
|
||||||
if (s instanceof Array) s = buildContent(s, html ? 'html' : 'markdown', (str) => str.translate(language));
|
|
||||||
return ensureTag(html ? xss.process(s) : markdown.render(s));
|
return ensureTag(html ? xss.process(s) : markdown.render(s));
|
||||||
});
|
});
|
||||||
this.addFilter('contentLang', (content) => {
|
this.addFilter('contentLang', (content) => {
|
||||||
@@ -103,9 +101,7 @@ class Nunjucks extends nunjucks.Environment {
|
|||||||
} catch {
|
} catch {
|
||||||
s = content;
|
s = content;
|
||||||
}
|
}
|
||||||
if (typeof s === 'object' && !(s instanceof Array)) {
|
if (typeof s === 'object') return Object.keys(s);
|
||||||
return Object.keys(s);
|
|
||||||
}
|
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
this.addFilter('log', (self) => {
|
this.addFilter('log', (self) => {
|
||||||
|
|||||||
@@ -218,23 +218,13 @@ export default class CodeforcesProvider extends BasicFetcher implements IBasicPr
|
|||||||
files,
|
files,
|
||||||
tag,
|
tag,
|
||||||
difficulty: getDifficulty(tag),
|
difficulty: getDifficulty(tag),
|
||||||
content: buildContent([
|
content: buildContent({
|
||||||
{
|
description: description.replace(/tex-span/g, 'katex'),
|
||||||
type: 'Text', sectionTitle: 'Description', subType: 'markdown', text: description.replace(/tex-span/g, 'katex'),
|
input: input?.replace(/tex-span/g, 'katex'),
|
||||||
},
|
output: output?.replace(/tex-span/g, 'katex'),
|
||||||
...input ? [{
|
samplesRaw: inputs.map((ipt, idx) => [ipt, outputs[idx]]).join('\n'),
|
||||||
type: 'Text', sectionTitle: 'Input', subType: 'markdown', text: input.replace(/tex-span/g, 'katex'),
|
hint: note?.replace(/tex-span/g, 'katex'),
|
||||||
}] : [],
|
}),
|
||||||
...output ? [{
|
|
||||||
type: 'Text', sectionTitle: 'Output', subType: 'markdown', text: output.replace(/tex-span/g, 'katex'),
|
|
||||||
}] : [],
|
|
||||||
...inputs.length ? [{
|
|
||||||
type: 'Plain', text: [...inputs, ...outputs].join('\n'),
|
|
||||||
}] : [] as any,
|
|
||||||
...note ? [{
|
|
||||||
type: 'Text', sectionTitle: 'Note', subType: 'markdown', text: note.replace(/tex-span/g, 'katex'),
|
|
||||||
}] : [],
|
|
||||||
]),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user